Compare commits

...

479 Commits

Author SHA1 Message Date
Nick O'Leary
813b457ba9 Add a node annotation if there info property set 2024-11-12 16:53:05 +00:00
Nick O'Leary
1d9586721e Merge pull request #4948 from node-red/pr_4935
Add a new `update available` widget to statusBar
2024-11-11 13:37:20 +00:00
Nick O'Leary
4ff748c1c1 Simplify error reporting 2024-11-08 16:46:19 +00:00
Nick O'Leary
99b3b65304 Merge pull request #4930 from node-red/monaco_0_52_0
Monaco Latest (0.52.0)
2024-11-08 15:44:44 +00:00
Nick O'Leary
a69a35aacf Separate catalogue refresh logic from UI creation 2024-11-08 15:29:03 +00:00
Nick O'Leary
d64fe36f6e Merge pull request #4937 from GogoVega/palette-queue
Add a queue while installing or removing a module from the Palette Manager
2024-11-08 13:45:51 +00:00
GogoVega
83c4ac5f65 Refresh the update status after all catalogs have been loaded
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-11-07 15:45:22 +01:00
GogoVega
69e4a02b28 Change icon, remove the widget color and ensure updateStatus is called on events 2024-11-07 15:32:06 +01:00
GogoVega
d048d2cf41 Fix update status after module updated 2024-10-30 22:36:14 +01:00
GogoVega
2f2162d531 startEvent must be called in the processQueue 2024-10-30 17:10:03 +01:00
GogoVega
245761ea03 Fix typo - bad tab to open 2024-10-30 16:09:26 +01:00
GogoVega
2b77590402 Add a queue while installing or removing a module 2024-10-30 15:54:42 +01:00
GogoVega
4248cb6205 Refresh status when registry changes + cleanup 2024-10-29 19:00:04 +01:00
GogoVega
48894d6a95 Add a new update available widget to statusBar 2024-10-29 15:36:49 +01:00
GogoVega
e5073cf374 Allow RED.statusBar to hide/show widget 2024-10-29 15:27:45 +01:00
Steve-Mcl
24c6f4d655 updates monaco to latest. Includes fix for action widget sizing 2024-10-28 11:03:29 +00:00
Nick O'Leary
83acc4836b Merge pull request #4768 from Rotzbua/fix_html_tags
fix(html): correct buggy html
2024-09-11 15:40:24 +01:00
Nick O'Leary
db51307e7f Merge branch 'dev' into fix_html_tags 2024-09-11 15:27:45 +01:00
Nick O'Leary
a2430b772b Merge pull request #4836 from node-red/update-dev
Update dev
2024-07-18 17:27:20 +01:00
Nick O'Leary
b3acd1a588 Merge branch 'master' into update-dev 2024-07-18 17:22:44 +01:00
Nick O'Leary
8af821d380 Merge pull request #4837 from node-red/knolleary-patch-1
Back off node 22.5
2024-07-18 17:22:32 +01:00
Nick O'Leary
97ee6c6745 Back off node 22.5 2024-07-18 17:17:50 +01:00
Nick O'Leary
b5fb15cd9b Merge pull request #4828 from GogoVega/fix-menu-selection
Fix menu to enable/disable selection when it's a group
2024-07-18 16:36:02 +01:00
Nick O'Leary
998219ae9a Merge pull request #4829 from node-red/Let-Batch-node-honour-parts-to-close-early
Let batch node terminate "early" if msg.parts set to end of sequence
2024-07-18 16:34:46 +01:00
Nick O'Leary
e8f0f80f65 Merge pull request #4835 from node-red/Fix-Split-Node-Name-Caps
Fix unintentional Capitalisation in Split node name
2024-07-18 16:33:48 +01:00
Nick O'Leary
45936285cc Bump dev to 4.1.0-beta.1 2024-07-18 16:32:12 +01:00
Dave Conway-Jones
ff35c46d5d Fix unintentianal Capitalisation in Split node
to close #4832
2024-07-16 14:37:16 +01:00
Dave Conway-Jones
05c924a9df Let batch node terminate "early" if msg.parts set to end of sequence
(ie index = count -1). Useful to close at end of files etc.
2024-07-04 15:09:55 +01:00
GogoVega
14ac309b6e Fix menu to enable/disable selection when it's a group 2024-07-02 11:38:56 +02:00
Nick O'Leary
29b128e5e0 Merge pull request #4825 from node-red/rel402
Bump for 4.0.2
2024-07-01 16:49:13 +01:00
Nick O'Leary
c0f1581370 Bump for 4.0.2 2024-07-01 16:38:28 +01:00
Nick O'Leary
9420a52ec7 Merge pull request #4818 from bonanitech/header-border
Use a more subtle border on the header
2024-07-01 12:52:09 +01:00
Mauricio Bonani
c113b3de13 Use a not-so-dark color for the header border 2024-07-01 06:18:17 -04:00
Mauricio Bonani
29058c163a Merge branch 'master' into header-border 2024-07-01 06:14:35 -04:00
Nick O'Leary
bb110ea230 Merge pull request #4824 from GogoVega/improve-french-translations
Improve the editor's French translations
2024-07-01 10:55:33 +01:00
Nick O'Leary
785f220cd8 Merge pull request #4821 from node-red/orphaned-monco-editors
Clean up orphaned editors
2024-07-01 10:48:49 +01:00
GogoVega
16570410a5 Improve French translations 2024-07-01 09:43:42 +02:00
GogoVega
8085eda431 Add missing French translations from v4 2024-06-30 22:27:38 +02:00
Steve-Mcl
6503498f0a Clean up orphaned editors
closes #4820
2024-06-29 18:14:54 +01:00
Nick O'Leary
da787a9993 Merge pull request #4812 from GogoVega/fix-required-prop
Fix node validation if the property is not required
2024-06-28 17:21:40 +01:00
Nick O'Leary
c873b57094 Merge pull request #4815 from node-red/update-cookie-auth
Allow auth cookie name to be customised
2024-06-28 16:58:36 +01:00
Nick O'Leary
93974ccd92 Merge pull request #4816 from node-red/4814-multiplayer-uncaught
Guard against undefined sessions in multiplayer
2024-06-28 16:58:25 +01:00
Nick O'Leary
d7aa792f97 Merge pull request #4817 from node-red/mermaid-caching
Ensure mermaid.min.js is cached properly between loads of the editor
2024-06-28 16:58:14 +01:00
Mauricio Bonani
375fa9da64 Use a more subtle border on the header 2024-06-28 09:51:23 -04:00
Nick O'Leary
28c41e17ad Ensure mermaid.min.js is cached properly between loads of the editor 2024-06-28 14:37:51 +01:00
Nick O'Leary
da3ad40968 Add more guards for undefined session 2024-06-28 14:19:18 +01:00
GogoVega
2464d9ad95 Check required prop for each case instead of top level 2024-06-28 14:15:51 +02:00
Nick O'Leary
011b47a108 Guard against undefined sessions in multiplayer 2024-06-28 11:41:05 +01:00
Nick O'Leary
ea747711c3 Allow auth cookie name to be customised 2024-06-28 10:24:51 +01:00
GogoVega
19a8fa09a8 Fix node validation if property is not required 2024-06-27 09:16:03 +02:00
Nick O'Leary
1b5b3f7f88 Merge pull request #4804 from node-red/rel401
Update for 4.0.1 release
2024-06-26 14:42:42 +01:00
Nick O'Leary
2123514c76 Update for 4.0.1 release 2024-06-26 14:38:28 +01:00
Nick O'Leary
379fbd7c7e Merge pull request #4803 from node-red/4798-include-groups-inflowapi
Ensure group nodes are properly exported in /flow api
2024-06-26 14:36:03 +01:00
Nick O'Leary
efdc1b1a1d Ensure group nodes are properly exported in /flow api 2024-06-26 14:30:33 +01:00
Nick O'Leary
52bbd82e18 Merge pull request #4802 from node-red/4801-fix-sf-instance-credentials
Ensure subflow instance credential property values are extracted
2024-06-26 11:57:31 +01:00
Nick O'Leary
20a9c051be Ensure subflow instance credential property values are extracted 2024-06-26 11:49:37 +01:00
Nick O'Leary
830e475969 Merge pull request #4800 from GogoVega/fix-config-select
Use `_ADD_` value for both `add new...` and `none` options
2024-06-26 10:51:39 +01:00
GogoVega
f75e2f221c Use _ADD_ value for add... and none options 2024-06-25 20:08:31 +02:00
Nick O'Leary
5a75440668 Merge pull request #4796 from node-red/Join-node-optional-use-of-parts
make using msg.parts optional in join node
2024-06-25 16:22:20 +01:00
Dave Conway-Jones
53d8b97fff join node - honour 3.x behaviour for old instances.
but don't use msg.parts for new instances in manual join mode unless set.
2024-06-25 12:57:09 +01:00
Nick O'Leary
c85667cc13 Merge pull request #4794 from node-red/4792-ui-proxy-fix
UI proxy should setup agents for both http_proxy and https_proxy
2024-06-25 10:56:44 +01:00
Dave Conway-Jones
1a8b37b4e3 make using msg.parts optional in join node 2024-06-24 21:05:00 +01:00
Steve-Mcl
40a2d90e08 remove .only 2024-06-24 20:06:22 +01:00
Steve-Mcl
ee269caa4a update and add tests to check correct proxy was returned for flow 2024-06-24 20:05:37 +01:00
Steve-Mcl
d820686e5a only initialise proxy if flow url is static 2024-06-24 20:04:26 +01:00
Steve-Mcl
aa2a585e00 should set https and http proxy agents for UI
closes #4792
2024-06-24 17:34:30 +01:00
Steve-Mcl
f9e6bccd46 Merge branch 'master' of github.com:node-red/node-red 2024-06-24 17:19:07 +01:00
Nick O'Leary
3230654ecd Merge pull request #4788 from GogoVega/fix-config-node-select
Fix the config node select value assignment
2024-06-24 16:59:16 +01:00
Gauthier Dandele
a5b53ee373 Fix the selected value if no config nodes available
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-06-24 17:37:28 +02:00
Nick O'Leary
ac420247ae Merge pull request #4786 from kazuhitoyokoi/master-addtooltip
Add tooltip for number of subflow instance on info tab
2024-06-24 16:25:54 +01:00
Steve-Mcl
61198bd7e3 Merge branch 'master' of github.com:node-red/node-red 2024-06-24 11:58:03 +01:00
GogoVega
9c511b6674 Revert the updateConfigNodeSelect changes 2024-06-24 12:39:22 +02:00
Nick O'Leary
9d054543a7 Merge pull request #4785 from kazuhitoyokoi/master-addjpn
Add Japanese translations for v4.0.0
2024-06-24 11:33:00 +01:00
Nick O'Leary
fc3ec2a0d7 Merge pull request #4791 from node-red/4787-revert-default-user-agent
Remove default user agent
2024-06-24 11:30:54 +01:00
Stephen McLaughlin
e7ef73222f Remove default user agent
closes #4787
2024-06-24 11:21:51 +01:00
GogoVega
6623e56a1e Fix setting of config node select value 2024-06-23 21:41:30 +02:00
Kazuhito Yokoi
582eca1877 Add tooltip for number of subflow instance on info tab 2024-06-23 20:59:26 +09:00
Kazuhito Yokoi
2783100f84 Add Japanese translations for v4.0.0 2024-06-23 20:48:34 +09:00
Rotzbua
8c5ddd68a4 fix(html): correct buggy html
- Merge "style" attribute
- Remove wrong end tag
- Remove trailing slash on void html elements
2024-06-22 22:38:24 +02:00
Nick O'Leary
cb0c484579 Merge branch 'dev' 2024-06-20 13:26:22 +01:00
Nick O'Leary
a1bf270ba6 Update tour version 2024-06-20 13:23:34 +01:00
Nick O'Leary
be5694f149 Merge pull request #4777 from node-red/rel400
Update for 4.0 final release
2024-06-20 11:53:56 +01:00
Nick O'Leary
4ff364e2c3 Reorder tour features 2024-06-20 11:45:23 +01:00
Nick O'Leary
2fa6f35873 Update for 4.0 final 2024-06-20 11:41:25 +01:00
Nick O'Leary
2a4fb7123d Merge pull request #4772 from node-red/rel3111
Bump for 3.1.11 release
2024-06-18 11:44:55 +01:00
Nick O'Leary
38a77d2b78 Bump for 3.1.11 release 2024-06-18 11:37:59 +01:00
Nick O'Leary
dc239db256 Merge pull request #4762 from node-red/Update-German-delay-node-translations
Add/Update German Translations for delay node
2024-06-17 16:07:20 +01:00
Nick O'Leary
a622d19ba7 Merge pull request #4761 from node-red/4759-add-httpStaticCors
Add `httpStaticCors`
2024-06-17 16:06:58 +01:00
Nick O'Leary
9842d9116c Merge pull request #4763 from node-red/bump-nr-admin
Update dependencies
2024-06-17 11:46:02 +01:00
Nick O'Leary
19ea8f8515 Update dependencies 2024-06-17 11:24:59 +01:00
Dave Conway-Jones
4ba3c937a8 Add/Update German Translations for delay node
To close #4748
2024-06-14 15:36:25 +01:00
Nick O'Leary
dbd3f0f85b Add httpStaticCors to default settings file 2024-06-14 15:21:02 +01:00
Nick O'Leary
48a2876c48 Add support for httpStaticCors 2024-06-14 15:18:40 +01:00
Nick O'Leary
10398b05d8 Merge pull request #4756 from node-red/sync-dev
Sync master to dev
2024-06-11 14:34:24 +01:00
Nick O'Leary
3a91fc17fd Merge branch 'master' into sync-dev 2024-06-11 10:14:01 +01:00
Nick O'Leary
02893d3e78 Merge pull request #4755 from node-red/rel3110
Bump for 3.1.10 release
2024-06-11 09:22:16 +01:00
Nick O'Leary
5124bc6bf8 Bump for 3.1.10 release 2024-06-10 21:14:20 +01:00
Nick O'Leary
3952a23ba3 Merge pull request #4747 from GogoVega/tooltip-input-validation
Add tooltip and message validation to `typedInput`
2024-06-10 20:44:34 +01:00
Nick O'Leary
1048b16f3c Merge pull request #4754 from node-red/4752-add-rewired-to-stoplist
Include rewired nodes when calculating Modified Flows stop list
2024-06-10 20:44:20 +01:00
Nick O'Leary
bbbbb1b1e0 Merge pull request #4753 from node-red/4751-fix-group-json
Fix clone of group env var properties
2024-06-10 20:42:49 +01:00
Nick O'Leary
14b452c996 Merge pull request #4750 from GogoVega/fix-4749
Fix losing links when importing a copy of links into a subflow
2024-06-10 20:42:36 +01:00
GogoVega
bb91a08939 Just move the block before the Id is updated 2024-06-10 18:28:30 +02:00
GogoVega
bffa923f05 Revert all changes for RED.popover 2024-06-10 18:02:28 +02:00
Nick O'Leary
526b3fda91 Include rewired nodes when calculating Modified Flows stop list 2024-06-10 16:56:49 +01:00
Nick O'Leary
27fc89ba33 Merge pull request #4744 from node-red/bcrypt-bump
Replace bcrypt with @node-rs/bcrypt
2024-06-10 16:16:19 +01:00
Nick O'Leary
d70b7ea924 Fix clone of group env var properties
Closes #4751
2024-06-10 16:15:06 +01:00
GogoVega
1d342a778d Fix new_nodes should be used instead of node_map 2024-06-05 14:12:35 +02:00
GogoVega
476016cbcc Fix node_map does not contain as key the new id of a copied node 2024-06-05 12:22:22 +02:00
GogoVega
bd2c020e84 My bad, options can be undefined 2024-06-03 21:11:13 +02:00
GogoVega
da7c7ede02 Fix a String value is an error, so return the value 2024-06-03 18:57:41 +02:00
GogoVega
34ed9c5cd8 Don't use the typedInput tooltip to get the validation message 2024-06-03 18:41:40 +02:00
Nick O'Leary
47dd08e74a Merge pull request #4746 from node-red/export-flow-save-view
Export Nodes dialog refinement
2024-06-03 16:11:22 +01:00
GogoVega
6aae50294f Cleanup + fix popover.getContent can be a function and apply #4450 changes 2024-06-02 20:16:10 +02:00
Steve-Mcl
ec2f6ec46f remember and restore active export view 2024-06-02 14:41:41 +01:00
Steve-Mcl
36805b6872 move export format button to main dialog 2024-06-02 14:41:15 +01:00
Steve-Mcl
d78cb2fec7 new common i18n message "format" 2024-06-02 12:10:30 +01:00
Nick O'Leary
51edb1ef19 Fix bcrypt.compare usage 2024-05-31 15:31:41 +01:00
Nick O'Leary
2ad3af1864 Replace bcrypt with @node-rs/bcrypt 2024-05-31 15:16:32 +01:00
Nick O'Leary
525d7356fe Merge pull request #4743 from node-red/rel400-b4
Update for 4-beta.4
2024-05-30 16:54:39 +01:00
Nick O'Leary
bdf37b0546 Update changelog 2024-05-30 16:50:02 +01:00
Nick O'Leary
0d330332f1 Merge pull request #4739 from Rotzbua/feat_build_fail
feat(grunt): fail if files are missing
2024-05-30 16:47:38 +01:00
Nick O'Leary
a5f290fd47 Merge pull request #4736 from Rotzbua/fix_pi_script
fix(node-red-pi): node-red not started by path
2024-05-30 16:47:25 +01:00
Nick O'Leary
46f4144172 Merge pull request #4735 from Rotzbua/fix_html_meta
fix(editor): remove trailing slash
2024-05-30 16:47:02 +01:00
Nick O'Leary
356e332f66 Merge pull request #4616 from Steve-Mcl/proxy-logiv-dev-v4
Perform Proxy logic more like cURL
2024-05-30 16:46:42 +01:00
Nick O'Leary
d16060bdd9 Merge pull request #4733 from Rotzbua/chore_remove_deprecated
fix: remove deprecated mqtt.js
2024-05-30 16:45:58 +01:00
Nick O'Leary
3aeb4bd868 Update dependencies 2024-05-30 16:43:33 +01:00
Stephen McLaughlin
b8bcab109a Merge branch 'dev' into proxy-logiv-dev-v4 2024-05-30 16:33:44 +01:00
Nick O'Leary
b94045fd86 Update packages/node_modules/node-red/settings.js 2024-05-30 16:32:21 +01:00
Nick O'Leary
87992823d5 Update for 4.0 final release 2024-05-30 16:12:56 +01:00
Nick O'Leary
93b914d4b0 Merge pull request #4740 from node-red/resyn-dev
Resync master to dev
2024-05-30 14:15:26 +01:00
Nick O'Leary
61b12f6bbe Merge pull request #3743 from bonanitech/fix-build-script
Ensure all CSS variables are in the output file
2024-05-30 13:43:25 +01:00
Nick O'Leary
d71b22412b Merge pull request #4734 from GogoVega/fix-sidebar-config
Fix the Sidebar Config is not refreshed after a deploy
2024-05-30 13:40:12 +01:00
Rotzbua
d115d6c241 feat(grunt): fail if files are missing 2024-05-30 14:35:55 +02:00
Stephen McLaughlin
e561efb5c5 Merge branch 'dev' into proxy-logiv-dev-v4 2024-05-30 11:44:53 +01:00
Nick O'Leary
e408c6b376 Merge pull request #4729 from GogoVega/fix-4728
Fix checkboxes are not updated when calling `typedInput("value", "")`
2024-05-30 10:32:27 +01:00
Rotzbua
cd95c63daf fix(node-red-pi): node-red not started by path
Double quote to prevent globbing and word splitting.
https://github.com/koalaman/shellcheck/wiki/SC2086
2024-05-30 01:53:51 +02:00
Rotzbua
bc6a4a20d2 fix(editor): remove trailing slash
html validator:
Info: Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.
2024-05-30 01:42:16 +02:00
GogoVega
bf30c24e8e Fix the Sidebar Config is not refreshed after a deploy 2024-05-29 20:44:49 +02:00
Nick O'Leary
25205f7440 Merge pull request #4725 from node-red/monaco-0-49-0
Monaco-0-49-0
2024-05-29 17:37:46 +01:00
Stephen McLaughlin
6317420d4a Merge branch 'dev' into monaco-0-49-0 2024-05-29 11:14:33 +01:00
GogoVega
6c14ed0ef5 Revert and fix an undefined value returned instead of a false value 2024-05-28 22:08:42 +02:00
Rotzbua
f53bdc8257 fix: remove deprecated mqtt.js
deprecated since 2016 and node-red 0.13.4
2024-05-28 16:03:04 +02:00
Nick O'Leary
6d41ecdae0 Merge pull request #4716 from corentin-sodebo-voile/master
Fix panning with middle mouse button on windows 10/11
2024-05-28 10:28:57 +01:00
Nick O'Leary
7bd61f2c96 Merge pull request #4718 from node-red/4717-add-httpAdminCookieOptions
Add httpAdminCookieOptions
2024-05-28 10:15:46 +01:00
Nick O'Leary
76338d4d32 Merge pull request #4724 from Rotzbua/chore_isArray
chore: migrate deprecated `util.isArray`
2024-05-28 09:51:28 +01:00
Nick O'Leary
3f89bc2733 Merge pull request #4727 from kazuhitoyokoi/master-addjpn
Add Japanese translation for sidebar tooltip
2024-05-28 09:49:35 +01:00
Nick O'Leary
b9add237b2 Merge pull request #4726 from kazuhitoyokoi/dev-jpn
Add Japanese translations for 4.0.0-beta.3
2024-05-28 09:49:18 +01:00
Nick O'Leary
87a25df162 Merge pull request #4730 from GogoVega/translate-multiple-option-label
Translate the number of items selected in the options list
2024-05-28 09:49:03 +01:00
GogoVega
341f43610a Translate the number of items selected in the options list 2024-05-27 19:56:51 +02:00
GogoVega
67cdf3ef96 Fix checkboxes are not updated when calling typedInput("value", "") 2024-05-27 18:59:25 +02:00
Kazuhito Yokoi
cd98f448e9 Add Japanese translation for sidebar tooltip 2024-05-26 23:34:32 +09:00
Kazuhito Yokoi
b66af1c8e2 Fix typo 2024-05-26 23:00:52 +09:00
Kazuhito Yokoi
06bad61569 Add Japanese translations for 4.0.0-beta.3 2024-05-26 22:54:59 +09:00
Steve-Mcl
f58766eacf typo 2024-05-26 13:15:23 +01:00
Steve-Mcl
8e62b2a749 update monaco to latest 2024-05-26 13:15:10 +01:00
Rotzbua
9b86874c2d chore: migrate deprecated util.isArray
Deprecated: Use Array.isArray() instead.

Source: https://nodejs.org/docs/latest-v18.x/api/util.html#utilisarrayobject
2024-05-25 22:55:09 +02:00
Nick O'Leary
805ed593fb Apply httpAdminCookieOptions to session cookie 2024-05-23 17:01:48 +01:00
Nick O'Leary
c604ac2207 Allow session cookie options to be customised
Closes #4717
2024-05-23 16:56:43 +01:00
corentin-sodebo-voile
fac79fd068 Fix panning with middle mouse button on windows 10/11
Without preventDefault, when you try to drag the canvas with middle mouse button on Windows (e.g. in Chrome), the cursor change to a "scroll cursor" and the canvas scrolls endlessly instead of being dragged accurately.
2024-05-23 15:30:13 +02:00
Nick O'Leary
da97c5d558 Merge pull request #4715 from GogoVega/fix-validateNodeEditorProperty
Fix a checkbox should return a Boolean value and not the string `on`
2024-05-23 13:43:55 +01:00
Nick O'Leary
ae7b9fe62e Merge pull request #4714 from GogoVega/fix-4712
Deleting a grouped node should update the group
2024-05-23 13:43:26 +01:00
GogoVega
e52c2911da Fix a checkbox must return a boolean value and not on 2024-05-23 10:57:51 +02:00
GogoVega
ca37d1ec9d Merge branch 'dev' into tooltip-input-validation 2024-05-22 18:19:06 +02:00
Nick O'Leary
07a29ff779 Merge pull request #4711 from GogoVega/change-config-node-cursor
Change the Config Node cursor to `pointer`
2024-05-22 17:02:05 +01:00
Nick O'Leary
1b4a8ebe83 Merge branch 'master' into fix-4712 2024-05-22 17:01:29 +01:00
GogoVega
abf2eacf18 Revert and fix without using RED.group.removeFromGroup 2024-05-21 19:48:21 +02:00
Gauthier Dandele
f808f4e2e8 Apply suggestions from code review
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-05-21 19:01:57 +02:00
Nick O'Leary
ca33d6b799 Merge pull request #4713 from GogoVega/add-missing-sidebar-tooltips
Add missing tooltips to Sidebar
2024-05-21 17:15:52 +01:00
Nick O'Leary
3fd2d07c75 Merge pull request #4706 from node-red/4648-readonly-feedback
Show lock on deploy if user is read-only
2024-05-21 17:14:48 +01:00
Nick O'Leary
940740f15d Merge pull request #4710 from node-red/4704-node-edit-history
Allow nodes to return additional history entries in onEditSave
2024-05-21 17:10:23 +01:00
GogoVega
51208fcd0c Fix indexOf returns -1 2024-05-21 16:06:15 +02:00
GogoVega
707152d82f Deleting a grouped node should update the group 2024-05-21 15:07:26 +02:00
GogoVega
5538f6dd8a Add missing tooltips to Sidebar 2024-05-21 12:58:29 +02:00
GogoVega
d601e2caa4 Change the config node cursor to pointer 2024-05-21 11:37:01 +02:00
Nick O'Leary
46fdf56c79 Allow nodes to return additional history entries in onEditSave 2024-05-20 16:41:44 +01:00
Stephen McLaughlin
b76d692a65 Merge pull request #4707 from node-red/4569-cli-version
Add --version cli args
2024-05-17 17:36:41 +01:00
Nick O'Leary
6600910163 Add os details to --version output 2024-05-17 17:16:01 +01:00
Nick O'Leary
a6973bd7ed Add node.js version to --version output 2024-05-17 17:13:50 +01:00
Nick O'Leary
d58127730f Add --version cli arg 2024-05-17 17:10:37 +01:00
Nick O'Leary
5494c167fc Show lock on deploy if user is read-only 2024-05-17 17:04:08 +01:00
Nick O'Leary
c5ae0be7b1 Merge pull request #4705 from node-red/rel4b31
Bump for beta 4-beta3-1
2024-05-16 10:39:08 +01:00
Nick O'Leary
b653914ee0 Bump for beta3-1 repackage 2024-05-16 10:37:16 +01:00
Nick O'Leary
c107c5fc92 Merge pull request #4698 from node-red/rel4-beta3
Update for beta.3
2024-05-15 16:58:47 +01:00
Nick O'Leary
0980c03129 Merge pull request #4700 from GogoVega/addfrench-v4beta3
Add French translation of tour for 4.0.0-beta.3
2024-05-15 16:52:07 +01:00
GogoVega
f6c3fdc806 Add French translation of tour 2024-05-14 20:54:00 +02:00
Nick O'Leary
2c2628d816 Update changelog and tour 2024-05-14 17:44:26 +01:00
Nick O'Leary
56fe2801eb Merge branch 'master' into dev 2024-05-14 17:27:02 +01:00
Nick O'Leary
87b1ee9642 Bump package version 2024-05-14 17:24:28 +01:00
Nick O'Leary
e1c36d232b Merge pull request #4697 from node-red/sync-dev-2
Sync master branch to dev
2024-05-14 17:23:31 +01:00
Nick O'Leary
13ee8cec24 Merge branch 'master' into sync-dev-2 2024-05-14 17:22:42 +01:00
Nick O'Leary
a977b87cb3 Merge pull request #4692 from node-red/improve-conflict-handling
Improve background-deploy notification handling
2024-05-14 16:04:58 +01:00
Nick O'Leary
14dfb9aef8 Merge pull request #4695 from node-red/improve-merge-diff-view
Improve diff view display of nodes that have only moved
2024-05-14 16:04:27 +01:00
Nick O'Leary
d520cde57a Merge pull request #4314 from Rotzbua/remove_outdated_node_check
fix: remove outdated Node 11+ check
2024-05-14 13:54:15 +01:00
Nick O'Leary
70167d7d1d Merge pull request #4694 from Rotzbua/test_add_node_22
feat(ci): add new nodejs v22
2024-05-14 13:52:34 +01:00
Nick O'Leary
3389c8160b Handle group w/h properties as move rather than change 2024-05-14 13:51:52 +01:00
Nick O'Leary
c214710f8e Improve diff view display of nodes that have only moved 2024-05-14 13:35:05 +01:00
Nick O'Leary
ac6a4945cb Merge pull request #4690 from Rotzbua/fix_node_requirement
fix(node): increase required node >=18.5
2024-05-14 13:29:52 +01:00
Rotzbua
fd1a001a23 feat(ci): add new nodejs v22 2024-05-14 13:39:32 +02:00
Rotzbua
f3c561cd86 fix(node): increase required node >=18.5
Statement can be simplified by increasing the required minor version.
2024-05-14 12:27:38 +02:00
Nick O'Leary
4e33e785fb Handle multiple background deploys 2024-05-14 10:27:55 +01:00
Nick O'Leary
f55ee6e665 Merge pull request #4685 from node-red/4683-preserve-full-error-obj
Pass full error object in Function node and copy over cause property
2024-05-13 15:25:02 +01:00
Nick O'Leary
edc5e88d5a Merge pull request #4689 from Rotzbua/fix_workaroud_dns
fix(dns): remove outdated node check
2024-05-13 14:41:35 +01:00
Nick O'Leary
47bf166a6e Update packages/node_modules/node-red/lib/red.js 2024-05-13 14:41:24 +01:00
Nick O'Leary
cf26209790 Merge pull request #4688 from Rotzbua/remove_import_polyfill
fix(polyfill): remove import module polyfill
2024-05-13 14:39:33 +01:00
Nick O'Leary
e55ebde170 Merge pull request #4686 from Rotzbua/fix_typo
Fix typo
2024-05-13 14:38:35 +01:00
Nick O'Leary
a745ddc164 Fix linting 2024-05-13 14:33:35 +01:00
Nick O'Leary
18d0fa2259 Improve background conflict handling 2024-05-13 14:19:24 +01:00
Rotzbua
d706c9cb37 fix: remove outdated Node 11+ check 2024-05-12 22:51:05 +02:00
Rotzbua
20d2450cac fix(polyfill): remove import module polyfill
Was required for node <12.17.
2024-05-12 22:38:03 +02:00
Rotzbua
34345461f1 fix(dns): remove outdated node check
Add reference to issue for this workaround.
2024-05-12 22:25:39 +02:00
Rotzbua
aa372a1707 Fix typo in source code comment 2024-05-12 18:25:08 +02:00
Nick O'Leary
03648dc7e8 Update tests for changed function node low-level output 2024-05-09 17:25:47 +01:00
Nick O'Leary
66a667fe58 Pass full error object in Function node and copy over cause property
Fixes #4683
2024-05-09 16:48:51 +01:00
Nick O'Leary
1bb3a0eca5 Merge pull request #4534 from patlux/master
Replacing vm.createScript in favour of vm.Script
2024-05-09 15:19:45 +01:00
Nick O'Leary
0e0bba25c1 Merge pull request #4679 from node-red/use-rfdc
Use rfdc for cloning pure JSON values
2024-05-08 16:18:13 +01:00
Nick O'Leary
af701d65ac Merge pull request #4681 from GogoVega/addfrench-v4beta.2
Add French translations for 4.0.0-beta.2
2024-05-08 16:17:57 +01:00
Nick O'Leary
08927dfb55 Merge pull request #4684 from node-red/4363-autoLogin-redirect-loop
Avoid login loops when autoLogin enabled but login fails
2024-05-08 15:48:21 +01:00
Nick O'Leary
b27483de9c Avoid login loops when autoLogin enabled but login fails
Fixes #4363
2024-05-08 15:09:51 +01:00
GogoVega
b02f69b77a Add translations for 4.0.0-beta.2 2024-05-05 15:00:42 +02:00
Nick O'Leary
598b0c84ab Merge pull request #4657 from node-red/tab-close-middle-click
Hide workspace tab on middle mouse click
2024-05-03 16:59:28 +01:00
Nick O'Leary
22cc8da088 Apply suggestions from code review 2024-05-03 16:59:17 +01:00
Nick O'Leary
a70618cdef Merge pull request #4666 from node-red/multiplayer-2
[multiplayer] Add user presence indicators
2024-05-03 16:52:50 +01:00
Nick O'Leary
faf142cf66 Merge pull request #4676 from kazuhitoyokoi/dev-fixpulldependencies
Enable updating dependency node of package.json in project feature
2024-05-03 16:52:33 +01:00
Nick O'Leary
1a3cc06935 Use rfdc module for cloning when we know its pure JSON 2024-05-03 16:45:50 +01:00
Nick O'Leary
a712a9363b Merge pull request #4674 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for 4.0.0-beta.2
2024-05-03 16:18:50 +01:00
Steve-Mcl
67e716466f handle middle click hide-tab in onclick handler 2024-04-29 20:14:45 +01:00
Steve-Mcl
3fae03da98 Merge branch 'dev' into tab-close-middle-click 2024-04-29 19:04:47 +01:00
Kazuhito Yokoi
361391ceb8 Load the latest project files when retrieving project information 2024-04-29 16:02:56 +09:00
Kazuhito Yokoi
bf0ca38350 Enable updating dependencies of package.json in project feature 2024-04-29 02:12:08 +09:00
Kazuhito Yokoi
437c28e2b8 Fix typos in welcome tour for 4.0.0-beta.2 2024-04-28 21:27:25 +09:00
Kazuhito Yokoi
c05d18ada1 Add Japanese translations for 4.0.0-beta.2 2024-04-28 21:22:15 +09:00
Nick O'Leary
cfb300ec06 Merge pull request #4672 from node-red/allow-blank-env-var-substitutions
Allow blank strings to be used for env var property substitutions
2024-04-24 23:37:23 +02:00
Nick O'Leary
236e668201 Allow blank strings to be used for env var property substitutions
Fixes #4663
2024-04-24 22:58:11 +02:00
Nick O'Leary
211d420fb2 Merge pull request #4667 from node-red/fix-subflow-property-undo
Fix undo of subflow env property edits
2024-04-23 23:45:46 +02:00
Nick O'Leary
c9b902c2b4 Merge pull request #4658 from node-red/fix-subflow-conf-type
Fix saving of conf-type properties in module packaged subflows
2024-04-23 23:45:29 +02:00
Nick O'Leary
b8ca4665c1 Merge pull request #4660 from JoshuaCWebDeveloper/patch-1
Fix three error typos in monaco.js
2024-04-23 23:45:16 +02:00
Nick O'Leary
ac8b1e19b7 Merge pull request #4662 from hardillb/timeout-npm-install
Add npm install timeout notification
2024-04-23 23:44:51 +02:00
Nick O'Leary
960af87fb0 Ensure subflow change state is cleared after deploy 2024-04-23 21:17:35 +02:00
Nick O'Leary
de7339ae97 Fix undo of subflow env property edits 2024-04-23 20:39:14 +02:00
Nick O'Leary
595933d046 Fix linting 2024-04-23 09:40:01 +02:00
Nick O'Leary
789426f80e Add user presence indication to tabs and nodes 2024-04-23 09:27:35 +02:00
Stephen McLaughlin
0995af62b6 Merge pull request #4664 from ZJvandeWeg/patch-3
docs: Add closing paragraph tag
2024-04-20 13:54:37 +01:00
Zeger-Jan van de Weg
c2e03a40b4 docs: Add closing paragraph tag
Minor change that only improves xpath parsing.
2024-04-20 14:20:59 +02:00
Ben Hardill
148e64c3da Update packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-04-18 14:22:50 +01:00
Ben Hardill
c6289ebb2c Merge branch 'dev' into timeout-npm-install 2024-04-18 12:12:53 +01:00
Ben Hardill
5f4ece6813 Move translation 2024-04-18 11:47:49 +01:00
Ben Hardill
c990ec39d6 revert DELETE change 2024-04-18 11:35:51 +01:00
Ben Hardill
1fdc600ecd Add npm install timeout notification
part of https://github.com/node-red/node-red/issues/4622
2024-04-18 11:27:32 +01:00
Joshua Carter
c855050bcf Fix three error typos in monaco.js 2024-04-15 08:09:26 -07:00
Nick O'Leary
e354d2ce29 Fix saving of conf-type properties in module packaged subflows 2024-04-12 14:08:07 +01:00
Nick O'Leary
d218af8619 Merge branch 'master' into dev 2024-04-12 13:04:54 +01:00
Steve-Mcl
d938e5fb6b close tab on middle mouse click 2024-04-12 11:42:55 +01:00
Nick O'Leary
29ed5b2792 Merge pull request #4655 from node-red/rel319
Bump for 3.1.9 release
2024-04-11 19:22:24 +01:00
Nick O'Leary
e39216e65a Bump for 3.1.9 release 2024-04-11 19:15:46 +01:00
Nick O'Leary
7ac7f9b4c8 Merge pull request #4654 from node-red/fix-subflow-recursion-check
Prevent subflow being added to itself
2024-04-11 19:12:43 +01:00
Stephen McLaughlin
4709eb9d49 Merge pull request #4652 from node-red/fix-windows-spawn
Fix use of spawn on windows with cmd files
2024-04-11 17:51:13 +01:00
Nick O'Leary
c13b8266dd Prevent subflow being added to itself 2024-04-11 17:05:10 +01:00
Nick O'Leary
bd58431603 Fix use of spawn on windows with cmd files 2024-04-11 14:40:29 +01:00
Nick O'Leary
3075b82792 Add one more tour image 2024-04-05 11:18:17 +01:00
Nick O'Leary
240082481f Merge pull request #4646 from node-red/rel400b2
Bump for beta.2
2024-04-05 10:53:03 +01:00
Nick O'Leary
ea95552285 Bump for beta.2 2024-04-04 18:25:10 +01:00
Nick O'Leary
5358b06123 Merge pull request #4645 from node-red/fix-mp-tests
Add placeholder tests for multiplayer
2024-04-04 16:19:53 +01:00
Nick O'Leary
99391431da Add placeholder tests for multiplayer 2024-04-04 16:17:30 +01:00
Nick O'Leary
d396f50a9a Merge pull request #4627 from GogoVega/button-add-config-node
Separate the "add new config-node" option into a new (+) button
2024-04-04 16:11:52 +01:00
Nick O'Leary
affa8ea42b Apply suggestions from code review 2024-04-04 16:08:59 +01:00
Nick O'Leary
d711b01fe5 Merge pull request #4629 from node-red/multiplayer-1
Introduce multiplayer feature
2024-04-04 15:24:29 +01:00
Nick O'Leary
6e7fa6f921 Merge branch 'dev' into button-add-config-node 2024-04-03 14:02:40 +01:00
Nick O'Leary
343cde75a2 Merge pull request #4644 from node-red/resyn-dev
Resync recent fixes from master to dev
2024-04-03 14:02:17 +01:00
Nick O'Leary
2dc446e45b Merge branch 'dev' into resyn-dev 2024-04-03 13:57:10 +01:00
Nick O'Leary
884b7fa16a Merge pull request #4643 from node-red/fix-subflow-mod-config-select
Fix handling of subflow config-node select type in sf module
2024-04-03 13:54:41 +01:00
Nick O'Leary
173e065b68 Merge pull request #4639 from node-red/4638-fix-change-replace-bool
Fix change node handling of replacing with boolean
2024-04-02 20:07:01 +01:00
Nick O'Leary
9a3cb0b2b5 Merge pull request #4640 from node-red/fix-subflow-init-err
Guard refresh of unknown subflow
2024-04-02 20:06:47 +01:00
Nick O'Leary
6beae5a806 Merge pull request #4642 from node-red/4641-fix-subflow-module-debug-logging
Fix subflow module sending messages to debug sidebar
2024-04-02 20:06:31 +01:00
Nick O'Leary
66f4008bb8 Fix handling of subflow config-node select type in sf module 2024-04-02 20:01:48 +01:00
Nick O'Leary
a0636632a1 Fix subflow module sending messages to debug sidebar
Fixes #4641
2024-04-02 17:42:19 +01:00
Nick O'Leary
5dfa47ab6c Guard refresh of unknown subflow 2024-04-02 15:54:34 +01:00
Nick O'Leary
e9efe493f9 Remove only 2024-04-02 13:59:15 +01:00
Nick O'Leary
3bd782e62a Fix change node handling of replacing with boolean
Fixes #4638
2024-04-02 13:57:19 +01:00
Steve-Mcl
9b49cb2b50 Merge branch 'dev' into proxy-logiv-dev-v4 2024-03-31 16:49:28 +01:00
Nick O'Leary
963fe87f14 Merge pull request #4637 from node-red/tidy-up-palette-state
Ensure palette filter reapplies and clear up unknown categories
2024-03-28 15:36:05 +00:00
Nick O'Leary
ade4679e8c Merge pull request #4636 from node-red/rel318
Bump for 3.1.8
2024-03-28 15:23:07 +00:00
Nick O'Leary
40060a470b Merge pull request #4635 from node-red/sync-dev
Sync dev
2024-03-28 15:22:57 +00:00
Nick O'Leary
a6e8fbb54a Ensure palette filter reapplies and clear up unknown categories 2024-03-28 15:21:04 +00:00
Nick O'Leary
410b938442 Bump for 3.1.8 2024-03-28 15:02:02 +00:00
Nick O'Leary
ab7e9f94fa Merge branch 'dev' into sync-dev 2024-03-28 14:56:36 +00:00
Nick O'Leary
28e9ccd372 Merge pull request #4634 from node-red/pr_4623
Retain Palette categories collapsed and filter to localStorage
2024-03-28 14:53:15 +00:00
Nick O'Leary
9a66d9addd Merge pull request #4620 from node-red/pr_4387
Add support for plugin (only) modules to the palette manager
2024-03-28 14:35:33 +00:00
Nick O'Leary
8843bda477 Merge pull request #4628 from node-red/comms-updates
Comms API updates
2024-03-28 14:33:10 +00:00
Nick O'Leary
3278303eec Clear localStorage state on logout 2024-03-28 14:31:31 +00:00
Nick O'Leary
f5fd6e3a36 Rework palette state management 2024-03-28 14:23:39 +00:00
Gauthier Dandele
a173e8e70f Apply suggestions from code review
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-03-28 12:57:04 +01:00
Nick O'Leary
19dcc3a683 Merge pull request #4632 from node-red/4625-sf-env-err-handling
Add validation and error handling on subflow instance properties
2024-03-28 11:10:28 +00:00
Nick O'Leary
20d067c1ea Merge pull request #4633 from node-red/4617-hide-library-context-options
Hide import/export context menu if disabled in theme
2024-03-28 11:10:14 +00:00
Nick O'Leary
9526566799 Hide import/export context menu if disabled in theme 2024-03-28 11:00:10 +00:00
Nick O'Leary
0b9dd82c91 Merge pull request #4631 from node-red/4626-subflow-change-notification
Show change indicator on subflow tabs
2024-03-27 19:10:39 +00:00
Nick O'Leary
19213434f9 Add validation to subflow instance env properties 2024-03-27 19:08:25 +00:00
Nick O'Leary
014691346a Handle malformed env var values and log errors 2024-03-27 18:23:12 +00:00
Nick O'Leary
6738b95c29 Merge pull request #4630 from node-red/bump-express
Bump dependencies
2024-03-27 18:11:54 +00:00
Nick O'Leary
6a8230ec1e Show change icon on subflow tabs
Fixes #4626
2024-03-27 18:10:04 +00:00
Nick O'Leary
5679d264b6 Bump dependencies 2024-03-27 18:00:06 +00:00
Nick O'Leary
b20c5f3a8d Merge pull request #4621 from GogoVega/addfrench-v4
Add French translations for 4.0.0-beta.1
2024-03-27 17:48:07 +00:00
Nick O'Leary
014f206e9c Initial multiplayer feature 2024-03-27 17:30:44 +00:00
Nick O'Leary
068b93befa CComms API updates 2024-03-27 17:21:12 +00:00
GogoVega
65d8872cea Separate the "add new config-node" option into a button 2024-03-27 14:59:49 +01:00
GogoVega
bffd1d61b2 Improve with error handling, storage cleanup and centralization in one object 2024-03-26 16:58:45 +01:00
GogoVega
4788b81220 Replace setTimeout with a listener 2024-03-26 13:05:38 +01:00
GogoVega
9a07fc03c6 Retain palette collapse and filter to localStorage 2024-03-25 20:47:55 +01:00
GogoVega
954f518030 Add French translations for 4.0.0-beta.1 2024-03-25 12:02:38 +01:00
GogoVega
9f8ff71757 Add French translations for TCP and CSV nodes 2024-03-25 11:58:35 +01:00
GogoVega
06dd59dc81 Add missing global-config translation 2024-03-25 10:51:12 +01:00
Nick O'Leary
37265cf4ef Merge pull request #4619 from node-red/4600-reset-workspace-index
Reset workspace index when clearing nodes
2024-03-21 17:38:39 +00:00
Nick O'Leary
2531a5283a Include module property in plugin metadata 2024-03-21 17:04:03 +00:00
Nick O'Leary
4cc1a5d846 Merge branch 'pr_4387' of github.com:node-red/node-red into pr_4387 2024-03-21 16:56:49 +00:00
Nick O'Leary
8a63275989 Merge pull request #4613 from kazuhitoyokoi/master-fixglobalconfig
Remove typo in global config
2024-03-21 16:54:01 +00:00
Nick O'Leary
2d3e5f4ce0 Fix linting 2024-03-21 16:43:47 +00:00
Nick O'Leary
5135545c6c Merge branch 'dev' into pr_4387 2024-03-21 16:41:24 +00:00
Nick O'Leary
fef93818c9 Ensure plugins api provides a filtered view 2024-03-21 16:37:05 +00:00
Nick O'Leary
7fc64a84e8 Bump test helper 2024-03-21 15:16:49 +00:00
Nick O'Leary
02f7cdd5aa Ensure all httpRequest test servers are ready before tests run 2024-03-21 15:03:37 +00:00
Nick O'Leary
d7dcceef60 Add debug for http tests 2024-03-21 11:32:29 +00:00
Nick O'Leary
ae5e1570ae Reset workspace index when clearing nodes
Fixes #4600
2024-03-21 11:14:34 +00:00
Nick O'Leary
50baad9624 Merge pull request #4612 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for 4.0.0-beta.1
2024-03-21 11:08:28 +00:00
Nick O'Leary
fc0041bd91 Merge pull request #4615 from Steve-Mcl/4614-monaco-and-types
Update monaco to latest and node types to 18 LTS
2024-03-21 09:25:52 +00:00
Steve-Mcl
283f7f5992 support env var NR_PROXY_MODE "legacy"|"strict" 2024-03-17 17:21:33 +00:00
Steve-Mcl
1cf7b95891 add missing ref to getProxyForUrl helper 2024-03-17 17:20:53 +00:00
Steve-Mcl
7bdb8db5ff http node fix tests 2024-03-17 17:11:29 +00:00
Steve-Mcl
de27968e4e (re)add node.insecureHTTPParser) 2024-03-17 17:10:56 +00:00
Steve-Mcl
d31abda28f typo 2024-03-17 17:01:25 +00:00
Steve-Mcl
dcfb4c9a79 update unit tests 2024-03-17 16:36:24 +00:00
Steve-Mcl
bf065ee11d default to strict mode but permit settings 2024-03-17 16:34:42 +00:00
Steve-Mcl
5e3cbadffc merge dev 2024-03-17 12:51:57 +00:00
Steve-Mcl
535ef82e48 Merge remote-tracking branch 'upstream/dev' into proxy-logiv-dev-v4 2024-03-17 12:50:59 +00:00
Steve-Mcl
c368bfea3f var/const updates 2024-03-17 12:37:42 +00:00
Steve-Mcl
4498e4100e Update Monaco editor and type completions
closes #4614
2024-03-17 11:08:47 +00:00
Kazuhito Yokoi
4f1e4faede Add Japanese translations for TCP and CSV nodes 2024-03-16 19:32:42 +09:00
Kazuhito Yokoi
3ca045394a Remove typo in global config 2024-03-16 18:51:13 +09:00
Kazuhito Yokoi
9a19a1113e Fix image paths in previous welcome tour 2024-03-16 18:44:25 +09:00
Kazuhito Yokoi
1cd550022b Fix typos 2024-03-16 18:43:03 +09:00
Kazuhito Yokoi
bad08bafd7 Add Japanese translations for 4.0.0-beta.1 2024-03-16 18:34:24 +09:00
Nick O'Leary
f041a21f22 Update publish-script for 4.x 2024-03-14 16:56:32 +00:00
Nick O'Leary
712d78ca39 Merge pull request #4611 from node-red/rel4-beta-tour
Update for 4.0.0-beta.1 release
2024-03-14 16:50:10 +00:00
Nick O'Leary
93f2910bd2 Bump to 4.0.0-beta.1 2024-03-14 16:05:15 +00:00
Nick O'Leary
d0ef12c486 Update tour for 4.0-beta.0 2024-03-14 16:01:54 +00:00
Nick O'Leary
241fd09053 Merge pull request #4587 from Steve-Mcl/config-in-subflow
Support config selection in a subflow env var
2024-03-14 15:11:54 +00:00
Nick O'Leary
208dd2a457 Merge pull request #4436 from marcus-j-davies/allow-ws-headers
Feat: Add ability to set headers for WebSocket client
2024-03-13 17:20:07 +00:00
Nick O'Leary
e34ee44b21 Update packages/node_modules/@node-red/nodes/core/network/22-websocket.html 2024-03-13 17:19:20 +00:00
Nick O'Leary
d5f59307b7 Update packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js 2024-03-13 17:08:58 +00:00
Nick O'Leary
64136cc565 Update packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js 2024-03-13 17:08:42 +00:00
Nick O'Leary
3e2508c740 Merge branch 'dev' into config-in-subflow 2024-03-13 17:08:23 +00:00
Nick O'Leary
0853cd65b2 Merge pull request #4598 from joepavitt/header-styling
Improve the appearance of the Node-RED primary header
2024-03-13 17:07:13 +00:00
Nick O'Leary
01802c817b Merge branch 'master' into dev 2024-03-13 16:55:50 +00:00
Marcus Davies
7e10093bb8 Remove global access + reduce header list 2024-03-12 19:26:27 +00:00
Nick O'Leary
179032cd4d Merge pull request #4608 from node-red/rel317
Bump for 3.1.7 release
2024-03-12 17:43:32 +00:00
Nick O'Leary
6a6f0d04d6 Bump for 3.1.7 release 2024-03-12 14:25:41 +00:00
Nick O'Leary
add4d9758c Merge pull request #4603 from kazuhitoyokoi/master-addjpn
Add Japanese translation for v3.1.6
2024-03-11 16:07:28 +00:00
Kazuhito Yokoi
a0d3ea62b2 Add Japanese translation for v3.1.6 2024-03-10 23:36:20 +09:00
Joe Pavitt
54c17c3175 Merge branch 'dev' into header-styling 2024-03-08 13:48:43 +00:00
Nick O'Leary
80e60538e2 Merge pull request #4596 from node-red/revert-4352-rfdc
Revert "changed cloning library to rfdc for runtime module"
2024-03-08 11:40:21 +00:00
Joe Pavitt
84a76909e2 Improve the appearance of the Node-RED primary header 2024-03-08 10:51:25 +00:00
Nick O'Leary
033405fdbc Fully remove rfdc 2024-03-07 16:41:30 +00:00
Nick O'Leary
9444009a9b Revert "changed cloning library to rfdc for runtime module" 2024-03-07 16:40:32 +00:00
Nick O'Leary
29e9def314 Merge pull request #4595 from node-red/fix-rfdc-dependency
Add rfdc to @node-red/runtime package.json
2024-03-07 16:33:06 +00:00
Nick O'Leary
8832a1aa20 Add rfdc to @node-red/runtime package.json 2024-03-07 16:31:31 +00:00
Nick O'Leary
5beb6dbeee Merge pull request #4352 from anshumanr/rfdc
changed cloning library to rfdc for runtime module
2024-03-07 16:29:31 +00:00
Nick O'Leary
1261d26b23 Merge pull request #4439 from ralphwetzel/dev_link
Click on id in debug panel highlights node or flow
2024-03-07 16:27:15 +00:00
Nick O'Leary
0b9dd11fff Merge pull request #4513 from gorenje/html_node_content_and_attributes
HTML node: add option for collecting attributes and content
2024-03-07 16:24:28 +00:00
Dave Conway-Jones
08a607aa6a Merge branch 'dev' of https://github.com/node-red/node-red into dev 2024-03-07 16:21:03 +00:00
Nick O'Leary
e12efc320b Merge pull request #4408 from node-red/Fix-join-to-ignore-parts-in-manual-mode
Fix join node to ignore parts in manual mode
2024-03-07 16:20:05 +00:00
Dave Conway-Jones
3ded9de803 TCP node- when resetting, if no payload, stay disconnected 2024-03-07 16:19:51 +00:00
Nick O'Leary
d5b424910f Merge branch 'dev' into Fix-join-to-ignore-parts-in-manual-mode 2024-03-07 16:05:41 +00:00
Nick O'Leary
d94d13737f Merge pull request #4572 from kevinGodell/dev
let settings.httpNodeAuth accept single middleware or array of middlewares
2024-03-07 15:48:12 +00:00
Nick O'Leary
b1fa4918e3 Merge pull request #4386 from node-red/make-split/join-more-flexible
let split node specify property to split on, and join auto join correctly
2024-03-07 15:47:08 +00:00
Nick O'Leary
742aa2fa0d Merge branch 'dev' into make-split/join-more-flexible 2024-03-07 15:40:56 +00:00
Nick O'Leary
ce133c1c04 Merge pull request #4540 from Steve-Mcl/3934-csv-rfc4180
Add RFC4180 compliant mode to CSV node
2024-03-07 15:39:25 +00:00
Nick O'Leary
e4dc1779c3 Merge pull request #4590 from node-red/jsonata-update
Upgrade to JSONata 2.x
2024-03-07 14:28:35 +00:00
Nick O'Leary
22b4ab6bb2 Merge pull request #4468 from node-red/timestamp-formatting
Timestamp formatting
2024-03-07 14:28:23 +00:00
Nick O'Leary
7447e88a50 Merge pull request #4593 from hardillb/hardillb-patch-1
Update jsonata version
2024-03-07 14:26:02 +00:00
Ben Hardill
a193b79d3d Bump jsonata to match utils 2024-03-05 10:31:03 +00:00
Ben Hardill
da380f7464 Update jsonata version
Pulls in fix for CVE-2024-27307
2024-03-05 10:22:49 +00:00
Nick O'Leary
2dcff51125 Update timestamp formats 2024-03-04 16:51:06 +00:00
Nick O'Leary
b50e0533eb Add js date option to inject 2024-03-04 16:35:59 +00:00
Nick O'Leary
711545539f Allow typedInput timestamp to specify format 2024-03-04 16:35:54 +00:00
Nick O'Leary
a6cbceed28 Upgrade to JSONata 2.x 2024-03-04 16:32:31 +00:00
Nick O'Leary
269cf02c0b Merge pull request #4586 from node-red/rel316
Bump for 3.1.6 release
2024-03-01 11:47:57 +00:00
Steve-Mcl
837d17ab65 Merge branch 'dev' into config-in-subflow 2024-03-01 11:36:35 +00:00
Steve-Mcl
eff31c4bdc linting 2024-03-01 11:35:13 +00:00
Steve-Mcl
6a8f653b73 typo 2024-03-01 11:17:16 +00:00
Steve-Mcl
0cdb36f73d support constrained items in editable list 2024-03-01 11:08:50 +00:00
Steve-Mcl
db249356e6 permit config selection in subflow and subflow template 2024-03-01 11:07:26 +00:00
Steve-Mcl
d509c1a57c add "conf-types" to typedInput 2024-03-01 11:06:27 +00:00
Nick O'Leary
fb50e2772a Bump for 3.1.6 release 2024-03-01 10:50:06 +00:00
Nick O'Leary
058c97138a Merge pull request #4582 from node-red/3795-allow-env-var-in-num-field-validation
Do not flag env var in num typedInput as error
2024-02-26 17:01:45 +00:00
Nick O'Leary
828ae29aed Merge pull request #4581 from node-red/4579-fix-undef-env-vars
Handle undefined env vars
2024-02-26 17:01:27 +00:00
Nick O'Leary
6a0f45140c Merge pull request #4568 from JaysonHurst/fips
fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 …
2024-02-26 17:00:26 +00:00
Nick O'Leary
50a267528d Merge pull request #4580 from giscafer/remove-never-use-code
chore: remove never use import code
2024-02-26 16:58:20 +00:00
Nick O'Leary
220786be60 Do not flag env var in num typedInput as error 2024-02-26 16:55:01 +00:00
Nick O'Leary
fa78bb3d78 Handle undefined env vars
Fixes #4579
2024-02-26 16:17:09 +00:00
Nick O'Leary
9a32ebd0c0 Merge pull request #4578 from kazuhitoyokoi/master-fiximportdialog
Fix example flow name in import dialog
2024-02-26 16:09:10 +00:00
giscafer
4643f5e8cc chore: remove never use import code 2024-02-25 22:44:01 +08:00
Kazuhito Yokoi
7de0984d6d Update test case for example flow name 2024-02-25 17:38:46 +09:00
Kazuhito Yokoi
635334f096 Fix example flow name in import dialog 2024-02-25 17:04:42 +09:00
Nick O'Leary
f0d0990b5a Merge pull request #4575 from giscafer/master
fix: template node zh-CN translation
2024-02-22 13:03:24 +00:00
giscafer
43b3589451 fix: template node zh-CN translation 2024-02-22 13:02:06 +08:00
Nick O'Leary
016a19ba7c Merge pull request #4570 from node-red/fix-icon-scaling
Fix missing node icons in workspace
2024-02-20 10:37:33 +00:00
Nick O'Leary
6802539ccc Merge pull request #4571 from node-red/bump-node-version
Bump minimum version to node 18
2024-02-20 10:37:25 +00:00
Kevin Godell
74efaa3c2d let settings.httpNodeAuth accept single middleware or array of middlewares to replace built-in basic-auth middleware at top level of RED.httpNode 2024-02-19 16:42:14 -06:00
Nick O'Leary
a5223709ba Bump minimum version to node 18 2024-02-19 16:38:06 +00:00
Nick O'Leary
2291dc6132 Merge branch 'master' into dev 2024-02-19 16:14:58 +00:00
Nick O'Leary
aeb79bce2a Fix missing node icons in workspace 2024-02-19 16:07:22 +00:00
Jayson Hurst
0ab9b9a5fd Merge branch 'master' into fips 2024-02-16 17:53:30 -07:00
Jayson Hurst
56e58521bd Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 crypto hashes to work with the FIPS crypto policy. 2024-02-17 00:35:03 +00:00
Steve-Mcl
b2548c158d fix layout and missing tip 2024-02-09 20:50:24 +00:00
Steve-Mcl
5a48d6d4cd Merge branch 'dev' into 3934-csv-rfc4180 2024-02-09 20:33:49 +00:00
Nick O'Leary
7ee2b93b10 Merge pull request #4553 from lgrkvst/dev
Allow RED.view.select to select links
2024-02-05 16:28:17 +00:00
Christian Lagerkvist
cc611a7a02 Fixes #4551 2024-01-30 10:37:52 +01:00
Steve-Mcl
1a9c34fe40 Merge branch 'dev' into 3934-csv-rfc4180 2024-01-23 10:59:05 +00:00
Steve-Mcl
ff8eb0ec2b Add RFC spec mode to CSV node
closes #3934
2024-01-23 10:35:08 +00:00
Patrick Wozniak
28907082f1 fix usage of vm.Script() 2024-01-21 02:16:00 +01:00
Patrick Wozniak
f83174c40a fix use of vm.Script by adding new 2024-01-21 01:23:07 +01:00
Patrick Wozniak
ec062d008f replace vm.createScript in favor of vm.Script 2024-01-21 01:13:00 +01:00
Patrick Wozniak
a587655a5a adding pollyfill for vm.createScript
adds support for bun.sh
2024-01-21 01:00:02 +01:00
Nick O'Leary
f66b48e586 Merge pull request #4525 from node-red/fix-change-node-boolean-response
Fix change node to return boolean if asked
2024-01-16 11:58:30 +00:00
Nick O'Leary
931a2344b4 Merge pull request #4480 from node-red/context-auto-complete
Add auto-complete to flow/global typedInput types
2024-01-15 15:53:46 +00:00
Dave Conway-Jones
dd3c75d298 Fix change node to return boolean if asked
to fix #4372
2024-01-14 12:56:25 +00:00
Nick O'Leary
4a4a15de93 Fix context store handling in autocomplete 2024-01-09 01:05:09 +00:00
Nick O'Leary
a007ab7f2e Merge pull request #4492 from node-red/envvar-auto-complete
Add auto-complete for env vars
2024-01-08 23:41:12 +00:00
Gerrit Riessen
7b01457038 HTML node: add option to for attribites and content 2024-01-06 00:12:09 +01:00
Nick O'Leary
54e6d60fe5 Add simple caching of env var lookup 2024-01-05 21:07:20 +00:00
Nick O'Leary
c2710f4f6f Add auto-complete for env vars 2023-12-20 17:52:52 +00:00
Nick O'Leary
20187b51b1 Fix up cache scope 2023-12-20 16:51:34 +00:00
Nick O'Leary
4be6d57d98 Apply suggestions from code review
Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com>
2023-12-20 16:39:52 +00:00
GogoVega
282d52cf0b Fix non-boolean returned value + try to find validation msg in tooltip 2023-12-17 19:22:44 +01:00
Nick O'Leary
a77f8cc3e9 Clear context cache when closing edit dialog 2023-12-15 15:09:15 +00:00
Nick O'Leary
ea4c0cdbee Fix error when switching context types 2023-12-14 17:14:56 +00:00
GogoVega
ba08cf0417 Add tooltip input validation msg 2023-12-12 17:17:33 +01:00
Nick O'Leary
7197153fd5 Support bracket-notation in auto complete when needed 2023-12-11 21:18:44 +00:00
Nick O'Leary
b9c1dedab3 Add auto-complete to flow/global typedInput types 2023-12-11 17:55:02 +00:00
Nick O'Leary
918943816f Merge branch 'master' into dev 2023-12-08 10:27:04 +00:00
Nick O'Leary
33cf34f7c7 Merge branch 'master' into dev 2023-12-04 15:58:45 +00:00
Ralph Wetzel
febc769df5 use of camelCasing: node_selector => nodeSelector 2023-11-21 20:48:52 +01:00
Ralph Wetzel
ea483218ea Click on id in debug panel highlights node or flow 2023-11-18 21:03:13 +01:00
anshumanr
c8f3ad8ac7 Merge branch 'dev' into rfdc 2023-11-17 13:17:26 -05:00
Marcus Davies
7916dc9c05 Slightly cleaner fasly checks 2023-11-14 22:31:25 +00:00
Marcus Davies
3123a5ee51 Feat: Add ability to set headers for WebSocket client 2023-11-14 20:44:04 +00:00
Nick O'Leary
5b5b06cc06 Merge pull request #4406 from node-red/tcp-request-node-reset-when-in-stay-connected-mode
Let msg.reset  reset Tcp request node connection when in stay connected mode
2023-11-07 17:42:17 +00:00
Dave Conway-Jones
f49f692ffa Better fix for TCP node reset
now handles reply out node,
and can specify which connection to reset.
2023-11-03 11:57:16 +00:00
Dave Conway-Jones
10ce681d46 Fix join node to ignore parts in manual mode
to fix #1957
2023-11-01 13:59:09 +00:00
Nick O'Leary
08c6ea94cb Merge pull request #4347 from ZJvandeWeg/zj-remove-production-flag-npm
npm: Remove production flag on npm invocation
2023-11-01 14:18:14 +01:00
Nick O'Leary
fea1da5542 Merge pull request #4402 from node-red/Let-debug-status-length-be-settable
Let debug node status msg length be settable via settings
2023-11-01 14:13:38 +01:00
Dave Conway-Jones
32e8f4eac6 Add help info 2023-11-01 12:33:57 +00:00
Dave Conway-Jones
bfe5a8a986 Update 31-tcpin.js
don't send if payload not defined.
2023-11-01 12:27:11 +00:00
Dave Conway-Jones
f2cb5ea44e Allow msg.reset to reset connection when tcp request in stay connected mode 2023-11-01 12:07:50 +00:00
Dave Conway-Jones
c7335ed25b Let debug node status msg length be settable via settings 2023-10-31 09:11:17 +00:00
Dave Conway-Jones
5fda57c730 too enthusiastic clearing up property
reverted
2023-10-18 13:51:40 +01:00
Ralph Wetzel
3df3096bb4 Fix to please linter 2023-10-18 08:07:49 +02:00
Ralph Wetzel
bb10d5bb94 Fix scope of variable 2023-10-18 08:07:06 +02:00
Ralph Wetzel
1704ab7454 Compensate for incomplete config data (e.g. test) 2023-10-18 08:06:15 +02:00
Ralph Wetzel
3275a76fb0 Adapt test after modification to uninstallModule 2023-10-18 08:04:52 +02:00
Ralph Wetzel
81937ddc45 Add plugin support to palette manager 2023-10-17 22:44:33 +02:00
Dave Conway-Jones
9fd929ac1e let split node specify property to split on
and let join auto join the correct property
or manually the specified one.
2023-10-17 21:12:13 +01:00
Mauricio Bonani
3e6f0acf79 Refactor code 2023-10-03 15:42:33 -04:00
Mauricio Bonani
7f93d943d7 Merge remote-tracking branch 'upstream/master' into fix-build-script 2023-10-03 09:04:40 -04:00
anshumanr
c48a15c915 Merge branch 'dev' into rfdc 2023-09-25 20:24:23 -04:00
Nick O'Leary
eb940d6d57 Merge pull request #4367 from hlovdal/timer_testing_fix
Timer testing fix
2023-09-25 18:19:30 +01:00
Håkon Løvdal
9091935d77 Update variable names 2023-09-25 18:53:11 +02:00
Håkon Løvdal
34e8d2b051 Add workaround for timers triggering too early in test 2023-09-24 18:16:59 +02:00
Håkon Løvdal
0c2ab13c48 Print all delta values in case of error, not just the last value
Which might not even be the one triggering the error condition.
2023-09-24 18:16:59 +02:00
Håkon Løvdal
9489953a8f Introduce timeout constant 2023-09-24 18:16:59 +02:00
asr
b0136d03ea reverted earlier change to use rfdc 2023-09-19 11:13:39 -04:00
asr
9fe73645ad remove package lock 2023-09-19 05:55:19 -04:00
ZJ van de Weg
54d4079457 npm: Remove production flag on npm invocation
When installing packages the `--production` flag used to be added to the
arguments that `npm` received. As npm wants developers to use the
`--omit=dev` flag instead it warned users on STDERR. Standard error was
captured by Node-RED and output to the logs as being an error. This
caught users off-guard and they expected something to have gone
wrong.

With this change the `--omit=dev` is used instead, to remove the
warning.

This change works for NPM of version 8 and beyond[1], included in
Node.JS 16. This change will not work on NPM version 6[2] which is included
in Node.JS 14[3].

[1]: https://docs.npmjs.com/cli/v8/commands/npm-install#omit
[2]: https://docs.npmjs.com/cli/v6/commands/npm-install
[3]: https://nodejs.org/en/download/releases#looking-for-latest-release-of-a-version-branch
2023-09-17 08:43:11 +02:00
asr
8e1a21e682 typo 2023-09-14 12:27:17 -04:00
asr
d84cdca43e Revert "typo"
This reverts commit 1c6dcd373d.
2023-09-14 12:24:52 -04:00
asr
1c6dcd373d typo 2023-09-14 12:22:34 -04:00
asr
4410ce1486 changed cloning library to rfdc 2023-09-14 11:40:36 -04:00
Nick O'Leary
cef3a01042 Merge pull request #4322 from node-red/prep4
Bump to 4.0.0-dev
2023-09-06 15:04:41 +01:00
Nick O'Leary
0c042abcab Bump to 4.0.0-dev 2023-09-06 14:45:45 +01:00
Steve-Mcl
f61971bc23 first pass at common proxy determining logic 2022-12-01 12:53:03 +00:00
Mauricio Bonani
12543d2c2a Ensure all CSS variables are in the output file 2022-07-06 19:06:11 -04:00
324 changed files with 55606 additions and 7458 deletions

View File

@@ -12,12 +12,11 @@ permissions:
jobs:
build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
node-version: [18, 20, 22.4.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
@@ -29,8 +28,3 @@ jobs:
- name: Run tests
run: |
npm run test
# - name: Publish to coveralls.io
# if: ${{ matrix.node-version == 16 }}
# uses: coverallsapp/github-action@v1.1.2
# with:
# github-token: ${{ github.token }}

View File

@@ -1,620 +1,175 @@
#### 3.1.5: Maintenance Release
Runtime
- Fix require of dns module (#4562) @knolleary
- Ensure global creds object is initialised when adding first cred (#4561) @knolleary
#### 3.1.4: Maintenance Release
#### 4.0.2: Maintenance Release
Editor
- Highlight errors in config node sidebar (#4529) @knolleary
- Improve feedback in import dialog to show conflicted nodes (#4550) @knolleary
- Modify node users info in config editor footer (#4528) @knolleary
- Handle modified-nodes deploy after replacing unknown config node (#4556) @knolleary
- Handle undefined default export when importing module (#4539) @knolleary
- Fix icon scaling for non .svg icons (#4491) @ralphwetzel
- (convertNode) Do not create the credentials object if there is nothing to export (#4544) @GogoVega
- Ensure subflow instance node has g property set (#4538) @knolleary
- Handle importing flow with existing subflow and instance node (#4546) @knolleary
- Update index.mst (#4483) @gorenje
- Include top level property name when copying path from context (#4527) @knolleary
- Add handling to disable items on context menu (#4500) @kazuhitoyokoi
- Focus Quick Add dialog from context menu (#4516) @kazuhitoyokoi
- Fix subflow ports in Quick Add dialog (#4518) @kazuhitoyokoi
- Fix location of subflow ports in palette (#4502) @kazuhitoyokoi
- Client/Editor Events: fix off-in-on pattern emulating once (#4484) @gorenje
- Restore caching busting functionality without using explict version number (#4512) @knolleary
- Do not translate the list of available languages (#4531) @GogoVega
- Add French translation of v3.1.3 changes (#4477) @GogoVega
- i18n(es-ES) Spanish Spain translation (#4495) @joebordes
- Add missing validation messages (#4487) @GogoVega
- Add Japanese translations for v3.1.3 (#4498) @kazuhitoyokoi
- Replace `rename` by `edit` for the menu flow label (#4506) @GogoVega
- Update editor.json fix typo in German translation (#4552) @guidoffm
- Use a more subtle border on the header (#4818) @bonanitech
- Improve the editor's French translations (#4824) @GogoVega
- Clean up orphaned editors (#4821) @Steve-Mcl
- Fix node validation if the property is not required (#4812) @GogoVega
- Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary
Runtime
- Bump the github-actions group with 1 update (#4554) @app/dependabot
- Clone objects types when getting env values (#4519) @knolleary
- Ensure global-config credential env vars are merged on deploy (#4526) @knolleary
- Allow auth cookie name to be customised (#4815) @knolleary
- Guard against undefined sessions in multiplayer (#4816) @knolleary
Nodes
- 21-httprequest.js remove unused code, because of broken use of toLowercase (#4522) @gorenje
#### 3.1.3: Maintenance Release
#### 4.0.1: Maintenance Release
Editor
- Add missing en-us messages (#4475) @knolleary
#### 3.1.2: Maintenance Release
Editor
- Relax some node validators to allow undefined value (#4471) @knolleary
- Fix switch validation of typeof field (#4465) @knolleary
- Use move cursor when hovering on group border (#4467) @knolleary
- Added action list Chinese (Simplified and Traditional) translation + v3.1.1 changes (#4470) @wangyiyi2056
- Add French translation of `action-list` + v3.1.1 changes (#4466) @GogoVega
Runtime
- Ensure nested groups inside subflows have their g props remapped (#4472) @knolleary
#### 3.1.1: Maintenance Release
Editor
- Fix debug filter (#4461) @knolleary
- Fix various issues with debug pop-out window (#4459) @knolleary
- Ensure subflow instances keep track of their groups (#4457) @knolleary
- Fix `validateNodeProperty` without validator provided (#4455) @GogoVega
- Debounce node-removed notifications (#4453) @knolleary
- Don't try to load the parents of the first commit (#4448) @bonanitech
- Allow a theme to specifiy which theme mermaid should use (#4441) @knolleary
- Update browser title with flow name if set (#4427) @knolleary
- Ensure typeSearch handles undefined node definitions (#4423) @knolleary
- Ensure group w/h are imported if present (#4426) @knolleary
- Hide node status background when there is no status to show (#4425) @knolleary
- Add a close button to the restart-required notification (#4407) @knolleary
- Extend typedInput "num" type validity check to NaN, binary, octal & hex (#4371) @ralphwetzel
- Fix unintended new line in node name (#4399) @kazuhitoyokoi
- Ctrl-Enter does not close tray (Monaco) #4377 (#4382) @hazymat
- fix buffer viewer to handle 0b style binary (#4393) @dceejay
- Rework mermaid integration to support off-DOM rendering (#4364) @knolleary
- Add missing nls labels to context menu (#4365) @knolleary
- Ensure subflow instance credential property values are extracted (#4802) @knolleary
- Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega
- Fix the config node select value assignment (#4788) @GogoVega
- Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi
- Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi
Runtime
- Bump the github-actions group with 2 updates (#4404) @app/dependabot
- Handle unknown node reference inside subflow module (#4460) @knolleary
- Add modules.install audit event when external module installed (#4452) @knolleary
- Allow import of modules with subpath in specifier (#4451) @knolleary
- Update node-red-admin version (#4438) @knolleary
- Handle false-like env vars properly (#4411) @knolleary
- Only save settings once during node load process (#4409) @knolleary
- Ensure global-config nodes lookup cred values properly (#4405) @knolleary
- Handle credential env var evaluation when no value set (#4362) @knolleary
- Don't commit package-lock.json (#4354) @bonanitech
- Fix env evaluation when one env references another in the same object (#4361) @knolleary
- Add dependabot for Github Actions (#4312) @Rotzbua
- Update outdated Github Actions (#4311) @Rotzbua
- github: Request `npm run test` in PR template (#4348) @ZJvandeWeg
- Add French translation of v3.1.0-beta.4 changes + slight improvements (#4329) @GogoVega
- Handle nodes with multiple input handlers properly (#4332) @knolleary
- Soften the language around unrequited PRs (#4351) @knolleary
Nodes
- CSV: make CSV export way faster by not re-allocating and handling huge string (#4349) @Fadoli
- Delay: Fix regression in delay node to not pass on msg.reset (#4350) @dceejay
- Link Call: Handle undefined linkType value for existing link-call nodes (#4331) @knolleary
- MQTT: Guard against node.broker being undefined (#4454) @knolleary
- MQTT: check topic length > 0 before publish (#4416) @dceejay
- Switch/Change: Improve validation of switch/change node rules (#4368) @knolleary
- Template: Fix height of description editor in template node (#4346) @kazuhitoyokoi
- Various: Add validators to any fields using msg-typed Input (#4440) @knolleary
#### 3.1.0: Milestone Release
Editor
- Default filter to All Catalogues and show nodes for small lists (#4318) @knolleary
- Better distinguish between ctrl and meta keys on mac (#4310) @knolleary
- Ensure junction appears when filtering quick-add list (#4297) @knolleary
- Update message catalogs for JSONata Expression editor (#4287) @kazuhitoyokoi
- Add tooltip to relevance sort button in user settings UI (#4288) @kazuhitoyokoi
- Capture workspace dirty state when quick-adding junction (#4283) @knolleary
- Add docs for $clone function (#4284) @knolleary
Runtime
- Dependency updates (#4317) @knolleary
- Ensure storage/util.writeFile handles concurrent write attempts (#4316) @knolleary
- Migrate http -> https for nodered.org (#4313) @Rotzbua
- Add Node 20 to GH Action test matrix (#4305) @Rotzbua
- Handle group-scoped nodes inside subflow (#4301) @knolleary
- Handle non-url-safe chars in context api (#4298) @knolleary
- Fix git pull operation in project feature (#4290) @kazuhitoyokoi
- Change linefeed codes in Korean message catalogs (#4286) @kazuhitoyokoi
- Fix file permissions of message catalogs (#4285) @kazuhitoyokoi
- Update tour (#4278) @knolleary
Nodes
- File: Fix handling in file nodes when number is specified as file name (#4267) @kazuhitoyokoi
- Function: Adding function timeout to settings file (#4265) (#4309) @knolleary
- Function: Fix function setup tab layout (#4299) @knolleary
- HTTP Request: Handle 204 in httprequest JSON (#4262) @sammachin
- JSON: Fix test cases of JSON node (#4275) @kazuhitoyokoi
- MQTT: Remove unnecessary check for clientid if autoUnsub set (#4302) @knolleary
##### 3.1.0-beta.4: Beta Release
Editor
- Add Japanese translation for 3.1.0 (#4252) @kazuhitoyokoi
- Improve Catalogue visibility (#4248) @Steve-Mcl
- Add support for wiring and moving junctions on touch device (#4244) @Steve-Mcl
- Show errors and statuses of config nodes in the sidebar when no catch node is available (#4231) @bvmensvoort
- Improve wiring for horizontally aligned nodes (#4232) @knolleary
- French translation of Welcome Tours (#4200) @GogoVega
- French translation of v3.1.0-beta.3 changes (#4199) @GogoVega
- add Japanese message for 3.1.0 beta 3 (#4209) @HiroyasuNishiyama
- Dont clone the group nodes `node` array when saving edits (#4208) @Steve-Mcl
Runtime
- Add NR_SUBFLOW_NAME/ID/PATH env vars (#4250) @knolleary
- Evaluate all env vars as part of async flow start (#4230) @knolleary
- Add support for httpStatic middleware (#4229) @knolleary
- Ensure group nodes are properly exported in /flow api (#4803) @knolleary
Nodes
- Fix JSONata in file nodes (#4246) @kazuhitoyokoi
- Fix timeout icon in function and link call nodes (#4253) @kazuhitoyokoi
- Fix connection keep-alive in http request node (#4228) @knolleary
- adding timeout attribute to function node (#4177) @k1ln
- Fix manual mode join when multiple sequences being handled (#4143) @BitCaesar
- Fix delay node flush issue (#4203) @dceejay
- Update status and catch node labels in group mode (#4207) @Steve-Mcl
- Joins: make using msg.parts optional in join node (#4796) @dceejay
- HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl
- HTTP Request: Remove default user agent (#4791) @Steve-Mcl
##### 3.1.0-beta.3: Beta Release
#### 4.0.0: Milestone Release
This marks the next major release of Node-RED. The following changes represent
those added since the last beta. Check the beta release details below for the complete
list.
Breaking Changes
- Node-RED now requires Node 18.x or later. At the time of release, we recommend
using Node 20.
Editor
- Select the item that is specified in a deep link URL (#4113) @Steve-Mcl
- Update to Monaco 0.38.0 (#4189) @Steve-Mcl
- Place subflow outputs/inputs relative to current view (#4183) @knolleary
- Enable RED.view.select to select group by id (#4184) @knolleary
- Combine existing env vars when merging groups (#4182) @knolleary
- Avoid creating empty global-config node if not needed (#4153) @knolleary
- Fix group selection when using lasso (#4108) @knolleary
- Use editor path in generating localStorage keys (#4151) @mw75
- Ensure no node credentials are included when exporting to clipboard (#4112) @knolleary
- Fix jsonata expression test ui (#4097) @knolleary
- Fix search button in palette popover (#4096) @knolleary
- Add `httpStaticCors` (#4761) @knolleary
- Update dependencies (#4763) @knolleary
- Sync master to dev (#4756) @knolleary
- Add tooltip and message validation to `typedInput` (#4747) @GogoVega
- Replace bcrypt with @node-rs/bcrypt (#4744) @knolleary
- Export Nodes dialog refinement (#4746) @Steve-Mcl
#### 4.0.0-beta.4: Beta Release
Editor
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
- Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi
- Translate the number of items selected in the options list (#4730) @GogoVega
- Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega
- Deleting a grouped node should update the group (#4714) @GogoVega
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
- Add missing tooltips to Sidebar (#4713) @GogoVega
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
- Update to Monaco 0.49.0 (#4725) @Steve-Mcl
- Add Japanese translations for 4.0.0-beta.3 (#4726) @kazuhitoyokoi
- Show lock on deploy if user is read-only (#4706) @knolleary
Runtime
- Allow options object on each httpStatic configuration (#4109) @kevinGodell
- Ensure non-zero exit codes for errors (#4181) @knolleary
- Ensure external modules are installed synchronously (#4180) @knolleary
- Update dependecies include got (#4155) @knolleary
- Add Japanese translations for v3.1 beta.2 (#4158) @kazuhitoyokoi
- Ensure express server options are applied consistently (#4178) @knolleary
- Remove version info from theme endpoint (#4179) @knolleary
- Add Japanese translations for welcome tour of 3.1.0 beta.2 (#4145) @kazuhitoyokoi
- Added SHA-256 and SHA-512-256 digest authentication (#4100) @sroebert
- Add "timers" types to known types (#4103) @Steve-Mcl
- Ensure all CSS variables are in the output file (#3743) @bonanitech
- Add httpAdminCookieOptions (#4718) @knolleary
- chore: migrate deprecated `util.isArray` (#4724) @Rotzbua
- Add --version cli args (#4707) @knolleary
- feat(grunt): fail if files are missing (#4739) @Rotzbua
- fix(node-red-pi): node-red not started by path (#4736) @Rotzbua
- fix(editor): remove trailing slash (#4735) @Rotzbua
- fix: remove deprecated mqtt.js (#4733) @Rotzbua
Nodes
- Allow Catch/Status nodes to be scoped to their group (#4185) @NetHans
- MQTT: Option to disable MQTT topic unsubscribe on disconnect (#4078) @flying7eleven
- Perform Proxy logic more like cURL (#4616) @Steve-Mcl
##### 3.1.0-beta.2: Beta Release
#### 4.0.0-beta.3: Beta Release
Editor
- NEW: Add change icon to tabs (#4068) @knolleary
- NEW: Complete overhaul of Group UX (#4079) @knolleary
- NEW: Add link to node help in node edit dialog footer (#4065) @knolleary
- NEW: Added editor feature for connecting multiple nodes to single node (#4051) @sonntam
- NEW: Increase workspace size to 8000x8000 (#4094) @knolleary
- Ensure node buttons are redrawn when flow lock state is changed (#4091) @knolleary
- Prevent loops being created with junction nodes (#4087) @knolleary
- Prevent opening locked node's edit dialog (#4069) @knolleary
- Reverse direction of tab scroll to expected direction (#4064) @knolleary
- Add cancel operation to editableList (#4077) @HiroyasuNishiyama
- Apply Mermaid diagram for project settings UI (#4054) @kazuhitoyokoi
- Add tooltip for show/hide button on info sidebar (#4050) @kazuhitoyokoi
- Fix align nodes on locked tab (#4072) @HiroyasuNishiyama
- Fix importing connected link nodes into a subflow (#4082) @knolleary
- Fix to add empty marker to empty group (#4060) @HiroyasuNishiyama
- Fix image URLs for v3.0 tour (#4053) @kazuhitoyokoi
- Show scrollbar in notification dialog only when needed (#4048) @kazuhitoyokoi
- Update-monaco-and-typings (#4089) @Steve-Mcl
- Update jquery UI (#4088) @knolleary
- Support i18n of lock/unlock buttons in flow property UI (#4049) @kazuhitoyokoi
- Translation kr (#3895) @hae-iotplatform
- Translation zhcn (请懂中文的帮忙review) (#3952) @cliyr
- Add French translation of nodes (#3964) @GogoVega
- Add French translation (#3962) @GogoVega
- Portuguese Brazilian (pt-BR) translation (#3804) @FabsMuller
- Improve background-deploy notification handling (#4692) @knolleary
- Hide workspace tab on middle mouse click (#4657) @Steve-Mcl
- multiplayer: Add user presence indicators (#4666) @knolleary
- Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi
- Add French translations for 4.0.0-beta.2 (#4681) @GogoVega
- Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi
- Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary
- Add npm install timeout notification (#4662) @hardillb
- Fix undo of subflow env property edits (#4667) @knolleary
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
Runtime
- NEW: Generate stable ids for subflow instance internal nodes (#4093) @knolleary
- NEW: Change default file name to flows.json in project feature (#4073) @kazuhitoyokoi
- NEW: Deprecate synchronous access to jsonata (#4090) @knolleary
- Add Node 18 to test matrix (#4084) @knolleary
- Bump minimum nodejs version supported to match documented value (#4086) @knolleary
- Update monaco docs link in settings.js (#4075) @Steve-Mcl
- Remove duplicated messages in the message catalog (#4066) @kazuhitoyokoi
- Ensure errors in preDeliver callback are handled (#3911) @knolleary
- Fix "EADDRINUSE" error (#4046) @bggbr
- Allow blank strings to be used for env var property substitutions (#4672) @knolleary
- Use rfdc for cloning pure JSON values (#4679) @knolleary
- fix: remove outdated Node 11+ check (#4314) @Rotzbua
- feat(ci): add new nodejs v22 (#4694) @Rotzbua
- fix(node): increase required node >=18.5 (#4690) @Rotzbua
- fix(dns): remove outdated node check (#4689) @Rotzbua
- fix(polyfill): remove import module polyfill (#4688) @Rotzbua
- Fix typo (#4686) @Rotzbua
Nodes
- Link Call: Clear link-call timeouts when node is closed (#4085) @knolleary
- Join: ensure inflight status is cleared when in auto mode (#4083) @knolleary
- File Out: Fix extra newline append for multipart file write (#3915) @dceejay
- Add validators for complete and link call nodes (#4056) @kazuhitoyokoi
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
##### 3.1.0-beta.1: Beta Release
#### 4.0.0-beta.2: Beta Release
Editor
- NEW: Locking Flows (#3938) @knolleary
- NEW: Improve UX around hiding flows via context menu (#3930) @knolleary
- NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama
- NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama
- NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary
- NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama
- Remember compact/pretty flow export user choice (#3974) @Steve-Mcl
- fix .red-ui-notification class (#4035) @xiaobinqt
- Fix border radius on Modules list header (#4038) @bonanitech
- fix workspace reference error in case of empty tabs (#4029) @HiroyasuNishiyama
- Disable delete tab menu when single tab exists (#4030) @HiroyasuNishiyama
- Disable hide all menu if all tabs hidden (#4031) @HiroyasuNishiyama
- fix hide subflow tooltip (#4033) @HiroyasuNishiyama
- Fix disabled menu items in project feature (#4027) @kazuhitoyokoi
- Let themes change radialMenu text colors (#3995) @bonanitech
- Add Japanese translations for v3.0.3 (#4012) @kazuhitoyokoi
- Add Japanese translation for v3.1.0-beta.0 (#3997) @kazuhitoyokoi
- Add Japanese translation for v3.1.0-beta.0 (#3916) @kazuhitoyokoi
- Hide subflow category after deleting subflow (#3980) @kazuhitoyokoi
- Prevent dbl-click opening node edit dialog with text selected (#3970) @knolleary
- Handle replacing unknown node inside group or subflow (#3921) @knolleary
- Fix #3939, red border red-ui-typedInput-container (#3949) @Steveorevo
- i18n item URL copy notification & add Japanese message (#3946) @HiroyasuNishiyama
- add Japanese message for item url copy actions (#3947) @HiroyasuNishiyama
- Fix autocomplete entry for responseUrl (#3884) @knolleary
- Fix Japanese translation for JSONata editor (#3872) @HiroyasuNishiyama
- Fix search type with spaces (#3841) @Steve-Mcl
- Fix error hanndling of JSONata expression editor for extended functions (#3871) @HiroyasuNishiyama
- Add button type to the adding SSH key button (#3866) @kazuhitoyokoi
- Check radio button as default in project dialog (#3879) @kazuhitoyokoi
- Add $clone as supported function (#3874) @HiroyasuNishiyama
- Env var jsonata (#3807) @HiroyasuNishiyama
- Add Japanese translation for v3.0.2 (#3852) @kazuhitoyokoi
- Introduce multiplayer feature (#4629) @knolleary
- Separate the "add new config-node" option into a new (+) button (#4627) @GogoVega
- Retain Palette categories collapsed and filter to localStorage (#4634) @knolleary
- Ensure palette filter reapplies and clear up unknown categories (#4637) @knolleary
- Add support for plugin (only) modules to the palette manager (#4620) @knolleary
- Update monaco to latest and node types to 18 LTS (#4615) @Steve-Mcl
Runtime
- Force IPv4 name resolution to have priority (#4019) @dceejay
- Fix async loading of modules containing both nodes and plugins (#3999) @knolleary
- Use main branch as default in project feature (#4036) @kazuhitoyokoi
- Rename package var to avoid strict mode error (#4020) @knolleary
- Fix typos in settings.js (#4013) @ypid
- Ensure credentials object is removed before returning node in getFlow request (#3971) @knolleary
- Ignore commit error in project feature (#3987) @kazuhitoyokoi
- Update dependencies (#3969) @knolleary
- Add check that node sends object rather than primitive type (#3909) @knolleary
- Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname (#3912) @knolleary
- Fix nodesDir scan when node package has js/html in sub dir to package.json (#3867) @Steve-Mcl
- Fix file permissions (#3917) @kazuhitoyokoi
- ci: add minimum GitHub token permissions for workflows (#3907) @boahc077
- Fix handling of subflow config-node select type in sf module (#4643) @knolleary
- Comms API updates (#4628) @knolleary
- Add French translations for 4.0.0-beta.1 (#4621) @GogoVega
- Add Japanese translations for 4.0.0-beta.1 (#4612) @kazuhitoyokoi
Nodes
- Fix change node handling of replacing with boolean (#4639) @knolleary
- Catch: fix typo in catch.html (#3965) @we11adam
- Change: Fix change node overwriting msg with itself (#3899) @dceejay
- Comment node: Clarify where the text will appear (#4004) @dirkjanfaber
- CSV: change replace to replaceAll (#3990) @dceejay
- CSV node: check header properties for ' and " (#3920) @dceejay
- CSV: Fix for CSV undefined property (#3906) @dceejay
- Delay: let delay node handle both flush then reset (#3898) @dceejay
- Function: Limit number of ports in function node (#3886) @kazuhitoyokoi
- Function: Remove dot from variable name for external module in function node (#3880) @kazuhitoyokoi
- Function: add function node monaco types util and promisify (#3868) @Steve-Mcl
- HTTP In: Ensure msg.req.headers is enumerable (#3908) @knolleary
- HTTP Request: Support form-data arrays (#3991) @hardillb
- HTTP Request: Fix httprequest tests to be more lenient on error message (#3922) @knolleary
- HTTP Request: Add missing property to node object HTTPRequest (#3842) @hardillb
- HTTP Request/Response: Support sortable list on property UI of http request and http response nodes (#3857) @kazuhitoyokoi
- HTTP Response: Ensure statusCode is a number (#3894) @hardillb
- Inject: Allow Inject node to work with async context stores (#4021) @knolleary
- Join/Batch: Add count to join and batch node labels (#4028) @dceejay
- MQTT: Fix birth topic handling in MQTT node (#3905) @Steve-Mcl
- MQTT: Fix pull-down menus of MQTT configuration node (#3890) @kazuhitoyokoi
- MQTT: Prevent invalid mqtt birth topic crashing node-red (#3869) @Steve-Mcl
- MQTT: ensure sessionExpiry(Interval) is applied (#3840) @Steve-Mcl
- MQTT: Fix mqtt nodes not reconnecting on modified-flows deploy (#3992) @knolleary
- MQTT: fix single subscription mqtt node status (#3966) @Steve-Mcl
- Range: Add drop mode to range node (#3935) @dceejay
- Remove done from describe (#3873) @HiroyasuNishiyama
- Split node: avoid duplicate done call for buffer split (#4000) @knolleary
- Status: Fix typo in 25-status.html (#3981) @kazuhitoyokoi
- TCP Node: ensure newline substitution applies to whole message (#4009) @dceejay
- Template: Add information about environment variable to template node (#3882) @kazuhitoyokoi
- Trigger: Hide trigger node repeat send option if sending nothing (#4023) @dceejay
- Watch: fix watch node test on MacOS/ARM (#3942) @HiroyasuNishiyama
#### 3.0.2: Maintenance Release
#### 4.0.0-beta.1: Beta Release
Editor
- Fix workspace chart bottom property (#3812) @bonanitech
- Update german translation (#3802) @Dennis14e
- Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi
- Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary
- Hide scrollbars until they're needed (#3808) @bonanitech
- Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary
- remove console.log (#3820) @Steve-Mcl
- Click on id in debug panel highlights node or flow (#4439) @ralphwetzel
- Support config selection in a subflow env var (#4587) @Steve-Mcl
- Add timestamp formatting options to TypedInput (#4468) @knolleary
- Allow RED.view.select to select links (#4553) @lgrkvst
- Add auto-complete to flow/global/env typedInput types (#4480) @knolleary
- Improve the appearance of the Node-RED primary header (#4598) @joepavitt
Runtime
- Register subflow module instance node with parent flow (#3818) @knolleary
- let settings.httpNodeAuth accept single middleware or array of middlewares (#4572) @kevinGodell
- Upgrade to JSONata 2.x (#4590) @knolleary
- Bump minimum version to node 18 (#4571) @knolleary
- npm: Remove production flag on npm invocation (#4347) @ZJvandeWeg
- Timer testing fix (#4367) @hlovdal
- Bump to 4.0.0-dev (#4322) @knolleary
Nodes
- HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb
#### 3.0.1: Maintenance Release
Editor
- Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl
- Sys info (diagnostics report) amendments (#3793) @Steve-Mcl
- Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl
- Fix focus issues (#3789) @Steve-Mcl
- Ensure all typedInput buttons have button type set (#3788) @knolleary
- Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary
- Properly position quick-add dialog in all cases (#3786) @knolleary
- Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary
- Remove use of Object.hasOwn (#3784) @knolleary
#### 3.0.0: Milestone Release
Editor
- Use theme page and header values if settings.js values are not present (#3767) @Steve-Mcl
- Focus editor for undo after some actions in menu (#3759) @kazuhitoyokoi
- Ensure node icon shade has properly rounded corners (#3763) @knolleary
- Fix storing subflow credential type when input has multiple types (#3762) @knolleary
- Ensure global-config and flow-config have info in the hierarchy popover (#3752) @Steve-Mcl
- Include dirty state in history event (#3748) @Steve-Mcl
- Fix display direction of context sub-menu (#3746) @knolleary
- Fix clear pinned paths of debug sidebar menu (#3745) @HiroyasuNishiyama
- prevent exception generating tooltip for deleted nodes (#3742) @Steve-Mcl
- Fix context menu issues ready for v3 beta.5 (#3741) @Steve-Mcl
- Do not generate new node-ids when pasting a cut flow (#3729) @knolleary
- Fix to prevent node from moving out of workspace (#3731) @HiroyasuNishiyama
- Don't let themes change disabled config node background color (#3736) @bonanitech
- Move colors left behind in #3692 to CSS variables (#3737) @bonanitech
- Fix handling of global debug message (#3733) @HiroyasuNishiyama
- Fix label overflow @ config-node palette (#3730) @ralphwetzel
- Fix defaulting to monaco if settings does not contain codeEditor (#3732) @knolleary
- Disable keyboard shortcut mapping when showing Edit[..]Dialog (#3700) @ralphwetzel
- Update add-junction menu to work in more cases (#3727) @knolleary
- Ensure importMap is not null when using import UI (#3723) @Steve-Mcl
- Add Japanese translations for v3.0-beta.4 (#3724) @kazuhitoyokoi
- Fix "split with" on virtual links (#3766) @Steve-Mcl
Runtime
- Do not remove unknown credentials of Subflow Modules (#3728) @knolleary
- Add missing entries from beta.4 changelog (#3721) @knolleary
Nodes
- Change: Fix change node, not handling from field properly when using context (#3754) @Fadoli
- Link Call: Fix linkcall registry bugs (#3751) @Steve-Mcl
- WebSocket: Fix close timeout of websocket node (#3734) @HiroyasuNishiyama
#### 3.0.0-beta.4: Beta Release
Editor
- Move all colours to CSS variables (#3692) @bonanitech
- Fix clicking on node in workspace to hide context menu (#3696) @knolleary
- Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama
- Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl
- Update german translation (#3691) @Dennis14e
- List welcome tours in help sidebar (#3717) @knolleary
- Ensure 'hidden flow' count doesn't include subflows (#3715) @knolleary
- Fix Chinese translate (#3706) @hotlong
- Fix use default button for node icon (#3714) @kazuhitoyokoi
- Fix select boxes vertical alignment (#3698) @bonanitech
- Ensure workspace clean after undoing dropped node (#3708) @Steve-Mcl
- Use solid colour as config node icon background to hide text overflow (#3710) @Steve-Mcl
- Increase quick-add height to reveal 2 most recent entries (#3711) @Steve-Mcl
- Set default editor to monaco in absence of user preference (#3702) @knolleary
- Add Japanese translations for v3.0-beta.3 (#3688) @kazuhitoyokoi
- Fix handling of spacebar inside JSON visual editor (#3687) @knolleary
- Fix menu padding to handle both icons and submenus (#3686) @knolleary
- Include scroll offset when positioning quick-add dialog (#3685) @knolleary
Runtime
- Allow flows to be stopped and started manually (#3719) @knolleary
- Import default export if node is a transpiled es module (#3669) @dschmidt
- Leave Monaco theme commented out by default (#3704) @bonanitech
Nodes
- CSV: Fix CSV node to handle when outputting text fields (#3716) @dceejay
- Delay: Fix delay rate limit last timing when empty (#3709) @dceejay
- Link: Ensure link-call cache is updated when link-in is modified (#3695) @Steve-Mcl
- Join: Join node in reduce mode doesn't keep existing msg properties (#3670) @dceejay
- Template: Add support for evalulating {{env.<var>}} within a template node (#3690) @cow0w
#### 3.0.0-beta.3: Beta Release
Editor
- Add Right-Click content menu (#3678) @knolleary
- Fix disable junction (#3671) @HiroyasuNishiyama
- Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi
- Reset mouse state when switching tabs (#3643) @knolleary
- Fix uncorrect fix of junction to subflow conversion (#3666) @HiroyasuNishiyama
- Fix undoing junction to subflow (#3653) @HiroyasuNishiyama
- Fix conversion of junction to subflow (#3652) @HiroyasuNishiyama
- Fix to include junction to exported nodes (#3650) @HiroyasuNishiyama
- Fix z-index value for shade to cover nodes in palette (#3649) @kazuhitoyokoi
- Fix to extend escaped subflow category characters (#3647) @HiroyasuNishiyama
- Fix to sanitize tab name (#3646) @HiroyasuNishiyama
- Fix selector placement (#3644) @bonanitech
- Add Japanese translations for v3.0-beta.2 (#3622) @kazuhitoyokoi
- Fix new folder menu of save to library dialog (#3633) @HiroyasuNishiyama
- Fix layer of palette node (#3638) @HiroyasuNishiyama
- Fix to place a node dragged from palette within the workspace (#3637) @HiroyasuNishiyama
- Fix typo in CSS (#3628) @bonanitech
- Use the correct variable for the gutter text color (#3615) @bonanitech
Runtime
- Support loading node modules from `nodesdir` (#3676) @Steve-Mcl
- fix buffer parse error message of evaluateNodeProperty (#3624) @HiroyasuNishiyama
Nodes
- File: Further simplify file node filename entry UX (v3) (#3677) @Steve-Mcl
- Function: Fix initial cursor position of init/finalize tab of function node (#3674) @HiroyasuNishiyama
- Function: Fix ESM module loading in Function node (#3645) @knolleary
- Inject: Fix JSONata evaluation of inject button (#3632) @HiroyasuNishiyama
- TCP: Dont delete TCP socket twice (#3630) @Steve-Mcl
- MQTT Node: define noproxy variable (#3626) @Steve-Mcl
- Debug: i18n debug sidebar node label (#3623) @HiroyasuNishiyama
#### 3.0.0-beta.2: Beta Release
**Migration from 2.x**
- The 'slice wires' action has changed from Ctrl-RightMouseButton to Alt-LeftMouseButton
Editor
- Rework Junctions to be more node like in their event handling (#3607) @knolleary
- Change slicing / slice-junction operations over to mouse button 0 (Left Mouse Button) (#3609) @Steve-Mcl
- Do not slice-junction link node wires (#3608) @knolleary
- Handle many-to-one slicing of wires (#3604) @knolleary
- Ensure ACE worker options are set (#3611) @Steve-Mcl
- Remove duplicate history add of ungroup event (#3605) @knolleary
- use text width instead of number of characters for deciding select fi… (#3603) @HiroyasuNishiyama
- Update Japanese info of link call node reflecting update of English info (#3600) @HiroyasuNishiyama
- Fix typedInput label not visible on themes (#3580) @bonanitech
- Fix project switching when junctions are present (#3595) @Steve-Mcl
- Fix junction: when wiring from a regular nodes INPUT, backwards to a junction (#3591) @Steve-Mcl
- Fix error initialising flow tab editor (#3585) @Steve-Mcl
- Add Japanese translations for v3.0-beta.1 (#3576) @kazuhitoyokoi
- Fix image paths where `red/image/typedInput/XXXX.png` should be `red/image/typedInput/XXXX.svg` (#3592) @kazuhitoyokoi
- Fix browser console error Uncaught TypeError when searching certain terms (#3584) @Steve-Mcl
Runtime
- fix error on system-info action (#3589) @HiroyasuNishiyama
Nodes
- I18n switch rule selector (#3602) @HiroyasuNishiyama
- Handle removal of event handlers to allow mqtt client.end() to work (#3594) @PhilDay-CT
- update link-call node info according to current behavior (#3597) @HiroyasuNishiyama
#### 3.0.0-beta.1: Beta Release
**Migration from 2.x**
- Node-RED now requires Node.js 14.x or later.
- New installs of Node-RED will default to the monaco editor.
Editor
- Add Junctions (#3462) @knolleary
- Allow node name to be auto-generated when added (#3478, #3538) @knolleary
- Set monaco as default code editor as of v3.x (#3543) @Steve-Mcl
- Update Monaco to V0.33.0 (#3522) @Steve-Mcl
- Auto-complete Improvements (#3521) @Steve-Mcl
- Add a tooltip to debug sidebar messages to reveal full path to node (#3503) @knolleary
- Fix down arrow triggering menu in search box (#3507) @Steve-Mcl
- Add Japanese translations for v3.0 (#3512) @kazuhitoyokoi
- Add feature: Continuous search tools (search previous, search next) (#3405) @Steve-Mcl
- Add feature: split-wire-to-links (#3399, #3476) @Steve-Mcl
- Add copy button to node properties tables (#3390) @knolleary
- Add info-tab search options dropdown to the regular search (#3395) @Steve-Mcl
- New Feature: Add ability to find modified nodes/flows. (#3392) @Steve-Mcl
- Code editor ux improvements around remembering state of each code editor in a flow (#3553) @Steve-Mcl
- Make it easier to apply themes on SVG icons (#3515) @bonanitech
- Add support of property validation message (#3438) @HiroyasuNishiyama
- Ensure node validation tooltip is closed when field becomes valid (#3570) @knolleary
- Add "search for" buttons to notifications (#3567) @Steve-Mcl
- Don't let themes change node config colors (#3564) @bonanitech
- Fix gap between typedInput containers borders (#3560) @bonanitech
- Fix recording removed links in edit history (#3547) @knolleary
- Remove unused SASS vars (#3536) @bonanitech
- Add custom style for jQuery widgets borders (#3537) @bonanitech
- fix out of scope reference of hasUnusedConfig variable (#3535) @HiroyasuNishiyama
- correct "non string" check parenthesis (#3524) @Steve-Mcl
- Ensure i18n of scoped package name (#3516) @Steve-Mcl
- Prevent shortcut deploy when deploy button shaded (#3517) @Steve-Mcl
- Fix: Sidebar "Configuration" filter button tooltip (#3500) @ralphwetzel
- Add the ability to customize diff colors even more (#3499) @bonanitech
- Do JSON comparison of old value/new value in editor (#3481) @Steve-Mcl
- Fix nodes losing their wires when in an iframe (#3484) @zettca
- Improve scroll into view (#3468) @Steve-Mcl
- Do not show 1st tab if hidden when loading (#3464) @Steve-Mcl
Runtime
- Fix importing external module from node-red module (#3541) @knolleary
- Add support for multiple static paths with optional static root (#3542) @Steve-Mcl
- Store external token when authenticating if provided (#3460) @ArFe
- Support OAuth/OpenID logout (#3388) @mw75
- Allow adminAuth to auto-login users when using passport strategy (#3519) @knolleary
- Add runtime diagnostics admin endpoint (#3511) @Steve-Mcl
- Don't start if user has no home directory (#3540) @hardillb
- Error on invalid encrypted credentials (#3498) @sammachin
Nodes
- Debug: Add message count option to Debug status (#3544 #3551) @rafaelmuynarsk @knolleary
- File: Change basic Filename field to a typedInput (#3533) @Steve-Mcl
- HTTP Request: Add UI for Http Request node headers (#3488) @Steve-Mcl
- Inject: let inject optionally fire at start in only at time mode. (#3385) @dceejay
- Link Call: Dynamic link call (#3463) @Steve-Mcl
- Link Call: Display link targets of nodes in a regular flow, for Link Call nodes inside a subflow (#3528) @Steve-Mcl
- MQTT: MQTT payload auto parsing improvements (#3530) @Steve-Mcl
- MQTT: Add client and Runtime MQTT topic validation (#3563) @Steve-Mcl [dev]
- MQTT: save and restore v5 config user props (#3562) @Steve-Mcl
- MQTT: Fix incorrect MQTT status (#3552) @Steve-Mcl
- MQTT: fix reference error of msg.status in debug node (#3526) @HiroyasuNishiyama
- MQTT: Add unit tests for MQTT nodes (#3497) @Steve-Mcl
- MQTT: fix typo of will properties (#3502) @Steve-Mcl
- MQTT: ensure mqtt v5 props can be set false (#3472) @Steve-Mcl
- Switch: add check for NaN in is of type number to be false (#3409) @dceejay
- TCP: TCP node better split (#3465) @dceejay
- Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary
- WebSocket: call done after ws disconnects (#3531) @Steve-Mcl
- TCP node - when resetting, if no payload, stay disconnected @dceejay
- HTML node: add option for collecting attributes and content (#4513) @gorenje
- let split node specify property to split on, and join auto join correctly (#4386) @dceejay
- Add RFC4180 compliant mode to CSV node (#4540) @Steve-Mcl
- Fix change node to return boolean if asked (#4525) @dceejay
- Let msg.reset reset Tcp request node connection when in stay connected mode (#4406) @dceejay
- Let debug node status msg length be settable via settings (#4402) @dceejay
- Feat: Add ability to set headers for WebSocket client (#4436) @marcus-j-davies
#### Older Releases

View File

@@ -143,6 +143,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/user.js",
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
"packages/node_modules/@node-red/editor-client/src/js/multiplayer.js",
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
@@ -207,38 +208,52 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
],
nonull: true,
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
},
vendor: {
files: {
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"node_modules/marked/marked.min.js",
"node_modules/dompurify/dist/purify.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"node_modules/i18next/i18next.min.js",
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
"node_modules/jquery-i18next/jquery-i18next.min.js",
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
],
// "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
// // TODO: resolve relative resource paths in
// // bootstrap/FA/jquery
// ],
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
],
"packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
"node_modules/mermaid/dist/mermaid.min.js"
]
}
files: [
{
src: [
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"node_modules/marked/marked.min.js",
"node_modules/dompurify/dist/purify.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"node_modules/i18next/i18next.min.js",
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
"node_modules/jquery-i18next/jquery-i18next.min.js",
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
],
nonull: true,
dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js"
},
// {
// src: [
// // TODO: resolve relative resource paths in
// // bootstrap/FA/jquery
// ],
// dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css"
// },
{
src: [
"node_modules/jsonata/jsonata-es5.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
],
nonull: true,
dest: "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js",
},
{
src: "node_modules/mermaid/dist/mermaid.min.js",
nonull: true,
dest: "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js",
},
]
}
},
uglify: {

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "3.1.5",
"version": "4.1.0-beta.0",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -26,25 +26,25 @@
}
],
"dependencies": {
"acorn": "8.8.2",
"acorn-walk": "8.2.0",
"ajv": "8.12.0",
"async-mutex": "0.4.0",
"acorn": "8.11.3",
"acorn-walk": "8.3.2",
"ajv": "8.14.0",
"async-mutex": "0.5.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"cheerio": "1.0.0-rc.10",
"clone": "2.1.2",
"content-type": "1.0.5",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-parser": "1.4.6",
"cors": "2.8.5",
"cronosjs": "1.7.1",
"denque": "2.1.0",
"express": "4.18.2",
"express-session": "1.17.3",
"express": "4.19.2",
"express-session": "1.18.0",
"form-data": "4.0.0",
"fs-extra": "11.1.1",
"fs-extra": "11.2.0",
"got": "12.6.0",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
@@ -54,35 +54,36 @@
"is-utf8": "0.2.1",
"js-yaml": "4.1.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.6",
"jsonata": "2.0.5",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.7",
"mime": "3.0.0",
"moment": "2.29.4",
"moment-timezone": "0.5.43",
"mqtt": "4.3.7",
"moment": "2.30.1",
"moment-timezone": "0.5.45",
"mqtt": "5.7.0",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
"node-red-admin": "^3.1.2",
"node-red-admin": "^4.0.0",
"node-watch": "0.7.4",
"nopt": "5.0.0",
"oauth2orize": "1.11.1",
"oauth2orize": "1.12.0",
"on-headers": "1.0.2",
"passport": "0.6.0",
"passport": "0.7.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2",
"rfdc": "^1.3.1",
"semver": "7.5.4",
"tar": "6.1.13",
"tough-cookie": "4.1.3",
"tar": "7.2.0",
"tough-cookie": "4.1.4",
"uglify-js": "3.17.4",
"uuid": "9.0.0",
"ws": "7.5.6",
"uuid": "9.0.1",
"ws": "7.5.10",
"xml2js": "0.6.2"
},
"optionalDependencies": {
"bcrypt": "5.1.1"
"@node-rs/bcrypt": "1.10.4"
},
"devDependencies": {
"dompurify": "2.4.1",
@@ -112,7 +113,7 @@
"mermaid": "^10.4.0",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.2",
"node-red-node-test-helper": "^0.3.3",
"nodemon": "2.0.20",
"proxy": "^1.0.2",
"sass": "1.62.1",
@@ -122,6 +123,6 @@
"supertest": "6.3.3"
},
"engines": {
"node": ">=14"
"node": ">=18.5"
}
}

View File

@@ -33,6 +33,9 @@ module.exports = {
store: req.query['store'],
req: apiUtils.getRequestLogObject(req)
}
if (req.query['keysOnly'] !== undefined) {
opts.keysOnly = true
}
runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result);
}).catch(function(err) {

View File

@@ -91,6 +91,7 @@ module.exports = {
// Plugins
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
adminApp.get(/^\/plugins\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("plugins.read"),plugins.getConfig,apiUtil.errorHandler);
adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);

View File

@@ -40,5 +40,31 @@ module.exports = {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
getConfig: function(req, res) {
let opts = {
user: req.user,
module: req.params[0],
req: apiUtils.getRequestLogObject(req)
}
if (req.get("accept") === "application/json") {
runtimeAPI.nodes.getNodeInfo(opts.module).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
opts.lang = "en-US";
}
runtimeAPI.plugins.getPluginConfig(opts).then(function(result) {
return res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}
};

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
var settings;
var theme = require("../editor/theme");

View File

@@ -160,20 +160,34 @@ function completeVerify(profile,done) {
function genericStrategy(adminApp,strategy) {
var crypto = require("crypto")
var session = require('express-session')
var MemoryStore = require('memorystore')(session)
const crypto = require("crypto")
const session = require('express-session')
const MemoryStore = require('memorystore')(session)
adminApp.use(session({
// As the session is only used across the life-span of an auth
// hand-shake, we can use a instance specific random string
secret: crypto.randomBytes(20).toString('hex'),
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 86400000 // prune expired entries every 24h
})
}));
const sessionOptions = {
// As the session is only used across the life-span of an auth
// hand-shake, we can use a instance specific random string
secret: crypto.randomBytes(20).toString('hex'),
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 86400000 // prune expired entries every 24h
})
}
if (settings.httpAdminCookieOptions) {
sessionOptions.cookie = {
path: '/',
httpOnly: true,
secure: false,
maxAge: null,
...settings.httpAdminCookieOptions
}
if (sessionOptions.cookie.name){
sessionOptions.name = sessionOptions.cookie.name
delete sessionOptions.cookie.name
}
}
adminApp.use(session(sessionOptions));
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.use(passport.session());
@@ -205,11 +219,12 @@ function genericStrategy(adminApp,strategy) {
passport.use(new strategy.strategy(options, verify));
adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot
passport.authenticate(strategy.name, {
session:false,
failWithError: true,
failureMessage: true
}),
completeGenerateStrategyAuth,
completeGenericStrategyAuth,
handleStrategyError
);
@@ -221,14 +236,14 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, {
session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot
failWithError: true
}),
completeGenerateStrategyAuth,
completeGenericStrategyAuth,
handleStrategyError
);
}
function completeGenerateStrategyAuth(req,res) {
function completeGenericStrategyAuth(req,res) {
var tokens = req.user.tokens;
delete req.user.tokens;
// Successful authentication, redirect home.
@@ -238,6 +253,8 @@ function handleStrategyError(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
// Remove the header that passport auto-adds as we don't need it
res.removeHeader('WWW-Authenticate')
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
}

View File

@@ -25,7 +25,7 @@ function hasPermission(userScope,permission) {
}
var i;
if (util.isArray(permission)) {
if (Array.isArray(permission)) {
// Multiple permissions requested - check each one
for (i=0;i<permission.length;i++) {
if (!hasPermission(userScope,permission[i])) {
@@ -36,7 +36,7 @@ function hasPermission(userScope,permission) {
return true;
}
if (util.isArray(userScope)) {
if (Array.isArray(userScope)) {
if (userScope.length === 0) {
return false;
}

View File

@@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
var passport = require("passport");
var crypto = require("crypto");
var util = require("util");
var Tokens = require("./tokens");

View File

@@ -17,7 +17,7 @@
var util = require("util");
var clone = require("clone");
var bcrypt;
try { bcrypt = require('bcrypt'); }
try { bcrypt = require('@node-rs/bcrypt'); }
catch(e) { bcrypt = require('bcryptjs'); }
var users = {};
var defaultUser = null;
@@ -33,11 +33,11 @@ function authenticate() {
if (args.length === 2) {
// Username/password authentication
var password = args[1];
return new Promise(function(resolve,reject) {
bcrypt.compare(password, user.password, function(err, res) {
resolve(res?cleanUser(user):null);
});
});
return bcrypt.compare(password, user.password).then(res => {
return res ? cleanUser(user) : null
}).catch(err => {
return null
})
} else {
// Try to extract common profile information
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
@@ -74,7 +74,7 @@ function init(config) {
} else {
var us = config.users;
/* istanbul ignore else */
if (!util.isArray(us)) {
if (!Array.isArray(us)) {
us = [us];
}
for (var i=0;i<us.length;i++) {

View File

@@ -77,6 +77,53 @@ function CommsConnection(ws, user) {
log.trace("comms.close "+self.session);
removeActiveConnection(self);
});
const handleAuthPacket = function(msg) {
Tokens.get(msg.auth).then(function(client) {
if (client) {
Users.get(client.user).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(msg, client.scope,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(msg, null,null,false);
}
});
} else {
Users.tokens(msg.auth).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(msg, user.permissions,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(msg, null,null,false);
}
});
}
});
}
const completeConnection = function(msg, userScope, session, sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
ws.close();
} else {
pendingAuth = false;
addActiveConnection(self);
self.token = msg.auth;
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
}
} catch(err) {
console.log(err.stack);
// Just in case the socket closes before we attempt
// to send anything.
}
}
ws.on('message', function(data,flags) {
var msg = null;
try {
@@ -86,68 +133,34 @@ function CommsConnection(ws, user) {
return;
}
if (!pendingAuth) {
if (msg.subscribe) {
if (msg.auth) {
handleAuthPacket(msg)
} else if (msg.subscribe) {
self.subscribe(msg.subscribe);
// handleRemoteSubscription(ws,msg.subscribe);
} else if (msg.topic) {
runtimeAPI.comms.receive({
user: self.user,
client: self,
topic: msg.topic,
data: msg.data
})
}
} else {
var completeConnection = function(userScope,session,sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
ws.close();
} else {
pendingAuth = false;
addActiveConnection(self);
self.token = msg.auth;
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
}
} catch(err) {
console.log(err.stack);
// Just in case the socket closes before we attempt
// to send anything.
}
}
if (msg.auth) {
Tokens.get(msg.auth).then(function(client) {
if (client) {
Users.get(client.user).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(client.scope,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,null,false);
}
});
} else {
Users.tokens(msg.auth).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(user.permissions,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,null,false);
}
});
}
});
handleAuthPacket(msg)
} else {
if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser});
self.user = anonymousUser;
completeConnection(anonymousUser.permissions,null,false);
completeConnection(msg, anonymousUser.permissions, null, false);
//TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) {
self.subscribe(msg.subscribe);
}
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,null,false);
completeConnection(msg, null,null,false);
}
}
}

View File

@@ -14,11 +14,9 @@
* limitations under the License.
**/
var express = require("express");
var path = require('path');
var comms = require("./comms");
var library = require("./library");
var info = require("./settings");
var auth = require("../auth");

View File

@@ -15,8 +15,6 @@
**/
var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var runtimeAPI;

View File

@@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var fs = require('fs');
var path = require('path');
// var apiUtil = require('../util');
var i18n = require("@node-red/util").i18n; // TODO: separate module

View File

@@ -15,7 +15,6 @@
**/
var apiUtils = require("../util");
var express = require("express");
var runtimeAPI;
var settings;

View File

@@ -14,7 +14,6 @@
* limitations under the License.
**/
var express = require("express");
var util = require("util");
var path = require("path");
var fs = require("fs");
@@ -71,7 +70,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
var result = [];
if (themeValue) {
var array = themeValue;
if (!util.isArray(array)) {
if (!Array.isArray(array)) {
array = [array];
}
@@ -234,6 +233,10 @@ module.exports = {
themeSettings.projects = theme.projects;
}
if (theme.hasOwnProperty("multiplayer")) {
themeSettings.multiplayer = theme.multiplayer;
}
if (theme.hasOwnProperty("keymap")) {
themeSettings.keymap = theme.keymap;
}

View File

@@ -99,7 +99,7 @@ module.exports = {
// settings.instanceId is set asynchronously to the editor-api
// being initiaised. So we defer calculating the cacheBuster hash
// until the first load of the editor
cacheBuster = crypto.createHash('md5').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
cacheBuster = crypto.createHash('sha1').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
}
let sessionMessages;

View File

@@ -24,11 +24,8 @@
* @namespace @node-red/editor-api
*/
var express = require("express");
var bodyParser = require("body-parser");
var util = require('util');
var passport = require('passport');
var cors = require('cors');
var auth = require("./auth");
var apiUtil = require("./util");

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "3.1.5",
"version": "4.1.0-beta.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,25 +16,25 @@
}
],
"dependencies": {
"@node-red/util": "3.1.5",
"@node-red/editor-client": "3.1.5",
"@node-red/util": "4.1.0-beta.0",
"@node-red/editor-client": "4.1.0-beta.0",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.17.3",
"express": "4.18.2",
"express-session": "1.18.0",
"express": "4.19.2",
"memorystore": "1.6.7",
"mime": "3.0.0",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
"oauth2orize": "1.11.1",
"oauth2orize": "1.12.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.6.0",
"ws": "7.5.6"
"passport": "0.7.0",
"ws": "7.5.10"
},
"optionalDependencies": {
"bcrypt": "5.1.0"
"@node-rs/bcrypt": "1.10.4"
}
}

View File

@@ -590,6 +590,8 @@
},
"nodeCount": "__label__ Node",
"nodeCount_plural": "__label__ Nodes",
"pluginCount": "__count__ Plugin",
"pluginCount_plural": "__count__ Plugins",
"moduleCount": "__count__ Modul verfügbar",
"moduleCount_plural": "__count__ Module verfügbar",
"inuse": "In Gebrauch",

View File

@@ -27,7 +27,8 @@
"lock": "Lock",
"unlock": "Unlock",
"locked": "Locked",
"unlocked": "Unlocked"
"unlocked": "Unlocked",
"format": "Format"
},
"type": {
"string": "string",
@@ -372,6 +373,7 @@
"deleted": "deleted",
"flowDeleted": "flow deleted",
"flowAdded": "flow added",
"moved": "moved",
"movedTo": "moved to __id__",
"movedFrom": "moved from __id__"
},
@@ -614,8 +616,12 @@
},
"nodeCount": "__label__ node",
"nodeCount_plural": "__label__ nodes",
"pluginCount": "__count__ plugin",
"pluginCount_plural": "__count__ plugins",
"moduleCount": "__count__ module available",
"moduleCount_plural": "__count__ modules available",
"updateCount": "__count__ update available",
"updateCount_plural": "__count__ updates available",
"inuse": "in use",
"enableall": "enable all",
"disableall": "disable all",
@@ -641,6 +647,7 @@
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"installTimeout": "<p>Install continuing the background.</p><p>Nodes will appear in palette when complete. Check the log for more information.</p>",
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
@@ -655,6 +662,9 @@
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
"title": "Remove nodes"
},
"removePlugin": {
"body": "<p>Removed plugin __module__. Please reload the editor to clear left-overs.</p>"
},
"update": {
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
"title": "Update nodes"
@@ -666,7 +676,8 @@
"review": "Open node information",
"install": "Install",
"remove": "Remove",
"update": "Update"
"update": "Update",
"understood": "Understood"
}
}
}
@@ -719,6 +730,7 @@
"nodeHelp": "Node Help",
"showHelp": "Show help",
"showInOutline": "Show in outline",
"hideTopics": "Hide topics",
"showTopics": "Show topics",
"noHelp": "No help topic selected",
"changeLog": "Change Log"
@@ -914,6 +926,8 @@
}
},
"typedInput": {
"selected": "__count__ selected",
"selected_plural": "__count__ selected",
"type": {
"str": "string",
"num": "number",
@@ -924,7 +938,14 @@
"date": "timestamp",
"jsonata": "expression",
"env": "env variable",
"cred": "credential"
"cred": "credential",
"conf-types": "config node"
},
"date": {
"format": {
"timestamp": "milliseconds since epoch",
"object": "JavaScript Date Object"
}
}
},
"editableList": {

View File

@@ -27,7 +27,8 @@
"lock": "Verrouiller",
"unlock": "Déverrouiller",
"locked": "Verrouillé",
"unlocked": "Déverrouillé"
"unlocked": "Déverrouillé",
"format": "Format"
},
"type": {
"string": "chaîne de caractères",
@@ -54,10 +55,10 @@
"workspace": {
"defaultName": "Flux __number__",
"editFlow": "Modifier le flux : __name__",
"confirmDelete": "Confirmation de la suppression",
"delete": "Etes-vous sûr de vouloir supprimer '__label__'?",
"dropFlowHere": "Déposer le flux ici",
"dropImageHere": "Déposer l'image ici",
"confirmDelete": "Confirmer la suppression",
"delete": "Êtes-vous sûr de vouloir supprimer '__label__' ?",
"dropFlowHere": "Lâchez le flux ici",
"dropImageHere": "Lâchez l'image ici",
"addFlow": "Ajouter un flux",
"addFlowToRight": "Ajouter un flux à droite",
"closeFlow": "Fermer le flux",
@@ -74,7 +75,7 @@
"enabled": "Activé",
"disabled": "Désactivé",
"info": "Description",
"selectNodes": "Cliquer sur les noeuds pour sélectionner",
"selectNodes": "Cliquer pour sélectionner",
"enableFlow": "Activer le flux",
"disableFlow": "Désactiver le flux",
"lockFlow": "Verrouiller le flux",
@@ -98,7 +99,7 @@
"rtl": "De droite à gauche",
"auto": "Contextuel",
"language": "Langue",
"browserDefault": "Navigateur par défaut"
"browserDefault": "Par défaut du Navigateur"
},
"sidebar": {
"show": "Afficher la barre latérale"
@@ -134,7 +135,7 @@
"disableSelectedNodes": "Désactiver les noeuds sélectionnés",
"showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés",
"hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés",
"showWelcomeTours": "Afficher les visites guidées pour les nouvelles versions",
"showWelcomeTours": "Afficher les visites guidées des nouvelles versions",
"help": "Site web de Node-RED",
"projects": "Projets",
"projects-new": "Nouveau projet",
@@ -143,7 +144,7 @@
"showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés",
"codeEditor": "Éditeur de code",
"groups": "Groupes",
"groupSelection": "Grouper cette sélection",
"groupSelection": "Grouper la sélection",
"ungroupSelection": "Dégrouper la sélection",
"groupMergeSelection": "Fusionner la sélection",
"groupRemoveSelection": "Supprimer du groupe",
@@ -155,7 +156,7 @@
"alignMiddle": "Aligner au milieu",
"alignBottom": "Aligner en bas",
"distributeHorizontally": "Répartir horizontalement",
"distributeVertically": "Distribuer verticalement",
"distributeVertically": "Répartir verticalement",
"moveToBack": "Déplacer vers l'arrière",
"moveToFront": "Déplacer vers l'avant",
"moveBackwards": "Reculer",
@@ -163,21 +164,21 @@
}
},
"actions": {
"toggle-navigator": "Basculer de navigateur",
"zoom-out": "Dézoomer",
"zoom-reset": "Réinitialiser le zoom",
"toggle-navigator": "Basculer l'affichage du navigateur",
"zoom-out": "Réduire",
"zoom-reset": "Réinitialiser",
"zoom-in": "Agrandir",
"search-flows": "Rechercher le flux",
"search-prev": "Précédent",
"search-next": "Suivant",
"search-counter": "\"__term__\" __result__ de __count__"
"search-counter": "\"__term__\" __result__ sur __count__"
},
"user": {
"loggedInAs": "Connecté en tant que __name__",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"login": "Connexion",
"loginFailed": "Échec de la connexion",
"login": "Se connecter",
"loginFailed": "Échec de connexion",
"notAuthorized": "Pas autorisé",
"errors": {
"settings": "Vous devez être connecté pour accéder aux paramètres",
@@ -193,16 +194,16 @@
"warning": "<strong>Attention</strong> : __message__",
"warnings": {
"undeployedChanges": "Le noeud a des modifications non déployées",
"nodeActionDisabled": "Actions de noeud désactivées",
"nodeActionDisabledSubflow": "Actions de noeud désactivées dans le sous-flux",
"nodeActionDisabled": "Les actions du noeud sont désactivées",
"nodeActionDisabledSubflow": "Les actions de noeud sont désactivées à l'intérieur du sous-flux",
"missing-types": "<p>Flux arrêtés en raison de types de noeuds manquants.</p>",
"missing-modules": "<p>Flux arrêtés en raison de modules manquants.</p>",
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer les changements pour redémarrer.</p>",
"safe-mode": "<p>Flux arrêtés en mode sans échec.</p><p>Vous pouvez modifier vos flux et déployer ensuite les changements afin de démarrer vos flux.</p>",
"restartRequired": "Node-RED doit être redémarré pour mettre à jour les modules",
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p>",
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré, mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification de flux existantes seront perdues.</p>",
"credentials_load_failed": "<p>Les flux se sont arrêtés car les informations d'identification n'ont pas pu être déchiffrées.</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p>",
"credentials_load_failed_reset": "<p>Les informations d'identification n'ont pas pu être déchiffrées</p><p>Le fichier d'informations d'identification du flux est chiffré mais la clé de chiffrement du projet est manquante ou invalide.</p><p>Le fichier d'informations d'identification du flux sera réinitialisé lors du prochain déploiement. Toutes les informations d'identification des flux existants seront perdues.</p>",
"missing_flow_file": "<p>Fichier contenant les flux introuvable.</p><p>Le projet n'est pas configuré avec un fichier de flux.</p>",
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet un fichier package.json.</p>",
"missing_package_file": "<p>Fichier de paquetage du projet introuvable.</p><p>Il manque au projet le fichier <code>package.json</code>.</p>",
"project_empty": "<p>Le projet est vide.</p><p>Voulez-vous créer un ensemble de fichiers de projet par défaut ?<br/>Sinon, vous devrez ajouter manuellement des fichiers au projet (en dehors de l'éditeur).</p>",
"project_not_found": "<p>Le projet '__project__' est introuvable.</p>",
"git_merge_conflict": "<p>La fusion automatique des modifications a échoué.</p><p>Corriger les conflits non fusionnés, puis valider le résultat.</p>"
@@ -219,7 +220,7 @@
},
"project": {
"change-branch": "Changer pour une branche locale '__project__'",
"merge-abort": "Git fusion abandonnée",
"merge-abort": "Fusion Git abandonnée",
"loaded": "Projet '__project__' chargé",
"updated": "Projet '__project__' mis à jour",
"pull": "Projet '__project__' rechargé",
@@ -352,7 +353,7 @@
"backgroundUpdate": "Les flux sur le serveur ont été mis à jour.",
"conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement",
"conflictAutoMerge": "Les modifications n'incluent aucun conflit et peuvent être fusionnées automatiquement.",
"conflictManualMerge": "Les changements incluent des conflits qui doivent être résolus avant de pouvoir être déployés.",
"conflictManualMerge": "Les modifications incluent des conflits qui doivent être résolus avant de pouvoir être déployées.",
"plusNMore": "+ __count__ en plus"
}
},
@@ -372,16 +373,17 @@
"deleted": "supprimé",
"flowDeleted": "flux supprimé",
"flowAdded": "flux ajouté",
"moved": "déplacé",
"movedTo": "déplacé vers __id__",
"movedFrom": "déplacé depuis __id__"
},
"nodeCount": "__count__ noeud",
"nodeCount_plural": "__count__ noeuds",
"local": "Changements locaux",
"remote": "Modifications à distance",
"remote": "Changements distants",
"reviewChanges": "Examiner les modifications",
"noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire",
"viewCommitDiff": "Afficher les modifications de validation",
"viewCommitDiff": "Afficher les modifications de la validation",
"compareChanges": "Comparer les modifications",
"saveConflict": "Enregistrer la résolution des conflits",
"conflictHeader": "<span>__resolved__</span> sur <span>__unresolved__</span> conflit(s) résolu(s)",
@@ -395,9 +397,9 @@
"edit": "Modifier le modèle du sous-flux",
"subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux",
"subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux",
"editSubflowProperties": "modifier les propriétés",
"input": "Entrées:",
"output": "Sorties:",
"editSubflowProperties": "Modifier les propriétés",
"input": "Entrées :",
"output": "Sorties :",
"status": "Statut du noeud",
"deleteSubflow": "Supprimer le sous-flux",
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
@@ -411,7 +413,7 @@
"version": "Version",
"versionPlaceholder": "x.y.z",
"keys": "Mots clés",
"keysPlaceholder": "Mots clés séparés par des virgules",
"keysPlaceholder": "Mots clés séparés par une virgule",
"author": "Auteur",
"authorPlaceholder": "Votre nom <email@exemple.com>",
"desc": "Description",
@@ -468,7 +470,7 @@
"select": "sélection",
"checkbox": "case à cocher",
"spinner": "valeurs à défiler",
"none": "aucune",
"none": "aucun",
"hidden": "masquer la propriété"
},
"types": {
@@ -496,7 +498,7 @@
"max": "Maximum"
},
"errors": {
"scopeChange": "La modification de la portée la rendra indisponible pour les noeuds d'autres flux qui l'utilisent",
"scopeChange": "La modification de la portée rendra indisponible ce noeud de configuration aux noeuds d'autres flux qui l'utilisent",
"invalidProperties": "Propriétés invalides :",
"credentialLoadFailed": "Échec du chargement des identifiants du noeud"
}
@@ -510,7 +512,7 @@
"unassigned": "Non attribué",
"global": "Global",
"workspace": "Espace de travail",
"editor": "Boîte de dialogue d'édition",
"editor": "Boîte d'édition",
"selectAll": "Tout sélectionner",
"selectNone": "Ne rien sélectionner",
"selectAllConnected": "Sélectionner tous les éléments connectés",
@@ -541,7 +543,7 @@
"openLibrary": "Ouvrir la bibliothèque...",
"saveToLibrary": "Enregistrer dans la bibliothèque...",
"typeLibrary": "__type__ bibliothèque",
"unnamedType": "Innomé __type__",
"unnamedType": "Sans nom __type__",
"exportedToLibrary": "Noeuds exportés vers la bibliothèque",
"dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?",
"invalidFilename": "Nom de fichier non valide",
@@ -558,7 +560,7 @@
"noInfo": "Pas d'information disponible",
"filter": "Rechercher le noeud",
"search": "Rechercher les modules",
"addCategory": "Ajouter un nouveau...",
"addCategory": "Ajouter une nouvelle...",
"label": {
"subflows": "Sous-flux",
"network": "Réseau",
@@ -614,6 +616,8 @@
},
"nodeCount": "__label__ noeud",
"nodeCount_plural": "__label__ noeuds",
"pluginCount": "__count__ plugin",
"pluginCount_plural": "__count__ plugins",
"moduleCount": "__count__ module disponible",
"moduleCount_plural": "__count__ modules disponibles",
"inuse": "En cours d'utilisation",
@@ -636,11 +640,12 @@
"sortAZ": "A-Z",
"sortRecent": "Récent",
"more": "+ __count__ en plus",
"upload": "Charger le fichier tgz du module",
"upload": "Charger le fichier .tgz du module",
"refresh": "Actualiser la liste des modules",
"errors": {
"catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>",
"installFailed": "<p>Échec lors de l'installation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"installTimeout": "<p>L'installation continue en arrière-plan.</p><p>Les noeuds apparaîtront dans la palette une fois l'installation terminée. Consulter le journal pour plus d'informations.</p>",
"removeFailed": "<p>Échec lors de la suppression : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"updateFailed": "<p>Échec lors de la mise à jour : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"enableFailed": "<p>Échec lors de l'activation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
@@ -648,25 +653,29 @@
},
"confirm": {
"install": {
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuiller lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
"body": "<p>Installation de '__module__'</p><p>Avant l'installation, veuillez lire la documentation du noeud. Certains noeuds ont des dépendances qui ne peuvent pas être résolues automatiquement et peuvent nécessiter un redémarrage de Node-RED.</p>",
"title": "Installer les noeuds"
},
"remove": {
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser des ressources jusqu'au redémarrage de Node-RED.</p>",
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser ses ressources jusqu'au redémarrage de Node-RED.</p>",
"title": "Supprimer les noeuds"
},
"removePlugin": {
"body": "<p>Suppression du plugin '__module__'. Veuillez recharger l'éditeur afin d'appliquer les changements.</p>"
},
"update": {
"body": "<p>Mise à jour de '__module__'</p><p>La mise à jour du noeud nécessitera un redémarrage de Node-RED pour terminer la mise à jour. Cela doit être fait manuellement.</p>",
"title": "Mettre à jour les noeuds"
},
"cannotUpdate": {
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuiller vous référer à la documentation pour savoir comment mettre à jour ce noeud."
"body": "Une mise à jour pour ce noeud est disponible, mais il n'est pas installé dans un emplacement que le gestionnaire de palette peut mettre à jour.<br/><br/>Veuillez vous référer à la documentation pour savoir comment mettre à jour ce noeud."
},
"button": {
"review": "Ouvrir la documentation",
"install": "Installer",
"remove": "Supprimer",
"update": "Mettre à jour"
"update": "Mettre à jour",
"understood": "Compris"
}
}
}
@@ -701,8 +710,8 @@
"nodeHelp": "Aide sur les noeuds",
"none": "Aucun",
"arrayItems": "__count__ éléments",
"showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres",
"outline": "Plan",
"showTips": "Vous pouvez afficher les astuces à partir du panneau des paramètres",
"outline": "Contour",
"empty": "Vide",
"globalConfig": "Noeuds de configuration globale",
"triggerAction": "Déclencher une action",
@@ -715,10 +724,11 @@
"help": {
"name": "Aide",
"label": "Aide",
"search": "Aide à la recherche",
"search": "Rechercher l'aide",
"nodeHelp": "Aide sur les noeuds",
"showHelp": "Afficher l'aide",
"showInOutline": "Afficher dans les grandes lignes",
"hideTopics": "Masquer les sujets",
"showTopics": "Afficher les sujets",
"noHelp": "Aucune rubrique d'aide sélectionnée",
"changeLog": "Journal des modifications"
@@ -793,7 +803,7 @@
"branches": "Branches",
"noBranches": "Pas de branche",
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.",
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Etes-vous sûr de vouloir la supprimer?",
"unmergedConfirm": "La branche locale '__name__' contient des modifications non fusionnées qui seront perdues. Êtes-vous sûr de vouloir la supprimer?",
"deleteUnmergedBranch": "Supprimer la branche non fusionnée",
"gitRemotes": "Git distant",
"addRemote": "Ajout distant",
@@ -837,17 +847,17 @@
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé."
},
"versionControl": {
"unstagedChanges": "Abandon des changements",
"stagedChanges": "Changement mis en place",
"unstageChange": "Ne pas mettre en place le changement",
"stageChange": "Mettre en place le changement",
"unstageAllChange": "Ne pas mettre en place tous les changements",
"stageAllChange": "Mettre en place tous les changements",
"unstagedChanges": "Changements non indexés",
"stagedChanges": "Changements indexés",
"unstageChange": "Annuler l'indexation des changements",
"stageChange": "Indexer les changements",
"unstageAllChange": "Annuler l'indexation de tous les changements",
"stageAllChange": "Indexer tous les changements",
"commitChanges": "Valider les changements",
"resolveConflicts": "Résoudre les conflits",
"head": "En-tête",
"staged": "Mis en place",
"unstaged": "Non mis en place",
"staged": "Indexé",
"unstaged": "Non indexé",
"local": "Local",
"remote": "Distant",
"revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.",
@@ -881,11 +891,11 @@
"pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.",
"push": "Envoyer",
"pull": "Tirer",
"unablePull": "<p>Impossible d'extraire les modifications à distance ; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
"showUnstagedChanges": "Afficher les modifications non mise en place",
"unablePull": "<p>Impossible d'extraire les modifications à distance; vos modifications locales non mises en place seraient écrasées.</p><p>Valider vos modifications et réessayer.</p>",
"showUnstagedChanges": "Afficher les modifications non indexées",
"connectionFailed": "Impossible de se connecter au référentiel distant: ",
"pullUnrelatedHistory": "<p>Le réferentiel distant a un historique de validations sans rapport.</p><p>Êtes-vous sûr de vouloir extraire les modifications dans votre référentiel local ?</p>",
"pullChanges": "Tirer les changements",
"pullChanges": "Tirer les changements distants",
"history": "Historique",
"projectHistory": "Historique du projet",
"daysAgo": "il y a __count__ jour",
@@ -914,6 +924,8 @@
}
},
"typedInput": {
"selected": "__count__ sélectionnée",
"selected_plural": "__count__ sélectionnées",
"type": {
"str": "chaîne de caractères",
"num": "nombre",
@@ -924,7 +936,14 @@
"date": "horodatage",
"jsonata": "expression",
"env": "variable d'environnement",
"cred": "identifiant"
"cred": "identifiant",
"conf-types": "noeud de configuration"
},
"date": {
"format": {
"timestamp": "millisecondes depuis l'époque",
"object": "Objet de date JavaScript"
}
}
},
"editableList": {
@@ -957,7 +976,7 @@
"result": "Résultat",
"format": "Format",
"compatMode": "Mode de compatibilité activé",
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuiller mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
"compatModeDesc": "<h3>Mode de compatibilité JSONata</h3><p> L'expression actuelle semble toujours faire référence à <code>msg</code> et sera donc évaluée en mode de compatibilité. Veuillez mettre à jour l'expression pour ne pas utiliser <code>msg</code> car ce mode sera supprimé à l'avenir.</p><p> Lorsque la prise en charge de JSONata a été ajoutée pour la première fois à Node-RED, il fallait que l'expression référencie l'objet <code>msg</code>. Par exemple, <code>msg.payload</code> serait utilisé pour accéder à la charge utile.</p><p> Cela n'est plus nécessaire car l'expression sera évaluée directement par rapport au message. Pour accéder à la charge utile, l'expression doit être simplement <code>charge utile</code>.</p>",
"noMatch": "Aucun résultat correspondant",
"errors": {
"invalid-expr": "Expression JSONata non valide :\n __message__",
@@ -980,7 +999,7 @@
},
"jsonEditor": {
"title": "Éditeur JSON",
"format": "Format JSON",
"format": "Formatter JSON",
"rawMode": "Modifier JSON",
"uiMode": "Afficher l'éditeur",
"rawMode-readonly": "JSON",
@@ -999,7 +1018,7 @@
"markdownEditor": {
"title": "Éditeur Markdown",
"expand": "Développer",
"format": "Formaté avec Markdown",
"format": "Formatter avec Markdown",
"heading1": "Rubrique 1",
"heading2": "Rubrique 2",
"heading3": "Rubrique 3",
@@ -1073,7 +1092,7 @@
"credential-key": "Clé de chiffrement des identifiants",
"cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
"already-exists2": "Existe déjà",
"git-error": "Erreur git",
"git-error": "Erreur Git",
"connection-failed": "La connexion a échoué",
"not-git-repo": "Ce n'est pas un dépôt Git",
"repo-not-found": "Référentiel introuvable"
@@ -1087,7 +1106,7 @@
"credentials-file": "Fichier d'identifiants"
},
"encryption-config": {
"setup": "Configuration du chiffrage de votre fichier d'informations d'identification",
"setup": "Configuration du chiffrement de votre fichier d'informations d'identification",
"desc0": "Votre fichier d'informations d'identification de flux peut être chiffré pour sécuriser son contenu.",
"desc1": "Si vous souhaitez stocker ces identifiants dans un référentiel Git public, vous devez les chiffrer en fournissant une phrase clé secrète.",
"desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.",
@@ -1144,9 +1163,9 @@
"add-ssh-key": "Ajouter une clé ssh",
"credentials-encryption-key": "Clé de chiffrement des identifiants",
"already-exists-2": "Existe déjà",
"git-error": "Erreur git",
"git-error": "Erreur Git",
"con-failed": "La connexion a échoué",
"not-git": "Ce n'est pas un dépôt git",
"not-git": "Ce n'est pas un dépôt Git",
"no-resource": "Référentiel introuvable",
"cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.",
"unexpected_error": "Erreur inattendue",
@@ -1184,7 +1203,7 @@
},
"errors": {
"no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.",
"unexpected": "Une erreur inattendue est apparue",
"unexpected": "Une erreur inattendue est survenue",
"code": "Code"
}
},
@@ -1253,7 +1272,7 @@
"list-modified-nodes": "Afficher les flux modifiés",
"list-hidden-flows": "Afficher les flux cachés",
"list-flows": "Lister les flux",
"list-subflows": "Liste les sous-flux",
"list-subflows": "Lister les sous-flux",
"go-to-previous-location": "Aller à l'emplacement précédent",
"go-to-next-location": "Aller à l'emplacement suivant",
"copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers",
@@ -1313,8 +1332,8 @@
"align-selection-to-bottom": "Aligner la sélection vers le bas",
"align-selection-to-middle": "Aligner la sélection au centre verticalement",
"align-selection-to-center": "Aligner la sélection au centre horizontalement",
"distribute-selection-horizontally": "Distribuer la sélection horizontalement",
"distribute-selection-vertical": "Distribuer la sélection verticalement",
"distribute-selection-horizontally": "Répartir la sélection horizontalement",
"distribute-selection-vertical": "Répartir la sélection verticalement",
"wire-series-of-nodes": "Connecter les noeuds en série",
"wire-node-to-multiple": "Connecter les noeuds à plusieurs",
"wire-multiple-to-node": "Connecter plusieurs au noeud",

View File

@@ -27,7 +27,8 @@
"lock": "固定",
"unlock": "固定を解除",
"locked": "固定済み",
"unlocked": "固定なし"
"unlocked": "固定なし",
"format": "形式"
},
"type": {
"string": "文字列",
@@ -281,8 +282,8 @@
"selected": "選択したフロー",
"current": "現在のタブ",
"all": "全てのタブ",
"compact": "インデントのないJSONフォーマット",
"formatted": "インデント付きのJSONフォーマット",
"compact": "インデントなし",
"formatted": "インデント付き",
"copy": "書き出し",
"export": "ライブラリに書き出し",
"exportAs": "書き出し先",
@@ -303,7 +304,8 @@
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
},
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
"alreadyExists": "本ノードは既に存在"
},
"copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました",
@@ -371,6 +373,7 @@
"deleted": "削除",
"flowDeleted": "削除されたフロー",
"flowAdded": "追加されたフロー",
"moved": "移動",
"movedTo": "__id__ へ移動",
"movedFrom": "__id__ から移動"
},
@@ -613,6 +616,8 @@
},
"nodeCount": "__label__ 個のノード",
"nodeCount_plural": "__label__ 個のノード",
"pluginCount": "__count__ 個のプラグイン",
"pluginCount_plural": "__count__ 個のプラグイン",
"moduleCount": "__count__ 個のモジュール",
"moduleCount_plural": "__count__ 個のモジュール",
"inuse": "使用中",
@@ -640,6 +645,7 @@
"errors": {
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"installTimeout": "<p>バックグラウンドでインストールが継続されます。</p><p>完了した時にノードが表示されます。詳細はログを確認してください。</p>",
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
@@ -654,6 +660,9 @@
"body": "<p>__module__ を削除します。</p><p>Node-REDからードを削除します。ードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
"title": "ノードを削除"
},
"removePlugin": {
"body": "<p>プラグイン __module__ を削除しました。ブラウザを再読み込みして残った表示を消してください。</p>"
},
"update": {
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
"title": "ノードの更新"
@@ -665,7 +674,8 @@
"review": "ノードの情報を参照",
"install": "追加",
"remove": "削除",
"update": "更新"
"update": "更新",
"understood": "了解"
}
}
}
@@ -718,6 +728,7 @@
"nodeHelp": "ノードヘルプ",
"showHelp": "ヘルプを表示",
"showInOutline": "アウトラインに表示",
"hideTopics": "トピックを非表示",
"showTopics": "トピックを表示",
"noHelp": "ヘルプのトピックが未選択",
"changeLog": "更新履歴"
@@ -913,6 +924,8 @@
}
},
"typedInput": {
"selected": "__count__個を選択",
"selected_plural": "__count__個を選択",
"type": {
"str": "文字列",
"num": "数値",
@@ -923,7 +936,14 @@
"date": "日時",
"jsonata": "JSONata式",
"env": "環境変数",
"cred": "認証情報"
"cred": "認証情報",
"conf-types": "設定ノード"
},
"date": {
"format": {
"timestamp": "エポックからの経過ミリ秒",
"object": "JavaScript日付オブジェクト"
}
}
},
"editableList": {
@@ -1229,7 +1249,7 @@
},
"env-var": {
"environment": "環境変数",
"header": "大域環境変数",
"header": "グローバル環境変数",
"revert": "破棄"
},
"action-list": {
@@ -1381,7 +1401,7 @@
"copy-item-edit-url": "要素の編集URLをコピー",
"move-flow-to-start": "フローを先頭に移動",
"move-flow-to-end": "フローを末尾に移動",
"show-global-env": "大域環境変数を表示",
"show-global-env": "グローバル環境変数を表示",
"lock-flow": "フローを固定",
"unlock-flow": "フローの固定を解除",
"show-node-help": "ノードのヘルプを表示"

View File

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

View File

@@ -26,6 +26,15 @@ RED.comms = (function() {
var reconnectAttempts = 0;
var active = false;
RED.events.on('login', function(username) {
// User has logged in
// Need to upgrade the connection to be authenticated
if (ws && ws.readyState == 1) {
const auth_tokens = RED.settings.get("auth-tokens");
ws.send(JSON.stringify({auth:auth_tokens.access_token}))
}
})
function connectWS() {
active = true;
var wspath;
@@ -56,6 +65,7 @@ RED.comms = (function() {
ws.send(JSON.stringify({subscribe:t}));
}
}
emit('connect')
}
ws = new WebSocket(wspath);
@@ -180,9 +190,53 @@ RED.comms = (function() {
}
}
function send(topic, msg) {
if (ws && ws.readyState == 1) {
ws.send(JSON.stringify({
topic,
data: msg
}))
}
}
const eventHandlers = {};
function on(evt,func) {
eventHandlers[evt] = eventHandlers[evt]||[];
eventHandlers[evt].push(func);
}
function off(evt,func) {
const handler = eventHandlers[evt];
if (handler) {
for (let i=0;i<handler.length;i++) {
if (handler[i] === func) {
handler.splice(i,1);
return;
}
}
}
}
function emit() {
const evt = arguments[0]
const args = Array.prototype.slice.call(arguments,1);
if (eventHandlers[evt]) {
let cpyHandlers = [...eventHandlers[evt]];
for (let i=0;i<cpyHandlers.length;i++) {
try {
cpyHandlers[i].apply(null, args);
} catch(err) {
console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
console.warn(err);
}
}
}
}
return {
connect: connectWS,
subscribe: subscribe,
unsubscribe:unsubscribe
unsubscribe:unsubscribe,
on,
off,
send
}
})();

View File

@@ -29,7 +29,14 @@ RED.history = (function() {
}
return RED.nodes.junction(id);
}
function ensureUnlocked(id, flowsToLock) {
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false;
if (flow && isLocked) {
flow.locked = false;
flowsToLock.add(flow)
}
}
function undoEvent(ev) {
var i;
var len;
@@ -59,18 +66,46 @@ RED.history = (function() {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: {},
rev: RED.nodes.version()
moved: {},
complete: true,
rev: RED.nodes.version(),
dirty: RED.nodes.dirty()
};
var selectedTab = RED.workspaces.active();
inverseEv.config.forEach(n => {
const node = RED.nodes.node(n.id)
if (node) {
inverseEv.changed[n.id] = node.changed
inverseEv.moved[n.id] = node.moved
}
})
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
// Clear all change flags from the import
RED.nodes.dirty(false);
const flowsToLock = new Set()
imported.nodes.forEach(function(n) {
if (ev.changed[n.id]) {
ensureUnlocked(n.z, flowsToLock)
n.changed = true;
inverseEv.changed[n.id] = true;
}
if (ev.moved[n.id]) {
ensureUnlocked(n.z, flowsToLock)
n.moved = true;
}
})
flowsToLock.forEach(flow => {
flow.locked = true
})
RED.nodes.version(ev.rev);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.workspaces.show(selectedTab, true);
RED.sidebar.config.refresh();
} else {
var importMap = {};
ev.config.forEach(function(n) {

View File

@@ -0,0 +1,490 @@
RED.multiplayer = (function () {
// activeSessionId - used to identify sessions across websocket reconnects
let activeSessionId
let headerWidget
// Map of session id to { session:'', user:{}, location:{}}
let sessions = {}
// Map of username to { user:{}, sessions:[] }
let users = {}
function addUserSession (session) {
if (sessions[session.session]) {
// This is an existing connection that has been authenticated
const existingSession = sessions[session.session]
if (existingSession.user.username !== session.user.username) {
removeUserHeaderButton(users[existingSession.user.username])
}
}
sessions[session.session] = session
const user = users[session.user.username] = users[session.user.username] || {
user: session.user,
sessions: []
}
if (session.user.profileColor === undefined) {
session.user.profileColor = (1 + Math.floor(Math.random() * 5))
}
session.location = session.location || {}
user.sessions.push(session)
if (session.session === activeSessionId) {
// This is the current user session - do not add a extra button for them
} else {
if (user.sessions.length === 1) {
if (user.button) {
clearTimeout(user.inactiveTimeout)
clearTimeout(user.removeTimeout)
user.button.removeClass('inactive')
} else {
addUserHeaderButton(user)
}
}
sessions[session.session].location = session.location
updateUserLocation(session.session)
}
}
function removeUserSession (sessionId, isDisconnected) {
removeUserLocation(sessionId)
const session = sessions[sessionId]
delete sessions[sessionId]
const user = users[session.user.username]
const i = user.sessions.indexOf(session)
user.sessions.splice(i, 1)
if (isDisconnected) {
removeUserHeaderButton(user)
} else {
if (user.sessions.length === 0) {
// Give the user 5s to reconnect before marking inactive
user.inactiveTimeout = setTimeout(() => {
user.button.addClass('inactive')
// Give the user further 20 seconds to reconnect before removing them
// from the user toolbar entirely
user.removeTimeout = setTimeout(() => {
removeUserHeaderButton(user)
}, 20000)
}, 5000)
}
}
}
function addUserHeaderButton (user) {
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
.attr('data-username', user.user.username)
.prependTo("#red-ui-multiplayer-user-list");
var button = user.button.find("button")
RED.popover.tooltip(button, user.user.username)
button.on('click', function () {
const location = user.sessions[0].location
revealUser(location)
})
const userProfile = RED.user.generateUserIcon(user.user)
userProfile.appendTo(button)
}
function removeUserHeaderButton (user) {
user.button.remove()
delete user.button
}
function getLocation () {
const location = {
workspace: RED.workspaces.active()
}
const editStack = RED.editor.getEditStack()
for (let i = editStack.length - 1; i >= 0; i--) {
if (editStack[i].id) {
location.node = editStack[i].id
break
}
}
return location
}
function publishLocation () {
const location = getLocation()
if (location.workspace !== 0) {
log('send', 'multiplayer/location', location)
RED.comms.send('multiplayer/location', location)
}
}
function revealUser(location, skipWorkspace) {
if (location.node) {
// Need to check if this is a known node, so we can fall back to revealing
// the workspace instead
const node = RED.nodes.node(location.node)
if (node) {
RED.view.reveal(location.node)
} else if (!skipWorkspace && location.workspace) {
RED.view.reveal(location.workspace)
}
} else if (!skipWorkspace && location.workspace) {
RED.view.reveal(location.workspace)
}
}
const workspaceTrays = {}
function getWorkspaceTray(workspaceId) {
// console.log('get tray for',workspaceId)
if (!workspaceTrays[workspaceId]) {
const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
const users = []
const userIcons = {}
const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
const userCountSpan = userCountIcon.find('span span')
userCountIcon.hide()
userCountSpan.text('')
userCountIcon.appendTo(tray)
const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
const content = $('<div>')
users.forEach(sessionId => {
$('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
evt.preventDefault()
revealUser(sessions[sessionId].location, true)
userCountTooltip.close()
})).appendTo(content)
})
return content
},
null,
true
)
const updateUserCount = function () {
const maxShown = 2
const children = tray.children()
children.each(function (index, element) {
const i = users.length - index
if (i > maxShown) {
$(this).hide()
} else if (i >= 0) {
$(this).show()
}
})
if (users.length < maxShown + 1) {
userCountIcon.hide()
} else {
userCountSpan.text('+'+(users.length - maxShown))
userCountIcon.show()
}
}
workspaceTrays[workspaceId] = {
attached: false,
tray,
users,
userIcons,
addUser: function (sessionId) {
if (users.indexOf(sessionId) === -1) {
// console.log(`addUser ws:${workspaceId} session:${sessionId}`)
users.push(sessionId)
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
userLocationIcon.prependTo(tray)
RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
userIcons[sessionId] = userLocationIcon
updateUserCount()
}
},
removeUser: function (sessionId) {
// console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
const index = users.indexOf(sessionId)
if (index > -1) {
users.splice(index, 1)
userIcons[sessionId].remove()
delete userIcons[sessionId]
}
updateUserCount()
},
updateUserCount
}
}
const trayDef = workspaceTrays[workspaceId]
if (!trayDef.attached) {
const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
if (workspaceTab.length > 0) {
trayDef.attached = true
trayDef.tray.appendTo(workspaceTab)
trayDef.users.forEach(sessionId => {
trayDef.userIcons[sessionId].on('click', function (evt) {
revealUser(sessions[sessionId].location, true)
})
})
}
}
return workspaceTrays[workspaceId]
}
function attachWorkspaceTrays () {
let viewTouched = false
for (let sessionId of Object.keys(sessions)) {
const location = sessions[sessionId].location
if (location) {
if (location.workspace) {
getWorkspaceTray(location.workspace).updateUserCount()
}
if (location.node) {
addUserToNode(sessionId, location.node)
viewTouched = true
}
}
}
if (viewTouched) {
RED.view.redraw()
}
}
function addUserToNode(sessionId, nodeId) {
const node = RED.nodes.node(nodeId)
if (node) {
if (!node._multiplayer) {
node._multiplayer = {
users: [sessionId]
}
node._multiplayer_refresh = true
} else {
if (node._multiplayer.users.indexOf(sessionId) === -1) {
node._multiplayer.users.push(sessionId)
node._multiplayer_refresh = true
}
}
}
}
function removeUserFromNode(sessionId, nodeId) {
const node = RED.nodes.node(nodeId)
if (node && node._multiplayer) {
const i = node._multiplayer.users.indexOf(sessionId)
if (i > -1) {
node._multiplayer.users.splice(i, 1)
}
if (node._multiplayer.users.length === 0) {
delete node._multiplayer
} else {
node._multiplayer_refresh = true
}
}
}
function removeUserLocation (sessionId) {
updateUserLocation(sessionId, {})
}
function updateUserLocation (sessionId, location) {
let viewTouched = false
const oldLocation = sessions[sessionId].location
if (location) {
if (oldLocation.workspace !== location.workspace) {
// console.log('removing', sessionId, oldLocation.workspace)
workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
}
if (oldLocation.node !== location.node) {
removeUserFromNode(sessionId, oldLocation.node)
viewTouched = true
}
sessions[sessionId].location = location
} else {
location = sessions[sessionId].location
}
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
if (location.workspace) {
getWorkspaceTray(location.workspace).addUser(sessionId)
}
if (location.node) {
addUserToNode(sessionId, location.node)
viewTouched = true
}
if (viewTouched) {
RED.view.redraw()
}
}
// function refreshUserLocations () {
// for (const session of Object.keys(sessions)) {
// if (session !== activeSessionId) {
// updateUserLocation(session)
// }
// }
// }
return {
init: function () {
function createAnnotationUser(user) {
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
const radius = 20
badge.setAttribute("cx",radius/2);
badge.setAttribute("cy",radius/2);
badge.setAttribute("r",radius/2);
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
group.appendChild(badge)
if (user && user.profileColor !== undefined) {
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
}
if (user && user.image) {
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
image.setAttribute("width", radius)
image.setAttribute("height", radius)
image.setAttribute("href", user.image)
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
group.appendChild(image)
} else if (user && user.anonymous) {
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
anonIconHead.setAttribute("cx", radius/2)
anonIconHead.setAttribute("cy", radius/2 - 2)
anonIconHead.setAttribute("r", 2.4)
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
group.appendChild(anonIconHead)
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
group.appendChild(anonIconBody)
} else {
const labelText = user.username ? user.username.substring(0,2) : user
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
if (user.username) {
label.setAttribute("class","red-ui-multiplayer-annotation-label");
label.textContent = user.username.substring(0,2)
} else {
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
label.textContent = user
}
label.setAttribute("text-anchor", "middle")
label.setAttribute("x",radius/2);
label.setAttribute("y",radius/2 + 3);
group.appendChild(label)
}
const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
border.setAttribute("cx",radius/2);
border.setAttribute("cy",radius/2);
border.setAttribute("r",radius/2);
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
group.appendChild(border)
return group
}
RED.view.annotations.register("red-ui-multiplayer",{
type: 'badge',
align: 'left',
class: "red-ui-multiplayer-annotation",
show: "_multiplayer",
refresh: "_multiplayer_refresh",
element: function(node) {
const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
containerGroup.setAttribute("transform","translate(0,-4)")
if (node._multiplayer) {
let y = 0
for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
const user = sessions[node._multiplayer.users[i]].user
const group = createAnnotationUser(user)
group.setAttribute("transform","translate("+y+",0)")
y += 15
containerGroup.appendChild(group)
}
if (node._multiplayer.users.length > 2) {
const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
group.setAttribute("transform","translate("+y+",0)")
y += 12
containerGroup.appendChild(group)
}
}
return containerGroup;
},
tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
});
// activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
// if (!activeSessionId) {
activeSessionId = RED.nodes.id()
// RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
// log('Session ID (new)', activeSessionId)
// } else {
log('Session ID', activeSessionId)
// }
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
RED.comms.on('connect', () => {
const location = getLocation()
const connectInfo = {
session: activeSessionId
}
if (location.workspace !== 0) {
connectInfo.location = location
}
RED.comms.send('multiplayer/connect', connectInfo)
})
RED.comms.subscribe('multiplayer/#', (topic, msg) => {
log('recv', topic, msg)
if (topic === 'multiplayer/init') {
// We have just reconnected, runtime has sent state to
// initialise the world
sessions = {}
users = {}
$('#red-ui-multiplayer-user-list').empty()
msg.sessions.forEach(session => {
addUserSession(session)
})
} else if (topic === 'multiplayer/connection-added') {
addUserSession(msg)
} else if (topic === 'multiplayer/connection-removed') {
removeUserSession(msg.session, msg.disconnected)
} else if (topic === 'multiplayer/location') {
const session = msg.session
delete msg.session
updateUserLocation(session, msg)
}
})
RED.events.on('workspace:change', (event) => {
getWorkspaceTray(event.workspace)
publishLocation()
})
RED.events.on('editor:open', () => {
publishLocation()
})
RED.events.on('editor:close', () => {
publishLocation()
})
RED.events.on('editor:change', () => {
publishLocation()
})
RED.events.on('login', () => {
publishLocation()
})
RED.events.on('flows:loaded', () => {
attachWorkspaceTrays()
})
RED.events.on('workspace:close', (event) => {
// A subflow tab has been closed. Need to mark its tray as detached
if (workspaceTrays[event.workspace]) {
workspaceTrays[event.workspace].attached = false
}
})
RED.events.on('logout', () => {
const disconnectInfo = {
session: activeSessionId
}
RED.comms.send('multiplayer/disconnect', disconnectInfo)
RED.settings.removeLocal('multiplayer:sessionId')
})
}
}
function log() {
if (RED.multiplayer.DEBUG) {
console.log('[multiplayer]', ...arguments)
}
}
})();

View File

@@ -91,6 +91,31 @@ RED.nodes = (function() {
getNodeTypes: function() {
return Object.keys(nodeDefinitions);
},
/**
* Get an array of node definitions
* @param {Object} options - options object
* @param {boolean} [options.configOnly] - if true, only return config nodes
* @param {function} [options.filter] - a filter function to apply to the list of nodes
* @returns array of node definitions
*/
getNodeDefinitions: function(options) {
const result = []
const configOnly = (options && options.configOnly)
const filter = (options && options.filter)
const keys = Object.keys(nodeDefinitions)
for (const key of keys) {
const def = nodeDefinitions[key]
if(!def) { continue }
if (configOnly && def.category !== "config") {
continue
}
if (filter && !filter(nodeDefinitions[key])) {
continue
}
result.push(nodeDefinitions[key])
}
return result
},
setNodeList: function(list) {
nodeList = [];
for(var i=0;i<list.length;i++) {
@@ -124,6 +149,8 @@ RED.nodes = (function() {
},
removeNodeSet: function(id) {
var ns = nodeSets[id];
if (!ns) { return {} }
for (var j=0;j<ns.types.length;j++) {
delete typeToId[ns.types[j]];
}
@@ -547,12 +574,16 @@ RED.nodes = (function() {
* @param {String} z tab id
*/
checkTabState: function (z) {
const ws = workspaces[z]
const ws = workspaces[z] || subflows[z]
if (ws) {
const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
if (Boolean(ws.contentsChanged) !== contentsChanged) {
ws.contentsChanged = contentsChanged
RED.events.emit("flows:change", ws);
if (ws.type === 'tab') {
RED.events.emit("flows:change", ws);
} else {
RED.events.emit("subflows:change", ws);
}
}
}
}
@@ -1025,7 +1056,22 @@ RED.nodes = (function() {
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{
name:{value:""},
env:{value:[]}
env:{value:[], validate: function(value) {
const errors = []
if (value) {
value.forEach(env => {
const r = RED.utils.validateTypedProperty(env.value, env.type)
if (r !== true) {
errors.push(env.name+': '+r)
}
})
}
if (errors.length === 0) {
return true
} else {
return errors
}
}}
},
icon: function() { return sf.icon||"subflow.svg" },
category: sf.category || "subflows",
@@ -2360,6 +2406,13 @@ RED.nodes = (function() {
} else {
delete n.g
}
// If importing into a subflow, ensure an outbound-link doesn't get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace);
});
}
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type) {
@@ -2383,14 +2436,6 @@ RED.nodes = (function() {
}
}
}
// If importing into a subflow, ensure an outbound-link doesn't
// get added
if (activeSubflow && /^link /.test(n.type) && n.links) {
n.links = n.links.filter(function(id) {
const otherNode = node_map[id] || RED.nodes.node(id);
return (otherNode && otherNode.z === activeWorkspace)
});
}
}
for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i];

View File

@@ -1,6 +1,7 @@
RED.plugins = (function() {
var plugins = {};
var pluginsByType = {};
var moduleList = {};
function registerPlugin(id,definition) {
plugins[id] = definition;
@@ -38,9 +39,43 @@ RED.plugins = (function() {
function getPluginsByType(type) {
return pluginsByType[type] || [];
}
function setPluginList(list) {
for(let i=0;i<list.length;i++) {
let p = list[i];
addPlugin(p);
}
}
function addPlugin(p) {
moduleList[p.module] = moduleList[p.module] || {
name:p.module,
version:p.version,
local:p.local,
sets:{},
plugin: true,
id: p.id
};
if (p.pending_version) {
moduleList[p.module].pending_version = p.pending_version;
}
moduleList[p.module].sets[p.name] = p;
RED.events.emit("registry:plugin-module-added",p.module);
}
function getModule(module) {
return moduleList[module];
}
return {
registerPlugin: registerPlugin,
getPlugin: getPlugin,
getPluginsByType: getPluginsByType
getPluginsByType: getPluginsByType,
setPluginList: setPluginList,
addPlugin: addPlugin,
getModule: getModule
}
})();

View File

@@ -25,6 +25,7 @@ var RED = (function() {
cache: false,
url: 'plugins',
success: function(data) {
RED.plugins.setPluginList(data);
loader.reportProgress(RED._("event.loadPlugins"), 13)
RED.i18n.loadPluginCatalogs(function() {
loadPlugins(function() {
@@ -297,6 +298,7 @@ var RED = (function() {
RED.workspaces.show(workspaces[0]);
}
}
RED.events.emit('flows:loaded')
} catch(err) {
console.warn(err);
RED.notify(
@@ -534,6 +536,41 @@ var RED = (function() {
RED.view.redrawStatus(node);
}
});
RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
if (topic == "notification/plugin/added") {
RED.settings.refreshSettings(function(err, data) {
let addedPlugins = [];
msg.forEach(function(m) {
let id = m.id;
RED.plugins.addPlugin(m);
m.plugins.forEach((p) => {
addedPlugins.push(p.id);
})
RED.i18n.loadNodeCatalog(id, function() {
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
$.ajax({
headers: {
"Accept":"text/html",
"Accept-Language": lang
},
cache: false,
url: 'plugins/'+id,
success: function(data) {
appendPluginConfig(data);
}
});
});
});
if (addedPlugins.length) {
let pluginList = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
// ToDo: Adapt notification (node -> plugin)
RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
}
})
}
});
let pendingNodeRemovedNotifications = []
let pendingNodeRemovedTimeout
@@ -803,6 +840,10 @@ var RED = (function() {
RED.nodes.init();
RED.runtime.init()
if (RED.settings.theme("multiplayer.enabled",false)) {
RED.multiplayer.init()
}
RED.comms.connect();
$("#red-ui-main-container").show();

View File

@@ -26,6 +26,7 @@ RED.clipboard = (function() {
var currentPopoverError;
var activeTab;
var libraryBrowser;
var clipboardTabs;
var activeLibraries = {};
@@ -215,6 +216,13 @@ RED.clipboard = (function() {
open: function( event, ui ) {
RED.keyboard.disable();
},
beforeClose: function(e) {
if (clipboardTabs && activeTab === "red-ui-clipboard-dialog-export-tab-clipboard") {
const jsonTabIndex = clipboardTabs.getTabIndex('red-ui-clipboard-dialog-export-tab-clipboard-json')
const activeTabIndex = clipboardTabs.activeIndex()
RED.settings.set("editor.dialog.export.json-view", activeTabIndex === jsonTabIndex )
}
},
close: function(e) {
RED.keyboard.enable();
if (popover) {
@@ -228,12 +236,23 @@ RED.clipboard = (function() {
exportNodesDialog =
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
'</span>'+
'<div style="display: flex; justify-content: space-between;">'+
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.format"></label>'+
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'<div class="red-ui-clipboard-dialog-box">'+
'<div class="red-ui-clipboard-dialog-tabs">'+
@@ -248,15 +267,9 @@ RED.clipboard = (function() {
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
'</div>'+
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
'<div class="form-row" style="height:calc(100% - 40px)">'+
'<div class="form-row" style="height:calc(100% - 10px)">'+
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
'</div>'+
'<div class="form-row" style="text-align: right;">'+
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+
@@ -569,7 +582,7 @@ RED.clipboard = (function() {
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
clipboardTabs = null
var tabs = RED.tabs.create({
id: "red-ui-clipboard-dialog-export-tabs",
vertical: true,
@@ -630,7 +643,7 @@ RED.clipboard = (function() {
$("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
$("#red-ui-clipboard-dialog-export").button("enable");
var clipboardTabs = RED.tabs.create({
clipboardTabs = RED.tabs.create({
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
onchange: function(tab) {
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
@@ -647,6 +660,9 @@ RED.clipboard = (function() {
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
label: RED._("editor.types.json")
});
if (RED.settings.get("editor.dialog.export.json-view") === true) {
clipboardTabs.activateTab("red-ui-clipboard-dialog-export-tab-clipboard-json");
}
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
data: []

View File

@@ -174,12 +174,24 @@
this.uiContainer.width(m[1]);
}
if (this.options.sortable) {
var isCanceled = false; // Flag to track if an item has been canceled from being dropped into a different list
var noDrop = false; // Flag to track if an item is being dragged into a different list
var handle = (typeof this.options.sortable === 'string')?
this.options.sortable :
".red-ui-editableList-item-handle";
var sortOptions = {
axis: "y",
update: function( event, ui ) {
// dont trigger update if the item is being canceled
const targetList = $(event.target);
const draggedItem = ui.item;
const draggedItemParent = draggedItem.parent();
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
noDrop = true;
}
if (isCanceled || noDrop) {
return;
}
if (that.options.sortItems) {
that.options.sortItems(that.items());
}
@@ -189,8 +201,32 @@
tolerance: "pointer",
forcePlaceholderSize:true,
placeholder: "red-ui-editabelList-item-placeholder",
start: function(e, ui){
ui.placeholder.height(ui.item.height()-4);
start: function (event, ui) {
isCanceled = false;
ui.placeholder.height(ui.item.height() - 4);
ui.item.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
},
stop: function (event, ui) {
ui.item.css('cursor', 'auto');
},
receive: function (event, ui) {
if (ui.item.hasClass("red-ui-editableList-item-constrained")) {
isCanceled = true;
$(ui.sender).sortable('cancel');
}
},
over: function (event, ui) {
// if the dragged item is constrained, prevent it from being dropped into a different list
const targetList = $(event.target);
const draggedItem = ui.item;
const draggedItemParent = draggedItem.parent();
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
noDrop = true;
draggedItem.css('cursor', 'no-drop'); // TODO: this doesn't seem to work, use a class instead?
} else {
noDrop = false;
draggedItem.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
}
}
};
if (this.options.connectWith) {

View File

@@ -211,7 +211,7 @@ RED.popover = (function() {
closePopup(true);
});
}
if (trigger === 'hover' && options.interactive) {
if (/*trigger === 'hover' && */options.interactive) {
div.on('mouseenter', function(e) {
clearTimeout(timer);
active = true;
@@ -445,9 +445,12 @@ RED.popover = (function() {
return {
create: createPopover,
tooltip: function(target,content, action) {
tooltip: function(target,content, action, interactive) {
var label = function() {
var label = content;
if (typeof content === 'function') {
label = content()
}
if (action) {
var shortcut = RED.keyboard.getShortcut(action);
if (shortcut && shortcut.key) {
@@ -463,6 +466,7 @@ RED.popover = (function() {
size: "small",
direction: "bottom",
content: label,
interactive,
delay: { show: 750, hide: 50 }
});
popover.setContent = function(newContent) {

View File

@@ -365,7 +365,10 @@ RED.tabs = (function() {
var thisTabA = thisTab.find("a");
if (options.onclick) {
options.onclick(tabs[thisTabA.attr('href').slice(1)]);
options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
return false
}
}
activateTab(thisTabA);
if (fireSelectionChanged) {
@@ -548,6 +551,8 @@ RED.tabs = (function() {
ul.find("li.red-ui-tab a")
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
.on("mouseup",onTabClick)
// prevent browser-default middle-click behaviour
.on("auxclick", function(evt) { evt.preventDefault() })
.on("click", function(evt) {evt.preventDefault(); })
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
@@ -816,6 +821,8 @@ RED.tabs = (function() {
}
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
link.on("mouseup",onTabClick);
// prevent browser-default middle-click behaviour
link.on("auxclick", function(evt) { evt.preventDefault() })
link.on("click", function(evt) { evt.preventDefault(); })
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })

View File

@@ -54,25 +54,26 @@
return icon;
}
var autoComplete = function(options) {
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
}
const msgAutoComplete = function(options) {
return function(val) {
var matches = [];
options.forEach(opt => {
@@ -102,6 +103,197 @@
}
}
function getEnvVars (obj, envVars = {}) {
contextKnownKeys.env = contextKnownKeys.env || {}
if (contextKnownKeys.env[obj.id]) {
return contextKnownKeys.env[obj.id]
}
let parent
if (obj.type === 'tab' || obj.type === 'subflow') {
RED.nodes.eachConfig(function (conf) {
if (conf.type === "global-config") {
parent = conf;
}
})
} else if (obj.g) {
parent = RED.nodes.group(obj.g)
} else if (obj.z) {
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
}
if (parent) {
getEnvVars(parent, envVars)
}
if (obj.env) {
obj.env.forEach(env => {
envVars[env.name] = obj
})
}
contextKnownKeys.env[obj.id] = envVars
return envVars
}
const envAutoComplete = function (val) {
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (!editingNode) {
return []
}
const envVarsMap = getEnvVars(editingNode)
const envVars = Object.keys(envVarsMap)
const matches = []
const i = val.lastIndexOf('${')
let searchKey = val
let isSubkey = false
if (i > -1) {
if (val.lastIndexOf('}') < i) {
searchKey = val.substring(i+2)
isSubkey = true
}
}
envVars.forEach(v => {
let valMatch = getMatch(v, searchKey);
if (valMatch.found) {
const optSrc = envVarsMap[v]
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
if (optSrc) {
const optEl = $('<div>').css({ "font-size": "0.8em" });
let label
if (optSrc.type === 'global-config') {
label = RED._('sidebar.context.global')
} else if (optSrc.type === 'group') {
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
} else {
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
}
optEl.append(generateSpans({ match: label }));
optEl.appendTo(element);
}
matches.push({
value: isSubkey ? val + v + '}' : v,
label: element,
i: valMatch.index
});
}
})
matches.sort(function(A,B){return A.i-B.i})
return matches
}
let contextKnownKeys = {}
let contextCache = {}
if (RED.events) {
RED.events.on("editor:close", function () {
contextCache = {}
contextKnownKeys = {}
});
}
const contextAutoComplete = function() {
const that = this
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
if (searchKey.length > 0) {
try {
RED.utils.normalisePropertyExpression(searchKey)
} catch (err) {
// Not a valid context key, so don't try looking up
done()
return
}
}
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
if (contextCache[url]) {
// console.log('CACHED', url)
done()
} else {
// console.log('GET', url)
$.getJSON(url, function(data) {
// console.log(data)
contextCache[url] = true
const result = data[store] || {}
const keys = result.keys || []
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
keys.forEach(key => {
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
contextKnownKeys[scope][store].add(keyPrefix + key)
} else {
contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
}
})
done()
})
}
}
const getContextKeys = function(key, done) {
const keyParts = key.split('.')
const partialKey = keyParts.pop()
let scope = that.propertyType
if (scope === 'flow') {
// Get the flow id of the node we're editing
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (editingNode.z) {
scope = `${scope}/${editingNode.z}`
} else {
done([])
return
}
}
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
const searchKey = keyParts.join('.')
getContextKeysFromRuntime(scope, store, searchKey, function() {
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
getContextKeysFromRuntime(scope, store, key, function() {
done(contextKnownKeys[scope][store])
})
}
done(contextKnownKeys[scope][store])
})
}
return function(val, done) {
getContextKeys(val, function (keys) {
const matches = []
keys.forEach(v => {
let optVal = v
let valMatch = getMatch(optVal, val);
if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
// Search key ends in '.' - but doesn't match. Check again
// with [" at the end instead so we match bracket notation
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
}
if (valMatch.found) {
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
matches.push({
value: optVal,
label: element,
});
}
})
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
done(matches);
})
}
}
// This is a hand-generated list of completions for the core nodes (based on the node help html).
var msgCompletions = [
{ value: "payload" },
@@ -166,68 +358,93 @@
{ value: "_session", source: ["websocket out","tcp out"] },
]
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
msg: { value: "msg", label: "msg.", validate: RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions) },
flow: { value: "flow", label: "flow.", hasValue: true,
options: [],
validate: RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
global: {
value: "global", label: "global.", hasValue: true,
options: [],
validate: RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
return (true === RED.utils.validateTypedProperty(v, "num"));
str: { value: "str", label: "string", icon: "red/images/typedInput/az.svg" },
num: { value: "num", label: "number", icon: "red/images/typedInput/09.svg", validate: function (v, o) {
return RED.utils.validateTypedProperty(v, "num", o);
} },
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
bool: { value: "bool", label: "boolean", icon: "red/images/typedInput/bool.svg", options: ["true", "false"] },
json: {
value:"json",
label:"JSON",
icon:"red/images/typedInput/json.svg",
validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}},
expand: function() {
value: "json",
label: "JSON",
icon: "red/images/typedInput/json.svg",
validate: function (v, o) {
return RED.utils.validateTypedProperty(v, "json", o);
},
expand: function () {
var that = this;
var value = this.value();
try {
value = JSON.stringify(JSON.parse(value),null,4);
} catch(err) {
value = JSON.stringify(JSON.parse(value), null, 4);
} catch (err) {
}
RED.editor.editJSON({
value: value,
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
focus: true,
complete: function(v) {
complete: function (v) {
var value = v;
try {
value = JSON.stringify(JSON.parse(v));
} catch(err) {
} catch (err) {
}
that.value(value);
}
})
}
},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
re: { value: "re", label: "regular expression", icon: "red/images/typedInput/re.svg" },
date: {
value: "date",
label: "timestamp",
icon: "fa fa-clock-o",
options: [
{
label: 'milliseconds since epoch',
value: ''
},
{
label: 'YYYY-MM-DDTHH:mm:ss.sssZ',
value: 'iso'
},
{
label: 'JavaScript Date Object',
value: 'object'
}
]
},
jsonata: {
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.svg",
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
expand:function() {
validate: function (v, o) {
return RED.utils.validateTypedProperty(v, "jsonata", o);
},
expand: function () {
var that = this;
RED.editor.editExpression({
value: this.value().replace(/\t/g,"\n"),
value: this.value().replace(/\t/g, "\n"),
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
focus: true,
complete: function(v) {
that.value(v.replace(/\n/g,"\t"));
complete: function (v) {
that.value(v.replace(/\n/g, "\t"));
}
})
}
@@ -236,13 +453,13 @@
value: "bin",
label: "buffer",
icon: "red/images/typedInput/bin.svg",
expand: function() {
expand: function () {
var that = this;
RED.editor.editBuffer({
value: this.value(),
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
focus: true,
complete: function(v) {
complete: function (v) {
that.value(v);
}
})
@@ -251,15 +468,16 @@
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.svg"
icon: "red/images/typedInput/env.svg",
autoComplete: envAutoComplete
},
node: {
value: "node",
label: "node",
icon: "red/images/typedInput/target.svg",
valueLabel: function(container,value) {
valueLabel: function (container, value) {
var node = RED.nodes.node(value);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).css({
var nodeDiv = $('<div>', { class: "red-ui-search-result-node" }).css({
"margin-top": "2px",
"margin-left": "3px"
}).appendTo(container);
@@ -268,133 +486,190 @@
"margin-left": "6px"
}).appendTo(container);
if (node) {
var colour = RED.utils.getNodeColor(node.type,node._def);
var icon_url = RED.utils.getNodeIcon(node._def,node);
var colour = RED.utils.getNodeColor(node.type, node._def);
var icon_url = RED.utils.getNodeIcon(node._def, node);
if (node.type === 'tab') {
colour = "#C0DEED";
}
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
nodeDiv.css('backgroundColor', colour);
var iconContainer = $('<div/>', { class: "red-ui-palette-icon-container" }).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true);
var l = RED.utils.getNodeLabel(node,node.id);
var l = RED.utils.getNodeLabel(node, node.id);
nodeLabel.text(l);
} else {
nodeDiv.css({
'backgroundColor': '#eee',
'border-style' : 'dashed'
'border-style': 'dashed'
});
}
},
expand: function() {
expand: function () {
var that = this;
RED.tray.hide();
RED.view.selectNodes({
single: true,
selected: [that.value()],
onselect: function(selection) {
onselect: function (selection) {
that.value(selection.id);
RED.tray.show();
},
oncancel: function() {
oncancel: function () {
RED.tray.show();
}
})
}
},
cred:{
value:"cred",
label:"credential",
icon:"fa fa-lock",
cred: {
value: "cred",
label: "credential",
icon: "fa fa-lock",
inputType: "password",
valueLabel: function(container,value) {
valueLabel: function (container, value) {
var that = this;
container.css("pointer-events","none");
container.css("flex-grow",0);
container.css("pointer-events", "none");
container.css("flex-grow", 0);
this.elementDiv.hide();
var buttons = $('<div>').css({
position: "absolute",
right:"6px",
right: "6px",
top: "6px",
"pointer-events":"all"
"pointer-events": "all"
}).appendTo(container);
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
width:"20px"
}).appendTo(buttons).on("click", function(evt) {
width: "20px"
}).appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
var cursorPosition = that.input[0].selectionStart;
var currentType = that.input.attr("type");
if (currentType === "text") {
that.input.attr("type","password");
that.input.attr("type", "password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
setTimeout(function() {
setTimeout(function () {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
},50);
}, 50);
} else {
that.input.attr("type","text");
that.input.attr("type", "text");
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
setTimeout(function() {
setTimeout(function () {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
},50);
}, 50);
}
}).hide();
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-2px").appendTo(eyeButton);
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left", "-2px").appendTo(eyeButton);
if (value === "__PWRD__") {
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
padding:"6px 6px",
borderRadius:"4px"
padding: "6px 6px",
borderRadius: "4px"
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function(evt) {
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
innerContainer.hide();
container.css("background","none");
container.css("pointer-events","none");
container.css("background", "none");
container.css("pointer-events", "none");
that.input.val("");
that.element.val("");
that.elementDiv.show();
editButton.hide();
cancelButton.show();
eyeButton.show();
setTimeout(function() {
setTimeout(function () {
that.input.focus();
},50);
}, 50);
});
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left", "3px").appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
innerContainer.show();
container.css("background","");
container.css("background", "");
that.input.val("__PWRD__");
that.element.val("__PWRD__");
that.elementDiv.hide();
editButton.show();
cancelButton.hide();
eyeButton.hide();
that.input.attr("type","password");
that.input.attr("type", "password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
}).hide();
} else {
container.css("background","none");
container.css("pointer-events","none");
container.css("background", "none");
container.css("pointer-events", "none");
this.elementDiv.show();
eyeButton.show();
}
}
},
'conf-types': {
value: "conf-types",
label: "config",
icon: "fa fa-cog",
// hasValue: false,
valueLabel: function (container, value) {
// get the selected option (for access to the "name" and "module" properties)
const _options = this._optionsCache || this.typeList.find(opt => opt.value === value)?.options || []
const selectedOption = _options.find(opt => opt.value === value) || {
title: '',
name: '',
module: ''
}
container.attr("title", selectedOption.title) // set tooltip to the full path/id of the module/node
container.text(selectedOption.name) // apply the "name" of the selected option
// set "line-height" such as to make the "name" appear further up, giving room for the "module" to be displayed below the value
container.css("line-height", "1.4em")
// add the module name in smaller, lighter font below the value
$('<div></div>').text(selectedOption.module).css({
// "font-family": "var(--red-ui-monospace-font)",
color: "var(--red-ui-tertiary-text-color)",
"font-size": "0.8em",
"line-height": "1em",
opacity: 0.8
}).appendTo(container);
},
// hasValue: false,
options: function () {
if (this._optionsCache) {
return this._optionsCache
}
const configNodes = RED.nodes.registry.getNodeDefinitions({configOnly: true, filter: (def) => def.type !== "global-config"}).map((def) => {
// create a container with with 2 rows (row 1 for the name, row 2 for the module name in smaller, lighter font)
const container = $('<div style="display: flex; flex-direction: column; justify-content: space-between; row-gap: 1px;">')
const row1Name = $('<div>').text(def.type)
const row2Module = $('<div style="font-size: 0.8em; color: var(--red-ui-tertiary-text-color);">').text(def.set.module)
container.append(row1Name, row2Module)
return {
value: def.type,
name: def.type,
enabled: def.set.enabled ?? true,
local: def.set.local,
title: def.set.id, // tooltip e.g. "node-red-contrib-foo/bar"
module: def.set.module,
icon: container[0].outerHTML.trim(), // the typeInput will interpret this as html text and render it in the anchor
}
})
this._optionsCache = configNodes
return configNodes
}
}
};
// 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) {
let _options = opt.options
if (typeof _options === "function") {
_options = _options.call(this)
}
if (!opt.multiple) {
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
for (var i=0;i<_options.length;i++) {
op = _options[i];
if (typeof op === "string" && op === currentVal) {
return {value:currentVal}
} else if (op.value === currentVal) {
@@ -411,8 +686,8 @@
currentValues[v] = true;
}
});
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
for (var i=0;i<_options.length;i++) {
op = _options[i];
var val = typeof op === "string" ? op : op.value;
if (currentValues.hasOwnProperty(val)) {
delete currentValues[val];
@@ -427,6 +702,7 @@
}
var nlsd = false;
let contextStoreOptions;
$.widget( "nodered.typedInput", {
_create: function() {
@@ -438,7 +714,7 @@
}
}
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
contextStoreOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
}).sort(function(A,B) {
if (A.value === RED.settings.context.default) {
@@ -449,13 +725,17 @@
return A.value.localeCompare(B.value);
}
})
if (contextOptions.length < 2) {
if (contextStoreOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
allOptions.flow.options = contextStoreOptions;
allOptions.global.options = contextStoreOptions;
}
// Translate timestamp options
allOptions.date.options.forEach(opt => {
opt.label = RED._("typedInput.date.format." + (opt.value || 'timestamp'), {defaultValue: opt.label})
})
}
nlsd = true;
var that = this;
@@ -544,7 +824,7 @@
that.element.trigger('paste',evt);
});
this.input.on('keydown', function(evt) {
if (that.typeMap[that.propertyType].autoComplete) {
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
return
}
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
@@ -734,12 +1014,12 @@
}
if (menu.opts.multiple) {
var selected = {};
this.value().split(",").forEach(function(f) {
selected[f] = true;
})
this.value().split(",").forEach(function(f) {
selected[f] = true;
});
menu.find('input[type="checkbox"]').each(function() {
$(this).prop("checked",selected[$(this).data('value')])
})
$(this).prop("checked", selected[$(this).data('value')] || false);
});
}
@@ -830,7 +1110,7 @@
this.input.trigger('change',[this.propertyType,this.value()]);
}
} else {
this.optionSelectLabel.text(o.length+" selected");
this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length }));
}
}
},
@@ -838,7 +1118,9 @@
if (this.optionMenu) {
this.optionMenu.remove();
}
this.menu.remove();
if (this.menu) {
this.menu.remove();
}
this.uiSelect.remove();
},
types: function(types) {
@@ -871,7 +1153,7 @@
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
if (!firstCall) {
this.type(this.typeList[0].value);
this.type(this.typeList[0]?.value || ""); // permit empty typeList
}
} else {
this.propertyType = null;
@@ -908,6 +1190,11 @@
var selectedOption = [];
var valueToCheck = value;
if (opt.options) {
let _options = opt.options
if (typeof opt.options === "function") {
_options = opt.options.call(this)
}
if (opt.hasValue && opt.parse) {
var parts = opt.parse(value);
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
@@ -921,8 +1208,8 @@
checkValues = valueToCheck.split(",");
}
checkValues.forEach(function(valueToCheck) {
for (var i=0;i<opt.options.length;i++) {
var op = opt.options[i];
for (var i=0;i<_options.length;i++) {
var op = _options[i];
if (typeof op === "string") {
if (op === valueToCheck || op === ""+valueToCheck) {
selectedOption.push(that.activeOptions[op]);
@@ -957,7 +1244,7 @@
},
type: function(type) {
if (!arguments.length) {
return this.propertyType;
return this.propertyType || this.options?.default || '';
} else {
var that = this;
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
@@ -967,6 +1254,9 @@
// If previousType is !null, then this is a change of the type, rather than the initialisation
var previousType = this.typeMap[this.propertyType];
previousValue = this.input.val();
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
if (previousType && this.typeChanged) {
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
@@ -1013,7 +1303,9 @@
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
}
if (previousType.autoComplete) {
this.input.autoComplete("destroy");
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
}
}
this.propertyType = type;
@@ -1053,6 +1345,10 @@
this.optionMenu = null;
}
if (opt.options) {
let _options = opt.options
if (typeof _options === "function") {
_options = opt.options.call(this);
}
if (this.optionExpandButton) {
this.optionExpandButton.hide();
this.optionExpandButton.shown = false;
@@ -1069,7 +1365,7 @@
this.valueLabelContainer.hide();
}
this.activeOptions = {};
opt.options.forEach(function(o) {
_options.forEach(function(o) {
if (typeof o === 'string') {
that.activeOptions[o] = {label:o,value:o};
} else {
@@ -1089,7 +1385,7 @@
if (validValues) {
that._updateOptionSelectLabel(validValues)
} else {
op = opt.options[0];
op = _options[0] || {value:""}; // permit zero options
if (typeof op === "string") {
this.value(op);
that._updateOptionSelectLabel({value:op});
@@ -1108,7 +1404,7 @@
that._updateOptionSelectLabel(validValues);
}
} else {
var selectedOption = this.optionValue||opt.options[0];
var selectedOption = this.optionValue||_options[0];
if (opt.parse) {
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
var parts = opt.parse(this.input.val(),selectedOptionObj);
@@ -1141,8 +1437,18 @@
} else {
this.optionSelectTrigger.hide();
}
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: searchFunction,
minLength: 0
})
}
}
this.optionMenu = this._createMenu(opt.options,opt,function(v){
this.optionMenu = this._createMenu(_options,opt,function(v){
if (!opt.multiple) {
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
@@ -1183,8 +1489,12 @@
this.valueLabelContainer.hide();
this.elementDiv.show();
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: opt.autoComplete,
search: searchFunction,
minLength: 0
})
}
@@ -1233,26 +1543,48 @@
}
}
},
validate: function() {
var result;
var value = this.value();
var type = this.type();
validate: function(options) {
let valid = true;
const value = this.value();
const type = this.type();
if (this.typeMap[type] && this.typeMap[type].validate) {
var val = this.typeMap[type].validate;
if (typeof val === 'function') {
result = val(value);
const validate = this.typeMap[type].validate;
if (typeof validate === 'function') {
valid = validate(value, {});
} else {
result = val.test(value);
// Regex
valid = validate.test(value);
if (!valid) {
valid = RED._("validator.errors.invalid-regexp");
}
}
}
if ((typeof valid === "string") || !valid) {
this.element.addClass("input-error");
this.uiSelect.addClass("input-error");
if (typeof valid === "string") {
let tooltip = this.element.data("tooltip");
if (tooltip) {
tooltip.setContent(valid);
} else {
tooltip = RED.popover.tooltip(this.elementDiv, valid);
this.element.data("tooltip", tooltip);
}
}
} else {
result = true;
this.element.removeClass("input-error");
this.uiSelect.removeClass("input-error");
const tooltip = this.element.data("tooltip");
if (tooltip) {
this.element.data("tooltip", null);
tooltip.delete();
}
}
if (result) {
this.uiSelect.removeClass('input-error');
} else {
this.uiSelect.addClass('input-error');
if (options?.returnErrorMessage === true) {
return valid;
}
return result;
// Must return a boolean for no 3.x validator
return (typeof valid === "string") ? false : valid;
},
show: function() {
this.uiSelect.show();

View File

@@ -32,24 +32,28 @@ RED.contextMenu = (function () {
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
if (hasSelection) {
selection.nodes.forEach(n => {
const nodes = selection.nodes.slice();
while (nodes.length) {
const n = nodes.shift();
if (n.type === 'group') {
hasGroup = true;
nodes.push(...n.nodes);
} else {
isAllGroups = false;
}
if (n.d) {
hasDisabledNode = true;
} else {
hasEnabledNode = true;
if (n.d) {
hasDisabledNode = true;
} else {
hasEnabledNode = true;
}
}
if (n.l === undefined || n.l) {
hasLabeledNode = true;
} else {
hasUnlabeledNode = true;
}
});
}
}
const offset = $("#red-ui-workspace-chart").offset()
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
@@ -118,10 +122,16 @@ RED.contextMenu = (function () {
onselect: 'core:split-wire-with-link-nodes',
disabled: !canEdit || !hasLinks
},
null,
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
null
)
if (RED.settings.theme("menu.menu-item-import-library", true)) {
insertOptions.push(
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
)
}
if (hasSelection && canEdit) {
const nodeOptions = []
if (!hasMultipleSelection && !isGroup) {
@@ -194,8 +204,14 @@ RED.contextMenu = (function () {
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
{ onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") },
)
if (RED.settings.theme("menu.menu-item-export-library", true)) {
menuItems.push(
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
)
}
menuItems.push(
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
)
}

View File

@@ -34,6 +34,8 @@ RED.deploy = (function() {
var currentDiff = null;
var activeBackgroundDeployNotification;
function changeDeploymentType(type) {
deploymentType = type;
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
@@ -61,7 +63,7 @@ RED.deploy = (function() {
'<img src="red/images/spin.svg"/>'+
'</span>'+
'</a>'+
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i><i class="fa fa-lock"></i></a>'+
'</span></li>').prependTo(".red-ui-header-toolbar");
const mainMenuItems = [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
@@ -112,53 +114,80 @@ RED.deploy = (function() {
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
}
window.addEventListener('beforeunload', function (event) {
if (RED.nodes.dirty()) {
event.preventDefault();
event.stopImmediatePropagation()
event.returnValue = RED._("deploy.confirm.undeployedChanges");
return
}
})
RED.events.on('workspace:dirty',function(state) {
if (RED.settings.user?.permissions === 'read') {
return
}
if (state.dirty) {
window.onbeforeunload = function() {
return RED._("deploy.confirm.undeployedChanges");
}
// window.onbeforeunload = function() {
// return
// }
$("#red-ui-header-button-deploy").removeClass("disabled");
} else {
window.onbeforeunload = null;
// window.onbeforeunload = null;
$("#red-ui-header-button-deploy").addClass("disabled");
}
});
var activeNotifyMessage;
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
if (!activeNotifyMessage) {
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
activeNotifyMessage = RED.notify(message,{
modal: true,
fixed: true,
buttons: [
{
text: RED._('deploy.confirm.button.ignore'),
click: function() {
activeNotifyMessage.close();
activeNotifyMessage = null;
}
},
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeNotifyMessage.close();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
activeNotifyMessage = null;
}
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
activeBackgroundDeployNotification.showNotification()
return
}
const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
const options = {
id: 'background-update',
type: 'compact',
modal: false,
fixed: true,
timeout: 10000,
buttons: [
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeBackgroundDeployNotification.hideNotification();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
}
]
});
}
]
}
if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
activeBackgroundDeployNotification = RED.notify(message, options)
} else {
activeBackgroundDeployNotification.update(message, options)
}
});
updateLockedState()
RED.events.on('login', updateLockedState)
}
function updateLockedState() {
if (RED.settings.user?.permissions === 'read') {
$(".red-ui-deploy-button-group").addClass("readOnly");
$("#red-ui-header-button-deploy").addClass("disabled");
} else {
$(".red-ui-deploy-button-group").removeClass("readOnly");
if (RED.nodes.dirty()) {
$("#red-ui-header-button-deploy").removeClass("disabled");
}
}
}
function getNodeInfo(node) {
@@ -213,7 +242,11 @@ RED.deploy = (function() {
class: "primary disabled",
click: function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff();
RED.diff.showRemoteDiff(null, {
onmerge: function () {
activeBackgroundDeployNotification.close()
}
});
conflictNotification.close();
}
}
@@ -226,6 +259,7 @@ RED.deploy = (function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff);
conflictNotification.close();
activeBackgroundDeployNotification.close()
}
}
}
@@ -238,6 +272,7 @@ RED.deploy = (function() {
click: function() {
save(true,activeDeploy);
conflictNotification.close();
activeBackgroundDeployNotification.close()
}
})
}
@@ -248,21 +283,17 @@ RED.deploy = (function() {
buttons: buttons
});
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
})
}
function cropList(list) {
@@ -612,7 +643,10 @@ RED.deploy = (function() {
}
});
RED.nodes.eachSubflow(function (subflow) {
subflow.changed = false;
if (subflow.changed) {
subflow.changed = false;
RED.events.emit("subflows:change", subflow);
}
});
RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) {
@@ -628,6 +662,7 @@ RED.deploy = (function() {
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.sidebar.config.refresh();
RED.events.emit("deploy");
}).fail(function (xhr, textStatus, err) {
RED.nodes.dirty(true);

View File

@@ -1,5 +1,4 @@
RED.diff = (function() {
var currentDiff = {};
var diffVisible = false;
var diffList;
@@ -62,12 +61,14 @@ RED.diff = (function() {
addedCount:0,
deletedCount:0,
changedCount:0,
movedCount:0,
unchangedCount: 0
},
remote: {
addedCount:0,
deletedCount:0,
changedCount:0,
movedCount:0,
unchangedCount: 0
},
conflicts: 0
@@ -138,7 +139,7 @@ RED.diff = (function() {
$(this).parent().toggleClass('collapsed');
});
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
selectState = "";
if (conflicts[tab.id]) {
flowStats.conflicts++;
@@ -208,19 +209,26 @@ RED.diff = (function() {
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
}
if (flowStats.local.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.local.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.local.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.local.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
}
@@ -246,19 +254,26 @@ RED.diff = (function() {
}
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
}
if (flowStats.remote.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.remote.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.remote.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.remote.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
}
@@ -293,7 +308,7 @@ RED.diff = (function() {
if (options.mode === "merge") {
diffPanel.addClass("red-ui-diff-panel-merge");
}
var diffList = createDiffTable(diffPanel, diff);
var diffList = createDiffTable(diffPanel, diff, options);
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -516,7 +531,6 @@ RED.diff = (function() {
var hasChanges = false; // exists in original and local/remote but with changes
var unChanged = true; // existing in original,local,remote unchanged
var localChanged = false;
if (localDiff.added[node.id]) {
stats.local.addedCount++;
@@ -535,12 +549,20 @@ RED.diff = (function() {
unChanged = false;
}
if (localDiff.changed[node.id]) {
stats.local.changedCount++;
if (localDiff.positionChanged[node.id]) {
stats.local.movedCount++
} else {
stats.local.changedCount++;
}
hasChanges = true;
unChanged = false;
}
if (remoteDiff && remoteDiff.changed[node.id]) {
stats.remote.changedCount++;
if (remoteDiff.positionChanged[node.id]) {
stats.remote.movedCount++
} else {
stats.remote.changedCount++;
}
hasChanges = true;
unChanged = false;
}
@@ -605,27 +627,32 @@ RED.diff = (function() {
localNodeDiv.addClass("red-ui-diff-status-moved");
var localMovedMessage = "";
if (node.z === localN.z) {
localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
} else {
localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
const movedToNodeTab = localDiff.newConfig.all[localN.z]
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
}
localChanged = true;
} else if (localDiff.deleted[node.z]) {
localNodeDiv.addClass("red-ui-diff-empty");
localChanged = true;
} else if (localDiff.deleted[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-deleted");
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
localChanged = true;
} else if (localDiff.changed[node.id]) {
if (localDiff.newConfig.all[node.id].z !== node.z) {
localNodeDiv.addClass("red-ui-diff-empty");
} else {
localNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
localChanged = true;
if (localDiff.positionChanged[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
} else {
localNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
}
}
} else {
if (localDiff.newConfig.all[node.id].z !== node.z) {
@@ -646,9 +673,13 @@ RED.diff = (function() {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
var remoteMovedMessage = "";
if (node.z === remoteN.z) {
remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
} else {
remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
}
@@ -661,8 +692,13 @@ RED.diff = (function() {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
remoteNodeDiv.addClass("red-ui-diff-empty");
} else {
remoteNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
if (remoteDiff.positionChanged[node.id]) {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
} else {
remoteNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
}
}
} else {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
@@ -788,7 +824,7 @@ RED.diff = (function() {
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) {
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
$('<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);
var localPosition = {x:localNode.x,y:localNode.y};
@@ -813,7 +849,7 @@ RED.diff = (function() {
if (remoteNode !== undefined) {
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
if (remoteNode) {
$('<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);
@@ -1099,11 +1135,11 @@ RED.diff = (function() {
// var diff = generateDiff(originalFlow,nns);
// showDiff(diff);
// }
function showRemoteDiff(diff) {
if (diff === undefined) {
getRemoteDiff(showRemoteDiff);
function showRemoteDiff(diff, options = {}) {
if (!diff) {
getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
} else {
showDiff(diff,{mode:'merge'});
showDiff(diff,{...options, mode:'merge'});
}
}
function parseNodes(nodeList) {
@@ -1144,23 +1180,53 @@ RED.diff = (function() {
}
}
function generateDiff(currentNodes,newNodes) {
var currentConfig = parseNodes(currentNodes);
var newConfig = parseNodes(newNodes);
var added = {};
var deleted = {};
var changed = {};
var moved = {};
const currentConfig = parseNodes(currentNodes);
const newConfig = parseNodes(newNodes);
const added = {};
const deleted = {};
const changed = {};
const positionChanged = {};
const moved = {};
Object.keys(currentConfig.all).forEach(function(id) {
var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
if (!newConfig.all.hasOwnProperty(id)) {
deleted[id] = true;
} else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
return
}
const currentConfigJSON = JSON.stringify(currentConfig.all[id])
const newConfigJSON = JSON.stringify(newConfig.all[id])
if (currentConfigJSON !== newConfigJSON) {
changed[id] = true;
if (currentConfig.all[id].z !== newConfig.all[id].z) {
moved[id] = true;
} else if (
currentConfig.all[id].x !== newConfig.all[id].x ||
currentConfig.all[id].y !== newConfig.all[id].y ||
currentConfig.all[id].w !== newConfig.all[id].w ||
currentConfig.all[id].h !== newConfig.all[id].h
) {
// This node's position on its parent has changed. We want to
// check if this is the *only* change for this given node
const currentNodeClone = JSON.parse(currentConfigJSON)
const newNodeClone = JSON.parse(newConfigJSON)
delete currentNodeClone.x
delete currentNodeClone.y
delete currentNodeClone.w
delete currentNodeClone.h
delete newNodeClone.x
delete newNodeClone.y
delete newNodeClone.w
delete newNodeClone.h
if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
// Only the position has changed - everything else is the same
positionChanged[id] = true
}
}
}
});
Object.keys(newConfig.all).forEach(function(id) {
@@ -1169,13 +1235,14 @@ RED.diff = (function() {
}
});
var diff = {
currentConfig: currentConfig,
newConfig: newConfig,
added: added,
deleted: deleted,
changed: changed,
moved: moved
const diff = {
currentConfig,
newConfig,
added,
deleted,
changed,
positionChanged,
moved
};
return diff;
}
@@ -1240,12 +1307,14 @@ RED.diff = (function() {
return diff;
}
function showDiff(diff,options) {
function showDiff(diff, options) {
if (diffVisible) {
return;
}
options = options || {};
var mode = options.mode || 'merge';
options.hidePositionChanges = true
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -1315,6 +1384,9 @@ RED.diff = (function() {
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
refreshConflictHeader(diff);
mergeDiff(diff);
if (options.onmerge) {
options.onmerge()
}
RED.tray.close();
}
}
@@ -1345,6 +1417,7 @@ RED.diff = (function() {
var newConfig = [];
var node;
var nodeChangedStates = {};
var nodeMovedStates = {};
var localChangedStates = {};
for (id in localDiff.newConfig.all) {
if (localDiff.newConfig.all.hasOwnProperty(id)) {
@@ -1352,12 +1425,14 @@ RED.diff = (function() {
if (resolutions[id] === 'local') {
if (node) {
nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
}
newConfig.push(localDiff.newConfig.all[id]);
} else if (resolutions[id] === 'remote') {
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
if (node) {
nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
}
localChangedStates[id] = 1;
newConfig.push(remoteDiff.newConfig.all[id]);
@@ -1381,8 +1456,9 @@ RED.diff = (function() {
}
return {
config: newConfig,
nodeChangedStates: nodeChangedStates,
localChangedStates: localChangedStates
nodeChangedStates,
nodeMovedStates,
localChangedStates
}
}
@@ -1393,6 +1469,7 @@ RED.diff = (function() {
var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates;
var nodeMovedStates = appliedDiff.nodeMovedStates;
var localChangedStates = appliedDiff.localChangedStates;
var isDirty = RED.nodes.dirty();
@@ -1401,33 +1478,56 @@ RED.diff = (function() {
t:"replace",
config: RED.nodes.createCompleteNodeSet(),
changed: nodeChangedStates,
moved: nodeMovedStates,
complete: true,
dirty: isDirty,
rev: RED.nodes.version()
}
RED.history.push(historyEvent);
var originalFlow = RED.nodes.originalFlow();
// originalFlow is what the editor things it loaded
// - add any newly added nodes from remote diff as they are now part of the record
for (var id in diff.remoteDiff.added) {
if (diff.remoteDiff.added.hasOwnProperty(id)) {
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
}
}
}
// var originalFlow = RED.nodes.originalFlow();
// // originalFlow is what the editor thinks it loaded
// // - add any newly added nodes from remote diff as they are now part of the record
// for (var id in diff.remoteDiff.added) {
// if (diff.remoteDiff.added.hasOwnProperty(id)) {
// if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
// originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
// }
// }
// }
RED.nodes.clear();
var imported = RED.nodes.import(newConfig);
// Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old
RED.nodes.originalFlow(originalFlow);
// // Restore the original flow so subsequent merge resolutions can properly
// // identify new-vs-old
// RED.nodes.originalFlow(originalFlow);
// Clear all change flags from the import
RED.nodes.dirty(false);
const flowsToLock = new Set()
function ensureUnlocked(id) {
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false;
if (flow && isLocked) {
flow.locked = false;
flowsToLock.add(flow)
}
}
imported.nodes.forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
if (nodeChangedStates[n.id]) {
ensureUnlocked(n.z)
n.changed = true;
}
if (nodeMovedStates[n.id]) {
ensureUnlocked(n.z)
n.moved = true;
}
})
flowsToLock.forEach(flow => {
flow.locked = true
})
RED.nodes.version(diff.remoteDiff.rev);

View File

@@ -157,6 +157,12 @@ RED.editor = (function() {
}
}
if (valid && "validate" in definition[property]) {
if (definition[property].hasOwnProperty("required") &&
definition[property].required === false) {
if (value === "") {
return true;
}
}
try {
var opt = {};
if (label) {
@@ -183,6 +189,11 @@ RED.editor = (function() {
});
}
} else if (valid) {
if (definition[property].hasOwnProperty("required") && definition[property].required === false) {
if (value === "") {
return true;
}
}
// If the validator is not provided in node property => Check if the input has a validator
if ("category" in node._def) {
const isConfig = node._def.category === "config";
@@ -190,7 +201,10 @@ RED.editor = (function() {
const input = $("#"+prefix+"-"+property);
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
if (isTypedInput) {
valid = input.typedInput("validate");
valid = input.typedInput("validate", { returnErrorMessage: true });
if (typeof valid === "string") {
return label ? label + ": " + valid : valid;
}
}
}
}
@@ -248,6 +262,8 @@ RED.editor = (function() {
var value = input.val();
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
value = input.text();
} else if (input.attr("type") === "checkbox") {
value = input.prop("checked");
}
var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) {
@@ -326,49 +342,101 @@ RED.editor = (function() {
/**
* Create a config-node select box for this property
* @param node - the node being edited
* @param property - the name of the field
* @param type - the type of the config-node
* @param {Object} node - the node being edited
* @param {String} property - the name of the node property
* @param {String} type - the type of the config-node
* @param {"node-config-input"|"node-input"|"node-input-subflow-env"} prefix - the prefix to use in the input element ids
* @param {Function} [filter] - a function to filter the list of config nodes
* @param {Object} [env] - the environment variable object (only used for subflow env vars)
*/
function prepareConfigNodeSelect(node,property,type,prefix,filter) {
var input = $("#"+prefix+"-"+property);
if (input.length === 0 ) {
function prepareConfigNodeSelect(node, property, type, prefix, filter, env) {
let nodeValue
if (prefix === 'node-input-subflow-env') {
nodeValue = env?.value
} else {
nodeValue = node[property]
}
const addBtnId = `${prefix}-btn-${property}-add`;
const editBtnId = `${prefix}-btn-${property}-edit`;
const selectId = prefix + '-' + property;
const input = $(`#${selectId}`);
if (input.length === 0) {
return;
}
var newWidth = input.width();
var attrStyle = input.attr('style');
var m;
const attrStyle = input.attr('style');
let newWidth;
let m;
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
newWidth = m[2].trim();
} else {
newWidth = "70%";
}
var outerWrap = $("<div></div>").css({
const outerWrap = $("<div></div>").css({
width: newWidth,
display:'inline-flex'
display: 'inline-flex'
});
var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(outerWrap);
const select = $('<select id="' + selectId + '"></select>').appendTo(outerWrap);
input.replaceWith(outerWrap);
// set the style attr directly - using width() on FF causes a value of 114%...
select.css({
'flex-grow': 1
});
updateConfigNodeSelect(property,type,node[property],prefix,filter);
$('<a id="'+prefix+'-lookup-'+property+'" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
.css({"margin-left":"10px"})
updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
// create the edit button
const editButton = $('<a id="' + editBtnId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
.css({ "margin-left": "10px" })
.appendTo(outerWrap);
$('#'+prefix+'-lookup-'+property).on("click", function(e) {
showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix,node);
RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
// create the add button
const addButton = $('<a id="' + addBtnId + '" class="red-ui-button"><i class="fa fa-plus"></i></a>')
.css({ "margin-left": "10px" })
.appendTo(outerWrap);
RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
const disableButton = function(button, disabled) {
$(button).prop("disabled", !!disabled)
$(button).toggleClass("disabled", !!disabled)
};
// add the click handler
addButton.on("click", function (e) {
if (addButton.prop("disabled")) { return }
showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
e.preventDefault();
});
editButton.on("click", function (e) {
const selectedOpt = select.find(":selected")
if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
if (editButton.prop("disabled")) { return }
showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
e.preventDefault();
});
var label = "";
var configNode = RED.nodes.node(node[property]);
var node_def = RED.nodes.getType(type);
if (configNode) {
label = RED.utils.getNodeLabel(configNode,configNode.id);
}
input.val(label);
// dont permit the user to click the button if the selected option is an env var
select.on("change", function () {
const selectedOpt = select.find(":selected");
const optionsLength = select.find("option").length;
if (selectedOpt?.data('env')) {
disableButton(addButton, true);
disableButton(editButton, true);
// disable the edit button if no options available or 'none' selected
} else if (optionsLength === 1 || selectedOpt.val() === "_ADD_") {
disableButton(addButton, false);
disableButton(editButton, true);
} else {
disableButton(addButton, false);
disableButton(editButton, false);
}
});
// If the value is "", 'add new...' option if no config node available or 'none' option
// Otherwise, it's a config node
select.val(nodeValue || '_ADD_');
}
/**
@@ -741,9 +809,16 @@ RED.editor = (function() {
}
try {
var rc = editing_node._def.oneditsave.call(editing_node);
const rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
editState.changed = true;
} else if (typeof rc === 'object' && rc !== null ) {
if (rc.changed === true) {
editState.changed = true
}
if (Array.isArray(rc.history) && rc.history.length > 0) {
editState.history = rc.history
}
}
} catch(err) {
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
@@ -768,12 +843,9 @@ RED.editor = (function() {
}
function defaultConfigNodeSort(A,B) {
if (A.__label__ < B.__label__) {
return -1;
} else if (A.__label__ > B.__label__) {
return 1;
}
return 0;
// sort case insensitive so that `[env] node-name` items are at the top and
// not mixed inbetween the the lower and upper case items
return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'})
}
function updateConfigNodeSelect(name,type,value,prefix,filter) {
@@ -788,7 +860,7 @@ RED.editor = (function() {
}
$("#"+prefix+"-"+name).val(value);
} else {
let inclSubflowEnvvars = false
var select = $("#"+prefix+"-"+name);
var node_def = RED.nodes.getType(type);
select.children().remove();
@@ -796,6 +868,7 @@ RED.editor = (function() {
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
if (!activeWorkspace) {
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
inclSubflowEnvvars = true
}
var configNodes = [];
@@ -811,6 +884,31 @@ RED.editor = (function() {
}
}
});
// as includeSubflowEnvvars is true, this is a subflow.
// include any 'conf-types' env vars as a list of avaiable configs
// in the config dropdown as `[env] node-name`
if (inclSubflowEnvvars && activeWorkspace.env) {
const parentEnv = activeWorkspace.env.filter(env => env.ui?.type === 'conf-types' && env.type === type)
if (parentEnv && parentEnv.length > 0) {
const locale = RED.i18n.lang()
for (let i = 0; i < parentEnv.length; i++) {
const tenv = parentEnv[i]
const ui = tenv.ui || {}
const labels = ui.label || {}
const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
const config = {
env: tenv,
id: '${' + parentEnv[0].name + '}',
type: type,
label: labelText,
__label__: `[env] ${labelText}`
}
configNodes.push(config)
}
}
}
var configSortFn = defaultConfigNodeSort;
if (typeof node_def.sort == "function") {
configSortFn = node_def.sort;
@@ -822,7 +920,10 @@ RED.editor = (function() {
}
configNodes.forEach(function(cn) {
$('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
const option = $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
if (cn.env) {
option.data('env', cn.env) // set a data attribute to indicate this is an env var (to inhibit the edit button)
}
delete cn.__label__;
});
@@ -835,7 +936,14 @@ RED.editor = (function() {
}
}
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
if (!configNodes.length) {
// Add 'add new...' option
select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
} else {
// Add 'none' option
select.append('<option value="_ADD_">' + RED._("editor.inputs.none") + '</option>');
}
window.setTimeout(function() { select.trigger("change");},50);
}
}
@@ -914,6 +1022,17 @@ RED.editor = (function() {
dirty: startDirty
}
if (editing_node.g) {
const group = RED.nodes.group(editing_node.g);
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
const index = group?.nodes.indexOf(editing_node) ?? -1;
if (index > -1) {
group.nodes.splice(index, 1);
RED.group.markDirty(group);
}
}
RED.nodes.dirty(true);
RED.view.redraw(true);
RED.history.push(historyEvent);
@@ -1015,7 +1134,7 @@ RED.editor = (function() {
}
});
}
var historyEvent = {
let historyEvent = {
t:'edit',
node:editing_node,
changes:editState.changes,
@@ -1031,6 +1150,15 @@ RED.editor = (function() {
instances:subflowInstances
}
}
if (editState.history) {
historyEvent = {
t: 'multi',
events: [ historyEvent, ...editState.history ],
dirty: wasDirty
}
}
RED.history.push(historyEvent);
}
editing_node.dirty = true;
@@ -1483,9 +1611,16 @@ RED.editor = (function() {
}
RED.tray.close(function() {
var filter = null;
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
filter = function(n) {
return editContext._def.defaults[configProperty].filter.call(editContext,n);
// when editing a config via subflow edit panel, the `configProperty` will not
// necessarily be a property of the editContext._def.defaults object
// Also, when editing via dashboard sidebar, editContext can be null
// so we need to guard both scenarios
if (editContext?._def) {
const isSubflow = (editContext._def.type === 'subflow' || /subflow:.*/.test(editContext._def.type))
if (editContext && !isSubflow && typeof editContext._def.defaults?.[configProperty]?.filter === 'function') {
filter = function(n) {
return editContext._def.defaults[configProperty].filter.call(editContext,n);
}
}
}
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
@@ -1546,7 +1681,7 @@ RED.editor = (function() {
RED.history.push(historyEvent);
RED.tray.close(function() {
var filter = null;
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') {
filter = function(n) {
return editContext._def.defaults[configProperty].filter.call(editContext,n);
}
@@ -1623,8 +1758,8 @@ RED.editor = (function() {
}
if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
editState.changes.env = editing_node.env;
editing_node.env = new_env;
editState.changed = true;
}
@@ -2087,6 +2222,7 @@ RED.editor = (function() {
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
getEditStack: function () { return [...editStack] },
buildEditForm: buildEditForm,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties,
@@ -2131,6 +2267,7 @@ RED.editor = (function() {
filteredEditPanes[type] = filter
}
editPanes[type] = definition;
}
},
prepareConfigNodeSelect: prepareConfigNodeSelect,
}
})();

View File

@@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() {
//Handles orphaned models
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
RED.events.on("editor:close",function() {
let models = window.monaco ? monaco.editor.getModels() : null;
if (!window.monaco) { return; }
const editors = window.monaco.editor.getEditors()
const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode()))
orphanEditors.forEach(editor => {
editor.dispose();
});
let models = monaco.editor.getModels()
if(models && models.length) {
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
for (let index = 0; index < models.length; index++) {
@@ -514,7 +520,7 @@ RED.editor.codeEditor.monaco = (function() {
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
} catch (error) {
console.warn("monaco - Error setting up json options", err)
console.warn("monaco - Error setting up json options", error)
}
}
@@ -526,7 +532,7 @@ RED.editor.codeEditor.monaco = (function() {
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
} catch (error) {
console.warn("monaco - Error setting up html options", err)
console.warn("monaco - Error setting up html options", error)
}
}
@@ -546,7 +552,7 @@ RED.editor.codeEditor.monaco = (function() {
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
} catch (error) {
console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
}
}
@@ -585,7 +591,7 @@ RED.editor.codeEditor.monaco = (function() {
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME,NR_SUBFLOW_NAME,NR_SUBFLOW_ID,NR_SUBFLOW_PATH|}");','Get env variable value',range),
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
["```typescript",
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
@@ -1124,6 +1130,7 @@ RED.editor.codeEditor.monaco = (function() {
$(el).remove();
$(toolbarRow).remove();
ed.dispose();
}
ed.resize = function resize() {

View File

@@ -1,8 +1,9 @@
RED.editor.envVarList = (function() {
var currentLocale = 'en-US';
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
const DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
const DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES = ['str','num','bool','json','bin','env','conf-types'];
const DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
/**
* Create env var edit interface
@@ -10,8 +11,8 @@ RED.editor.envVarList = (function() {
* @param node - subflow node
*/
function buildPropertiesList(envContainer, node) {
var isTemplateNode = (node.type === "subflow");
if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) }
const isTemplateNode = (node.type === "subflow");
envContainer
.css({
@@ -83,7 +84,14 @@ RED.editor.envVarList = (function() {
// if `opt.ui` does not exist, then apply defaults. If these
// defaults do not change then they will get stripped off
// before saving.
if (opt.type === 'cred') {
if (opt.type === 'conf-types') {
opt.ui = opt.ui || {
icon: "fa fa-cog",
type: "conf-types",
opts: {opts:[]}
}
opt.ui.type = "conf-types";
} else if (opt.type === 'cred') {
opt.ui = opt.ui || {
icon: "",
type: "cred"
@@ -119,7 +127,7 @@ RED.editor.envVarList = (function() {
}
});
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
buildEnvEditRow(uiRow, opt, nameField, valueField);
nameField.trigger('change');
}
},
@@ -181,21 +189,23 @@ RED.editor.envVarList = (function() {
* @param nameField - name field of env var
* @param valueField - value field of env var
*/
function buildEnvEditRow(container, ui, nameField, valueField) {
function buildEnvEditRow(container, opt, nameField, valueField) {
const ui = opt.ui
if(RED.editor.envVarList.debug) { console.log('envVarList: buildEnvEditRow', container, ui, nameField, valueField) }
container.addClass("red-ui-editor-subflow-env-ui-row")
var topRow = $('<div></div>').appendTo(container);
$('<div></div>').appendTo(topRow);
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
$('<div>').text(RED._("editor.label")).appendTo(topRow);
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
$('<div class="red-env-ui-input-type-col">').text(RED._("editor.inputType")).appendTo(topRow);
var row = $('<div></div>').appendTo(container);
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
var typeOptions = {
'input': {types:DEFAULT_ENV_TYPE_LIST},
'select': {opts:[]},
'spinner': {},
'cred': {}
'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES},
'select': {opts:[]},
'spinner': {},
'cred': {}
};
if (ui.opts) {
typeOptions[ui.type] = ui.opts;
@@ -260,15 +270,16 @@ RED.editor.envVarList = (function() {
labelInput.attr("placeholder",$(this).val())
});
var inputCell = $('<div></div>').appendTo(row);
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row);
var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
if (ui.type === "input") {
inputCellInput.val(ui.opts.types.join(","));
uiInputTypeInput.val(ui.opts.types.join(","));
}
var checkbox;
var selectBox;
inputCellInput.typedInput({
// the options presented in the UI section for an "input" type selection
uiInputTypeInput.typedInput({
types: [
{
value:"input",
@@ -429,7 +440,7 @@ RED.editor.envVarList = (function() {
}
});
ui.opts.opts = vals;
inputCellInput.typedInput('value',Date.now())
uiInputTypeInput.typedInput('value',Date.now())
}
}
}
@@ -496,12 +507,13 @@ RED.editor.envVarList = (function() {
} else {
delete ui.opts.max;
}
inputCellInput.typedInput('value',Date.now())
uiInputTypeInput.typedInput('value',Date.now())
}
}
}
}
},
'conf-types',
{
value:"none",
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
@@ -519,14 +531,20 @@ RED.editor.envVarList = (function() {
// In the case of 'input' type, the typedInput uses the multiple-option
// mode. Its value needs to be set to a comma-separately list of the
// selected options.
inputCellInput.typedInput('value',ui.opts.types.join(","))
uiInputTypeInput.typedInput('value',ui.opts.types.join(","))
} else if (ui.type === 'conf-types') {
// In the case of 'conf-types' type, the typedInput will be populated
// with a list of all config nodes types installed.
// Restore the value to the last selected type
uiInputTypeInput.typedInput('value', opt.type)
} else {
// No other type cares about `value`, but doing this will
// force a refresh of the label now that `ui.opts` has
// been updated.
inputCellInput.typedInput('value',Date.now())
uiInputTypeInput.typedInput('value',Date.now())
}
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:typedinputtypechange. ui.type = ' + ui.type) }
switch (ui.type) {
case 'input':
valueField.typedInput('types',ui.opts.types);
@@ -544,7 +562,7 @@ RED.editor.envVarList = (function() {
valueField.typedInput('types',['cred']);
break;
default:
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST);
}
if (ui.type === 'checkbox') {
valueField.typedInput('type','bool');
@@ -556,8 +574,46 @@ RED.editor.envVarList = (function() {
}
}).on("change", function(evt,type) {
if (ui.type === 'input') {
var types = inputCellInput.typedInput('value');
const selectedType = $(this).typedInput('type') // the UI typedInput type
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:change. selectedType = ' + selectedType) }
if (selectedType === 'conf-types') {
const selectedConfigType = $(this).typedInput('value') || opt.type
let activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
if (!activeWorkspace) {
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
}
// get a list of all config nodes matching the selectedValue
const configNodes = [];
RED.nodes.eachConfig(function(config) {
if (config.type == selectedConfigType && (!config.z || config.z === activeWorkspace.id)) {
const modulePath = config._def?.set?.id || ''
let label = RED.utils.getNodeLabel(config, config.id) || config.id;
label += config.d ? ' ['+RED._('workspace.disabled')+']' : '';
const _config = {
_type: selectedConfigType,
value: config.id,
label: label,
title: modulePath ? modulePath + ' - ' + label : label,
enabled: config.d !== true,
disabled: config.d === true,
}
configNodes.push(_config);
}
});
const tiTypes = {
value: selectedConfigType,
label: "config",
icon: "fa fa-cog",
options: configNodes,
}
valueField.typedInput('types', [tiTypes]);
valueField.typedInput('type', selectedConfigType);
valueField.typedInput('value', opt.value);
} else if (ui.type === 'input') {
var types = uiInputTypeInput.typedInput('value');
ui.opts.types = (types === "") ? ["str"] : types.split(",");
valueField.typedInput('types',ui.opts.types);
}
@@ -569,7 +625,7 @@ RED.editor.envVarList = (function() {
})
// Set the input to the right type. This will trigger the 'typedinputtypechange'
// event handler (just above ^^) to update the value if needed
inputCellInput.typedInput('type',ui.type)
uiInputTypeInput.typedInput('type',ui.type)
}
function setLocale(l, list) {

View File

@@ -11,9 +11,22 @@ RED.editor.mermaid = (function () {
if (!initializing) {
initializing = true
$.getScript(
'vendor/mermaid/mermaid.min.js',
function (data, stat, jqxhr) {
// Find the cache-buster:
let cacheBuster
$('script').each(function (i, el) {
if (!cacheBuster) {
const src = el.getAttribute('src')
const m = /\?v=(.+)$/.exec(src)
if (m) {
cacheBuster = m[1]
}
}
})
$.ajax({
url: `vendor/mermaid/mermaid.min.js?v=${cacheBuster}`,
dataType: "script",
cache: true,
success: function (data, stat, jqxhr) {
mermaid.initialize({
startOnLoad: false,
theme: RED.settings.get('mermaid', {}).theme
@@ -24,7 +37,7 @@ RED.editor.mermaid = (function () {
render(pending)
}
}
)
});
}
} else {
const nodes = document.querySelectorAll(selector)

View File

@@ -153,10 +153,6 @@ RED.envVar = (function() {
}
function init(done) {
if (!RED.user.hasPermission("settings.write")) {
RED.notify(RED._("user.errors.settings"),"error");
return;
}
RED.userSettings.add({
id:'envvar',
title: RED._("env-var.environment"),

View File

@@ -221,12 +221,12 @@ RED.notifications = (function() {
if (newType) {
n.className = "red-ui-notification red-ui-notification-"+newType;
}
newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
if (!fixed || newOptions.fixed === false) {
newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
newTimeout = newTimeout || 5000
}
if (newOptions.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
newOptions.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) {
@@ -272,6 +272,15 @@ RED.notifications = (function() {
};
})());
n.timeoutid = window.setTimeout(n.close,timeout||5000);
} else if (timeout) {
$(n).on("click.red-ui-notification-close", (function() {
var nn = n;
return function() {
nn.hideNotification();
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
}
currentNotifications.push(n);
if (options.id) {

View File

@@ -115,8 +115,65 @@ RED.palette.editor = (function() {
});
})
}
function installNodeModule(id,version,url,callback) {
var requestBody = {
const moduleQueue = [];
const processQueue = function () {
if (moduleQueue.length === 0) {
return;
}
const { type, body, callback } = moduleQueue[0];
if (type === "install") {
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install") + ` : ${body.module} ${body.version}`);
$.ajax({
url: "nodes",
type: "POST",
data: JSON.stringify(body),
contentType: "application/json; charset=utf-8"
}).done(function(_data, _textStatus, _xhr) {
callback();
}).fail(function(xhr, textStatus, err) {
callback(xhr, textStatus, err);
}).always(function () {
// Remove the task from the list
moduleQueue.shift();
// Process the next task
processQueue();
});
} else if (type === "remove") {
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove") + ` : ${body.id}`);
$.ajax({
url: "nodes/" + body.id,
type: "DELETE"
}).done(function(_data, _textStatus, _xhr) {
callback();
}).fail(function(xhr, _textStatus, _err) {
callback(xhr);
}).always(function () {
// Remove the task from the list
moduleQueue.shift();
// Process the next task
processQueue();
});
}
};
/**
* Adds a module to the processing queue to install or remove it
* @param {string} type the type of request to apply to the module
* @param {Object} body an object with module info
* @param {(xhr?: JQuery.jqXHR, textStatus?: JQuery.Ajax.ErrorTextStatus, err?: string) => void} callback a callback function called when the request is done
*/
function addModuleToQueue(type, body, callback) {
moduleQueue.push({ type, body, callback });
if (moduleQueue.length === 1) {
processQueue();
}
}
function installNodeModule(id, version, url, callback) {
const requestBody = {
module: id
};
if (version) {
@@ -125,26 +182,10 @@ RED.palette.editor = (function() {
if (url) {
requestBody.url = url;
}
$.ajax({
url:"nodes",
type: "POST",
data: JSON.stringify(requestBody),
contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
});
addModuleToQueue("install", requestBody, callback);
}
function removeNodeModule(id,callback) {
$.ajax({
url:"nodes/"+id,
type: "DELETE"
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
})
function removeNodeModule(id, callback) {
addModuleToQueue("remove", { id: id }, callback);
}
function refreshNodeModuleList() {
@@ -248,86 +289,106 @@ RED.palette.editor = (function() {
var moduleInfo = nodeEntries[module].info;
var nodeEntry = nodeEntries[module].elements;
if (nodeEntry) {
var activeTypeCount = 0;
var typeCount = 0;
var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
if (moduleInfo.plugin) {
nodeEntry.enableButton.hide();
nodeEntry.removeButton.show();
for (var setName in moduleInfo.sets) {
if (moduleInfo.sets.hasOwnProperty(setName)) {
var inUseCount = 0;
var set = moduleInfo.sets[setName];
var setElements = nodeEntry.sets[setName];
if (set.err) {
errorCount++;
var errMessage = set.err;
if (set.err.message) {
errMessage = set.err.message;
} else if (set.err.code) {
errMessage = set.err.code;
let pluginCount = 0;
for (let setName in moduleInfo.sets) {
if (moduleInfo.sets.hasOwnProperty(setName)) {
let set = moduleInfo.sets[setName];
if (set.plugins) {
pluginCount += set.plugins.length;
}
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
}
if (set.enabled) {
activeTypeCount += set.types.length;
}
typeCount += set.types.length;
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
var t = moduleInfo.sets[setName].types[i];
inUseCount += (typesInUse[t]||0);
var swatch = setElements.swatches[t];
}
nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
} else {
var activeTypeCount = 0;
var typeCount = 0;
var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
for (var setName in moduleInfo.sets) {
if (moduleInfo.sets.hasOwnProperty(setName)) {
var inUseCount = 0;
const set = moduleInfo.sets[setName];
const setElements = nodeEntry.sets[setName]
if (set.err) {
errorCount++;
var errMessage = set.err;
if (set.err.message) {
errMessage = set.err.message;
} else if (set.err.code) {
errMessage = set.err.code;
}
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
}
if (set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
swatch.css({background:RED.utils.getNodeColor(t,def)});
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
activeTypeCount += set.types.length;
}
typeCount += set.types.length;
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
var t = moduleInfo.sets[setName].types[i];
inUseCount += (typesInUse[t]||0);
if (setElements && set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
setElements.swatches[t].css({background:RED.utils.getNodeColor(t,def)});
setElements.swatches[t].css({border: "1px solid "+getContrastingBorder(setElements.swatches[t].css('backgroundColor'))})
}
}
}
}
nodeEntries[module].setUseCount[setName] = inUseCount;
nodeEntries[module].totalUseCount += inUseCount;
nodeEntries[module].setUseCount[setName] = inUseCount;
nodeEntries[module].totalUseCount += inUseCount;
if (inUseCount > 0) {
setElements.enableButton.text(RED._('palette.editor.inuse'));
setElements.enableButton.addClass('disabled');
} else {
setElements.enableButton.removeClass('disabled');
if (set.enabled) {
setElements.enableButton.text(RED._('palette.editor.disable'));
} else {
setElements.enableButton.text(RED._('palette.editor.enable'));
if (setElements) {
if (inUseCount > 0) {
setElements.enableButton.text(RED._('palette.editor.inuse'));
setElements.enableButton.addClass('disabled');
} else {
setElements.enableButton.removeClass('disabled');
if (set.enabled) {
setElements.enableButton.text(RED._('palette.editor.disable'));
} else {
setElements.enableButton.text(RED._('palette.editor.enable'));
}
}
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
}
}
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
}
}
if (errorCount === 0) {
nodeEntry.errorRow.hide()
} else {
nodeEntry.errorRow.show();
}
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
if (nodeEntries[module].totalUseCount > 0) {
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
nodeEntry.enableButton.addClass('disabled');
nodeEntry.removeButton.hide();
} else {
nodeEntry.enableButton.removeClass('disabled');
if (moduleInfo.local) {
nodeEntry.removeButton.css('display', 'inline-block');
}
if (activeTypeCount === 0) {
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
if (errorCount === 0) {
nodeEntry.errorRow.hide()
} else {
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
nodeEntry.errorRow.show();
}
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
if (nodeEntries[module].totalUseCount > 0) {
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
nodeEntry.enableButton.addClass('disabled');
nodeEntry.removeButton.hide();
} else {
nodeEntry.enableButton.removeClass('disabled');
if (moduleInfo.local) {
nodeEntry.removeButton.css('display', 'inline-block');
}
if (activeTypeCount === 0) {
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
} else {
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
}
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
}
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
}
}
if (moduleInfo.pending_version) {
@@ -365,54 +426,62 @@ RED.palette.editor = (function() {
var catalogueCount;
var catalogueLoadStatus = [];
var catalogueLoadStart;
var catalogueLoadErrors = false;
var activeSort = sortModulesRelevance;
function handleCatalogResponse(err,catalog,index,v) {
const url = catalog.url
catalogueLoadStatus.push(err||v);
if (!err) {
if (v.modules) {
v.modules = v.modules.filter(function(m) {
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.types) {
m.index = m.index.concat(m.types);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
m.catalog = catalog;
m.catalogIndex = index;
return true;
function refreshCatalogues (done) {
catalogueLoadStatus = [];
catalogueCount = catalogues.length;
loadedList = []
loadedIndex = {}
loadedCatalogs.length = 0
let handled = 0
for (let index = 0; index < catalogues.length; index++) {
const url = catalogues[index];
$.getJSON(url, {_: new Date().getTime()},function(v) {
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
handleCatalogResponse({ url: url, name: v.name},index,v);
}).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",url,":",error);
}).always(function() {
handled++;
if (handled === catalogueCount) {
//sort loadedCatalogs by e.index ascending
loadedCatalogs.sort((a, b) => a.index - b.index)
refreshUpdateStatus();
if (done) {
done()
}
return false;
})
loadedList = loadedList.concat(v.modules);
}
} else {
catalogueLoadErrors = true;
}
})
}
if (catalogueCount > 1) {
$(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>"+catalogueLoadStatus.length+"/"+catalogueCount);
}
if (catalogueLoadStatus.length === catalogueCount) {
if (catalogueLoadErrors) {
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
}
var delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#red-ui-palette-module-install-shade").hide();
},Math.max(delta,0));
}
function handleCatalogResponse(catalog,index,v) {
if (v.modules) {
v.modules = v.modules.filter(function(m) {
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
m.index = m.index.concat(m.keywords);
}
if (m.types) {
m.index = m.index.concat(m.types);
}
if (m.updated_at) {
m.timestamp = new Date(m.updated_at).getTime();
} else {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
m.catalog = catalog;
m.catalogIndex = index;
return true;
}
return false;
})
loadedList = loadedList.concat(v.modules);
}
}
@@ -424,35 +493,19 @@ RED.palette.editor = (function() {
packageList.editableList('empty');
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
catalogueLoadStatus = [];
catalogueLoadErrors = false;
catalogueCount = catalogues.length;
if (catalogues.length > 1) {
$(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"<br>0/"+catalogues.length);
}
$("#red-ui-palette-module-install-shade").show();
catalogueLoadStart = Date.now();
var handled = 0;
loadedCatalogs.length = 0; // clear the loadedCatalogs array
for (let index = 0; index < catalogues.length; index++) {
const url = catalogues[index];
$.getJSON(url, {_: new Date().getTime()},function(v) {
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
handleCatalogResponse(null,{ url: url, name: v.name},index,v);
refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",url,":",error);
handleCatalogResponse(jqxhr,url,index);
}).always(function() {
handled++;
if (handled === catalogueCount) {
//sort loadedCatalogs by e.index ascending
loadedCatalogs.sort((a, b) => a.index - b.index)
updateCatalogFilter(loadedCatalogs)
}
})
}
catalogueLoadStart = Date.now()
refreshCatalogues(function () {
refreshNodeModuleList();
updateCatalogFilter(loadedCatalogs)
const delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() {
$("#red-ui-palette-module-install-shade").hide();
},Math.max(delta,0));
})
} else {
refreshNodeModuleList();
updateCatalogFilter(loadedCatalogs)
}
}
@@ -466,7 +519,6 @@ RED.palette.editor = (function() {
if (catalogSelection.length === 0) {
// sidebar not yet loaded (red-catalogue-filter-select is not in dom)
if (maxRetry > 0) {
// console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
// try again in 100ms
setTimeout(() => {
updateCatalogFilter(catalogEntries, maxRetry - 1)
@@ -605,12 +657,18 @@ RED.palette.editor = (function() {
}
})
// Add the update status to the status bar
addUpdateInfoToStatusBar();
refreshCatalogues()
RED.actions.add("core:manage-palette",function() {
RED.userSettings.show('palette');
});
RED.events.on('registry:module-updated', function(ns) {
refreshNodeModule(ns.module);
refreshUpdateStatus();
});
RED.events.on('registry:node-set-enabled', function(ns) {
refreshNodeModule(ns.module);
@@ -622,12 +680,14 @@ RED.palette.editor = (function() {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
refreshUpdateStatus();
}
});
RED.events.on('registry:node-type-removed', function(nodeType) {
if (!/^subflow:/.test(nodeType)) {
var ns = RED.nodes.registry.getNodeSetForType(nodeType);
refreshNodeModule(ns.module);
refreshUpdateStatus();
}
});
RED.events.on('registry:node-set-added', function(ns) {
@@ -678,6 +738,34 @@ RED.palette.editor = (function() {
}
}
})
RED.events.on("registry:plugin-module-added", function(module) {
if (!nodeEntries.hasOwnProperty(module)) {
nodeEntries[module] = {info:RED.plugins.getModule(module)};
var index = [module];
for (var s in nodeEntries[module].info.sets) {
if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
index.push(s);
index = index.concat(nodeEntries[module].info.sets[s].types)
}
}
nodeEntries[module].index = index.join(",").toLowerCase();
nodeList.editableList('addItem', nodeEntries[module]);
} else {
_refreshNodeModule(module);
}
refreshUpdateStatus();
for (var i=0;i<filteredList.length;i++) {
if (filteredList[i].info.id === module) {
var installButton = filteredList[i].elements.installButton;
installButton.addClass('disabled');
installButton.text(RED._('palette.editor.installed'));
break;
}
}
});
}
var settingsPane;
@@ -804,6 +892,7 @@ RED.palette.editor = (function() {
errorRow: errorRow,
errorList: errorList,
setCount: setCount,
setButton: setButton,
container: container,
shade: shade,
versionSpan: versionSpan,
@@ -814,49 +903,88 @@ RED.palette.editor = (function() {
if (container.hasClass('expanded')) {
container.removeClass('expanded');
contentRow.slideUp();
setTimeout(() => {
contentRow.empty()
}, 200)
object.elements.sets = {}
} else {
container.addClass('expanded');
populateSetList()
contentRow.slideDown();
}
})
var setList = Object.keys(entry.sets)
setList.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
});
setList.forEach(function(setName) {
var set = entry.sets[setName];
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
var typeSwatches = {};
set.types.forEach(function(t) {
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
})
var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
enableButton.on("click", function(evt) {
evt.preventDefault();
if (object.setUseCount[setName] === 0) {
var currentSet = RED.nodes.registry.getNodeSet(set.id);
shade.show();
var newState = !currentSet.enabled
changeNodeState(set.id,newState,shade,function(xhr){
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
const populateSetList = function () {
var setList = Object.keys(entry.sets)
setList.sort(function(A,B) {
return A.toLowerCase().localeCompare(B.toLowerCase());
});
setList.forEach(function(setName) {
var set = entry.sets[setName];
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
var typeSwatches = {};
let enableButton;
if (set.types) {
set.types.forEach(function(t) {
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
if (set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)});
typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))})
}
}
});
}
})
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
})
enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
enableButton.on("click", function(evt) {
evt.preventDefault();
if (object.setUseCount[setName] === 0) {
var currentSet = RED.nodes.registry.getNodeSet(set.id);
shade.show();
var newState = !currentSet.enabled
changeNodeState(set.id,newState,shade,function(xhr){
if (xhr) {
if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
}
}
});
}
})
object.elements.sets[set.name] = {
setRow: setRow,
enableButton: enableButton,
swatches: typeSwatches
};
});
if (object.setUseCount[setName] > 0) {
enableButton.text(RED._('palette.editor.inuse'));
enableButton.addClass('disabled');
} else {
enableButton.removeClass('disabled');
if (set.enabled) {
enableButton.text(RED._('palette.editor.disable'));
} else {
enableButton.text(RED._('palette.editor.enable'));
}
}
setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
}
if (set.plugins) {
set.plugins.forEach(function(p) {
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
// typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
$('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
})
}
object.elements.sets[set.name] = {
setRow: setRow,
enableButton: enableButton,
swatches: typeSwatches
};
});
}
enableButton.on("click", function(evt) {
evt.preventDefault();
if (object.totalUseCount === 0) {
@@ -1144,7 +1272,6 @@ RED.palette.editor = (function() {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
installNodeModule(entry.name,version,url,function(xhr) {
spinner.remove();
if (xhr) {
@@ -1203,7 +1330,6 @@ RED.palette.editor = (function() {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove")+" : "+entry.name);
removeNodeModule(entry.name, function(xhr) {
spinner.remove();
if (xhr) {
@@ -1226,7 +1352,56 @@ RED.palette.editor = (function() {
}
}
]
}); }
});
}
} else {
// dedicated list management for plugins
if (entry.plugin) {
let e = nodeEntries[entry.name];
if (e) {
nodeList.editableList('removeItem', e);
delete nodeEntries[entry.name];
refreshUpdateStatus();
}
// We assume that a plugin that implements onremove
// cleans the editor accordingly of its left-overs.
let found_onremove = true;
let keys = Object.keys(entry.sets);
keys.forEach((key) => {
let set = entry.sets[key];
for (let i=0; i<set.plugins?.length; i++) {
let plgn = RED.plugins.getPlugin(set.plugins[i].id);
if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
plgn.onremove();
} else {
if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
// if there's no 'onadd', there shouldn't be any left-overs
found_onremove = false;
}
}
}
});
if (!found_onremove) {
let removeNotify = RED.notify(RED._("palette.editor.confirm.removePlugin.body",{module:entry.name}),{
modal: true,
fixed: true,
type: 'warning',
buttons: [
{
text: RED._("palette.editor.confirm.button.understood"),
class:"primary",
click: function(e) {
removeNotify.close();
}
}
]
});
}
}
}
})
notification.close();
@@ -1269,10 +1444,28 @@ RED.palette.editor = (function() {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
spinner.remove();
if (xhr) {
if (err && xhr.status === 504) {
var notification = RED.notify(RED._("palette.editor.errors.installTimeout"), {
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
})
} else if (xhr) {
if (xhr.responseJSON) {
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
type: 'error',
@@ -1308,6 +1501,66 @@ RED.palette.editor = (function() {
})
}
const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
let updateAvailable = [];
function addUpdateInfoToStatusBar() {
updateStatusWidget.on("click", function (evt) {
RED.actions.invoke("core:manage-palette", {
view: "nodes",
filter: '"' + updateAvailable.join('", "') + '"'
});
});
RED.popover.tooltip(updateStatusWidget, function () {
const count = updateAvailable.length || 0;
return RED._("palette.editor.updateCount", { count: count });
});
RED.statusBar.add({
id: "update",
align: "right",
element: updateStatusWidget
});
updateStatus({ count: 0 });
}
let pendingRefreshTimeout
function refreshUpdateStatus() {
clearTimeout(pendingRefreshTimeout)
pendingRefreshTimeout = setTimeout(() => {
updateAvailable = [];
for (const module of Object.keys(nodeEntries)) {
if (loadedIndex.hasOwnProperty(module)) {
const moduleInfo = nodeEntries[module].info;
if (moduleInfo.pending_version) {
// Module updated
continue;
}
if (updateAllowed &&
semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 &&
RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList)
) {
updateAvailable.push(module);
}
}
}
updateStatus({ count: updateAvailable.length });
}, 200)
}
function updateStatus(opts) {
if (opts.count) {
RED.statusBar.show("update");
updateStatusWidget.empty();
$('<span><i class="fa fa-cube"></i> ' + opts.count + '</span>').appendTo(updateStatusWidget);
} else {
RED.statusBar.hide("update");
}
}
return {
init: init,
install: install

View File

@@ -35,6 +35,10 @@ RED.palette = (function() {
var categoryContainers = {};
var sidebarControls;
let paletteState = { filter: "", collapsed: [] };
let filterRefreshTimeout
function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
@@ -60,20 +64,57 @@ RED.palette = (function() {
catDiv.data('label',label);
categoryContainers[category] = {
container: catDiv,
close: function() {
hide: function (instant) {
if (instant) {
catDiv.hide()
} else {
catDiv.slideUp()
}
},
show: function () {
catDiv.show()
},
isOpen: function () {
return !!catDiv.hasClass("red-ui-palette-open")
},
getNodeCount: function (visibleOnly) {
const nodes = catDiv.find(".red-ui-palette-node")
if (visibleOnly) {
return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
} else {
return nodes.length
}
},
close: function(instant, skipSaveState) {
catDiv.removeClass("red-ui-palette-open");
catDiv.addClass("red-ui-palette-closed");
$("#red-ui-palette-base-category-"+category).slideUp();
if (instant) {
$("#red-ui-palette-base-category-"+category).hide();
} else {
$("#red-ui-palette-base-category-"+category).slideUp();
}
$("#red-ui-palette-header-"+category+" i").removeClass("expanded");
if (!skipSaveState) {
if (!paletteState.collapsed.includes(category)) {
paletteState.collapsed.push(category);
savePaletteState();
}
}
},
open: function() {
open: function(skipSaveState) {
catDiv.addClass("red-ui-palette-open");
catDiv.removeClass("red-ui-palette-closed");
$("#red-ui-palette-base-category-"+category).slideDown();
$("#red-ui-palette-header-"+category+" i").addClass("expanded");
if (!skipSaveState) {
if (paletteState.collapsed.includes(category)) {
paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1);
savePaletteState();
}
}
},
toggle: function() {
if (catDiv.hasClass("red-ui-palette-open")) {
if (categoryContainers[category].isOpen()) {
categoryContainers[category].close();
} else {
categoryContainers[category].open();
@@ -415,8 +456,16 @@ RED.palette = (function() {
var categoryNode = $("#red-ui-palette-container-"+rootCategory);
if (categoryNode.find(".red-ui-palette-node").length === 1) {
categoryContainers[rootCategory].open();
if (!paletteState?.collapsed?.includes(rootCategory)) {
categoryContainers[rootCategory].open();
} else {
categoryContainers[rootCategory].close(true);
}
}
clearTimeout(filterRefreshTimeout)
filterRefreshTimeout = setTimeout(() => {
refreshFilter()
}, 200)
}
}
@@ -516,7 +565,8 @@ RED.palette = (function() {
paletteNode.css("backgroundColor", sf.color);
}
function filterChange(val) {
function refreshFilter() {
const val = $("#red-ui-palette-search input").val()
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
var currentLabel = $(el).attr("data-palette-label");
@@ -528,16 +578,26 @@ RED.palette = (function() {
}
});
for (var category in categoryContainers) {
for (let category in categoryContainers) {
if (categoryContainers.hasOwnProperty(category)) {
if (categoryContainers[category].container
.find(".red-ui-palette-node")
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
categoryContainers[category].close();
categoryContainers[category].container.slideUp();
const categorySection = categoryContainers[category]
if (categorySection.getNodeCount(true) === 0) {
categorySection.hide()
} else {
categoryContainers[category].open();
categoryContainers[category].container.show();
categorySection.show()
if (val) {
// There is a filter being applied and it has matched
// something in this category - show the contents
categorySection.open(true)
} else {
// No filter. Only show the category if it isn't in lastState
if (!paletteState.collapsed.includes(category)) {
categorySection.open(true)
} else if (categorySection.isOpen()) {
// This section should be collapsed but isn't - so make it so
categorySection.close(true, true)
}
}
}
}
}
@@ -553,6 +613,9 @@ RED.palette = (function() {
$("#red-ui-palette > .red-ui-palette-spinner").show();
RED.events.on('logout', function () {
RED.settings.removeLocal('palette-state')
})
RED.events.on('registry:node-type-added', function(nodeType) {
var def = RED.nodes.getType(nodeType);
@@ -596,14 +659,14 @@ RED.palette = (function() {
RED.events.on("subflows:change",refreshSubflow);
$("#red-ui-palette-search input").searchBox({
delay: 100,
change: function() {
filterChange($(this).val());
refreshFilter();
paletteState.filter = $(this).val();
savePaletteState();
}
})
});
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
@@ -669,7 +732,23 @@ RED.palette = (function() {
togglePalette(state);
}
});
try {
paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
if (paletteState.filter) {
// Apply the category filter
$("#red-ui-palette-search input").searchBox("value", paletteState.filter);
}
} catch (error) {
console.error("Unexpected error loading palette state from localStorage: ", error);
}
setTimeout(() => {
// Lazily tidy up any categories that haven't been reloaded
paletteState.collapsed = paletteState.collapsed.filter(category => !!categoryContainers[category])
savePaletteState()
}, 10000)
}
function togglePalette(state) {
if (!state) {
$("#red-ui-main-container").addClass("red-ui-palette-closed");
@@ -689,6 +768,15 @@ RED.palette = (function() {
})
return categories;
}
function savePaletteState() {
try {
RED.settings.setLocal("palette-state", JSON.stringify(paletteState));
} catch (error) {
console.error("Unexpected error saving palette state to localStorage: ", error);
}
}
return {
init: init,
add:addNodeType,

View File

@@ -287,7 +287,7 @@ RED.projects.settings = (function() {
var notInstalledCount = 0;
for (var m in modulesInUse) {
if (modulesInUse.hasOwnProperty(m)) {
if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) {
depsList.editableList('addItem',{
id: modulesInUse[m].module,
version: modulesInUse[m].version,
@@ -307,8 +307,8 @@ RED.projects.settings = (function() {
if (activeProject.dependencies) {
for (var m in activeProject.dependencies) {
if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) {
var installed = !!RED.nodes.registry.getModule(m);
if (activeProject.dependencies.hasOwnProperty(m)) {
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version;
depsList.editableList('addItem',{
id: m,
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,

View File

@@ -33,6 +33,7 @@ RED.statusBar = (function() {
var el = $('<span class="red-ui-statusbar-widget"></span>');
el.prop('id', options.id);
options.element.appendTo(el);
options.elementDiv = el;
if (options.align === 'left') {
leftBucket.append(el);
} else if (options.align === 'right') {
@@ -40,12 +41,30 @@ RED.statusBar = (function() {
}
}
function hideWidget(id) {
const widget = widgets[id];
if (widget && widget.elementDiv) {
widget.elementDiv.hide();
}
}
function showWidget(id) {
const widget = widgets[id];
if (widget && widget.elementDiv) {
widget.elementDiv.show();
}
}
return {
init: function() {
leftBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-left">').appendTo("#red-ui-workspace-footer");
rightBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-right">').appendTo("#red-ui-workspace-footer");
},
add: addWidget
add: addWidget,
hide: hideWidget,
show: showWidget
}
})();

View File

@@ -909,17 +909,19 @@ RED.subflow = (function() {
/**
* Create interface for controlling env var UI definition
* Build the edit dialog for a subflow template (creating/modifying a subflow template)
* @param {Object} uiContainer - the jQuery container for the environment variable list
* @param {Object} node - the subflow template node
*/
function buildEnvControl(envList,node) {
function buildEnvControl(uiContainer,node) {
var tabs = RED.tabs.create({
id: "subflow-env-tabs",
onchange: function(tab) {
if (tab.id === "subflow-env-tab-preview") {
var inputContainer = $("#subflow-input-ui");
var list = envList.editableList("items");
var list = uiContainer.editableList("items");
var exportedEnv = exportEnvList(list, true);
buildEnvUI(inputContainer, exportedEnv,node);
buildEnvUI(inputContainer, exportedEnv, node);
}
$("#subflow-env-tabs-content").children().hide();
$("#" + tab.id).show();
@@ -957,12 +959,33 @@ RED.subflow = (function() {
RED.editor.envVarList.setLocale(locale);
}
function buildEnvUIRow(row, tenv, ui, node) {
/**
* Build a UI row for a subflow instance environment variable
* Also used to build the UI row for subflow template preview
* @param {JQuery} row - A form row element
* @param {Object} tenv - A template environment variable
* @param {String} tenv.name - The name of the environment variable
* @param {String} tenv.type - The type of the environment variable
* @param {String} tenv.value - The value set for this environment variable
* @param {Object} tenv.parent - The parent environment variable
* @param {String} tenv.parent.value - The value set for the parent environment variable
* @param {String} tenv.parent.type - The type of the parent environment variable
* @param {Object} tenv.ui - The UI configuration for the environment variable
* @param {String} tenv.ui.icon - The icon for the environment variable
* @param {Object} tenv.ui.label - The label for the environment variable
* @param {String} tenv.ui.type - The type of the UI control for the environment variable
* @param {Object} node - The subflow instance node
*/
function buildEnvUIRow(row, tenv, node) {
if(RED.subflow.debug) { console.log("buildEnvUIRow", tenv) }
const ui = tenv.ui || {}
ui.label = ui.label||{};
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
ui.type = "cred";
ui.opts = {};
} else if (tenv.type === "conf-types") {
ui.type = "conf-types"
ui.opts = { types: ['conf-types'] }
} else if (!ui.type) {
ui.type = "input";
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
@@ -1006,9 +1029,10 @@ RED.subflow = (function() {
if (tenv.hasOwnProperty('type')) {
val.type = tenv.type;
}
const elId = getSubflowEnvPropertyName(tenv.name)
switch(ui.type) {
case "input":
input = $('<input type="text">').css('width','70%').appendTo(row);
input = $('<input type="text">').css('width','70%').attr('id', elId).appendTo(row);
if (ui.opts.types && ui.opts.types.length > 0) {
var inputType = val.type;
if (ui.opts.types.indexOf(inputType) === -1) {
@@ -1035,7 +1059,7 @@ RED.subflow = (function() {
}
break;
case "select":
input = $('<select>').css('width','70%').appendTo(row);
input = $('<select>').css('width','70%').attr('id', elId).appendTo(row);
if (ui.opts.opts) {
ui.opts.opts.forEach(function(o) {
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
@@ -1046,7 +1070,7 @@ RED.subflow = (function() {
case "checkbox":
label.css("cursor","default");
var cblabel = $('<label>').css('width','70%').appendTo(row);
input = $('<input type="checkbox">').css({
input = $('<input type="checkbox">').attr('id', elId).css({
marginTop: 0,
width: 'auto',
height: '34px'
@@ -1064,7 +1088,7 @@ RED.subflow = (function() {
input.prop("checked",boolVal);
break;
case "spinner":
input = $('<input>').css('width','70%').appendTo(row);
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
var spinnerOpts = {};
if (ui.opts.hasOwnProperty('min')) {
spinnerOpts.min = ui.opts.min;
@@ -1076,7 +1100,7 @@ RED.subflow = (function() {
input.val(val.value);
break;
case "cred":
input = $('<input type="password">').css('width','70%').appendTo(row);
input = $('<input type="password">').css('width','70%').attr('id', elId).appendTo(row);
if (node.credentials) {
if (node.credentials[tenv.name]) {
input.val(node.credentials[tenv.name]);
@@ -1093,18 +1117,25 @@ RED.subflow = (function() {
default: 'cred'
})
break;
}
if (input) {
input.attr('id',getSubflowEnvPropertyName(tenv.name))
case "conf-types":
// let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000);
// clsId = clsId.replace(/\W/g, '-');
// input = $('<input>').css('width','70%').addClass(clsId).attr('id', elId).appendTo(row);
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
const _type = tenv.parent?.type || tenv.type;
RED.editor.prepareConfigNodeSelect(node, tenv.name, _type, 'node-input-subflow-env', null, tenv);
break;
}
}
/**
* Create environment variable input UI
* Build the edit form for a subflow instance
* Also used to build the preview form in the subflow template edit dialog
* @param uiContainer - container for UI
* @param envList - env var definitions of template
*/
function buildEnvUI(uiContainer, envList, node) {
if(RED.subflow.debug) { console.log("buildEnvUI",envList) }
uiContainer.empty();
for (var i = 0; i < envList.length; i++) {
var tenv = envList[i];
@@ -1112,7 +1143,7 @@ RED.subflow = (function() {
continue;
}
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
buildEnvUIRow(row,tenv, tenv.ui || {}, node);
buildEnvUIRow(row, tenv, node);
}
}
// buildEnvUI
@@ -1185,6 +1216,9 @@ RED.subflow = (function() {
delete ui.opts
}
break;
case "conf-types":
delete ui.opts;
break;
default:
delete ui.opts;
}
@@ -1207,8 +1241,9 @@ RED.subflow = (function() {
if (/^subflow:/.test(node.type)) {
var subflowDef = RED.nodes.subflow(node.type.substring(8));
if (subflowDef.env) {
subflowDef.env.forEach(function(env) {
subflowDef.env.forEach(function(env, i) {
var item = {
index: i,
name:env.name,
parent: {
type: env.type,
@@ -1245,14 +1280,20 @@ RED.subflow = (function() {
var nodePropValue = nodeProp;
if (prop.ui && prop.ui.type === "cred") {
nodePropType = "cred";
} else if (prop.ui && prop.ui.type === "conf-types") {
nodePropType = prop.value.type
} else {
switch(typeof nodeProp) {
case "string": nodePropType = "str"; break;
case "number": nodePropType = "num"; break;
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
default:
nodePropType = nodeProp.type;
nodePropValue = nodeProp.value;
if (nodeProp) {
nodePropType = nodeProp.type;
nodePropValue = nodeProp.value;
} else {
nodePropType = 'str'
}
}
}
var item = {
@@ -1273,6 +1314,7 @@ RED.subflow = (function() {
}
function exportSubflowInstanceEnv(node) {
if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) }
var env = [];
// First, get the values for the SubflowTemplate defined properties
// - these are the ones with custom UI elements
@@ -1304,7 +1346,7 @@ RED.subflow = (function() {
}
break;
case "cred":
item.value = input.val();
item.value = input.typedInput('value');
item.type = 'cred';
break;
case "spinner":
@@ -1319,6 +1361,9 @@ RED.subflow = (function() {
item.type = 'bool';
item.value = ""+input.prop("checked");
break;
case "conf-types":
item.value = input.val()
item.type = "conf-type"
}
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
env.push(item);
@@ -1332,8 +1377,15 @@ RED.subflow = (function() {
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
}
// Called by subflow.oneditprepare for both instances and templates
/**
* Build the subflow edit form
* Called by subflow.oneditprepare for both instances and templates
* @param {"subflow"|"subflow-template"} type - the type of subflow being edited
* @param {Object} node - the node being edited
*/
function buildEditForm(type,node) {
if(RED.subflow.debug) { console.log("buildEditForm",type,node) }
if (type === "subflow-template") {
// This is the tabbed UI that offers the env list - with UI options
// plus the preview tab

View File

@@ -382,9 +382,11 @@ RED.sidebar.config = (function() {
refreshConfigNodeList();
}
});
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-collapse-all'), RED._("palette.actions.collapse-all"));
RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all"));
}
function flashConfigNode(el) {

View File

@@ -36,7 +36,13 @@ RED.sidebar.help = (function() {
toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content);
$('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar)
var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc')
RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics"));
RED.popover.tooltip(showTOCButton, function () {
if ($(showTOCButton).hasClass('selected')) {
return RED._("sidebar.help.hideTopics");
} else {
return RED._("sidebar.help.showTopics");
}
});
showTOCButton.on("click",function(e) {
e.preventDefault();
if ($(this).hasClass('selected')) {
@@ -158,8 +164,10 @@ RED.sidebar.help = (function() {
function refreshSubflow(sf) {
var item = treeList.treeList('get',"node-type:subflow:"+sf.id);
item.subflowLabel = sf._def.label().toLowerCase();
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
if (item) {
item.subflowLabel = sf._def.label().toLowerCase();
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
}
}
function hideTOC() {

View File

@@ -103,7 +103,7 @@ RED.sidebar.info.outliner = (function() {
evt.stopPropagation();
RED.search.show("type:subflow:"+n.id);
})
// RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})});
RED.popover.tooltip(subflowInstanceBadge,function() { return RED._('subflow.subflowInstances',{count:n.instances.length})});
}
if (n._def.category === "config" && n.type !== "group") {
var userCountBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.users.length).appendTo(controls).on("click",function(evt) {

View File

@@ -435,10 +435,15 @@ RED.tourGuide = (function() {
function listTour() {
return [
{
id: "4_0",
label: "4.0",
path: "./tours/welcome.js"
},
{
id: "3_1",
label: "3.1",
path: "./tours/welcome.js"
path: "./tours/3.1/welcome.js"
},
{
id: "3_0",

View File

@@ -264,6 +264,7 @@
setTimeout(function() {
oldTray.tray.detach();
showTray(options);
RED.events.emit('editor:change')
},250)
} else {
if (stack.length > 0) {
@@ -333,6 +334,7 @@
RED.view.focus();
} else {
stack[stack.length-1].tray.css("z-index", "auto");
RED.events.emit('editor:change')
}
},250)
}

View File

@@ -483,6 +483,16 @@ RED.utils = (function() {
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
}
let n = RED.nodes.node(obj) ?? RED.nodes.workspace(obj);
if (n) {
if (options.nodeSelector && "function" == typeof options.nodeSelector) {
e.css('cursor', 'pointer').on("click", function(evt) {
evt.preventDefault();
options.nodeSelector(n.id);
})
}
}
} else if (typeof obj === 'number') {
e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj);
@@ -589,6 +599,7 @@ RED.utils = (function() {
exposeApi: exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
}
).appendTo(row);
}
@@ -619,6 +630,7 @@ RED.utils = (function() {
exposeApi: exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
}
).appendTo(row);
}
@@ -675,6 +687,7 @@ RED.utils = (function() {
exposeApi: exposeApi,
// tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
nodeSelector: options.nodeSelector,
}
).appendTo(row);
}
@@ -888,11 +901,25 @@ RED.utils = (function() {
return parts;
}
function validatePropertyExpression(str) {
/**
* Validate a property expression
* @param {*} str - the property value
* @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
*/
function validatePropertyExpression(str, opt) {
try {
var parts = normalisePropertyExpression(str);
const parts = normalisePropertyExpression(str);
return true;
} catch(err) {
// If the validator has opt, it is a 3.x validator that
// can return a String to mean 'invalid' and provide a reason
if (opt) {
if (opt.label) {
return opt.label + ': ' + err.message;
}
return err.message;
}
// Otherwise, a 2.x returns a false value
return false;
}
}
@@ -906,23 +933,28 @@ RED.utils = (function() {
* @returns true if valid, String if invalid
*/
function validateTypedProperty(propertyValue, propertyType, opt) {
let error
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
// Allow ${ENV_VAR} value
return true
}
let error;
if (propertyType === 'json') {
try {
JSON.parse(propertyValue);
} catch(err) {
error = RED._("validator.errors.invalid-json", {
error: err.message
})
});
}
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
if (!RED.utils.validatePropertyExpression(propertyValue)) {
error = RED._("validator.errors.invalid-prop")
// To avoid double label
const valid = RED.utils.validatePropertyExpression(propertyValue, opt ? {} : null);
if (valid !== true) {
error = opt ? valid : RED._("validator.errors.invalid-prop");
}
} else if (propertyType === 'num') {
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
error = RED._("validator.errors.invalid-num")
error = RED._("validator.errors.invalid-num");
}
} else if (propertyType === 'jsonata') {
try {
@@ -930,16 +962,16 @@ RED.utils = (function() {
} catch(err) {
error = RED._("validator.errors.invalid-expr", {
error: err.message
})
});
}
}
if (error) {
if (opt && opt.label) {
return opt.label+': '+error
return opt.label + ': ' + error;
}
return error
return error;
}
return true
return true;
}
function getMessageProperty(msg,expr) {

View File

@@ -9,14 +9,27 @@ RED.view.annotations = (function() {
addAnnotation(evt.node.__pendingAnnotation__,evt);
delete evt.node.__pendingAnnotation__;
}
var badgeDX = 0;
var controlDX = 0;
for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
var annotation = evt.el.__annotations__[i];
let badgeRDX = 0;
let badgeLDX = 0;
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
const annotation = evt.el.__annotations__[i];
if (annotations.hasOwnProperty(annotation.id)) {
var opts = annotations[annotation.id];
var showAnnotation = true;
var isBadge = opts.type === 'badge';
const opts = annotations[annotation.id];
let showAnnotation = true;
const isBadge = opts.type === 'badge';
if (opts.refresh !== undefined) {
let refreshAnnotation = false
if (typeof opts.refresh === "string") {
refreshAnnotation = !!evt.node[opts.refresh]
delete evt.node[opts.refresh]
} else if (typeof opts.refresh === "function") {
refreshAnnotation = opts.refresh(evnt.node)
}
if (refreshAnnotation) {
refreshAnnotationElement(annotation.id, annotation.node, annotation.element)
}
}
if (opts.show !== undefined) {
if (typeof opts.show === "string") {
showAnnotation = !!evt.node[opts.show]
@@ -29,17 +42,24 @@ RED.view.annotations = (function() {
}
if (isBadge) {
if (showAnnotation) {
var rect = annotation.element.getBoundingClientRect();
badgeDX += rect.width;
annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
badgeDX += 4;
}
} else {
if (showAnnotation) {
var rect = annotation.element.getBoundingClientRect();
annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
controlDX += rect.width + 4;
const rect = annotation.element.getBoundingClientRect();
let annotationX
if (!opts.align || opts.align === 'right') {
annotationX = evt.node.w - 3 - badgeRDX - rect.width
badgeRDX += rect.width + 4;
} else if (opts.align === 'left') {
annotationX = 3 + badgeLDX
badgeLDX += rect.width + 4;
}
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
}
// } else {
// if (showAnnotation) {
// var rect = annotation.element.getBoundingClientRect();
// annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
// controlDX += rect.width + 4;
// }
}
} else {
annotation.element.parentNode.removeChild(annotation.element);
@@ -95,15 +115,25 @@ RED.view.annotations = (function() {
annotationGroup.setAttribute("class",opts.class || "");
evt.el.__annotations__.push({
id:id,
node: evt.node,
element: annotationGroup
});
var annotation = opts.element(evt.node);
refreshAnnotationElement(id, evt.node, annotationGroup)
evt.el.appendChild(annotationGroup);
}
function refreshAnnotationElement(id, node, annotationGroup) {
const opts = annotations[id];
const annotation = opts.element(node);
if (opts.tooltip) {
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip));
annotation.addEventListener("mouseleave", annotationMouseLeave);
}
if (annotationGroup.hasChildNodes()) {
annotationGroup.removeChild(annotationGroup.firstChild)
}
annotationGroup.appendChild(annotation);
evt.el.appendChild(annotationGroup);
}

View File

@@ -646,120 +646,128 @@ RED.view = (function() {
}
d3.event = event;
var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
var result = createNode(selected_tool);
if (!result) {
return;
}
var historyEvent = result.historyEvent;
var nn = RED.nodes.add(result.node);
var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
nn.l = showLabel;
}
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var helperWidth = ui.helper.width();
var helperHeight = ui.helper.height();
var mousePos = d3.touches(this)[0]||d3.mouse(this);
try {
var isLink = (nn.type === "link in" || nn.type === "link out")
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
var label = RED.utils.getNodeLabel(nn, nn.type);
var labelParts = getLabelParts(label, "red-ui-flow-node-label");
if (hideLabel) {
nn.w = node_height;
nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
} else {
nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
var result = createNode(selected_tool);
if (!result) {
return;
}
} catch(err) {
}
var historyEvent = result.historyEvent;
var nn = RED.nodes.add(result.node);
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor;
var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
nn.l = showLabel;
}
nn.x = mousePos[0];
nn.y = mousePos[1];
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var helperWidth = ui.helper.width();
var helperHeight = ui.helper.height();
var mousePos = d3.touches(this)[0]||d3.mouse(this);
var minX = nn.w/2 -5;
if (nn.x < minX) {
nn.x = minX;
}
var minY = nn.h/2 -5;
if (nn.y < minY) {
nn.y = minY;
}
var maxX = space_width -nn.w/2 +5;
if (nn.x > maxX) {
nn.x = maxX;
}
var maxY = space_height -nn.h +5;
if (nn.y > maxY) {
nn.y = maxY;
}
try {
var isLink = (nn.type === "link in" || nn.type === "link out")
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
if (snapGrid) {
var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
nn.x -= gridOffset.x;
nn.y -= gridOffset.y;
}
var label = RED.utils.getNodeLabel(nn, nn.type);
var labelParts = getLabelParts(label, "red-ui-flow-node-label");
if (hideLabel) {
nn.w = node_height;
nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
} else {
nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
}
} catch(err) {
}
var linkToSplice = $(ui.helper).data("splice");
if (linkToSplice) {
spliceLink(linkToSplice, nn, historyEvent)
}
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor;
nn.x = mousePos[0];
nn.y = mousePos[1];
var minX = nn.w/2 -5;
if (nn.x < minX) {
nn.x = minX;
}
var minY = nn.h/2 -5;
if (nn.y < minY) {
nn.y = minY;
}
var maxX = space_width -nn.w/2 +5;
if (nn.x > maxX) {
nn.x = maxX;
}
var maxY = space_height -nn.h +5;
if (nn.y > maxY) {
nn.y = maxY;
}
if (snapGrid) {
var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
nn.x -= gridOffset.x;
nn.y -= gridOffset.y;
}
var linkToSplice = $(ui.helper).data("splice");
if (linkToSplice) {
spliceLink(linkToSplice, nn, historyEvent)
}
var group = $(ui.helper).data("group");
if (group) {
var oldX = group.x;
var oldY = group.y;
RED.group.addToGroup(group, nn);
var moveEvent = null;
if ((group.x !== oldX) ||
(group.y !== oldY)) {
moveEvent = {
t: "move",
nodes: [{n: group,
ox: oldX, oy: oldY,
dx: group.x -oldX,
dy: group.y -oldY}],
dirty: true
};
}
historyEvent = {
t: 'multi',
events: [historyEvent],
var group = $(ui.helper).data("group");
if (group) {
var oldX = group.x;
var oldY = group.y;
RED.group.addToGroup(group, nn);
var moveEvent = null;
if ((group.x !== oldX) ||
(group.y !== oldY)) {
moveEvent = {
t: "move",
nodes: [{n: group,
ox: oldX, oy: oldY,
dx: group.x -oldX,
dy: group.y -oldY}],
dirty: true
};
if (moveEvent) {
historyEvent.events.push(moveEvent)
}
historyEvent.events.push({
t: "addToGroup",
group: group,
nodes: nn
})
}
historyEvent = {
t: 'multi',
events: [historyEvent],
};
if (moveEvent) {
historyEvent.events.push(moveEvent)
RED.history.push(historyEvent);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
movingSet.add(nn);
updateActiveNodes();
updateSelection();
redraw();
if (nn._def.autoedit) {
RED.editor.edit(nn);
}
} catch (error) {
if (error.code != "NODE_RED") {
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
} else {
RED.notify(RED._("notification.error",{message:error.message}),"error");
}
historyEvent.events.push({
t: "addToGroup",
group: group,
nodes: nn
})
}
RED.history.push(historyEvent);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
movingSet.add(nn);
updateActiveNodes();
updateSelection();
redraw();
if (nn._def.autoedit) {
RED.editor.edit(nn);
}
}
});
@@ -857,6 +865,39 @@ RED.view = (function() {
RED.view.navigator.init();
RED.view.tools.init();
RED.view.annotations.register("red-ui-flow-node-docs",{
type: "badge",
class: "red-ui-flow-node-docs",
element: function(node) {
const docsBadge = document.createElementNS("http://www.w3.org/2000/svg","g")
const pageOutline = document.createElementNS("http://www.w3.org/2000/svg","rect");
pageOutline.setAttribute("x",0);
pageOutline.setAttribute("y",0);
pageOutline.setAttribute("rx",2);
pageOutline.setAttribute("width",7);
pageOutline.setAttribute("height",10);
docsBadge.appendChild(pageOutline)
const pageLines = document.createElementNS("http://www.w3.org/2000/svg","path");
pageLines.setAttribute("d", "M 7 3 h -3 v -3 M 2 8 h 3 M 2 6 h 3 M 2 4 h 2")
docsBadge.appendChild(pageLines)
$(docsBadge).on('click', function (evt) {
if (node.type === "subflow") {
RED.editor.editSubflow(activeSubflow, 'editor-tab-description');
} else if (node.type === "group") {
RED.editor.editGroup(node, 'editor-tab-description');
} else {
RED.editor.edit(node, 'editor-tab-description');
}
})
return docsBadge;
},
show: function(n) { return !!n.info }
})
RED.view.annotations.register("red-ui-flow-node-changed",{
type: "badge",
@@ -1182,6 +1223,7 @@ RED.view = (function() {
if (d3.event.button === 1) {
// Middle Click pan
d3.event.preventDefault();
mouse_mode = RED.state.PANNING;
mouse_position = [d3.event.pageX,d3.event.pageY]
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
@@ -4156,7 +4198,7 @@ RED.view = (function() {
}
var width = img.width * scaleFactor;
if (width > 20) {
scalefactor *= 20/width;
scaleFactor *= 20/width;
width = 20;
}
var height = img.height * scaleFactor;
@@ -6063,14 +6105,19 @@ RED.view = (function() {
function createNode(type, x, y, z) {
const wasDirty = RED.nodes.dirty()
var m = /^subflow:(.+)$/.exec(type);
var activeSubflow = z ? RED.nodes.subflow(z) : null;
var activeSubflow = (z || RED.workspaces.active()) ? RED.nodes.subflow(z || RED.workspaces.active()) : null;
if (activeSubflow && m) {
var subflowId = m[1];
let err
if (subflowId === activeSubflow.id) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") }))
err = new Error(RED._("notification.errors.cannotAddSubflowToItself"))
} else if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
err = new Error(RED._("notification.errors.cannotAddCircularReference"))
}
if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
if (err) {
err.code = 'NODE_RED'
throw err
}
}
@@ -6252,6 +6299,10 @@ RED.view = (function() {
}
})
}
if (selection.links) {
selectedLinks.clear();
selection.links.forEach(selectedLinks.add);
}
}
}
updateSelection();

View File

@@ -359,11 +359,17 @@ RED.workspaces = (function() {
RED.sidebar.config.refresh();
RED.view.focus();
},
onclick: function(tab) {
if (tab.id !== activeWorkspace) {
addToViewStack(activeWorkspace);
onclick: function(tab, evt) {
if(evt.which === 2) {
evt.preventDefault();
evt.stopPropagation();
RED.actions.invoke("core:hide-flow", tab)
} else {
if (tab.id !== activeWorkspace) {
addToViewStack(activeWorkspace);
}
RED.view.focus();
}
RED.view.focus();
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
@@ -401,6 +407,7 @@ RED.workspaces = (function() {
if (tab.type === "tab") {
workspaceTabCount--;
} else {
RED.events.emit("workspace:close",{workspace: tab.id})
hideStack.push(tab.id);
}
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
@@ -491,6 +498,11 @@ RED.workspaces = (function() {
createWorkspaceTabs();
RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.events.on("workspace:clear", () => {
// Reset the index used to generate new flow names
workspaceIndex = 0
})
RED.actions.add("core:show-next-tab",function() {
var oldActive = activeWorkspace;
workspace_tabs.nextTab();
@@ -657,6 +669,9 @@ RED.workspaces = (function() {
RED.events.on("flows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
})
RED.events.on("subflows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
})
hideWorkspace();
}

View File

@@ -187,6 +187,7 @@ RED.user = (function() {
}
function logout() {
RED.events.emit('logout')
var tokens = RED.settings.get("auth-tokens");
var token = tokens?tokens.access_token:"";
$.ajax({
@@ -211,6 +212,8 @@ RED.user = (function() {
function updateUserMenu() {
$("#red-ui-header-button-user-submenu li").remove();
const userMenu = $("#red-ui-header-button-user")
userMenu.empty()
if (RED.settings.user.anonymous) {
RED.menu.addItem("red-ui-header-button-user",{
id:"usermenu-item-login",
@@ -238,7 +241,8 @@ RED.user = (function() {
}
});
}
const userIcon = generateUserIcon(RED.settings.user)
userIcon.appendTo(userMenu);
}
function init() {
@@ -247,14 +251,6 @@ RED.user = (function() {
var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
.prependTo(".red-ui-header-toolbar");
if (RED.settings.user.image) {
$('<span class="user-profile"></span>').css({
backgroundImage: "url("+RED.settings.user.image+")",
}).appendTo(userMenu.find("a"));
} else {
$('<i class="fa fa-user"></i>').appendTo(userMenu.find("a"));
}
RED.menu.init({id:"red-ui-header-button-user",
options: []
});
@@ -317,12 +313,30 @@ RED.user = (function() {
return false;
}
function generateUserIcon(user) {
const userIcon = $('<span class="red-ui-user-profile"></span>')
if (user.image) {
userIcon.addClass('has_profile_image')
userIcon.css({
backgroundImage: "url("+user.image+")",
})
} else if (user.anonymous) {
$('<i class="fa fa-user"></i>').appendTo(userIcon);
} else {
$('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
}
if (user.profileColor !== undefined) {
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
}
return userIcon
}
return {
init: init,
login: login,
logout: logout,
hasPermission: hasPermission
hasPermission: hasPermission,
generateUserIcon
}
})();

View File

@@ -16,8 +16,20 @@
RED.validators = {
number: function(blankAllowed,mopt){
return function(v, opt) {
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
return true;
if (blankAllowed && (v === '' || v === undefined)) {
return true
}
if (v !== '') {
if (/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(v)) {
return true
}
if (/^\${[^}]+}$/.test(v)) {
// Allow ${ENV_VAR} value
return true
}
}
if (!isNaN(v)) {
return true
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", {

View File

@@ -38,7 +38,7 @@ body {
}
#red-ui-main-container {
position: absolute;
top:40px; left:0; bottom: 0; right:0;
top: var(--red-ui-header-height); left:0; bottom: 0; right:0;
overflow:hidden;
}

View File

@@ -259,7 +259,8 @@ $deploy-button-background-disabled-hover: #555;
$header-background: #000;
$header-button-background-active: #121212;
$header-menu-color: #C7C7C7;
$header-accent: #C02020;
$header-menu-color: #eee;
$header-menu-color-disabled: #666;
$header-menu-heading-color: #fff;
$header-menu-sublabel-color: #aeaeae;
@@ -313,6 +314,16 @@ $spinner-color: #999;
$tab-icon-color: #dedede;
// Anonymous User Colors
$user-profile-colors: (
1: #822e81,
2: #955e42,
3: #9c914f,
4: #748e54,
5: #06bcc1
);
// Deprecated
$text-color-green: $text-color-success;
$info-text-code-color: $text-color-code;

View File

@@ -242,6 +242,18 @@ svg:not(.red-ui-workspace-lasso-active) {
stroke-linecap: round;
}
.red-ui-flow-node-docs {
stroke-width: 1px;
stroke-linejoin: round;
stroke-linecap: round;
stroke: var(--red-ui-node-border);
fill: none;
cursor: pointer;
rect {
fill: white;
}
}
g.red-ui-flow-node-selected {
.red-ui-workspace-select-mode & {
opacity: 1;

View File

@@ -23,16 +23,20 @@
top: 0;
left: 0;
width: 100%;
height: 40px;
height: var(--red-ui-header-height);
background: var(--red-ui-header-background);
box-sizing: border-box;
padding: 0px 0px 0px 20px;
color: var(--red-ui-header-menu-color);
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid var(--red-ui-header-accent);
padding-top: 2px;
span.red-ui-header-logo {
float: left;
margin-top: 5px;
font-size: 30px;
line-height: 30px;
text-decoration: none;
@@ -42,7 +46,7 @@
vertical-align: middle;
font-size: 16px !important;
&:not(:first-child) {
margin-left: 5px;
margin-left: 8px;
}
}
img {
@@ -59,25 +63,29 @@
}
.red-ui-header-toolbar {
display: flex;
align-items: stretch;
padding: 0;
margin: 0;
list-style: none;
float: right;
> li {
display: inline-block;
display: inline-flex;
align-items: stretch;
padding: 0;
margin: 0;
position: relative;
}
}
.button {
height: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
text-align: center;
line-height: 40px;
display: inline-block;
font-size: 20px;
padding: 0px 12px;
text-decoration: none;
@@ -178,6 +186,20 @@
}
}
.red-ui-deploy-button-group.readOnly {
.fa-caret-down { display: none; }
.fa-lock { display: inline-block; }
}
.red-ui-deploy-button-group:not(.readOnly) {
.fa-caret-down { display: inline-block; }
.fa-lock { display: none; }
}
.red-ui-deploy-button-group.readOnly {
a {
pointer-events: none;
}
}
li.open .button {
background: var(--red-ui-header-button-background-active);
border-color: var(--red-ui-header-button-background-active);
@@ -266,18 +288,44 @@
#usermenu-item-username > .red-ui-menu-label {
color: var(--red-ui-header-menu-heading-color);
}
}
#red-ui-header-button-user .user-profile {
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
display: inline-block;
width: 40px;
height: 35px;
vertical-align: middle;
.red-ui-user-profile {
background-color: var(--red-ui-header-background);
border: 2px solid var(--red-ui-header-menu-color);
border-radius: 30px;
overflow: hidden;
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
display: inline-flex;
justify-content: center;
align-items: center;
vertical-align: middle;
width: 30px;
height: 30px;
font-size: 20px;
&.red-ui-user-profile-color-1 {
background-color: var(--red-ui-user-profile-colors-1);
}
&.red-ui-user-profile-color-2 {
background-color: var(--red-ui-user-profile-colors-2);
}
&.red-ui-user-profile-color-3 {
background-color: var(--red-ui-user-profile-colors-3);
}
&.red-ui-user-profile-color-4 {
background-color: var(--red-ui-user-profile-colors-4);
}
&.red-ui-user-profile-color-5 {
background-color: var(--red-ui-user-profile-colors-5);
}
}
@media only screen and (max-width: 450px) {
span.red-ui-header-logo > span {
display: none;

View File

@@ -0,0 +1,116 @@
#red-ui-multiplayer-user-list {
display: inline-flex;
align-items: center;
margin: 0 5px;
li {
display: inline-flex;
align-items: center;
margin: 0 2px;
}
}
.red-ui-multiplayer-user-icon {
background: none;
border: none;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
box-sizing: border-box;
text-decoration: none;
color: var(--red-ui-header-menu-color);
padding: 0px;
margin: 0px;
vertical-align: middle;
&:focus {
outline: none;
}
.red-ui-multiplayer-user.inactive & {
opacity: 0.5;
}
.red-ui-user-profile {
width: 20px;
border-radius: 20px;
height: 20px;
font-size: 12px
}
}
.red-ui-multiplayer-users-tray {
position: absolute;
top: 5px;
right: 20px;
line-height: normal;
cursor: pointer;
// &:hover {
// .red-ui-multiplayer-user-location {
// margin-left: 1px;
// }
// }
}
$multiplayer-user-icon-background: var(--red-ui-primary-background);
$multiplayer-user-icon-border: var(--red-ui-view-background);
$multiplayer-user-icon-text-color: var(--red-ui-header-menu-color);
$multiplayer-user-icon-count-text-color: var(--red-ui-primary-color);
$multiplayer-user-icon-shadow: 0px 0px 4px var(--red-ui-shadow);
.red-ui-multiplayer-user-location {
display: inline-block;
margin-left: -6px;
transition: margin-left 0.2s;
.red-ui-user-profile {
border: 1px solid $multiplayer-user-icon-border;
color: $multiplayer-user-icon-text-color;
width: 18px;
height: 18px;
border-radius: 18px;
font-size: 10px;
font-weight: normal;
box-shadow: $multiplayer-user-icon-shadow;
&.red-ui-multiplayer-user-count {
color: $multiplayer-user-icon-count-text-color;
background-color: $multiplayer-user-icon-background;
}
}
}
.red-ui-multiplayer-annotation {
.red-ui-multiplayer-annotation-background {
filter: drop-shadow($multiplayer-user-icon-shadow);
fill: $multiplayer-user-icon-background;
&.red-ui-user-profile-color-1 {
fill: var(--red-ui-user-profile-colors-1);
}
&.red-ui-user-profile-color-2 {
fill: var(--red-ui-user-profile-colors-2);
}
&.red-ui-user-profile-color-3 {
fill: var(--red-ui-user-profile-colors-3);
}
&.red-ui-user-profile-color-4 {
fill: var(--red-ui-user-profile-colors-4);
}
&.red-ui-user-profile-color-5 {
fill: var(--red-ui-user-profile-colors-5);
}
}
.red-ui-multiplayer-annotation-border {
stroke: $multiplayer-user-icon-border;
stroke-width: 1px;
fill: none;
}
.red-ui-multiplayer-annotation-anon-label {
fill: $multiplayer-user-icon-text-color;
stroke: none;
}
text {
user-select: none;
fill: $multiplayer-user-icon-text-color;
stroke: none;
font-size: 10px;
&.red-ui-multiplayer-user-count {
fill: $multiplayer-user-icon-count-text-color;
}
}
}

View File

@@ -305,3 +305,8 @@ button.red-ui-palette-editor-upload-button {
margin-left: 10px;
}
}
button.red-ui-update-status {
width: auto;
padding: 0 3px;
}

View File

@@ -0,0 +1,17 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
$header-height: 48px;

View File

@@ -15,4 +15,5 @@
**/
@import "colors";
@import "sizes";
@import "variables";

View File

@@ -15,6 +15,7 @@
**/
@import "colors";
@import "sizes";
@import "variables";
@import "mixins";
@@ -72,3 +73,5 @@
@import "radialMenu";
@import "tourGuide";
@import "multiplayer";

View File

@@ -37,7 +37,6 @@ ul.red-ui-sidebar-node-config-list {
}
.red-ui-palette-node {
// overflow: hidden;
cursor: default;
&.selected {
border-color: transparent;
box-shadow: 0 0 0 2px var(--red-ui-node-selected-color);

View File

@@ -16,6 +16,9 @@
--red-ui-shadow: #{$shadow};
// Header Height
--red-ui-header-height: #{$header-height};
// Main body text
--red-ui-primary-text-color: #{$primary-text-color};
// UI control label text
@@ -240,6 +243,7 @@
--red-ui-header-background: #{$header-background};
--red-ui-header-accent: #{$header-accent};
--red-ui-header-button-background-active: #{$header-button-background-active};
--red-ui-header-menu-color: #{$header-menu-color};
--red-ui-header-menu-color-disabled: #{$header-menu-color-disabled};
@@ -295,4 +299,7 @@
--red-ui-tab-icon-color: #{$tab-icon-color};
@each $current-color in 1 2 3 4 5 {
--red-ui-user-profile-colors-#{"" + $current-color}: #{map-get($user-profile-colors, $current-color)};
}
}

View File

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

View File

@@ -0,0 +1,231 @@
export default {
version: "3.1.0",
steps: [
{
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 3.1!",
"ja": "Node-RED 3.1へようこそ!",
"fr": "Bienvenue dans Node-RED 3.1!"
},
description: {
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
}
},
{
title: {
"en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法",
"fr": "De nouvelles façons de travailler avec les groupes"
},
description: {
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
<ul>
<li>They don't get in the way when clicking on a node</li>
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
<li>Multiple nodes can be dragged into a group in one go</li>
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
</ul>`,
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
<ul>
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
</ul>`,
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
<ul>
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
</ul>`
}
},
{
title: {
"en-US": "Change notification on tabs",
"ja": "タブ上の変更通知",
"fr": "Notification de changement sur les onglets"
},
image: '3.1/images/tab-changes.png',
description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`,
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`,
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
même style d'icône de changement utilisé par les noeuds.</p>
<p>Cela facilitera grandement le suivi des modifications lorsque vous
travaillez sur plusieurs flux.</p>`
}
},
{
title: {
"en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス",
"fr": "Un canevas plus grand pour travailler"
},
description: {
"en-US": `<p>The default canvas size has been increased so you can fit more
into one flow.</p>
<p>We still recommend using tools such as subflows and Link Nodes to help
keep things organised, but now you have more room to work in.</p>`,
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`,
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
sur un seul flux.</p>
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
}
},
{
title: {
"en-US": "Finding help",
"ja": "ヘルプを見つける",
"fr": "Trouver de l'aide"
},
image: '3.1/images/node-help.png',
description: {
"en-US": `<p>All node edit dialogs now include a link to that node's help
in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`,
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
dans le pied de page.</p>
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
}
},
{
title: {
"en-US": "Improved Context Menu",
"ja": "コンテキストメニューの改善",
"fr": "Menu contextuel amélioré"
},
image: '3.1/images/context-menu.png',
description: {
"en-US": `<p>The editor's context menu has been expanded to make lots more of
the built-in actions available.</p>
<p>Adding nodes, working with groups and plenty
of other useful tools are now just a click away.</p>
<p>The flow tab bar also has its own context menu to make working
with your flows much easier.</p>`,
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
<p>ノードの追加、グループの操作、その他の便利なツールをクリックするだけで実行できるようになりました。</p>
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`,
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
}
},
{
title: {
"en-US": "Hiding Flows",
"ja": "フローを非表示",
"fr": "Masquage de flux"
},
image: '3.1/images/hiding-flows.png',
description: {
"en-US": `<p>Hiding flows is now done through the flow context menu.</p>
<p>The 'hide' button in previous releases has been removed from the tabs
as they were being clicked accidentally too often.</p>`,
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`,
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
car il était cliqué accidentellement trop souvent.</p>`
},
},
{
title: {
"en-US": "Locking Flows",
"ja": "フローを固定",
"fr": "Verrouillage de flux"
},
image: '3.1/images/locking-flows.png',
description: {
"en-US": `<p>Flows can now be locked to prevent accidental changes being made.</p>
<p>When locked you cannot modify the nodes in any way.</p>
<p>The flow context menu provides the options to lock and unlock flows,
as well as in the Info sidebar explorer.</p>`,
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
<p>固定されている時は、ノードを修正することはできません。</p>
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`,
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
},
},
{
title: {
"en-US": "Adding Images to node/flow descriptions",
"ja": "ノードやフローの説明へ画像を追加",
"fr": "Ajout d'images aux descriptions de noeud/flux"
},
// image: 'images/debug-path-tooltip.png',
description: {
"en-US": `<p>You can now add images to a node's or flows's description.</p>
<p>Simply drag the image into the text editor and it will get added inline.</p>
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
<p>画像をテキストエディタにドラッグするだけで、行内に埋め込まれます。</p>
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`,
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
},
},
{
title: {
"en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加",
"fr": "Ajout de diagrammes Mermaid"
},
image: '3.1/images/mermaid.png',
description: {
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
<p>This gives you much richer options for documenting your flows.</p>`,
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`,
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
},
},
{
title: {
"en-US": "Managing Global Environment Variables",
"ja": "グローバル環境変数の管理",
"fr": "Gestion des variables d'environnement globales"
},
image: '3.1/images/global-env-vars.png',
description: {
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
'Global Environment Variables' section of User Settings.</p>`,
"ja": `<p>ユーザ設定に新しく追加された「グローバル環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
section "Global Environment Variables" des paramètres utilisateur.</p>`
},
},
{
title: {
"en-US": "Node Updates",
"ja": "ノードの更新",
"fr": "Mises à jour des noeuds"
},
// image: "images/",
description: {
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,12 +1,12 @@
export default {
version: "3.1.0",
version: "4.0.0",
steps: [
{
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 3.1!",
"ja": "Node-RED 3.1へようこそ!",
"fr": "Bienvenue dans Node-RED 3.1!"
"en-US": "Welcome to Node-RED 4.0!",
"ja": "Node-RED 4.0 へようこそ!",
"fr": "Bienvenue dans Node-RED 4.0!"
},
description: {
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
@@ -16,202 +16,184 @@ export default {
},
{
title: {
"en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法",
"fr": "De nouvelles façons de travailler avec les groupes"
"en-US": "Multiplayer Mode",
"ja": "複数ユーザ同時利用モード",
"fr": "Mode Multi-utilisateur"
},
image: 'images/nr4-multiplayer-location.png',
description: {
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
to work with when you have multiple people editing flows at the same time.</p>
<p>When this feature is enabled, you will now see who else has the editor open and some
basic information on where they are in the editor.</p>
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
lorsque plusieurs personnes modifient des flux en même temps.</p>
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si dautres utilisateurs ont
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
dans votre fichier de paramètres.</p>`
}
},
{
title: {
"en-US": "Better background deploy handling",
"ja": "バックグラウンドのデプロイ処理の改善",
"fr": "Meilleure gestion du déploiement en arrière-plan"
},
image: 'images/nr4-background-deploy.png',
description: {
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
of changes.</p>`,
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
}
},
{
title: {
"en-US": "Improved flow diffs",
"ja": "フローの差分表示の改善",
"fr": "Amélioration des différences de flux"
},
image: 'images/nr4-diff-update.png',
description: {
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
changes and those that have only been moved.<p>
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたードと、移動されただけのードを区別するようになりました。<p>
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
plus importants.</p>`
}
},
{
title: {
"en-US": "Better Configuration Node UX",
"ja": "設定ードのUXが向上",
"fr": "Meilleure expérience utilisateur du noeud de configuration"
},
image: 'images/nr4-config-select.png',
description: {
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
next to the select box.</p>
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
"ja": `<p>設定ードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
}
},
{
title: {
"en-US": "Timestamp formatting options",
"ja": "タイムスタンプの形式の項目",
"fr": "Options de formatage de l'horodatage"
},
image: 'images/nr4-timestamp-formatting.png',
description: {
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
<p>We're keeping it simple to begin with by providing three options:<p>
<ul>
<li>They don't get in the way when clicking on a node</li>
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
<li>Multiple nodes can be dragged into a group in one go</li>
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
<li>ISO 8601 - a common format used by many systems</li>
<li>JavaScript Date Object</li>
</ul>`,
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
<ul>
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
<li>JavaScript日付オブジェクト</li>
</ul>`,
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
<p>Nous gardons les choses simples en proposant trois options :<p>
<ul>
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
<li>Objet Date JavaScript</li>
</ul>`
}
},
{
title: {
"en-US": "Change notification on tabs",
"ja": "タブ上の変更通知",
"fr": "Notification de changement sur les onglets"
"en-US": "Auto-complete of flow/global and env types",
"ja": "フロー/グローバル、環境変数の型の自動補完",
"fr": "Saisie automatique des types de flux/global et env"
},
image: 'images/tab-changes.png',
image: 'images/nr4-auto-complete.png',
description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`,
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`,
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
même style d'icône de changement utilisé par les noeuds.</p>
<p>Cela facilitera grandement le suivi des modifications lorsque vous
travaillez sur plusieurs flux.</p>`
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
now all include auto-complete suggestions based on the live state of your flows.</p>
`,
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
`,
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
`,
}
},
{
title: {
"en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス",
"fr": "Un canevas plus grand pour travailler"
"en-US": "Config node customisation in Subflows",
"ja": "サブフローでの設定ノードのカスタマイズ",
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
},
image: 'images/nr4-sf-config.png',
description: {
"en-US": `<p>The default canvas size has been increased so you can fit more
into one flow.</p>
<p>We still recommend using tools such as subflows and Link Nodes to help
keep things organised, but now you have more room to work in.</p>`,
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`,
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
sur un seul flux.</p>
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
config node of a selected type.</p>
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
of the messages received can be pointed at a different broker.</p>
`,
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
`,
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
noeud de configuration d'un type sélectionné.</p>
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
des messages reçus peut être pointée vers un autre courtier.</p>
`
}
},
{
title: {
"en-US": "Finding help",
"ja": "ヘルプを見つける",
"fr": "Trouver de l'aide"
"en-US": "Remembering palette state",
"ja": "パレットの状態を維持",
"fr": "Mémorisation de l'état de la palette"
},
image: 'images/node-help.png',
description: {
"en-US": `<p>All node edit dialogs now include a link to that node's help
in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`,
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
dans le pied de page.</p>
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
filter you have applied.</p>`,
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
ainsi que le filtre que vous avez appliqué.</p>`
}
},
{
title: {
"en-US": "Improved Context Menu",
"ja": "コンテキストメニューの改善",
"fr": "Menu contextuel amélioré"
"en-US": "Plugins shown in the Palette Manager",
"ja": "パレット管理にプラグインを表示",
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
},
image: 'images/context-menu.png',
image: 'images/nr4-plugins.png',
description: {
"en-US": `<p>The editor's context menu has been expanded to make lots more of
the built-in actions available.</p>
<p>Adding nodes, working with groups and plenty
of other useful tools are now just a click away.</p>
<p>The flow tab bar also has its own context menu to make working
with your flows much easier.</p>`,
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
<p>ノードの追加、グループの操作、その他の便利なツールをクリックするだけで実行できるようになりました。</p>
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`,
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
nodes for the palette.</p>`,
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
des noeuds pour la palette.</p>`
}
},
{
title: {
"en-US": "Hiding Flows",
"ja": "フローを非表示",
"fr": "Masquage de flux"
},
image: 'images/hiding-flows.png',
description: {
"en-US": `<p>Hiding flows is now done through the flow context menu.</p>
<p>The 'hide' button in previous releases has been removed from the tabs
as they were being clicked accidentally too often.</p>`,
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`,
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
car il était cliqué accidentellement trop souvent.</p>`
},
},
{
title: {
"en-US": "Locking Flows",
"ja": "フローを固定",
"fr": "Verrouillage de flux"
},
image: 'images/locking-flows.png',
description: {
"en-US": `<p>Flows can now be locked to prevent accidental changes being made.</p>
<p>When locked you cannot modify the nodes in any way.</p>
<p>The flow context menu provides the options to lock and unlock flows,
as well as in the Info sidebar explorer.</p>`,
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
<p>固定されている時は、ノードを修正することはできません。</p>
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`,
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
},
},
{
title: {
"en-US": "Adding Images to node/flow descriptions",
"ja": "ノードやフローの説明へ画像を追加",
"fr": "Ajout d'images aux descriptions de noeud/flux"
},
// image: 'images/debug-path-tooltip.png',
description: {
"en-US": `<p>You can now add images to a node's or flows's description.</p>
<p>Simply drag the image into the text editor and it will get added inline.</p>
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
<p>画像をテキストエディタにドラッグするだけで、行内に埋め込まれます。</p>
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`,
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
},
},
{
title: {
"en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加",
"fr": "Ajout de diagrammes Mermaid"
},
image: 'images/mermaid.png',
description: {
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
<p>This gives you much richer options for documenting your flows.</p>`,
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`,
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
},
},
{
title: {
"en-US": "Managing Global Environment Variables",
"ja": "グローバル環境変数の管理",
"fr": "Gestion des variables d'environnement globales"
},
image: 'images/global-env-vars.png',
description: {
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
'Global Environment Variables' section of User Settings.</p>`,
"ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
section "Global Environment Variables" des paramètres utilisateur.</p>`
},
},
{
title: {
"en-US": "Node Updates",
@@ -221,10 +203,28 @@ export default {
// image: "images/",
description: {
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
<ul>
<li>A fully RFC4180 compliant CSV mode</li>
<li>Customisable headers on the WebSocket node</li>
<li>Split node now can operate on any message property</li>
<li>and lots more...</li>
</ul>`,
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
<ul>
<li>RFC4180に完全に準拠したCSVモード</li>
<li>WebSocketードのカスタマイズ可能なヘッダ</li>
<li>Splitードは、メッセージプロパティで操作できるようになりました</li>
<li>他にも沢山あります...</li>
</ul>`,
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
<ul>
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
<li>En-têtes personnalisables pour le noeud WebSocket</li>
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
<li>Et bien plus encore...</li>
</ul>`
}
}
]

View File

@@ -1,10 +1,10 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
interface NodeMessage {
topic?: string;
payload?: any;
_msgid?: string;
/** `_msgid` is generated internally. It not something you typically need to set or modify. */ _msgid?: string;
[other: string]: any; //permit other properties
}
@@ -19,15 +19,15 @@ declare const promisify:typeof import('util').promisify
/**
* @typedef NodeStatus
* @type {object}
* @property {string} [fill] The fill property can be: red, green, yellow, blue or grey.
* @property {string} [shape] The shape property can be: ring or dot.
* @property {string} [text] The text to display
* @property {'red'|'green'|'yellow'|'blue'|'grey'|string} [fill] - The fill property can be: red, green, yellow, blue or grey.
* @property {'ring'|'dot'|string} [shape] The shape property can be: ring or dot.
* @property {string|boolean|number} [text] The text to display
*/
interface NodeStatus {
/** The fill property can be: red, green, yellow, blue or grey */
fill?: string,
fill?: 'red'|'green'|'yellow'|'blue'|'grey'|string,
/** The shape property can be: ring or dot */
shape?: string,
shape?: 'ring'|'dot'|string,
/** The text to display */
text?: string|boolean|number
}
@@ -37,25 +37,24 @@ declare class node {
* Send 1 or more messages asynchronously
* @param {object | object[]} msg The msg object
* @param {Boolean} [clone=true] Flag to indicate the `msg` should be cloned. Default = `true`
* @see node-red documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
* @see Node-RED documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
*/
static send(msg:object|object[], clone?:Boolean): void;
static send(msg:NodeMessage|NodeMessage[], clone?:Boolean): void;
/** Inform runtime this instance has completed its operation */
static done();
/** Send an error to the console and debug side bar. Include `msg` in the 2nd parameter to trigger the catch node. */
static error(err:string|Error, msg?:object);
static error(err:string|Error, msg?:NodeMessage);
/** Log a warn message to the console and debug sidebar */
static warn(warning:string|object);
/** Log an info message to the console (not sent to sidebar)' */
static log(info:string|object);
/** Sets the status icon and text underneath the node.
* @param {NodeStatus} status - The status object `{fill, shape, text}`
* @see node-red documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
* @see Node-RED documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
*/
static status(status:NodeStatus);
/** Sets the status text underneath the node.
* @param {string} status - The status to display
* @see node-red documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
* @see Node-RED documentation [writing-functions: adding-status](https://nodered.org/docs/user-guide/writing-functions#adding-status)
*/
static status(status:string|boolean|number);
/** the id of this node */
@@ -264,9 +263,12 @@ declare class global {
/** Get an array of the keys in the context store */
static keys(store: string, callback: Function);
}
// (string & {}) is a workaround for offering string type completion without enforcing it. See https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
type NR_ENV_NAME_STRING = 'NR_NODE_ID'|'NR_NODE_NAME'|'NR_NODE_PATH'|'NR_GROUP_ID'|'NR_GROUP_NAME'|'NR_FLOW_ID'|'NR_FLOW_NAME'|'NR_SUBFLOW_ID'|'NR_SUBFLOW_NAME'|'NR_SUBFLOW_PATH' | (string & {})
declare class env {
/**
* Get an environment variable value
* Get an environment variable value defined in the OS, or in the global/flow/subflow/group environment variables.
*
* Predefined node-red variables...
* * `NR_NODE_ID` - the ID of the node
@@ -276,9 +278,16 @@ declare class env {
* * `NR_GROUP_NAME` - the Name of the containing group
* * `NR_FLOW_ID` - the ID of the flow the node is on
* * `NR_FLOW_NAME` - the Name of the flow the node is on
* @param name Name of the environment variable to get
* * `NR_SUBFLOW_ID` - the ID of the subflow the node is in
* * `NR_SUBFLOW_NAME` - the Name of the subflow the node is in
* * `NR_SUBFLOW_PATH` - the Path of the subflow the node is in
* @param name - The name of the environment variable
* @example
* ```const flowName = env.get("NR_FLOW_NAME");```
* ```const flowName = env.get("NR_FLOW_NAME") // get the name of the flow```
* @example
* ```const systemHomeDir = env.get("HOME") // get the user's home directory```
* @example
* ```const systemHomeDir = env.get("LABEL1") // get the value of a global/flow/subflow/group defined variable named "LABEL1"```
*/
static get(name:string) :any;
static get(name:NR_ENV_NAME_STRING) :any;
}

View File

@@ -1,10 +1,10 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* The `assert` module provides a set of assertion functions for verifying
* invariants.
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/assert.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/assert.js)
*/
declare module 'assert' {
/**
@@ -290,8 +290,8 @@ declare module 'assert' {
* > Stability: 3 - Legacy: Use {@link strictEqual} instead.
*
* Tests shallow, coercive equality between the `actual` and `expected` parameters
* using the [Abstract Equality Comparison](https://tc39.github.io/ecma262/#sec-abstract-equality-comparison) ( `==` ). `NaN` is special handled
* and treated as being identical in case both sides are `NaN`.
* using the [`==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality). `NaN` is specially handled
* and treated as being identical if both sides are `NaN`.
*
* ```js
* import assert from 'assert';
@@ -323,9 +323,8 @@ declare module 'assert' {
*
* > Stability: 3 - Legacy: Use {@link notStrictEqual} instead.
*
* Tests shallow, coercive inequality with the [Abstract Equality Comparison](https://tc39.github.io/ecma262/#sec-abstract-equality-comparison)(`!=` ). `NaN` is special handled and treated as
* being identical in case both
* sides are `NaN`.
* Tests shallow, coercive inequality with the [`!=` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality). `NaN` is
* specially handled and treated as being identical if both sides are `NaN`.
*
* ```js
* import assert from 'assert';
@@ -415,7 +414,7 @@ declare module 'assert' {
function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void;
/**
* Tests strict equality between the `actual` and `expected` parameters as
* determined by the [SameValue Comparison](https://tc39.github.io/ecma262/#sec-samevalue).
* determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
*
* ```js
* import assert from 'assert/strict';
@@ -453,7 +452,7 @@ declare module 'assert' {
function strictEqual<T>(actual: unknown, expected: T, message?: string | Error): asserts actual is T;
/**
* Tests strict inequality between the `actual` and `expected` parameters as
* determined by the [SameValue Comparison](https://tc39.github.io/ecma262/#sec-samevalue).
* determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
*
* ```js
* import assert from 'assert/strict';

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
declare module 'assert/strict' {
import { strict } from 'node:assert';

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* The `async_hooks` module provides an API to track asynchronous resources. It
@@ -9,7 +9,7 @@
* import async_hooks from 'async_hooks';
* ```
* @experimental
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/async_hooks.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/async_hooks.js)
*/
declare module 'async_hooks' {
/**
@@ -367,7 +367,7 @@ declare module 'async_hooks' {
*
* Each instance of `AsyncLocalStorage` maintains an independent storage context.
* Multiple instances can safely exist simultaneously without risk of interfering
* with each other data.
* with each other's data.
* @since v13.10.0, v12.17.0
*/
class AsyncLocalStorage<T> {
@@ -398,8 +398,9 @@ declare module 'async_hooks' {
getStore(): T | undefined;
/**
* Runs a function synchronously within a context and returns its
* return value. The store is not accessible outside of the callback function or
* the asynchronous operations created within the callback.
* return value. The store is not accessible outside of the callback function.
* The store is accessible to any asynchronous operations created within the
* callback.
*
* The optional `args` are passed to the callback function.
*
@@ -413,6 +414,9 @@ declare module 'async_hooks' {
* try {
* asyncLocalStorage.run(store, () => {
* asyncLocalStorage.getStore(); // Returns the store object
* setTimeout(() => {
* asyncLocalStorage.getStore(); // Returns the store object
* }, 200);
* throw new Error();
* });
* } catch (e) {

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* `Buffer` objects are used to represent a fixed-length sequence of bytes. Many
@@ -44,7 +44,7 @@
* // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
* const buf7 = Buffer.from('tést', 'latin1');
* ```
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/buffer.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/buffer.js)
*/
declare module 'buffer' {
import { BinaryLike } from 'node:crypto';
@@ -117,18 +117,17 @@ declare module 'buffer' {
/**
* A [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) encapsulates immutable, raw data that can be safely shared across
* multiple worker threads.
* @since v15.7.0
* @experimental
* @since v15.7.0, v14.18.0
*/
export class Blob {
/**
* The total size of the `Blob` in bytes.
* @since v15.7.0
* @since v15.7.0, v14.18.0
*/
readonly size: number;
/**
* The content-type of the `Blob`.
* @since v15.7.0
* @since v15.7.0, v14.18.0
*/
readonly type: string;
/**
@@ -143,13 +142,13 @@ declare module 'buffer' {
/**
* Returns a promise that fulfills with an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) containing a copy of
* the `Blob` data.
* @since v15.7.0
* @since v15.7.0, v14.18.0
*/
arrayBuffer(): Promise<ArrayBuffer>;
/**
* Creates and returns a new `Blob` containing a subset of this `Blob` objects
* data. The original `Blob` is not altered.
* @since v15.7.0
* @since v15.7.0, v14.18.0
* @param start The starting index.
* @param end The ending index.
* @param type The content-type for the new `Blob`
@@ -158,7 +157,7 @@ declare module 'buffer' {
/**
* Returns a promise that fulfills with the contents of the `Blob` decoded as a
* UTF-8 string.
* @since v15.7.0
* @since v15.7.0, v14.18.0
*/
text(): Promise<string>;
/**
@@ -169,6 +168,12 @@ declare module 'buffer' {
}
export import atob = globalThis.atob;
export import btoa = globalThis.btoa;
import { Blob as NodeBlob } from 'buffer';
// This conditional type will be the existing global Blob in a browser, or
// the copy below in a Node environment.
type __Blob = typeof globalThis extends { onmessage: any, Blob: infer T }
? T : NodeBlob;
global {
// Buffer class
type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
@@ -394,7 +399,7 @@ declare module 'buffer' {
* @since v0.11.13
* @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details.
*/
compare(buf1: Uint8Array, buf2: Uint8Array): number;
compare(buf1: Uint8Array, buf2: Uint8Array): -1 | 0 | 1;
/**
* Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
*
@@ -447,7 +452,7 @@ declare module 'buffer' {
* Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown.
*
* The underlying memory for `Buffer` instances created in this way is _not_
* _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
*
* ```js
* import { Buffer } from 'buffer';
@@ -485,7 +490,7 @@ declare module 'buffer' {
* if `size` is 0.
*
* The underlying memory for `Buffer` instances created in this way is _not_
* _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `buf.fill(0)` to initialize
* _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `buf.fill(0)` to initialize
* such `Buffer` instances with zeroes.
*
* When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
@@ -708,7 +713,7 @@ declare module 'buffer' {
* @param [sourceStart=0] The offset within `buf` at which to begin comparison.
* @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive).
*/
compare(target: Uint8Array, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number;
compare(target: Uint8Array, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): -1 | 0 | 1;
/**
* Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`.
*
@@ -767,8 +772,6 @@ declare module 'buffer' {
* Returns a new `Buffer` that references the same memory as the original, but
* offset and cropped by the `start` and `end` indices.
*
* This is the same behavior as `buf.subarray()`.
*
* This method is not compatible with the `Uint8Array.prototype.slice()`,
* which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`.
*
@@ -784,8 +787,17 @@ declare module 'buffer' {
*
* console.log(buf.toString());
* // Prints: buffer
*
* // With buf.slice(), the original buffer is modified.
* const notReallyCopiedBuf = buf.slice();
* notReallyCopiedBuf[0]++;
* console.log(notReallyCopiedBuf.toString());
* // Prints: cuffer
* console.log(buf.toString());
* // Also prints: cuffer (!)
* ```
* @since v0.3.0
* @deprecated Use `subarray` instead.
* @param [start=0] Where the new `Buffer` will start.
* @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
*/
@@ -1952,7 +1964,7 @@ declare module 'buffer' {
*
* * a string, `value` is interpreted according to the character encoding in`encoding`.
* * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety.
* To compare a partial `Buffer`, use `buf.slice()`.
* To compare a partial `Buffer`, use `buf.subarray`.
* * a number, `value` will be interpreted as an unsigned 8-bit integer
* value between `0` and `255`.
*
@@ -2208,7 +2220,7 @@ declare module 'buffer' {
* **binary data and predate the introduction of typed arrays in JavaScript.**
* **For code running using Node.js APIs, converting between base64-encoded strings**
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
* @since v15.13.0
* @since v15.13.0, v14.17.0
* @deprecated Use `Buffer.from(data, 'base64')` instead.
* @param data The Base64-encoded input string.
*/
@@ -2224,11 +2236,24 @@ declare module 'buffer' {
* **binary data and predate the introduction of typed arrays in JavaScript.**
* **For code running using Node.js APIs, converting between base64-encoded strings**
* **and binary data should be performed using `Buffer.from(str, 'base64')` and`buf.toString('base64')`.**
* @since v15.13.0
* @since v15.13.0, v14.17.0
* @deprecated Use `buf.toString('base64')` instead.
* @param data An ASCII (Latin1) string.
*/
function btoa(data: string): string;
interface Blob extends __Blob {}
/**
* `Blob` class is a global reference for `require('node:buffer').Blob`
* https://nodejs.org/api/buffer.html#class-blob
* @since v18.0.0
*/
var Blob: typeof globalThis extends {
onmessage: any;
Blob: infer T;
}
? T
: typeof NodeBlob;
}
}
declare module 'node:buffer' {

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* The `child_process` module provides the ability to spawn subprocesses in
@@ -31,8 +31,11 @@
* identical to the behavior of pipes in the shell. Use the `{ stdio: 'ignore' }`option if the output will not be consumed.
*
* The command lookup is performed using the `options.env.PATH` environment
* variable if it is in the `options` object. Otherwise, `process.env.PATH` is
* used.
* variable if `env` is in the `options` object. Otherwise, `process.env.PATH` is
* used. If `options.env` is set without `PATH`, lookup on Unix is performed
* on a default search path search of `/usr/bin:/bin` (see your operating system's
* manual for execvpe/execvp), on Windows the current processes environment
* variable `PATH` is used.
*
* On Windows, environment variables are case-insensitive. Node.js
* lexicographically sorts the `env` keys and uses the first one that
@@ -63,7 +66,7 @@
* For certain use cases, such as automating shell scripts, the `synchronous counterparts` may be more convenient. In many cases, however,
* the synchronous methods can have significant impact on performance due to
* stalling the event loop while spawned processes complete.
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/child_process.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/child_process.js)
*/
declare module 'child_process' {
import { ObjectEncodingOptions } from 'node:fs';
@@ -624,7 +627,7 @@ declare module 'child_process' {
}
interface CommonOptions extends ProcessEnvOptions {
/**
* @default false
* @default true
*/
windowsHide?: boolean | undefined;
/**

View File

@@ -1,10 +1,11 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* A single instance of Node.js runs in a single thread. To take advantage of
* multi-core systems, the user will sometimes want to launch a cluster of Node.js
* processes to handle the load.
* Clusters of Node.js processes can be used to run multiple instances of Node.js
* that can distribute workloads among their application threads. When process
* isolation is not needed, use the `worker_threads` module instead, which
* allows running multiple application threads within a single Node.js instance.
*
* The cluster module allows easy creation of child processes that all share
* server ports.
@@ -52,7 +53,7 @@
* ```
*
* On Windows, it is not yet possible to set up a named pipe server in a worker.
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/cluster.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/cluster.js)
*/
declare module 'cluster' {
import * as child from 'node:child_process';
@@ -102,9 +103,9 @@ declare module 'cluster' {
/**
* Send a message to a worker or primary, optionally with a handle.
*
* In the primary this sends a message to a specific worker. It is identical to `ChildProcess.send()`.
* In the primary, this sends a message to a specific worker. It is identical to `ChildProcess.send()`.
*
* In a worker this sends a message to the primary. It is identical to`process.send()`.
* In a worker, this sends a message to the primary. It is identical to`process.send()`.
*
* This example will echo back all messages from the primary:
*
@@ -126,19 +127,13 @@ declare module 'cluster' {
send(message: child.Serializable, sendHandle: child.SendHandle, callback?: (error: Error | null) => void): boolean;
send(message: child.Serializable, sendHandle: child.SendHandle, options?: child.MessageOptions, callback?: (error: Error | null) => void): boolean;
/**
* This function will kill the worker. In the primary, it does this
* by disconnecting the `worker.process`, and once disconnected, killing
* with `signal`. In the worker, it does it by disconnecting the channel,
* and then exiting with code `0`.
* This function will kill the worker. In the primary worker, it does this by
* disconnecting the `worker.process`, and once disconnected, killing with`signal`. In the worker, it does it by killing the process with `signal`.
*
* Because `kill()` attempts to gracefully disconnect the worker process, it is
* susceptible to waiting indefinitely for the disconnect to complete. For example,
* if the worker enters an infinite loop, a graceful disconnect will never occur.
* If the graceful disconnect behavior is not needed, use `worker.process.kill()`.
* The `kill()` function kills the worker process without waiting for a graceful
* disconnect, it has the same behavior as `worker.process.kill()`.
*
* Causes `.exitedAfterDisconnect` to be set.
*
* This method is aliased as `worker.destroy()` for backward compatibility.
* This method is aliased as `worker.destroy()` for backwards compatibility.
*
* In a worker, `process.kill()` exists, but it is not this function;
* it is `kill()`.
@@ -256,7 +251,8 @@ declare module 'cluster' {
*/
isDead(): boolean;
/**
* This property is `true` if the worker exited due to `.kill()` or`.disconnect()`. If the worker exited any other way, it is `false`. If the
* This property is `true` if the worker exited due to `.disconnect()`.
* If the worker exited any other way, it is `false`. If the
* worker has not exited, it is `undefined`.
*
* The boolean `worker.exitedAfterDisconnect` allows distinguishing between

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* The `console` module provides a simple debugging console that is similar to the
@@ -56,7 +56,7 @@
* myConsole.warn(`Danger ${name}! Danger!`);
* // Prints: Danger Will Robinson! Danger!, to err
* ```
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/console.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/console.js)
*/
declare module 'console' {
import console = require('node:console');

View File

@@ -1,5 +1,5 @@
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/Steve-Mcl/monaco-editor-esm-i18n */
/* NOTE: Do not edit directly! This file is generated using `npm run update-types` in https://github.com/node-red/nr-monaco-build */
/**
* The `crypto` module provides cryptographic functionality that includes a set of
@@ -16,12 +16,64 @@
* // Prints:
* // c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e
* ```
* @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/crypto.js)
* @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/crypto.js)
*/
declare module 'crypto' {
import * as stream from 'node:stream';
import { PeerCertificate } from 'node:tls';
interface Certificate {
/**
* SPKAC is a Certificate Signing Request mechanism originally implemented by
* Netscape and was specified formally as part of [HTML5's `keygen` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen).
*
* `<keygen>` is deprecated since [HTML 5.2](https://www.w3.org/TR/html52/changes.html#features-removed) and new projects
* should not use this element anymore.
*
* The `crypto` module provides the `Certificate` class for working with SPKAC
* data. The most common usage is handling output generated by the HTML5`<keygen>` element. Node.js uses [OpenSSL's SPKAC
* implementation](https://www.openssl.org/docs/man1.1.0/apps/openssl-spkac.html) internally.
* @since v0.11.8
*/
class Certificate {
/**
* ```js
* const { Certificate } = await import('crypto');
* const spkac = getSpkacSomehow();
* const challenge = Certificate.exportChallenge(spkac);
* console.log(challenge.toString('utf8'));
* // Prints: the challenge as a UTF8 string
* ```
* @since v9.0.0
* @param encoding The `encoding` of the `spkac` string.
* @return The challenge component of the `spkac` data structure, which includes a public key and a challenge.
*/
static exportChallenge(spkac: BinaryLike): Buffer;
/**
* ```js
* const { Certificate } = await import('crypto');
* const spkac = getSpkacSomehow();
* const publicKey = Certificate.exportPublicKey(spkac);
* console.log(publicKey);
* // Prints: the public key as <Buffer ...>
* ```
* @since v9.0.0
* @param encoding The `encoding` of the `spkac` string.
* @return The public key component of the `spkac` data structure, which includes a public key and a challenge.
*/
static exportPublicKey(spkac: BinaryLike, encoding?: string): Buffer;
/**
* ```js
* import { Buffer } from 'buffer';
* const { Certificate } = await import('crypto');
*
* const spkac = getSpkacSomehow();
* console.log(Certificate.verifySpkac(Buffer.from(spkac)));
* // Prints: true or false
* ```
* @since v9.0.0
* @param encoding The `encoding` of the `spkac` string.
* @return `true` if the given `spkac` data structure is valid, `false` otherwise.
*/
static verifySpkac(spkac: NodeJS.ArrayBufferView): boolean;
/**
* @deprecated
* @param spkac
@@ -45,31 +97,6 @@ declare module 'crypto' {
*/
verifySpkac(spkac: NodeJS.ArrayBufferView): boolean;
}
const Certificate: Certificate & {
/** @deprecated since v14.9.0 - Use static methods of `crypto.Certificate` instead. */
new (): Certificate;
/** @deprecated since v14.9.0 - Use static methods of `crypto.Certificate` instead. */
(): Certificate;
/**
* @param spkac
* @returns The challenge component of the `spkac` data structure,
* which includes a public key and a challenge.
*/
exportChallenge(spkac: BinaryLike): Buffer;
/**
* @param spkac
* @param encoding The encoding of the spkac string.
* @returns The public key component of the `spkac` data structure,
* which includes a public key and a challenge.
*/
exportPublicKey(spkac: BinaryLike, encoding?: string): Buffer;
/**
* @param spkac
* @returns `true` if the given `spkac` data structure is valid,
* `false` otherwise.
*/
verifySpkac(spkac: NodeJS.ArrayBufferView): boolean;
};
namespace constants {
// https://nodejs.org/dist/latest-v10.x/docs/api/crypto.html#crypto_crypto_constants
const OPENSSL_VERSION_NUMBER: number;
@@ -175,7 +202,7 @@ declare module 'crypto' {
*
* The `algorithm` is dependent on the available algorithms supported by the
* version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
* On recent releases of OpenSSL, `openssl list -digest-algorithms`(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
* On recent releases of OpenSSL, `openssl list -digest-algorithms` will
* display the available digest algorithms.
*
* Example: generating the sha256 sum of a file
@@ -215,7 +242,7 @@ declare module 'crypto' {
*
* The `algorithm` is dependent on the available algorithms supported by the
* version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
* On recent releases of OpenSSL, `openssl list -digest-algorithms`(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
* On recent releases of OpenSSL, `openssl list -digest-algorithms` will
* display the available digest algorithms.
*
* The `key` is the HMAC key used to generate the cryptographic HMAC hash. If it is
@@ -253,7 +280,7 @@ declare module 'crypto' {
*/
function createHmac(algorithm: string, key: BinaryLike | KeyObject, options?: stream.TransformOptions): Hmac;
// https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings
type BinaryToTextEncoding = 'base64' | 'base64url' | 'hex';
type BinaryToTextEncoding = 'base64' | 'base64url' | 'hex' | 'binary';
type CharacterEncoding = 'utf8' | 'utf-8' | 'utf16le' | 'latin1';
type LegacyCharacterEncoding = 'ascii' | 'binary' | 'ucs2' | 'ucs-2';
type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding;
@@ -662,12 +689,13 @@ declare module 'crypto' {
* Creates and returns a `Cipher` object that uses the given `algorithm` and`password`.
*
* The `options` argument controls stream behavior and is optional except when a
* cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the`authTagLength` option is required and specifies the length of the
* cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the`authTagLength` option is required and specifies the length of the
* authentication tag in bytes, see `CCM mode`. In GCM mode, the `authTagLength`option is not required but can be used to set the length of the authentication
* tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
* For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
*
* The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
* recent OpenSSL releases, `openssl list -cipher-algorithms`(`openssl list-cipher-algorithms` for older versions of OpenSSL) will
* recent OpenSSL releases, `openssl list -cipher-algorithms` will
* display the available cipher algorithms.
*
* The `password` is used to derive the cipher key and initialization vector (IV).
@@ -700,12 +728,13 @@ declare module 'crypto' {
* initialization vector (`iv`).
*
* The `options` argument controls stream behavior and is optional except when a
* cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the`authTagLength` option is required and specifies the length of the
* cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the`authTagLength` option is required and specifies the length of the
* authentication tag in bytes, see `CCM mode`. In GCM mode, the `authTagLength`option is not required but can be used to set the length of the authentication
* tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
* For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
*
* The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
* recent OpenSSL releases, `openssl list -cipher-algorithms`(`openssl list-cipher-algorithms` for older versions of OpenSSL) will
* recent OpenSSL releases, `openssl list -cipher-algorithms` will
* display the available cipher algorithms.
*
* The `key` is the raw key used by the `algorithm` and `iv` is an [initialization vector](https://en.wikipedia.org/wiki/Initialization_vector). Both arguments must be `'utf8'` encoded
@@ -925,8 +954,9 @@ declare module 'crypto' {
* Creates and returns a `Decipher` object that uses the given `algorithm` and`password` (key).
*
* The `options` argument controls stream behavior and is optional except when a
* cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the`authTagLength` option is required and specifies the length of the
* cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the`authTagLength` option is required and specifies the length of the
* authentication tag in bytes, see `CCM mode`.
* For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
*
* The implementation of `crypto.createDecipher()` derives keys using the OpenSSL
* function [`EVP_BytesToKey`](https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html) with the digest algorithm set to MD5, one
@@ -951,12 +981,13 @@ declare module 'crypto' {
* Creates and returns a `Decipher` object that uses the given `algorithm`, `key`and initialization vector (`iv`).
*
* The `options` argument controls stream behavior and is optional except when a
* cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the`authTagLength` option is required and specifies the length of the
* cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the`authTagLength` option is required and specifies the length of the
* authentication tag in bytes, see `CCM mode`. In GCM mode, the `authTagLength`option is not required but can be used to restrict accepted authentication tags
* to those with the specified length.
* For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
*
* The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
* recent OpenSSL releases, `openssl list -cipher-algorithms`(`openssl list-cipher-algorithms` for older versions of OpenSSL) will
* recent OpenSSL releases, `openssl list -cipher-algorithms` will
* display the available cipher algorithms.
*
* The `key` is the raw key used by the `algorithm` and `iv` is an [initialization vector](https://en.wikipedia.org/wiki/Initialization_vector). Both arguments must be `'utf8'` encoded
@@ -1162,13 +1193,11 @@ declare module 'crypto' {
format?: KeyFormat | undefined;
type?: 'pkcs1' | 'pkcs8' | 'sec1' | undefined;
passphrase?: string | Buffer | undefined;
encoding?: string | undefined;
}
interface PublicKeyInput {
key: string | Buffer;
format?: KeyFormat | undefined;
type?: 'pkcs1' | 'spki' | undefined;
encoding?: string | undefined;
}
/**
* Asynchronously generates a new random secret key of the given `length`. The`type` will determine which validations will be performed on the `length`.
@@ -1279,7 +1308,6 @@ declare module 'crypto' {
interface VerifyKeyObjectInput extends SigningOptions {
key: KeyObject;
}
interface VerifyJsonWebKeyInput extends JsonWebKeyInput, SigningOptions {}
type KeyLike = string | Buffer | KeyObject;
/**
* The `Sign` class is a utility for generating signatures. It can be used in one
@@ -1434,8 +1462,8 @@ declare module 'crypto' {
* be passed instead of a public key.
* @since v0.1.92
*/
verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: NodeJS.ArrayBufferView): boolean;
verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: string, signature_format?: BinaryToTextEncoding): boolean;
verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: NodeJS.ArrayBufferView): boolean;
verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: string, signature_format?: BinaryToTextEncoding): boolean;
}
/**
* Creates a `DiffieHellman` key exchange object using the supplied `prime` and an
@@ -1453,10 +1481,10 @@ declare module 'crypto' {
* @param [generator=2]
* @param generatorEncoding The `encoding` of the `generator` string.
*/
function createDiffieHellman(primeLength: number, generator?: number | NodeJS.ArrayBufferView): DiffieHellman;
function createDiffieHellman(prime: NodeJS.ArrayBufferView): DiffieHellman;
function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding): DiffieHellman;
function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator: number | NodeJS.ArrayBufferView): DiffieHellman;
function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman;
function createDiffieHellman(prime: ArrayBuffer | NodeJS.ArrayBufferView, generator?: number | ArrayBuffer | NodeJS.ArrayBufferView): DiffieHellman;
function createDiffieHellman(prime: ArrayBuffer | NodeJS.ArrayBufferView, generator: string, generatorEncoding: BinaryToTextEncoding): DiffieHellman;
function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator?: number | ArrayBuffer | NodeJS.ArrayBufferView): DiffieHellman;
function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator: string, generatorEncoding: BinaryToTextEncoding): DiffieHellman;
/**
* The `DiffieHellman` class is a utility for creating Diffie-Hellman key
@@ -1805,7 +1833,7 @@ declare module 'crypto' {
* Return a random integer `n` such that `min <= n < max`. This
* implementation avoids [modulo bias](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias).
*
* The range (`max - min`) must be less than `2**48`. `min` and `max` must
* The range (`max - min`) must be less than 2^48. `min` and `max` must
* be [safe integers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger).
*
* If the `callback` function is not provided, the random integer is
@@ -2281,11 +2309,11 @@ declare module 'crypto' {
* If `encoding` is specified, a string is returned; otherwise a `Buffer` is
* returned.
* @since v0.11.14
* @param encoding The `encoding` of the return value.
* @param [encoding] The `encoding` of the return value.
* @param [format='uncompressed']
* @return The EC Diffie-Hellman public key in the specified `encoding` and `format`.
*/
getPublicKey(): Buffer;
getPublicKey(encoding?: null, format?: ECDHKeyFormat): Buffer;
getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string;
/**
* Sets the EC Diffie-Hellman private key.
@@ -2316,7 +2344,8 @@ declare module 'crypto' {
* comparing HMAC digests or secret values like authentication cookies or [capability urls](https://www.w3.org/TR/capability-urls/).
*
* `a` and `b` must both be `Buffer`s, `TypedArray`s, or `DataView`s, and they
* must have the same byte length.
* must have the same byte length. An error is thrown if `a` and `b` have
* different byte lengths.
*
* If at least one of `a` and `b` is a `TypedArray` with more than one byte per
* entry, such as `Uint16Array`, the result will be computed using the platform
@@ -2331,7 +2360,7 @@ declare module 'crypto' {
/** @deprecated since v10.0.0 */
const DEFAULT_ENCODING: BufferEncoding;
type KeyType = 'rsa' | 'rsa-pss' | 'dsa' | 'ec' | 'ed25519' | 'ed448' | 'x25519' | 'x448';
type KeyFormat = 'pem' | 'der' | 'jwk';
type KeyFormat = 'pem' | 'der';
interface BasePrivateKeyEncodingOptions<T extends KeyFormat> {
format: T;
cipher?: string | undefined;
@@ -2942,16 +2971,11 @@ declare module 'crypto' {
* If the `callback` function is provided this function uses libuv's threadpool.
* @since v12.0.0
*/
function verify(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: NodeJS.ArrayBufferView): boolean;
function verify(
algorithm: string | null | undefined,
data: NodeJS.ArrayBufferView,
key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
signature: NodeJS.ArrayBufferView
): boolean;
function verify(
algorithm: string | null | undefined,
data: NodeJS.ArrayBufferView,
key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
signature: NodeJS.ArrayBufferView,
callback: (error: Error | null, result: boolean) => void
): void;
@@ -3100,34 +3124,33 @@ declare module 'crypto' {
*/
disableEntropyCache?: boolean | undefined;
}
type UUID = `${string}-${string}-${string}-${string}-${string}`;
/**
* Generates a random [RFC 4122](https://www.rfc-editor.org/rfc/rfc4122.txt) version 4 UUID. The UUID is generated using a
* cryptographic pseudorandom number generator.
* @since v15.6.0
* @since v15.6.0, v14.17.0
*/
function randomUUID(options?: RandomUUIDOptions): UUID;
function randomUUID(options?: RandomUUIDOptions): string;
interface X509CheckOptions {
/**
* @default 'always'
*/
subject?: 'always' | 'default' | 'never';
subject: 'always' | 'never';
/**
* @default true
*/
wildcards?: boolean;
wildcards: boolean;
/**
* @default true
*/
partialWildcards?: boolean;
partialWildcards: boolean;
/**
* @default false
*/
multiLabelWildcards?: boolean;
multiLabelWildcards: boolean;
/**
* @default false
*/
singleLabelSubdomains?: boolean;
singleLabelSubdomains: boolean;
}
/**
* Encapsulates an X509 certificate and provides read-only access to
@@ -3144,12 +3167,16 @@ declare module 'crypto' {
*/
class X509Certificate {
/**
* Will be \`true\` if this is a Certificate Authority (ca) certificate.
* Will be \`true\` if this is a Certificate Authority (CA) certificate.
* @since v15.6.0
*/
readonly ca: boolean;
/**
* The SHA-1 fingerprint of this certificate.
*
* Because SHA-1 is cryptographically broken and because the security of SHA-1 is
* significantly worse than that of algorithms that are commonly used to sign
* certificates, consider using `x509.fingerprint256` instead.
* @since v15.6.0
*/
readonly fingerprint: string;
@@ -3208,6 +3235,10 @@ declare module 'crypto' {
readonly raw: Buffer;
/**
* The serial number of this certificate.
*
* Serial numbers are assigned by certificate authorities and do not uniquely
* identify certificates. Consider using `x509.fingerprint256` as a unique
* identifier instead.
* @since v15.6.0
*/
readonly serialNumber: string;
@@ -3224,18 +3255,50 @@ declare module 'crypto' {
constructor(buffer: BinaryLike);
/**
* Checks whether the certificate matches the given email address.
*
* If the `'subject'` option is undefined or set to `'default'`, the certificate
* subject is only considered if the subject alternative name extension either does
* not exist or does not contain any email addresses.
*
* If the `'subject'` option is set to `'always'` and if the subject alternative
* name extension either does not exist or does not contain a matching email
* address, the certificate subject is considered.
*
* If the `'subject'` option is set to `'never'`, the certificate subject is never
* considered, even if the certificate contains no subject alternative names.
* @since v15.6.0
* @return Returns `email` if the certificate matches, `undefined` if it does not.
*/
checkEmail(email: string, options?: Pick<X509CheckOptions, 'subject'>): string | undefined;
/**
* Checks whether the certificate matches the given host name.
*
* If the certificate matches the given host name, the matching subject name is
* returned. The returned name might be an exact match (e.g., `foo.example.com`)
* or it might contain wildcards (e.g., `*.example.com`). Because host name
* comparisons are case-insensitive, the returned subject name might also differ
* from the given `name` in capitalization.
*
* If the `'subject'` option is undefined or set to `'default'`, the certificate
* subject is only considered if the subject alternative name extension either does
* not exist or does not contain any DNS names. This behavior is consistent with [RFC 2818](https://www.rfc-editor.org/rfc/rfc2818.txt) ("HTTP Over TLS").
*
* If the `'subject'` option is set to `'always'` and if the subject alternative
* name extension either does not exist or does not contain a matching DNS name,
* the certificate subject is considered.
*
* If the `'subject'` option is set to `'never'`, the certificate subject is never
* considered, even if the certificate contains no subject alternative names.
* @since v15.6.0
* @return Returns `name` if the certificate matches, `undefined` if it does not.
* @return Returns a subject name that matches `name`, or `undefined` if no subject name matches `name`.
*/
checkHost(name: string, options?: X509CheckOptions): string | undefined;
/**
* Checks whether the certificate matches the given IP address (IPv4 or IPv6).
*
* Only [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280.txt) `iPAddress` subject alternative names are considered, and they
* must match the given `ip` address exactly. Other subject alternative names as
* well as the subject field of the certificate are ignored.
* @since v15.6.0
* @return Returns `ip` if the certificate matches, `undefined` if it does not.
*/
@@ -3408,6 +3471,19 @@ declare module 'crypto' {
* @param [flags=crypto.constants.ENGINE_METHOD_ALL]
*/
function setEngine(engine: string, flags?: number): void;
/**
* A convenient alias for `crypto.webcrypto.getRandomValues()`.
* This implementation is not compliant with the Web Crypto spec,
* to write web-compatible code use `crypto.webcrypto.getRandomValues()` instead.
* @since v17.4.0
* @returns Returns `typedArray`.
*/
function getRandomValues<T extends webcrypto.BufferSource>(typedArray: T): T;
/**
* A convenient alias for `crypto.webcrypto.subtle`.
* @since v17.4.0
*/
const subtle: webcrypto.SubtleCrypto;
/**
* An implementation of the Web Crypto API standard.
*
@@ -3565,7 +3641,7 @@ declare module 'crypto' {
* The UUID is generated using a cryptographic pseudorandom number generator.
* @since v16.7.0
*/
randomUUID(): UUID;
randomUUID(): string;
CryptoKey: CryptoKeyConstructor;
}
// This constructor throws ILLEGAL_CONSTRUCTOR so it should not be newable.
@@ -3650,17 +3726,22 @@ declare module 'crypto' {
/**
* Using the method and parameters specified in `algorithm` and the keying material provided by `baseKey`,
* `subtle.deriveBits()` attempts to generate `length` bits.
* The Node.js implementation requires that `length` is a multiple of `8`.
* The Node.js implementation requires that when `length` is a number it must be multiple of `8`.
* When `length` is `null` the maximum number of bits for a given algorithm is generated. This is allowed
* for the `'ECDH'`, `'X25519'`, and `'X448'` algorithms.
* If successful, the returned promise will be resolved with an `<ArrayBuffer>` containing the generated data.
*
* The algorithms currently supported include:
*
* - `'ECDH'`
* - `'X25519'`
* - `'X448'`
* - `'HKDF'`
* - `'PBKDF2'`
* @since v15.0.0
*/
deriveBits(algorithm: AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number): Promise<ArrayBuffer>;
deriveBits(algorithm: EcdhKeyDeriveParams, baseKey: CryptoKey, length: number | null): Promise<ArrayBuffer>;
deriveBits(algorithm: AlgorithmIdentifier | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number): Promise<ArrayBuffer>;
/**
* Using the method and parameters specified in `algorithm`, and the keying material provided by `baseKey`,
* `subtle.deriveKey()` attempts to generate a new <CryptoKey>` based on the method and parameters in `derivedKeyAlgorithm`.
@@ -3671,6 +3752,8 @@ declare module 'crypto' {
* The algorithms currently supported include:
*
* - `'ECDH'`
* - `'X25519'`
* - `'X448'`
* - `'HKDF'`
* - `'PBKDF2'`
* @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}.
@@ -3739,7 +3822,11 @@ declare module 'crypto' {
* - `'RSA-PSS'`
* - `'RSA-OAEP'`
* - `'ECDSA'`
* - `'Ed25519'`
* - `'Ed448'`
* - `'ECDH'`
* - `'X25519'`
* - `'X448'`
* The `<CryptoKey>` (secret key) generating algorithms supported include:
*
* - `'HMAC'`
@@ -3787,6 +3874,8 @@ declare module 'crypto' {
* - `'RSASSA-PKCS1-v1_5'`
* - `'RSA-PSS'`
* - `'ECDSA'`
* - `'Ed25519'`
* - `'Ed448'`
* - `'HMAC'`
* @since v15.0.0
*/
@@ -3812,7 +3901,11 @@ declare module 'crypto' {
* - `'RSA-PSS'`
* - `'RSA-OAEP'`
* - `'ECDSA'`
* - `'Ed25519'`
* - `'Ed448'`
* - `'ECDH'`
* - `'X25519'`
* - `'X448'`
* - `'HMAC'`
* - `'AES-CTR'`
* - `'AES-CBC'`
@@ -3841,6 +3934,8 @@ declare module 'crypto' {
* - `'RSASSA-PKCS1-v1_5'`
* - `'RSA-PSS'`
* - `'ECDSA'`
* - `'Ed25519'`
* - `'Ed448'`
* - `'HMAC'`
* @since v15.0.0
*/

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