Compare commits
	
		
			399 Commits
		
	
	
		
			make-split
			...
			4.0.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 29b128e5e0 | ||
|  | c0f1581370 | ||
|  | 9420a52ec7 | ||
|  | c113b3de13 | ||
|  | 29058c163a | ||
|  | bb110ea230 | ||
|  | 785f220cd8 | ||
|  | 16570410a5 | ||
|  | 8085eda431 | ||
|  | 6503498f0a | ||
|  | da787a9993 | ||
|  | c873b57094 | ||
|  | 93974ccd92 | ||
|  | d7aa792f97 | ||
|  | 375fa9da64 | ||
|  | 28c41e17ad | ||
|  | da3ad40968 | ||
|  | 2464d9ad95 | ||
|  | 011b47a108 | ||
|  | ea747711c3 | ||
|  | 19a8fa09a8 | ||
|  | 1b5b3f7f88 | ||
|  | 2123514c76 | ||
|  | 379fbd7c7e | ||
|  | efdc1b1a1d | ||
|  | 52bbd82e18 | ||
|  | 20a9c051be | ||
|  | 830e475969 | ||
|  | f75e2f221c | ||
|  | 5a75440668 | ||
|  | 53d8b97fff | ||
|  | c85667cc13 | ||
|  | 1a8b37b4e3 | ||
|  | 40a2d90e08 | ||
|  | ee269caa4a | ||
|  | d820686e5a | ||
|  | aa2a585e00 | ||
|  | f9e6bccd46 | ||
|  | 3230654ecd | ||
|  | a5b53ee373 | ||
|  | ac420247ae | ||
|  | 61198bd7e3 | ||
|  | 9c511b6674 | ||
|  | 9d054543a7 | ||
|  | fc3ec2a0d7 | ||
|  | e7ef73222f | ||
|  | 6623e56a1e | ||
|  | 582eca1877 | ||
|  | 2783100f84 | ||
|  | cb0c484579 | ||
|  | a1bf270ba6 | ||
|  | be5694f149 | ||
|  | 4ff364e2c3 | ||
|  | 2fa6f35873 | ||
|  | 2a4fb7123d | ||
|  | 38a77d2b78 | ||
|  | dc239db256 | ||
|  | a622d19ba7 | ||
|  | 9842d9116c | ||
|  | 19ea8f8515 | ||
|  | 4ba3c937a8 | ||
|  | dbd3f0f85b | ||
|  | 48a2876c48 | ||
|  | 10398b05d8 | ||
|  | 3a91fc17fd | ||
|  | 02893d3e78 | ||
|  | 5124bc6bf8 | ||
|  | 3952a23ba3 | ||
|  | 1048b16f3c | ||
|  | bbbbb1b1e0 | ||
|  | 14b452c996 | ||
|  | bb91a08939 | ||
|  | bffa923f05 | ||
|  | 526b3fda91 | ||
|  | 27fc89ba33 | ||
|  | d70b7ea924 | ||
|  | 1d342a778d | ||
|  | 476016cbcc | ||
|  | bd2c020e84 | ||
|  | da7c7ede02 | ||
|  | 34ed9c5cd8 | ||
|  | 47dd08e74a | ||
|  | 6aae50294f | ||
|  | ec2f6ec46f | ||
|  | 36805b6872 | ||
|  | d78cb2fec7 | ||
|  | 51edb1ef19 | ||
|  | 2ad3af1864 | ||
|  | 525d7356fe | ||
|  | bdf37b0546 | ||
|  | 0d330332f1 | ||
|  | a5f290fd47 | ||
|  | 46f4144172 | ||
|  | 356e332f66 | ||
|  | d16060bdd9 | ||
|  | 3aeb4bd868 | ||
|  | b8bcab109a | ||
|  | b94045fd86 | ||
|  | 87992823d5 | ||
|  | 93b914d4b0 | ||
|  | 61b12f6bbe | ||
|  | d71b22412b | ||
|  | d115d6c241 | ||
|  | e561efb5c5 | ||
|  | e408c6b376 | ||
|  | cd95c63daf | ||
|  | bc6a4a20d2 | ||
|  | bf30c24e8e | ||
|  | 25205f7440 | ||
|  | 6317420d4a | ||
|  | 6c14ed0ef5 | ||
|  | f53bdc8257 | ||
|  | 6d41ecdae0 | ||
|  | 7bd61f2c96 | ||
|  | 76338d4d32 | ||
|  | 3f89bc2733 | ||
|  | b9add237b2 | ||
|  | 87a25df162 | ||
|  | 341f43610a | ||
|  | 67cdf3ef96 | ||
|  | cd98f448e9 | ||
|  | b66af1c8e2 | ||
|  | 06bad61569 | ||
|  | f58766eacf | ||
|  | 8e62b2a749 | ||
|  | 9b86874c2d | ||
|  | 805ed593fb | ||
|  | c604ac2207 | ||
|  | fac79fd068 | ||
|  | da97c5d558 | ||
|  | ae7b9fe62e | ||
|  | e52c2911da | ||
|  | ca37d1ec9d | ||
|  | 07a29ff779 | ||
|  | 1b4a8ebe83 | ||
|  | abf2eacf18 | ||
|  | f808f4e2e8 | ||
|  | ca33d6b799 | ||
|  | 3fd2d07c75 | ||
|  | 940740f15d | ||
|  | 51208fcd0c | ||
|  | 707152d82f | ||
|  | 5538f6dd8a | ||
|  | d601e2caa4 | ||
|  | 46fdf56c79 | ||
|  | b76d692a65 | ||
|  | 6600910163 | ||
|  | a6973bd7ed | ||
|  | d58127730f | ||
|  | 5494c167fc | ||
|  | c5ae0be7b1 | ||
|  | b653914ee0 | ||
|  | c107c5fc92 | ||
|  | 0980c03129 | ||
|  | f6c3fdc806 | ||
|  | 2c2628d816 | ||
|  | 56fe2801eb | ||
|  | 87b1ee9642 | ||
|  | e1c36d232b | ||
|  | 13ee8cec24 | ||
|  | a977b87cb3 | ||
|  | 14dfb9aef8 | ||
|  | d520cde57a | ||
|  | 70167d7d1d | ||
|  | 3389c8160b | ||
|  | c214710f8e | ||
|  | ac6a4945cb | ||
|  | fd1a001a23 | ||
|  | f3c561cd86 | ||
|  | 4e33e785fb | ||
|  | f55ee6e665 | ||
|  | edc5e88d5a | ||
|  | 47bf166a6e | ||
|  | cf26209790 | ||
|  | e55ebde170 | ||
|  | a745ddc164 | ||
|  | 18d0fa2259 | ||
|  | d706c9cb37 | ||
|  | 20d2450cac | ||
|  | 34345461f1 | ||
|  | aa372a1707 | ||
|  | 03648dc7e8 | ||
|  | 66a667fe58 | ||
|  | 1bb3a0eca5 | ||
|  | 0e0bba25c1 | ||
|  | af701d65ac | ||
|  | 08927dfb55 | ||
|  | b27483de9c | ||
|  | b02f69b77a | ||
|  | 598b0c84ab | ||
|  | 22cc8da088 | ||
|  | a70618cdef | ||
|  | faf142cf66 | ||
|  | 1a3cc06935 | ||
|  | a712a9363b | ||
|  | 67e716466f | ||
|  | 3fae03da98 | ||
|  | 361391ceb8 | ||
|  | bf0ca38350 | ||
|  | 437c28e2b8 | ||
|  | c05d18ada1 | ||
|  | cfb300ec06 | ||
|  | 236e668201 | ||
|  | 211d420fb2 | ||
|  | c9b902c2b4 | ||
|  | b8ca4665c1 | ||
|  | ac8b1e19b7 | ||
|  | 960af87fb0 | ||
|  | de7339ae97 | ||
|  | 595933d046 | ||
|  | 789426f80e | ||
|  | 0995af62b6 | ||
|  | c2e03a40b4 | ||
|  | 148e64c3da | ||
|  | c6289ebb2c | ||
|  | 5f4ece6813 | ||
|  | c990ec39d6 | ||
|  | 1fdc600ecd | ||
|  | c855050bcf | ||
|  | e354d2ce29 | ||
|  | d218af8619 | ||
|  | d938e5fb6b | ||
|  | 29ed5b2792 | ||
|  | e39216e65a | ||
|  | 7ac7f9b4c8 | ||
|  | 4709eb9d49 | ||
|  | c13b8266dd | ||
|  | bd58431603 | ||
|  | 3075b82792 | ||
|  | 240082481f | ||
|  | ea95552285 | ||
|  | 5358b06123 | ||
|  | 99391431da | ||
|  | d396f50a9a | ||
|  | affa8ea42b | ||
|  | d711b01fe5 | ||
|  | 6e7fa6f921 | ||
|  | 343cde75a2 | ||
|  | 2dc446e45b | ||
|  | 884b7fa16a | ||
|  | 173e065b68 | ||
|  | 9a3cb0b2b5 | ||
|  | 6beae5a806 | ||
|  | 66f4008bb8 | ||
|  | a0636632a1 | ||
|  | 5dfa47ab6c | ||
|  | e9efe493f9 | ||
|  | 3bd782e62a | ||
|  | 9b49cb2b50 | ||
|  | 963fe87f14 | ||
|  | ade4679e8c | ||
|  | 40060a470b | ||
|  | a6e8fbb54a | ||
|  | 410b938442 | ||
|  | ab7e9f94fa | ||
|  | 28e9ccd372 | ||
|  | 9a66d9addd | ||
|  | 8843bda477 | ||
|  | 3278303eec | ||
|  | f5fd6e3a36 | ||
|  | a173e8e70f | ||
|  | 19dcc3a683 | ||
|  | 20d067c1ea | ||
|  | 9526566799 | ||
|  | 0b9dd82c91 | ||
|  | 19213434f9 | ||
|  | 014691346a | ||
|  | 6738b95c29 | ||
|  | 6a8230ec1e | ||
|  | 5679d264b6 | ||
|  | b20c5f3a8d | ||
|  | 014f206e9c | ||
|  | 068b93befa | ||
|  | 65d8872cea | ||
|  | bffd1d61b2 | ||
|  | 4788b81220 | ||
|  | 9a07fc03c6 | ||
|  | 954f518030 | ||
|  | 9f8ff71757 | ||
|  | 06dd59dc81 | ||
|  | 37265cf4ef | ||
|  | 2531a5283a | ||
|  | 4cc1a5d846 | ||
|  | 8a63275989 | ||
|  | 2d3e5f4ce0 | ||
|  | 5135545c6c | ||
|  | fef93818c9 | ||
|  | 7fc64a84e8 | ||
|  | 02f7cdd5aa | ||
|  | d7dcceef60 | ||
|  | ae5e1570ae | ||
|  | 50baad9624 | ||
|  | fc0041bd91 | ||
|  | 283f7f5992 | ||
|  | 1cf7b95891 | ||
|  | 7bdb8db5ff | ||
|  | de27968e4e | ||
|  | d31abda28f | ||
|  | dcfb4c9a79 | ||
|  | bf065ee11d | ||
|  | 5e3cbadffc | ||
|  | 535ef82e48 | ||
|  | c368bfea3f | ||
|  | 4498e4100e | ||
|  | 4f1e4faede | ||
|  | 3ca045394a | ||
|  | 9a19a1113e | ||
|  | 1cd550022b | ||
|  | bad08bafd7 | ||
|  | f041a21f22 | ||
|  | 712d78ca39 | ||
|  | 93f2910bd2 | ||
|  | d0ef12c486 | ||
|  | 241fd09053 | ||
|  | 208dd2a457 | ||
|  | e34ee44b21 | ||
|  | d5f59307b7 | ||
|  | 64136cc565 | ||
|  | 3e2508c740 | ||
|  | 0853cd65b2 | ||
|  | 01802c817b | ||
|  | 7e10093bb8 | ||
|  | 179032cd4d | ||
|  | 6a6f0d04d6 | ||
|  | add4d9758c | ||
|  | a0d3ea62b2 | ||
|  | 54c17c3175 | ||
|  | 80e60538e2 | ||
|  | 84a76909e2 | ||
|  | 033405fdbc | ||
|  | 9444009a9b | ||
|  | 29e9def314 | ||
|  | 8832a1aa20 | ||
|  | 5beb6dbeee | ||
|  | 1261d26b23 | ||
|  | 0b9dd11fff | ||
|  | 08a607aa6a | ||
|  | e12efc320b | ||
|  | 3ded9de803 | ||
|  | d5b424910f | ||
|  | d94d13737f | ||
|  | b1fa4918e3 | ||
|  | 7447e88a50 | ||
|  | a193b79d3d | ||
|  | da380f7464 | ||
|  | 269cf02c0b | ||
|  | 837d17ab65 | ||
|  | eff31c4bdc | ||
|  | 6a8f653b73 | ||
|  | 0cdb36f73d | ||
|  | db249356e6 | ||
|  | d509c1a57c | ||
|  | fb50e2772a | ||
|  | 058c97138a | ||
|  | 828ae29aed | ||
|  | 6a0f45140c | ||
|  | 50a267528d | ||
|  | 220786be60 | ||
|  | fa78bb3d78 | ||
|  | 9a32ebd0c0 | ||
|  | 4643f5e8cc | ||
|  | 7de0984d6d | ||
|  | 635334f096 | ||
|  | f0d0990b5a | ||
|  | 43b3589451 | ||
|  | 016a19ba7c | ||
|  | 74efaa3c2d | ||
|  | aeb79bce2a | ||
|  | 0ab9b9a5fd | ||
|  | 56e58521bd | ||
|  | 28907082f1 | ||
|  | f83174c40a | ||
|  | ec062d008f | ||
|  | a587655a5a | ||
|  | 7b01457038 | ||
|  | 282d52cf0b | ||
|  | ba08cf0417 | ||
|  | febc769df5 | ||
|  | ea483218ea | ||
|  | c8f3ad8ac7 | ||
|  | 7916dc9c05 | ||
|  | 3123a5ee51 | ||
|  | 10ce681d46 | ||
|  | 3df3096bb4 | ||
|  | bb10d5bb94 | ||
|  | 1704ab7454 | ||
|  | 3275a76fb0 | ||
|  | 81937ddc45 | ||
|  | 3e6f0acf79 | ||
|  | 7f93d943d7 | ||
|  | c48a15c915 | ||
|  | b0136d03ea | ||
|  | 9fe73645ad | ||
|  | 8e1a21e682 | ||
|  | d84cdca43e | ||
|  | 1c6dcd373d | ||
|  | 4410ce1486 | ||
|  | f61971bc23 | ||
|  | 12543d2c2a | 
							
								
								
									
										2
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -16,7 +16,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         node-version: [18, 20] |         node-version: [18, 20, 22] | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v4 |     - uses: actions/checkout@v4 | ||||||
|     - name: Use Node.js ${{ matrix.node-version }} |     - name: Use Node.js ${{ matrix.node-version }} | ||||||
|   | |||||||
							
								
								
									
										679
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -1,620 +1,175 @@ | |||||||
| #### 3.1.5: Maintenance Release | #### 4.0.2: 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 |  | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|  |  | ||||||
|  - Highlight errors in config node sidebar (#4529) @knolleary |  - Use a more subtle border on the header (#4818) @bonanitech | ||||||
|  - Improve feedback in import dialog to show conflicted nodes (#4550) @knolleary |  - Improve the editor's French translations (#4824) @GogoVega | ||||||
|  - Modify node users info in config editor footer (#4528) @knolleary |  - Clean up orphaned editors (#4821) @Steve-Mcl | ||||||
|  - Handle modified-nodes deploy after replacing unknown config node (#4556) @knolleary |  - Fix node validation if the property is not required (#4812) @GogoVega | ||||||
|  - Handle undefined default export when importing module (#4539) @knolleary |  - Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @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 |  | ||||||
|  |  | ||||||
| Runtime | Runtime | ||||||
|  |  | ||||||
|  - Bump the github-actions group with 1 update (#4554) @app/dependabot |  - Allow auth cookie name to be customised (#4815) @knolleary | ||||||
|  - Clone objects types when getting env values (#4519) @knolleary |  - Guard against undefined sessions in multiplayer (#4816) @knolleary | ||||||
|  - Ensure global-config credential env vars are merged on deploy (#4526) @knolleary |  | ||||||
|  |  | ||||||
| Nodes | #### 4.0.1: Maintenance Release | ||||||
|  |  | ||||||
|  - 21-httprequest.js remove unused code, because of broken use of toLowercase (#4522) @gorenje |  | ||||||
|  |  | ||||||
| #### 3.1.3: Maintenance Release |  | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|  |  | ||||||
|  - Add missing en-us messages (#4475) @knolleary |  - Ensure subflow instance credential property values are extracted (#4802) @knolleary | ||||||
|  |  - Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega | ||||||
| #### 3.1.2: Maintenance Release |  - Fix the config node select value assignment (#4788) @GogoVega | ||||||
|  |  - Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi | ||||||
| Editor  |  - Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi | ||||||
|  |  | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| Runtime | Runtime | ||||||
|  |  | ||||||
|  - Bump the github-actions group with 2 updates (#4404) @app/dependabot |  - Ensure group nodes are properly exported in /flow api (#4803) @knolleary | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
|  Nodes |  Nodes | ||||||
|  |  | ||||||
|  - Fix JSONata in file nodes (#4246) @kazuhitoyokoi |  - Joins: make using msg.parts optional in join node (#4796) @dceejay | ||||||
|  - Fix timeout icon in function and link call nodes (#4253) @kazuhitoyokoi |  - HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl | ||||||
|  - Fix connection keep-alive in http request node (#4228) @knolleary |  - HTTP Request: Remove default user agent (#4791) @Steve-Mcl | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| ##### 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 | Editor | ||||||
|  |  | ||||||
|  - Select the item that is specified in a deep link URL (#4113) @Steve-Mcl |  - Add `httpStaticCors` (#4761) @knolleary | ||||||
|  - Update to Monaco 0.38.0 (#4189) @Steve-Mcl |  - Update dependencies (#4763) @knolleary | ||||||
|  - Place subflow outputs/inputs relative to current view (#4183) @knolleary |  - Sync master to dev (#4756) @knolleary | ||||||
|  - Enable RED.view.select to select group by id (#4184) @knolleary |  - Add tooltip and message validation to `typedInput` (#4747) @GogoVega | ||||||
|  - Combine existing env vars when merging groups (#4182) @knolleary |  - Replace bcrypt with @node-rs/bcrypt (#4744) @knolleary | ||||||
|  - Avoid creating empty global-config node if not needed (#4153) @knolleary |  - Export Nodes dialog refinement (#4746) @Steve-Mcl | ||||||
|  - Fix group selection when using lasso (#4108) @knolleary |  | ||||||
|  - Use editor path in generating localStorage keys (#4151) @mw75 | #### 4.0.0-beta.4: Beta Release | ||||||
|  - Ensure no node credentials are included when exporting to clipboard (#4112) @knolleary |  | ||||||
|  - Fix jsonata expression test ui (#4097) @knolleary | Editor | ||||||
|  - Fix search button in palette popover (#4096) @knolleary |  | ||||||
|  |  - 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 | Runtime | ||||||
|  |  | ||||||
|  - Allow options object on each httpStatic configuration (#4109) @kevinGodell |  - Ensure all CSS variables are in the output file (#3743) @bonanitech | ||||||
|  - Ensure non-zero exit codes for errors (#4181) @knolleary |  - Add httpAdminCookieOptions (#4718) @knolleary | ||||||
|  - Ensure external modules are installed synchronously (#4180) @knolleary |  - chore: migrate deprecated `util.isArray` (#4724) @Rotzbua | ||||||
|  - Update dependecies include got (#4155) @knolleary |  - Add --version cli args (#4707) @knolleary | ||||||
|  - Add Japanese translations for v3.1 beta.2 (#4158) @kazuhitoyokoi |  - feat(grunt): fail if files are missing (#4739) @Rotzbua | ||||||
|  - Ensure express server options are applied consistently (#4178) @knolleary |  - fix(node-red-pi): node-red not started by path (#4736) @Rotzbua | ||||||
|  - Remove version info from theme endpoint (#4179) @knolleary |  - fix(editor): remove trailing slash (#4735) @Rotzbua | ||||||
|  - Add Japanese translations for welcome tour of 3.1.0 beta.2 (#4145) @kazuhitoyokoi |  - fix: remove deprecated mqtt.js (#4733) @Rotzbua  | ||||||
|  - Added SHA-256 and SHA-512-256 digest authentication (#4100) @sroebert |  | ||||||
|  - Add "timers" types to known types (#4103) @Steve-Mcl |  | ||||||
|  |  | ||||||
| Nodes | Nodes | ||||||
|  |  | ||||||
|  - Allow Catch/Status nodes to be scoped to their group (#4185) @NetHans |  - Perform Proxy logic more like cURL (#4616) @Steve-Mcl | ||||||
|  - MQTT: Option to disable MQTT topic unsubscribe on disconnect (#4078) @flying7eleven |  | ||||||
|  |  | ||||||
|  | #### 4.0.0-beta.3: Beta Release | ||||||
| ##### 3.1.0-beta.2: Beta Release |  | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|  |  | ||||||
|  - NEW: Add change icon to tabs (#4068) @knolleary |  - Improve background-deploy notification handling (#4692) @knolleary | ||||||
|  - NEW: Complete overhaul of Group UX (#4079) @knolleary |  - Hide workspace tab on middle mouse click (#4657) @Steve-Mcl | ||||||
|  - NEW: Add link to node help in node edit dialog footer (#4065) @knolleary |  - multiplayer: Add user presence indicators (#4666) @knolleary | ||||||
|  - NEW: Added editor feature for connecting multiple nodes to single node (#4051) @sonntam |  - Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi | ||||||
|  - NEW: Increase workspace size to 8000x8000 (#4094) @knolleary |  - Add French translations for 4.0.0-beta.2 (#4681) @GogoVega | ||||||
|  - Ensure node buttons are redrawn when flow lock state is changed (#4091) @knolleary |  - Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi | ||||||
|  - Prevent loops being created with junction nodes (#4087) @knolleary |  - Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary | ||||||
|  - Prevent opening locked node's edit dialog (#4069) @knolleary |  - Add npm install timeout notification (#4662) @hardillb | ||||||
|  - Reverse direction of tab scroll to expected direction (#4064) @knolleary |  - Fix undo of subflow env property edits (#4667) @knolleary | ||||||
|  - Add cancel operation to editableList (#4077) @HiroyasuNishiyama |  - Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper | ||||||
|  - Apply Mermaid diagram for project settings UI (#4054) @kazuhitoyokoi |  - docs: Add closing paragraph tag (#4664) @ZJvandeWeg | ||||||
|  - Add tooltip for show/hide button on info sidebar (#4050) @kazuhitoyokoi |  - Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary | ||||||
|  - 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 |  | ||||||
|   |  | ||||||
|  |  | ||||||
| Runtime | Runtime | ||||||
|  |  | ||||||
|  - NEW: Generate stable ids for subflow instance internal nodes (#4093) @knolleary |  - Allow blank strings to be used for env var property substitutions (#4672) @knolleary | ||||||
|  - NEW: Change default file name to flows.json in project feature (#4073) @kazuhitoyokoi |  - Use rfdc for cloning pure JSON values (#4679) @knolleary | ||||||
|  - NEW: Deprecate synchronous access to jsonata (#4090) @knolleary |  - fix: remove outdated Node 11+ check (#4314) @Rotzbua | ||||||
|  - Add Node 18 to test matrix (#4084) @knolleary |  - feat(ci): add new nodejs v22 (#4694) @Rotzbua | ||||||
|  - Bump minimum nodejs version supported to match documented value (#4086) @knolleary |  - fix(node): increase required node >=18.5 (#4690) @Rotzbua | ||||||
|  - Update monaco docs link in settings.js (#4075) @Steve-Mcl |  - fix(dns): remove outdated node check (#4689) @Rotzbua | ||||||
|  - Remove duplicated messages in the message catalog (#4066) @kazuhitoyokoi |  - fix(polyfill): remove import module polyfill (#4688) @Rotzbua | ||||||
|  - Ensure errors in preDeliver callback are handled (#3911) @knolleary |  - Fix typo (#4686) @Rotzbua | ||||||
|  - Fix "EADDRINUSE" error (#4046) @bggbr |  | ||||||
|  |  | ||||||
| Nodes | Nodes | ||||||
|  |  | ||||||
|  - Link Call: Clear link-call timeouts when node is closed (#4085) @knolleary |  - Pass full error object in Function node and copy over cause property (#4685) @knolleary | ||||||
|  - Join: ensure inflight status is cleared when in auto mode (#4083) @knolleary |  - Replacing vm.createScript in favour of vm.Script (#4534) @patlux | ||||||
|  - File Out: Fix extra newline append for multipart file write (#3915) @dceejay |  | ||||||
|  - Add validators for complete and link call nodes (#4056) @kazuhitoyokoi |  | ||||||
|  |  | ||||||
| ##### 3.1.0-beta.1: Beta Release | #### 4.0.0-beta.2: Beta Release | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|  |  | ||||||
|  - NEW: Locking Flows (#3938) @knolleary |  - Introduce multiplayer feature (#4629) @knolleary | ||||||
|  - NEW: Improve UX around hiding flows via context menu (#3930) @knolleary |  - Separate the "add new config-node" option into a new (+) button (#4627) @GogoVega | ||||||
|  - NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama |  - Retain Palette categories collapsed and filter to localStorage (#4634) @knolleary | ||||||
|  - NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama |  - Ensure palette filter reapplies and clear up unknown categories (#4637) @knolleary | ||||||
|  - NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary |  - Add support for plugin (only) modules to the palette manager (#4620) @knolleary | ||||||
|  - NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama |  - Update monaco to latest and node types to 18 LTS (#4615) @Steve-Mcl | ||||||
|  |  | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| Runtime | Runtime | ||||||
|  |  | ||||||
|  - Force IPv4 name resolution to have priority (#4019) @dceejay |  - Fix handling of subflow config-node select type in sf module (#4643) @knolleary | ||||||
|  - Fix async loading of modules containing both nodes and plugins (#3999) @knolleary |  - Comms API updates (#4628) @knolleary | ||||||
|  - Use main branch as default in project feature (#4036) @kazuhitoyokoi |  - Add French translations for 4.0.0-beta.1 (#4621) @GogoVega | ||||||
|  - Rename package var to avoid strict mode error (#4020) @knolleary |  - Add Japanese translations for 4.0.0-beta.1 (#4612) @kazuhitoyokoi | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| Nodes | Nodes | ||||||
|  |  - Fix change node handling of replacing with boolean (#4639) @knolleary | ||||||
|  |  | ||||||
|  - Catch: fix typo in catch.html (#3965) @we11adam | #### 4.0.0-beta.1: Beta Release | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| Editor | Editor | ||||||
|  |  | ||||||
|  - Fix workspace chart bottom property (#3812) @bonanitech |  - Click on id in debug panel highlights node or flow (#4439) @ralphwetzel | ||||||
|  - Update german translation (#3802) @Dennis14e |  - Support config selection in a subflow env var (#4587) @Steve-Mcl | ||||||
|  - Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi |  - Add timestamp formatting options to TypedInput (#4468) @knolleary | ||||||
|  - Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary |  - Allow RED.view.select to select links (#4553) @lgrkvst | ||||||
|  - Hide scrollbars until they're needed (#3808) @bonanitech |  - Add auto-complete to flow/global/env typedInput types (#4480) @knolleary | ||||||
|  - Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary |  - Improve the appearance of the Node-RED primary header (#4598) @joepavitt | ||||||
|  - remove console.log (#3820) @Steve-Mcl |  | ||||||
|  |  | ||||||
| Runtime | 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 | Nodes | ||||||
|  |  | ||||||
|  - HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb |  - TCP node - when resetting, if no payload, stay disconnected @dceejay | ||||||
|  |  - HTML node: add option for collecting attributes and content (#4513) @gorenje | ||||||
| #### 3.0.1: Maintenance Release |  - 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 | ||||||
| Editor |  - 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 | ||||||
|  - Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl |  - Let debug node status msg length be settable via settings (#4402) @dceejay | ||||||
|  - Sys info (diagnostics report) amendments (#3793) @Steve-Mcl |  - Feat: Add ability to set headers for WebSocket client (#4436) @marcus-j-davies | ||||||
|  - 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 |  | ||||||
|  |  | ||||||
| #### Older Releases | #### Older Releases | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						| @@ -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/user.js", | ||||||
|                     "packages/node_modules/@node-red/editor-client/src/js/comms.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/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/bidi.js", | ||||||
|                     "packages/node_modules/@node-red/editor-client/src/js/text/format.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", |                     "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/touch/radialMenu.js", | ||||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.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" |                 dest: "packages/node_modules/@node-red/editor-client/public/red/red.js" | ||||||
|             }, |             }, | ||||||
|             vendor: { |             vendor: { | ||||||
|                 files: { |                 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", |                         src: [ | ||||||
|                         "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-3.5.1.min.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", |                             "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", |                             "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", | ||||||
|                         "node_modules/marked/marked.min.js", |                             "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", | ||||||
|                         "node_modules/dompurify/dist/purify.min.js", |                             "node_modules/marked/marked.min.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", |                             "node_modules/dompurify/dist/purify.min.js", | ||||||
|                         "node_modules/i18next/i18next.min.js", |                             "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", | ||||||
|                         "node_modules/i18next-http-backend/i18nextHttpBackend.min.js", |                             "node_modules/i18next/i18next.min.js", | ||||||
|                         "node_modules/jquery-i18next/jquery-i18next.min.js", |                             "node_modules/i18next-http-backend/i18nextHttpBackend.min.js", | ||||||
|                         "node_modules/jsonata/jsonata-es5.min.js", |                             "node_modules/jquery-i18next/jquery-i18next.min.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js", |                             "node_modules/jsonata/jsonata-es5.min.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js", |                             "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js", | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.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 |                         nonull: true, | ||||||
|                     //     //       bootstrap/FA/jquery |                         dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js" | ||||||
|                     // ], |                     }, | ||||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [ |                     // { | ||||||
|                         "node_modules/jsonata/jsonata-es5.min.js", |                     //     src: [ | ||||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js" |                     //         // TODO: resolve relative resource paths in | ||||||
|                     ], |                     //         //       bootstrap/FA/jquery | ||||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [ |                     //     ], | ||||||
|                         "node_modules/mermaid/dist/mermaid.min.js" |                     //     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: { |         uglify: { | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|     "name": "node-red", |     "name": "node-red", | ||||||
|     "version": "4.0.0-dev", |     "version": "4.0.2", | ||||||
|     "description": "Low-code programming for event-driven applications", |     "description": "Low-code programming for event-driven applications", | ||||||
|     "homepage": "https://nodered.org", |     "homepage": "https://nodered.org", | ||||||
|     "license": "Apache-2.0", |     "license": "Apache-2.0", | ||||||
| @@ -26,25 +26,25 @@ | |||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "acorn": "8.8.2", |         "acorn": "8.11.3", | ||||||
|         "acorn-walk": "8.2.0", |         "acorn-walk": "8.3.2", | ||||||
|         "ajv": "8.12.0", |         "ajv": "8.14.0", | ||||||
|         "async-mutex": "0.4.0", |         "async-mutex": "0.5.0", | ||||||
|         "basic-auth": "2.0.1", |         "basic-auth": "2.0.1", | ||||||
|         "bcryptjs": "2.4.3", |         "bcryptjs": "2.4.3", | ||||||
|         "body-parser": "1.20.2", |         "body-parser": "1.20.2", | ||||||
|         "cheerio": "1.0.0-rc.10", |         "cheerio": "1.0.0-rc.10", | ||||||
|         "clone": "2.1.2", |         "clone": "2.1.2", | ||||||
|         "content-type": "1.0.5", |         "content-type": "1.0.5", | ||||||
|         "cookie": "0.5.0", |         "cookie": "0.6.0", | ||||||
|         "cookie-parser": "1.4.6", |         "cookie-parser": "1.4.6", | ||||||
|         "cors": "2.8.5", |         "cors": "2.8.5", | ||||||
|         "cronosjs": "1.7.1", |         "cronosjs": "1.7.1", | ||||||
|         "denque": "2.1.0", |         "denque": "2.1.0", | ||||||
|         "express": "4.18.2", |         "express": "4.19.2", | ||||||
|         "express-session": "1.17.3", |         "express-session": "1.18.0", | ||||||
|         "form-data": "4.0.0", |         "form-data": "4.0.0", | ||||||
|         "fs-extra": "11.1.1", |         "fs-extra": "11.2.0", | ||||||
|         "got": "12.6.0", |         "got": "12.6.0", | ||||||
|         "hash-sum": "2.0.0", |         "hash-sum": "2.0.0", | ||||||
|         "hpagent": "1.2.0", |         "hpagent": "1.2.0", | ||||||
| @@ -54,35 +54,36 @@ | |||||||
|         "is-utf8": "0.2.1", |         "is-utf8": "0.2.1", | ||||||
|         "js-yaml": "4.1.0", |         "js-yaml": "4.1.0", | ||||||
|         "json-stringify-safe": "5.0.1", |         "json-stringify-safe": "5.0.1", | ||||||
|         "jsonata": "2.0.4", |         "jsonata": "2.0.5", | ||||||
|         "lodash.clonedeep": "^4.5.0", |         "lodash.clonedeep": "^4.5.0", | ||||||
|         "media-typer": "1.1.0", |         "media-typer": "1.1.0", | ||||||
|         "memorystore": "1.6.7", |         "memorystore": "1.6.7", | ||||||
|         "mime": "3.0.0", |         "mime": "3.0.0", | ||||||
|         "moment": "2.29.4", |         "moment": "2.30.1", | ||||||
|         "moment-timezone": "0.5.43", |         "moment-timezone": "0.5.45", | ||||||
|         "mqtt": "4.3.7", |         "mqtt": "5.7.0", | ||||||
|         "multer": "1.4.5-lts.1", |         "multer": "1.4.5-lts.1", | ||||||
|         "mustache": "4.2.0", |         "mustache": "4.2.0", | ||||||
|         "node-red-admin": "^3.1.2", |         "node-red-admin": "^4.0.0", | ||||||
|         "node-watch": "0.7.4", |         "node-watch": "0.7.4", | ||||||
|         "nopt": "5.0.0", |         "nopt": "5.0.0", | ||||||
|         "oauth2orize": "1.11.1", |         "oauth2orize": "1.12.0", | ||||||
|         "on-headers": "1.0.2", |         "on-headers": "1.0.2", | ||||||
|         "passport": "0.6.0", |         "passport": "0.7.0", | ||||||
|         "passport-http-bearer": "1.0.1", |         "passport-http-bearer": "1.0.1", | ||||||
|         "passport-oauth2-client-password": "0.1.2", |         "passport-oauth2-client-password": "0.1.2", | ||||||
|         "raw-body": "2.5.2", |         "raw-body": "2.5.2", | ||||||
|  |         "rfdc": "^1.3.1", | ||||||
|         "semver": "7.5.4", |         "semver": "7.5.4", | ||||||
|         "tar": "6.1.13", |         "tar": "7.2.0", | ||||||
|         "tough-cookie": "4.1.3", |         "tough-cookie": "4.1.4", | ||||||
|         "uglify-js": "3.17.4", |         "uglify-js": "3.17.4", | ||||||
|         "uuid": "9.0.0", |         "uuid": "9.0.1", | ||||||
|         "ws": "7.5.6", |         "ws": "7.5.10", | ||||||
|         "xml2js": "0.6.2" |         "xml2js": "0.6.2" | ||||||
|     }, |     }, | ||||||
|     "optionalDependencies": { |     "optionalDependencies": { | ||||||
|         "bcrypt": "5.1.1" |         "@node-rs/bcrypt": "1.10.4" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "dompurify": "2.4.1", |         "dompurify": "2.4.1", | ||||||
| @@ -112,7 +113,7 @@ | |||||||
|         "mermaid": "^10.4.0", |         "mermaid": "^10.4.0", | ||||||
|         "minami": "1.2.3", |         "minami": "1.2.3", | ||||||
|         "mocha": "9.2.2", |         "mocha": "9.2.2", | ||||||
|         "node-red-node-test-helper": "^0.3.2", |         "node-red-node-test-helper": "^0.3.3", | ||||||
|         "nodemon": "2.0.20", |         "nodemon": "2.0.20", | ||||||
|         "proxy": "^1.0.2", |         "proxy": "^1.0.2", | ||||||
|         "sass": "1.62.1", |         "sass": "1.62.1", | ||||||
| @@ -122,6 +123,6 @@ | |||||||
|         "supertest": "6.3.3" |         "supertest": "6.3.3" | ||||||
|     }, |     }, | ||||||
|     "engines": { |     "engines": { | ||||||
|         "node": ">=18" |         "node": ">=18.5" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -91,6 +91,7 @@ module.exports = { | |||||||
|         // Plugins |         // Plugins | ||||||
|         adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); |         adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); | ||||||
|         adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, 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); |         adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,5 +40,31 @@ module.exports = { | |||||||
|             console.log(err.stack); |             console.log(err.stack); | ||||||
|             apiUtils.rejectHandler(req,res,err); |             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); | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  **/ |  **/ | ||||||
| var apiUtils = require("../util"); |  | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
| var settings; | var settings; | ||||||
| var theme = require("../editor/theme"); | var theme = require("../editor/theme"); | ||||||
|   | |||||||
| @@ -160,20 +160,34 @@ function completeVerify(profile,done) { | |||||||
|  |  | ||||||
|  |  | ||||||
| function genericStrategy(adminApp,strategy) { | function genericStrategy(adminApp,strategy) { | ||||||
|     var crypto = require("crypto") |     const crypto = require("crypto") | ||||||
|     var session = require('express-session') |     const session = require('express-session') | ||||||
|     var MemoryStore = require('memorystore')(session) |     const MemoryStore = require('memorystore')(session) | ||||||
|  |  | ||||||
|     adminApp.use(session({ |     const sessionOptions = { | ||||||
|       // As the session is only used across the life-span of an auth |         // As the session is only used across the life-span of an auth | ||||||
|       // hand-shake, we can use a instance specific random string |         // hand-shake, we can use a instance specific random string | ||||||
|       secret: crypto.randomBytes(20).toString('hex'), |         secret: crypto.randomBytes(20).toString('hex'), | ||||||
|       resave: false, |         resave: false, | ||||||
|       saveUninitialized: false, |         saveUninitialized: false, | ||||||
|       store: new MemoryStore({ |         store: new MemoryStore({ | ||||||
|         checkPeriod: 86400000 // prune expired entries every 24h |           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 |     //TODO: all passport references ought to be in ./auth | ||||||
|     adminApp.use(passport.initialize()); |     adminApp.use(passport.initialize()); | ||||||
|     adminApp.use(passport.session()); |     adminApp.use(passport.session()); | ||||||
| @@ -205,11 +219,12 @@ function genericStrategy(adminApp,strategy) { | |||||||
|     passport.use(new strategy.strategy(options, verify)); |     passport.use(new strategy.strategy(options, verify)); | ||||||
|  |  | ||||||
|     adminApp.get('/auth/strategy', |     adminApp.get('/auth/strategy', | ||||||
|         passport.authenticate(strategy.name, {session:false, |         passport.authenticate(strategy.name, { | ||||||
|             failureMessage: true, |             session:false, | ||||||
|             failureRedirect: settings.httpAdminRoot |             failWithError: true, | ||||||
|  |             failureMessage: true | ||||||
|         }), |         }), | ||||||
|         completeGenerateStrategyAuth, |         completeGenericStrategyAuth, | ||||||
|         handleStrategyError |         handleStrategyError | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| @@ -221,14 +236,14 @@ function genericStrategy(adminApp,strategy) { | |||||||
|         passport.authenticate(strategy.name, { |         passport.authenticate(strategy.name, { | ||||||
|             session:false, |             session:false, | ||||||
|             failureMessage: true, |             failureMessage: true, | ||||||
|             failureRedirect: settings.httpAdminRoot |             failWithError: true | ||||||
|         }), |         }), | ||||||
|         completeGenerateStrategyAuth, |         completeGenericStrategyAuth, | ||||||
|         handleStrategyError |         handleStrategyError | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| } | } | ||||||
| function completeGenerateStrategyAuth(req,res) { | function completeGenericStrategyAuth(req,res) { | ||||||
|     var tokens = req.user.tokens; |     var tokens = req.user.tokens; | ||||||
|     delete req.user.tokens; |     delete req.user.tokens; | ||||||
|     // Successful authentication, redirect home. |     // Successful authentication, redirect home. | ||||||
| @@ -238,6 +253,8 @@ function handleStrategyError(err, req, res, next) { | |||||||
|     if (res.headersSent) { |     if (res.headersSent) { | ||||||
|         return next(err) |         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()}); |     log.audit({event: "auth.login.fail.oauth",error:err.toString()}); | ||||||
|     res.redirect(settings.httpAdminRoot + '?session_message='+err.toString()); |     res.redirect(settings.httpAdminRoot + '?session_message='+err.toString()); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ function hasPermission(userScope,permission) { | |||||||
|     } |     } | ||||||
|     var i; |     var i; | ||||||
|  |  | ||||||
|     if (util.isArray(permission)) { |     if (Array.isArray(permission)) { | ||||||
|         // Multiple permissions requested - check each one |         // Multiple permissions requested - check each one | ||||||
|         for (i=0;i<permission.length;i++) { |         for (i=0;i<permission.length;i++) { | ||||||
|             if (!hasPermission(userScope,permission[i])) { |             if (!hasPermission(userScope,permission[i])) { | ||||||
| @@ -36,7 +36,7 @@ function hasPermission(userScope,permission) { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (util.isArray(userScope)) { |     if (Array.isArray(userScope)) { | ||||||
|         if (userScope.length === 0) { |         if (userScope.length === 0) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy; | |||||||
| var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; | var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; | ||||||
|  |  | ||||||
| var passport = require("passport"); | var passport = require("passport"); | ||||||
| var crypto = require("crypto"); |  | ||||||
| var util = require("util"); | var util = require("util"); | ||||||
|  |  | ||||||
| var Tokens = require("./tokens"); | var Tokens = require("./tokens"); | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ | |||||||
| var util = require("util"); | var util = require("util"); | ||||||
| var clone = require("clone"); | var clone = require("clone"); | ||||||
| var bcrypt; | var bcrypt; | ||||||
| try { bcrypt = require('bcrypt'); } | try { bcrypt = require('@node-rs/bcrypt'); } | ||||||
| catch(e) { bcrypt = require('bcryptjs'); } | catch(e) { bcrypt = require('bcryptjs'); } | ||||||
| var users = {}; | var users = {}; | ||||||
| var defaultUser = null; | var defaultUser = null; | ||||||
| @@ -33,11 +33,11 @@ function authenticate() { | |||||||
|             if (args.length === 2) { |             if (args.length === 2) { | ||||||
|                 // Username/password authentication |                 // Username/password authentication | ||||||
|                 var password = args[1]; |                 var password = args[1]; | ||||||
|                 return new Promise(function(resolve,reject) { |                 return bcrypt.compare(password, user.password).then(res => { | ||||||
|                     bcrypt.compare(password, user.password, function(err, res) { |                     return res ? cleanUser(user) : null | ||||||
|                         resolve(res?cleanUser(user):null); |                 }).catch(err => { | ||||||
|                     }); |                     return null | ||||||
|                 }); |                 }) | ||||||
|             } else { |             } else { | ||||||
|                 // Try to extract common profile information |                 // Try to extract common profile information | ||||||
|                 if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) { |                 if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) { | ||||||
| @@ -74,7 +74,7 @@ function init(config) { | |||||||
|             } else { |             } else { | ||||||
|                 var us = config.users; |                 var us = config.users; | ||||||
|                 /* istanbul ignore else */ |                 /* istanbul ignore else */ | ||||||
|                 if (!util.isArray(us)) { |                 if (!Array.isArray(us)) { | ||||||
|                     us = [us]; |                     us = [us]; | ||||||
|                 } |                 } | ||||||
|                 for (var i=0;i<us.length;i++) { |                 for (var i=0;i<us.length;i++) { | ||||||
|   | |||||||
| @@ -77,6 +77,53 @@ function CommsConnection(ws, user) { | |||||||
|         log.trace("comms.close "+self.session); |         log.trace("comms.close "+self.session); | ||||||
|         removeActiveConnection(self); |         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) { |     ws.on('message', function(data,flags) { | ||||||
|         var msg = null; |         var msg = null; | ||||||
|         try { |         try { | ||||||
| @@ -86,68 +133,34 @@ function CommsConnection(ws, user) { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         if (!pendingAuth) { |         if (!pendingAuth) { | ||||||
|             if (msg.subscribe) { |             if (msg.auth) { | ||||||
|  |                 handleAuthPacket(msg) | ||||||
|  |             } else if (msg.subscribe) { | ||||||
|                 self.subscribe(msg.subscribe); |                 self.subscribe(msg.subscribe); | ||||||
|                 // handleRemoteSubscription(ws,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 { |         } 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) { |             if (msg.auth) { | ||||||
|                 Tokens.get(msg.auth).then(function(client) { |                 handleAuthPacket(msg) | ||||||
|                     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); |  | ||||||
|                             } |  | ||||||
|                         }); |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             } else { |             } else { | ||||||
|                 if (anonymousUser) { |                 if (anonymousUser) { | ||||||
|                     log.audit({event: "comms.auth",user:anonymousUser}); |                     log.audit({event: "comms.auth",user:anonymousUser}); | ||||||
|                     self.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 |                     //TODO: duplicated code - pull non-auth message handling out | ||||||
|                     if (msg.subscribe) { |                     if (msg.subscribe) { | ||||||
|                         self.subscribe(msg.subscribe); |                         self.subscribe(msg.subscribe); | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     log.audit({event: "comms.auth.fail"}); |                     log.audit({event: "comms.auth.fail"}); | ||||||
|                     completeConnection(null,null,false); |                     completeConnection(msg, null,null,false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -14,11 +14,9 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var express = require("express"); |  | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
|  |  | ||||||
| var comms = require("./comms"); | var comms = require("./comms"); | ||||||
| var library = require("./library"); |  | ||||||
| var info = require("./settings"); | var info = require("./settings"); | ||||||
|  |  | ||||||
| var auth = require("../auth"); | var auth = require("../auth"); | ||||||
|   | |||||||
| @@ -15,8 +15,6 @@ | |||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var apiUtils = require("../util"); | var apiUtils = require("../util"); | ||||||
| var fs = require('fs'); |  | ||||||
| var fspath = require('path'); |  | ||||||
|  |  | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,9 +13,6 @@ | |||||||
|  * See the License for the specific language governing permissions and |  * See the License for the specific language governing permissions and | ||||||
|  * limitations under the License. |  * 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 | var i18n = require("@node-red/util").i18n; // TODO: separate module | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ | |||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var apiUtils = require("../util"); | var apiUtils = require("../util"); | ||||||
| var express = require("express"); |  | ||||||
| var runtimeAPI; | var runtimeAPI; | ||||||
| var settings; | var settings; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  **/ |  **/ | ||||||
|  |  | ||||||
| var express = require("express"); |  | ||||||
| var util = require("util"); | var util = require("util"); | ||||||
| var path = require("path"); | var path = require("path"); | ||||||
| var fs = require("fs"); | var fs = require("fs"); | ||||||
| @@ -71,7 +70,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) { | |||||||
|     var result = []; |     var result = []; | ||||||
|     if (themeValue) { |     if (themeValue) { | ||||||
|         var array = themeValue; |         var array = themeValue; | ||||||
|         if (!util.isArray(array)) { |         if (!Array.isArray(array)) { | ||||||
|             array = [array]; |             array = [array]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -234,6 +233,10 @@ module.exports = { | |||||||
|             themeSettings.projects = theme.projects; |             themeSettings.projects = theme.projects; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (theme.hasOwnProperty("multiplayer")) { | ||||||
|  |             themeSettings.multiplayer = theme.multiplayer; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (theme.hasOwnProperty("keymap")) { |         if (theme.hasOwnProperty("keymap")) { | ||||||
|             themeSettings.keymap = theme.keymap; |             themeSettings.keymap = theme.keymap; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -99,7 +99,7 @@ module.exports = { | |||||||
|             // settings.instanceId is set asynchronously to the editor-api |             // settings.instanceId is set asynchronously to the editor-api | ||||||
|             // being initiaised. So we defer calculating the cacheBuster hash |             // being initiaised. So we defer calculating the cacheBuster hash | ||||||
|             // until the first load of the editor |             // 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; |         let sessionMessages; | ||||||
|   | |||||||
| @@ -24,11 +24,8 @@ | |||||||
|   * @namespace @node-red/editor-api |   * @namespace @node-red/editor-api | ||||||
|   */ |   */ | ||||||
|  |  | ||||||
| var express = require("express"); |  | ||||||
| var bodyParser = require("body-parser"); | var bodyParser = require("body-parser"); | ||||||
| var util = require('util'); |  | ||||||
| var passport = require('passport'); | var passport = require('passport'); | ||||||
| var cors = require('cors'); |  | ||||||
|  |  | ||||||
| var auth = require("./auth"); | var auth = require("./auth"); | ||||||
| var apiUtil = require("./util"); | var apiUtil = require("./util"); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|     "name": "@node-red/editor-api", |     "name": "@node-red/editor-api", | ||||||
|     "version": "4.0.0-dev", |     "version": "4.0.2", | ||||||
|     "license": "Apache-2.0", |     "license": "Apache-2.0", | ||||||
|     "main": "./lib/index.js", |     "main": "./lib/index.js", | ||||||
|     "repository": { |     "repository": { | ||||||
| @@ -16,25 +16,25 @@ | |||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "@node-red/util": "4.0.0-dev", |         "@node-red/util": "4.0.2", | ||||||
|         "@node-red/editor-client": "4.0.0-dev", |         "@node-red/editor-client": "4.0.2", | ||||||
|         "bcryptjs": "2.4.3", |         "bcryptjs": "2.4.3", | ||||||
|         "body-parser": "1.20.2", |         "body-parser": "1.20.2", | ||||||
|         "clone": "2.1.2", |         "clone": "2.1.2", | ||||||
|         "cors": "2.8.5", |         "cors": "2.8.5", | ||||||
|         "express-session": "1.17.3", |         "express-session": "1.18.0", | ||||||
|         "express": "4.18.2", |         "express": "4.19.2", | ||||||
|         "memorystore": "1.6.7", |         "memorystore": "1.6.7", | ||||||
|         "mime": "3.0.0", |         "mime": "3.0.0", | ||||||
|         "multer": "1.4.5-lts.1", |         "multer": "1.4.5-lts.1", | ||||||
|         "mustache": "4.2.0", |         "mustache": "4.2.0", | ||||||
|         "oauth2orize": "1.11.1", |         "oauth2orize": "1.12.0", | ||||||
|         "passport-http-bearer": "1.0.1", |         "passport-http-bearer": "1.0.1", | ||||||
|         "passport-oauth2-client-password": "0.1.2", |         "passport-oauth2-client-password": "0.1.2", | ||||||
|         "passport": "0.6.0", |         "passport": "0.7.0", | ||||||
|         "ws": "7.5.6" |         "ws": "7.5.10" | ||||||
|     }, |     }, | ||||||
|     "optionalDependencies": { |     "optionalDependencies": { | ||||||
|         "bcrypt": "5.1.0" |         "@node-rs/bcrypt": "1.10.4" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -590,6 +590,8 @@ | |||||||
|             }, |             }, | ||||||
|             "nodeCount": "__label__ Node", |             "nodeCount": "__label__ Node", | ||||||
|             "nodeCount_plural": "__label__ Nodes", |             "nodeCount_plural": "__label__ Nodes", | ||||||
|  |             "pluginCount": "__count__ Plugin", | ||||||
|  |             "pluginCount_plural": "__count__ Plugins",     | ||||||
|             "moduleCount": "__count__ Modul verfügbar", |             "moduleCount": "__count__ Modul verfügbar", | ||||||
|             "moduleCount_plural": "__count__ Module verfügbar", |             "moduleCount_plural": "__count__ Module verfügbar", | ||||||
|             "inuse": "In Gebrauch", |             "inuse": "In Gebrauch", | ||||||
|   | |||||||
| @@ -27,7 +27,8 @@ | |||||||
|             "lock": "Lock", |             "lock": "Lock", | ||||||
|             "unlock": "Unlock", |             "unlock": "Unlock", | ||||||
|             "locked": "Locked", |             "locked": "Locked", | ||||||
|             "unlocked": "Unlocked" |             "unlocked": "Unlocked", | ||||||
|  |             "format": "Format" | ||||||
|         }, |         }, | ||||||
|         "type": { |         "type": { | ||||||
|             "string": "string", |             "string": "string", | ||||||
| @@ -372,6 +373,7 @@ | |||||||
|             "deleted": "deleted", |             "deleted": "deleted", | ||||||
|             "flowDeleted": "flow deleted", |             "flowDeleted": "flow deleted", | ||||||
|             "flowAdded": "flow added", |             "flowAdded": "flow added", | ||||||
|  |             "moved": "moved", | ||||||
|             "movedTo": "moved to __id__", |             "movedTo": "moved to __id__", | ||||||
|             "movedFrom": "moved from __id__" |             "movedFrom": "moved from __id__" | ||||||
|         }, |         }, | ||||||
| @@ -614,6 +616,8 @@ | |||||||
|             }, |             }, | ||||||
|             "nodeCount": "__label__ node", |             "nodeCount": "__label__ node", | ||||||
|             "nodeCount_plural": "__label__ nodes", |             "nodeCount_plural": "__label__ nodes", | ||||||
|  |             "pluginCount": "__count__ plugin", | ||||||
|  |             "pluginCount_plural": "__count__ plugins", | ||||||
|             "moduleCount": "__count__ module available", |             "moduleCount": "__count__ module available", | ||||||
|             "moduleCount_plural": "__count__ modules available", |             "moduleCount_plural": "__count__ modules available", | ||||||
|             "inuse": "in use", |             "inuse": "in use", | ||||||
| @@ -641,6 +645,7 @@ | |||||||
|             "errors": { |             "errors": { | ||||||
|                 "catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>", |                 "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>", |                 "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>", |                 "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>", |                 "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>", |                 "enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>", | ||||||
| @@ -655,6 +660,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>", |                     "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" |                     "title": "Remove nodes" | ||||||
|                 }, |                 }, | ||||||
|  |                 "removePlugin": { | ||||||
|  |                     "body": "<p>Removed plugin __module__. Please reload the editor to clear left-overs.</p>" | ||||||
|  |                 }, | ||||||
|                 "update": { |                 "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>", |                     "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" |                     "title": "Update nodes" | ||||||
| @@ -666,7 +674,8 @@ | |||||||
|                     "review": "Open node information", |                     "review": "Open node information", | ||||||
|                     "install": "Install", |                     "install": "Install", | ||||||
|                     "remove": "Remove", |                     "remove": "Remove", | ||||||
|                     "update": "Update" |                     "update": "Update", | ||||||
|  |                     "understood": "Understood" | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -719,6 +728,7 @@ | |||||||
|             "nodeHelp": "Node Help", |             "nodeHelp": "Node Help", | ||||||
|             "showHelp": "Show help", |             "showHelp": "Show help", | ||||||
|             "showInOutline": "Show in outline", |             "showInOutline": "Show in outline", | ||||||
|  |             "hideTopics": "Hide topics", | ||||||
|             "showTopics": "Show topics", |             "showTopics": "Show topics", | ||||||
|             "noHelp": "No help topic selected", |             "noHelp": "No help topic selected", | ||||||
|             "changeLog": "Change Log" |             "changeLog": "Change Log" | ||||||
| @@ -914,6 +924,8 @@ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "typedInput": { |     "typedInput": { | ||||||
|  |         "selected": "__count__ selected", | ||||||
|  |         "selected_plural": "__count__ selected", | ||||||
|         "type": { |         "type": { | ||||||
|             "str": "string", |             "str": "string", | ||||||
|             "num": "number", |             "num": "number", | ||||||
| @@ -924,7 +936,8 @@ | |||||||
|             "date": "timestamp", |             "date": "timestamp", | ||||||
|             "jsonata": "expression", |             "jsonata": "expression", | ||||||
|             "env": "env variable", |             "env": "env variable", | ||||||
|             "cred": "credential" |             "cred": "credential", | ||||||
|  |             "conf-types": "config node" | ||||||
|         }, |         }, | ||||||
|         "date": { |         "date": { | ||||||
|             "format": { |             "format": { | ||||||
|   | |||||||
| @@ -27,7 +27,8 @@ | |||||||
|       "lock": "Verrouiller", |       "lock": "Verrouiller", | ||||||
|       "unlock": "Déverrouiller", |       "unlock": "Déverrouiller", | ||||||
|       "locked": "Verrouillé", |       "locked": "Verrouillé", | ||||||
|       "unlocked": "Déverrouillé" |       "unlocked": "Déverrouillé", | ||||||
|  |       "format": "Format" | ||||||
|     }, |     }, | ||||||
|     "type": { |     "type": { | ||||||
|       "string": "chaîne de caractères", |       "string": "chaîne de caractères", | ||||||
| @@ -54,10 +55,10 @@ | |||||||
|   "workspace": { |   "workspace": { | ||||||
|     "defaultName": "Flux __number__", |     "defaultName": "Flux __number__", | ||||||
|     "editFlow": "Modifier le flux : __name__", |     "editFlow": "Modifier le flux : __name__", | ||||||
|     "confirmDelete": "Confirmation de la suppression", |     "confirmDelete": "Confirmer la suppression", | ||||||
|     "delete": "Etes-vous sûr de vouloir supprimer '__label__'?", |     "delete": "Êtes-vous sûr de vouloir supprimer '__label__' ?", | ||||||
|     "dropFlowHere": "Déposer le flux ici", |     "dropFlowHere": "Lâchez le flux ici", | ||||||
|     "dropImageHere": "Déposer l'image ici", |     "dropImageHere": "Lâchez l'image ici", | ||||||
|     "addFlow": "Ajouter un flux", |     "addFlow": "Ajouter un flux", | ||||||
|     "addFlowToRight": "Ajouter un flux à droite", |     "addFlowToRight": "Ajouter un flux à droite", | ||||||
|     "closeFlow": "Fermer le flux", |     "closeFlow": "Fermer le flux", | ||||||
| @@ -74,7 +75,7 @@ | |||||||
|     "enabled": "Activé", |     "enabled": "Activé", | ||||||
|     "disabled": "Désactivé", |     "disabled": "Désactivé", | ||||||
|     "info": "Description", |     "info": "Description", | ||||||
|     "selectNodes": "Cliquer sur les noeuds pour sélectionner", |     "selectNodes": "Cliquer pour sélectionner", | ||||||
|     "enableFlow": "Activer le flux", |     "enableFlow": "Activer le flux", | ||||||
|     "disableFlow": "Désactiver le flux", |     "disableFlow": "Désactiver le flux", | ||||||
|     "lockFlow": "Verrouiller le flux", |     "lockFlow": "Verrouiller le flux", | ||||||
| @@ -98,7 +99,7 @@ | |||||||
|         "rtl": "De droite à gauche", |         "rtl": "De droite à gauche", | ||||||
|         "auto": "Contextuel", |         "auto": "Contextuel", | ||||||
|         "language": "Langue", |         "language": "Langue", | ||||||
|         "browserDefault": "Navigateur par défaut" |         "browserDefault": "Par défaut du Navigateur" | ||||||
|       }, |       }, | ||||||
|       "sidebar": { |       "sidebar": { | ||||||
|         "show": "Afficher la barre latérale" |         "show": "Afficher la barre latérale" | ||||||
| @@ -134,7 +135,7 @@ | |||||||
|       "disableSelectedNodes": "Désactiver les noeuds sélectionnés", |       "disableSelectedNodes": "Désactiver les noeuds sélectionnés", | ||||||
|       "showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés", |       "showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés", | ||||||
|       "hideSelectedNodeLabels": "Masquer 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", |       "help": "Site web de Node-RED", | ||||||
|       "projects": "Projets", |       "projects": "Projets", | ||||||
|       "projects-new": "Nouveau projet", |       "projects-new": "Nouveau projet", | ||||||
| @@ -143,7 +144,7 @@ | |||||||
|       "showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés", |       "showNodeLabelDefault": "Afficher l'étiquette des noeuds nouvellement ajoutés", | ||||||
|       "codeEditor": "Éditeur de code", |       "codeEditor": "Éditeur de code", | ||||||
|       "groups": "Groupes", |       "groups": "Groupes", | ||||||
|       "groupSelection": "Grouper cette sélection", |       "groupSelection": "Grouper la sélection", | ||||||
|       "ungroupSelection": "Dégrouper la sélection", |       "ungroupSelection": "Dégrouper la sélection", | ||||||
|       "groupMergeSelection": "Fusionner la sélection", |       "groupMergeSelection": "Fusionner la sélection", | ||||||
|       "groupRemoveSelection": "Supprimer du groupe", |       "groupRemoveSelection": "Supprimer du groupe", | ||||||
| @@ -155,7 +156,7 @@ | |||||||
|       "alignMiddle": "Aligner au milieu", |       "alignMiddle": "Aligner au milieu", | ||||||
|       "alignBottom": "Aligner en bas", |       "alignBottom": "Aligner en bas", | ||||||
|       "distributeHorizontally": "Répartir horizontalement", |       "distributeHorizontally": "Répartir horizontalement", | ||||||
|       "distributeVertically": "Distribuer verticalement", |       "distributeVertically": "Répartir verticalement", | ||||||
|       "moveToBack": "Déplacer vers l'arrière", |       "moveToBack": "Déplacer vers l'arrière", | ||||||
|       "moveToFront": "Déplacer vers l'avant", |       "moveToFront": "Déplacer vers l'avant", | ||||||
|       "moveBackwards": "Reculer", |       "moveBackwards": "Reculer", | ||||||
| @@ -163,21 +164,21 @@ | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "actions": { |   "actions": { | ||||||
|     "toggle-navigator": "Basculer de navigateur", |     "toggle-navigator": "Basculer l'affichage du navigateur", | ||||||
|     "zoom-out": "Dézoomer", |     "zoom-out": "Réduire", | ||||||
|     "zoom-reset": "Réinitialiser le zoom", |     "zoom-reset": "Réinitialiser", | ||||||
|     "zoom-in": "Agrandir", |     "zoom-in": "Agrandir", | ||||||
|     "search-flows": "Rechercher le flux", |     "search-flows": "Rechercher le flux", | ||||||
|     "search-prev": "Précédent", |     "search-prev": "Précédent", | ||||||
|     "search-next": "Suivant", |     "search-next": "Suivant", | ||||||
|     "search-counter": "\"__term__\" __result__ de __count__" |     "search-counter": "\"__term__\" __result__ sur __count__" | ||||||
|   }, |   }, | ||||||
|   "user": { |   "user": { | ||||||
|     "loggedInAs": "Connecté en tant que __name__", |     "loggedInAs": "Connecté en tant que __name__", | ||||||
|     "username": "Nom d'utilisateur", |     "username": "Nom d'utilisateur", | ||||||
|     "password": "Mot de passe", |     "password": "Mot de passe", | ||||||
|     "login": "Connexion", |     "login": "Se connecter", | ||||||
|     "loginFailed": "Échec de la connexion", |     "loginFailed": "Échec de connexion", | ||||||
|     "notAuthorized": "Pas autorisé", |     "notAuthorized": "Pas autorisé", | ||||||
|     "errors": { |     "errors": { | ||||||
|       "settings": "Vous devez être connecté pour accéder aux paramètres", |       "settings": "Vous devez être connecté pour accéder aux paramètres", | ||||||
| @@ -193,16 +194,16 @@ | |||||||
|     "warning": "<strong>Attention</strong> : __message__", |     "warning": "<strong>Attention</strong> : __message__", | ||||||
|     "warnings": { |     "warnings": { | ||||||
|       "undeployedChanges": "Le noeud a des modifications non déployées", |       "undeployedChanges": "Le noeud a des modifications non déployées", | ||||||
|       "nodeActionDisabled": "Actions de noeud désactivées", |       "nodeActionDisabled": "Les actions du noeud sont désactivées", | ||||||
|       "nodeActionDisabledSubflow": "Actions de noeud désactivées dans le sous-flux", |       "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-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>", |       "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", |       "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": "<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_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_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_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>", |       "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>" |       "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": { |     "project": { | ||||||
|       "change-branch": "Changer pour une branche locale '__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é", |       "loaded": "Projet '__project__' chargé", | ||||||
|       "updated": "Projet '__project__' mis à jour", |       "updated": "Projet '__project__' mis à jour", | ||||||
|       "pull": "Projet '__project__' rechargé", |       "pull": "Projet '__project__' rechargé", | ||||||
| @@ -352,7 +353,7 @@ | |||||||
|       "backgroundUpdate": "Les flux sur le serveur ont été mis à jour.", |       "backgroundUpdate": "Les flux sur le serveur ont été mis à jour.", | ||||||
|       "conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement", |       "conflictChecking": "Vérifier si les modifications peuvent être fusionnées automatiquement", | ||||||
|       "conflictAutoMerge": "Les modifications n'incluent aucun conflit et 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" |       "plusNMore": "+ __count__ en plus" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| @@ -372,16 +373,17 @@ | |||||||
|       "deleted": "supprimé", |       "deleted": "supprimé", | ||||||
|       "flowDeleted": "flux supprimé", |       "flowDeleted": "flux supprimé", | ||||||
|       "flowAdded": "flux ajouté", |       "flowAdded": "flux ajouté", | ||||||
|  |       "moved": "déplacé", | ||||||
|       "movedTo": "déplacé vers __id__", |       "movedTo": "déplacé vers __id__", | ||||||
|       "movedFrom": "déplacé depuis __id__" |       "movedFrom": "déplacé depuis __id__" | ||||||
|     }, |     }, | ||||||
|     "nodeCount": "__count__ noeud", |     "nodeCount": "__count__ noeud", | ||||||
|     "nodeCount_plural": "__count__ noeuds", |     "nodeCount_plural": "__count__ noeuds", | ||||||
|     "local": "Changements locaux", |     "local": "Changements locaux", | ||||||
|     "remote": "Modifications à distance", |     "remote": "Changements distants", | ||||||
|     "reviewChanges": "Examiner les modifications", |     "reviewChanges": "Examiner les modifications", | ||||||
|     "noBinaryFileShowed": "Impossible d'afficher le contenu du fichier binaire", |     "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", |     "compareChanges": "Comparer les modifications", | ||||||
|     "saveConflict": "Enregistrer la résolution des conflits", |     "saveConflict": "Enregistrer la résolution des conflits", | ||||||
|     "conflictHeader": "<span>__resolved__</span> sur <span>__unresolved__</span> conflit(s) résolu(s)", |     "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", |     "edit": "Modifier le modèle du sous-flux", | ||||||
|     "subflowInstances": "Il existe __count__ instance de ce modèle de 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", |     "subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux", | ||||||
|     "editSubflowProperties": "modifier les propriétés", |     "editSubflowProperties": "Modifier les propriétés", | ||||||
|     "input": "Entrées:", |     "input": "Entrées :", | ||||||
|     "output": "Sorties:", |     "output": "Sorties :", | ||||||
|     "status": "Statut du noeud", |     "status": "Statut du noeud", | ||||||
|     "deleteSubflow": "Supprimer le sous-flux", |     "deleteSubflow": "Supprimer le sous-flux", | ||||||
|     "confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?", |     "confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?", | ||||||
| @@ -411,7 +413,7 @@ | |||||||
|     "version": "Version", |     "version": "Version", | ||||||
|     "versionPlaceholder": "x.y.z", |     "versionPlaceholder": "x.y.z", | ||||||
|     "keys": "Mots clés", |     "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", |     "author": "Auteur", | ||||||
|     "authorPlaceholder": "Votre nom <email@exemple.com>", |     "authorPlaceholder": "Votre nom <email@exemple.com>", | ||||||
|     "desc": "Description", |     "desc": "Description", | ||||||
| @@ -468,7 +470,7 @@ | |||||||
|       "select": "sélection", |       "select": "sélection", | ||||||
|       "checkbox": "case à cocher", |       "checkbox": "case à cocher", | ||||||
|       "spinner": "valeurs à défiler", |       "spinner": "valeurs à défiler", | ||||||
|       "none": "aucune", |       "none": "aucun", | ||||||
|       "hidden": "masquer la propriété" |       "hidden": "masquer la propriété" | ||||||
|     }, |     }, | ||||||
|     "types": { |     "types": { | ||||||
| @@ -496,7 +498,7 @@ | |||||||
|       "max": "Maximum" |       "max": "Maximum" | ||||||
|     }, |     }, | ||||||
|     "errors": { |     "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 :", |       "invalidProperties": "Propriétés invalides :", | ||||||
|       "credentialLoadFailed": "Échec du chargement des identifiants du noeud" |       "credentialLoadFailed": "Échec du chargement des identifiants du noeud" | ||||||
|     } |     } | ||||||
| @@ -510,7 +512,7 @@ | |||||||
|     "unassigned": "Non attribué", |     "unassigned": "Non attribué", | ||||||
|     "global": "Global", |     "global": "Global", | ||||||
|     "workspace": "Espace de travail", |     "workspace": "Espace de travail", | ||||||
|     "editor": "Boîte de dialogue d'édition", |     "editor": "Boîte d'édition", | ||||||
|     "selectAll": "Tout sélectionner", |     "selectAll": "Tout sélectionner", | ||||||
|     "selectNone": "Ne rien sélectionner", |     "selectNone": "Ne rien sélectionner", | ||||||
|     "selectAllConnected": "Sélectionner tous les éléments connectés", |     "selectAllConnected": "Sélectionner tous les éléments connectés", | ||||||
| @@ -541,7 +543,7 @@ | |||||||
|     "openLibrary": "Ouvrir la bibliothèque...", |     "openLibrary": "Ouvrir la bibliothèque...", | ||||||
|     "saveToLibrary": "Enregistrer dans la bibliothèque...", |     "saveToLibrary": "Enregistrer dans la bibliothèque...", | ||||||
|     "typeLibrary": "__type__ bibliothèque", |     "typeLibrary": "__type__ bibliothèque", | ||||||
|     "unnamedType": "Innomé __type__", |     "unnamedType": "Sans nom __type__", | ||||||
|     "exportedToLibrary": "Noeuds exportés vers la bibliothèque", |     "exportedToLibrary": "Noeuds exportés vers la bibliothèque", | ||||||
|     "dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?", |     "dialogSaveOverwrite": "Une __libraryType__ appelée __libraryName__ existe déjà. Écraser ?", | ||||||
|     "invalidFilename": "Nom de fichier non valide", |     "invalidFilename": "Nom de fichier non valide", | ||||||
| @@ -558,7 +560,7 @@ | |||||||
|     "noInfo": "Pas d'information disponible", |     "noInfo": "Pas d'information disponible", | ||||||
|     "filter": "Rechercher le noeud", |     "filter": "Rechercher le noeud", | ||||||
|     "search": "Rechercher les modules", |     "search": "Rechercher les modules", | ||||||
|     "addCategory": "Ajouter un nouveau...", |     "addCategory": "Ajouter une nouvelle...", | ||||||
|     "label": { |     "label": { | ||||||
|       "subflows": "Sous-flux", |       "subflows": "Sous-flux", | ||||||
|       "network": "Réseau", |       "network": "Réseau", | ||||||
| @@ -614,6 +616,8 @@ | |||||||
|       }, |       }, | ||||||
|       "nodeCount": "__label__ noeud", |       "nodeCount": "__label__ noeud", | ||||||
|       "nodeCount_plural": "__label__ noeuds", |       "nodeCount_plural": "__label__ noeuds", | ||||||
|  |       "pluginCount": "__count__ plugin", | ||||||
|  |       "pluginCount_plural": "__count__ plugins", | ||||||
|       "moduleCount": "__count__ module disponible", |       "moduleCount": "__count__ module disponible", | ||||||
|       "moduleCount_plural": "__count__ modules disponibles", |       "moduleCount_plural": "__count__ modules disponibles", | ||||||
|       "inuse": "En cours d'utilisation", |       "inuse": "En cours d'utilisation", | ||||||
| @@ -636,11 +640,12 @@ | |||||||
|       "sortAZ": "A-Z", |       "sortAZ": "A-Z", | ||||||
|       "sortRecent": "Récent", |       "sortRecent": "Récent", | ||||||
|       "more": "+ __count__ en plus", |       "more": "+ __count__ en plus", | ||||||
|       "upload": "Charger le fichier tgz du module", |       "upload": "Charger le fichier .tgz du module", | ||||||
|       "refresh": "Actualiser la liste des modules", |       "refresh": "Actualiser la liste des modules", | ||||||
|       "errors": { |       "errors": { | ||||||
|         "catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>", |         "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>", |         "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>", |         "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>", |         "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>", |         "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": { |       "confirm": { | ||||||
|         "install": { |         "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" |           "title": "Installer les noeuds" | ||||||
|         }, |         }, | ||||||
|         "remove": { |         "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" |           "title": "Supprimer les noeuds" | ||||||
|         }, |         }, | ||||||
|  |         "removePlugin": { | ||||||
|  |           "body": "<p>Suppression du plugin '__module__'. Veuillez recharger l'éditeur afin d'appliquer les changements.</p>" | ||||||
|  |         }, | ||||||
|         "update": { |         "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>", |           "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" |           "title": "Mettre à jour les noeuds" | ||||||
|         }, |         }, | ||||||
|         "cannotUpdate": { |         "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": { |         "button": { | ||||||
|           "review": "Ouvrir la documentation", |           "review": "Ouvrir la documentation", | ||||||
|           "install": "Installer", |           "install": "Installer", | ||||||
|           "remove": "Supprimer", |           "remove": "Supprimer", | ||||||
|           "update": "Mettre à jour" |           "update": "Mettre à jour", | ||||||
|  |           "understood": "Compris" | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -701,8 +710,8 @@ | |||||||
|       "nodeHelp": "Aide sur les noeuds", |       "nodeHelp": "Aide sur les noeuds", | ||||||
|       "none": "Aucun", |       "none": "Aucun", | ||||||
|       "arrayItems": "__count__ éléments", |       "arrayItems": "__count__ éléments", | ||||||
|       "showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres", |       "showTips": "Vous pouvez afficher les astuces à partir du panneau des paramètres", | ||||||
|       "outline": "Plan", |       "outline": "Contour", | ||||||
|       "empty": "Vide", |       "empty": "Vide", | ||||||
|       "globalConfig": "Noeuds de configuration globale", |       "globalConfig": "Noeuds de configuration globale", | ||||||
|       "triggerAction": "Déclencher une action", |       "triggerAction": "Déclencher une action", | ||||||
| @@ -715,10 +724,11 @@ | |||||||
|     "help": { |     "help": { | ||||||
|       "name": "Aide", |       "name": "Aide", | ||||||
|       "label": "Aide", |       "label": "Aide", | ||||||
|       "search": "Aide à la recherche", |       "search": "Rechercher l'aide", | ||||||
|       "nodeHelp": "Aide sur les noeuds", |       "nodeHelp": "Aide sur les noeuds", | ||||||
|       "showHelp": "Afficher l'aide", |       "showHelp": "Afficher l'aide", | ||||||
|       "showInOutline": "Afficher dans les grandes lignes", |       "showInOutline": "Afficher dans les grandes lignes", | ||||||
|  |       "hideTopics": "Masquer les sujets", | ||||||
|       "showTopics": "Afficher les sujets", |       "showTopics": "Afficher les sujets", | ||||||
|       "noHelp": "Aucune rubrique d'aide sélectionnée", |       "noHelp": "Aucune rubrique d'aide sélectionnée", | ||||||
|       "changeLog": "Journal des modifications" |       "changeLog": "Journal des modifications" | ||||||
| @@ -793,7 +803,7 @@ | |||||||
|         "branches": "Branches", |         "branches": "Branches", | ||||||
|         "noBranches": "Pas de branche", |         "noBranches": "Pas de branche", | ||||||
|         "deleteConfirm": "Êtes-vous sûr de vouloir supprimer la branche locale '__name__' ? Ça ne peut pas être annulé.", |         "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", |         "deleteUnmergedBranch": "Supprimer la branche non fusionnée", | ||||||
|         "gitRemotes": "Git distant", |         "gitRemotes": "Git distant", | ||||||
|         "addRemote": "Ajout 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é." |         "deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé." | ||||||
|       }, |       }, | ||||||
|       "versionControl": { |       "versionControl": { | ||||||
|         "unstagedChanges": "Abandon des changements", |         "unstagedChanges": "Changements non indexés", | ||||||
|         "stagedChanges": "Changement mis en place", |         "stagedChanges": "Changements indexés", | ||||||
|         "unstageChange": "Ne pas mettre en place le changement", |         "unstageChange": "Annuler l'indexation des changements", | ||||||
|         "stageChange": "Mettre en place le changement", |         "stageChange": "Indexer les changements", | ||||||
|         "unstageAllChange": "Ne pas mettre en place tous les changements", |         "unstageAllChange": "Annuler l'indexation de tous les changements", | ||||||
|         "stageAllChange": "Mettre en place tous les changements", |         "stageAllChange": "Indexer tous les changements", | ||||||
|         "commitChanges": "Valider les changements", |         "commitChanges": "Valider les changements", | ||||||
|         "resolveConflicts": "Résoudre les conflits", |         "resolveConflicts": "Résoudre les conflits", | ||||||
|         "head": "En-tête", |         "head": "En-tête", | ||||||
|         "staged": "Mis en place", |         "staged": "Indexé", | ||||||
|         "unstaged": "Non mis en place", |         "unstaged": "Non indexé", | ||||||
|         "local": "Local", |         "local": "Local", | ||||||
|         "remote": "Distant", |         "remote": "Distant", | ||||||
|         "revert": "Voulez-vous vraiment annuler les modifications apportées à '__file__' ? Ça ne peut pas être annulé.", |         "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.", |         "pushFailed": "L'envoi a échoué car la branche a des validations plus récentes. Tirer et fusionner d'abord, puis envoyer à nouveau.", | ||||||
|         "push": "Envoyer", |         "push": "Envoyer", | ||||||
|         "pull": "Tirer", |         "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>", |         "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", |         "showUnstagedChanges": "Afficher les modifications non indexées", | ||||||
|         "connectionFailed": "Impossible de se connecter au référentiel distant: ", |         "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>", |         "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", |         "history": "Historique", | ||||||
|         "projectHistory": "Historique du projet", |         "projectHistory": "Historique du projet", | ||||||
|         "daysAgo": "il y a __count__ jour", |         "daysAgo": "il y a __count__ jour", | ||||||
| @@ -914,6 +924,8 @@ | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "typedInput": { |   "typedInput": { | ||||||
|  |     "selected": "__count__ sélectionnée", | ||||||
|  |     "selected_plural": "__count__ sélectionnées", | ||||||
|     "type": { |     "type": { | ||||||
|       "str": "chaîne de caractères", |       "str": "chaîne de caractères", | ||||||
|       "num": "nombre", |       "num": "nombre", | ||||||
| @@ -924,7 +936,14 @@ | |||||||
|       "date": "horodatage", |       "date": "horodatage", | ||||||
|       "jsonata": "expression", |       "jsonata": "expression", | ||||||
|       "env": "variable d'environnement", |       "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": { |   "editableList": { | ||||||
| @@ -957,7 +976,7 @@ | |||||||
|     "result": "Résultat", |     "result": "Résultat", | ||||||
|     "format": "Format", |     "format": "Format", | ||||||
|     "compatMode": "Mode de compatibilité activé", |     "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", |     "noMatch": "Aucun résultat correspondant", | ||||||
|     "errors": { |     "errors": { | ||||||
|       "invalid-expr": "Expression JSONata non valide :\n  __message__", |       "invalid-expr": "Expression JSONata non valide :\n  __message__", | ||||||
| @@ -980,7 +999,7 @@ | |||||||
|   }, |   }, | ||||||
|   "jsonEditor": { |   "jsonEditor": { | ||||||
|     "title": "Éditeur JSON", |     "title": "Éditeur JSON", | ||||||
|     "format": "Format JSON", |     "format": "Formatter JSON", | ||||||
|     "rawMode": "Modifier JSON", |     "rawMode": "Modifier JSON", | ||||||
|     "uiMode": "Afficher l'éditeur", |     "uiMode": "Afficher l'éditeur", | ||||||
|     "rawMode-readonly": "JSON", |     "rawMode-readonly": "JSON", | ||||||
| @@ -999,7 +1018,7 @@ | |||||||
|   "markdownEditor": { |   "markdownEditor": { | ||||||
|     "title": "Éditeur Markdown", |     "title": "Éditeur Markdown", | ||||||
|     "expand": "Développer", |     "expand": "Développer", | ||||||
|     "format": "Formaté avec Markdown", |     "format": "Formatter avec Markdown", | ||||||
|     "heading1": "Rubrique 1", |     "heading1": "Rubrique 1", | ||||||
|     "heading2": "Rubrique 2", |     "heading2": "Rubrique 2", | ||||||
|     "heading3": "Rubrique 3", |     "heading3": "Rubrique 3", | ||||||
| @@ -1073,7 +1092,7 @@ | |||||||
|       "credential-key": "Clé de chiffrement des identifiants", |       "credential-key": "Clé de chiffrement des identifiants", | ||||||
|       "cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", |       "cant-get-ssh-key": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", | ||||||
|       "already-exists2": "Existe déjà", |       "already-exists2": "Existe déjà", | ||||||
|       "git-error": "Erreur git", |       "git-error": "Erreur Git", | ||||||
|       "connection-failed": "La connexion a échoué", |       "connection-failed": "La connexion a échoué", | ||||||
|       "not-git-repo": "Ce n'est pas un dépôt Git", |       "not-git-repo": "Ce n'est pas un dépôt Git", | ||||||
|       "repo-not-found": "Référentiel introuvable" |       "repo-not-found": "Référentiel introuvable" | ||||||
| @@ -1087,7 +1106,7 @@ | |||||||
|       "credentials-file": "Fichier d'identifiants" |       "credentials-file": "Fichier d'identifiants" | ||||||
|     }, |     }, | ||||||
|     "encryption-config": { |     "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.", |       "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.", |       "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é.", |       "desc2": "Votre fichier d'identifiants de flux n'est actuellement pas chiffré.", | ||||||
| @@ -1144,9 +1163,9 @@ | |||||||
|       "add-ssh-key": "Ajouter une clé ssh", |       "add-ssh-key": "Ajouter une clé ssh", | ||||||
|       "credentials-encryption-key": "Clé de chiffrement des identifiants", |       "credentials-encryption-key": "Clé de chiffrement des identifiants", | ||||||
|       "already-exists-2": "Existe déjà", |       "already-exists-2": "Existe déjà", | ||||||
|       "git-error": "Erreur git", |       "git-error": "Erreur Git", | ||||||
|       "con-failed": "La connexion a échoué", |       "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", |       "no-resource": "Référentiel introuvable", | ||||||
|       "cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", |       "cant-get-ssh-key-path": "Erreur! Impossible d'obtenir le chemin de la clé SSH sélectionnée.", | ||||||
|       "unexpected_error": "Erreur inattendue", |       "unexpected_error": "Erreur inattendue", | ||||||
| @@ -1184,7 +1203,7 @@ | |||||||
|     }, |     }, | ||||||
|     "errors": { |     "errors": { | ||||||
|       "no-username-email": "Votre client Git n'est pas configuré avec un nom d'utilisateur/e-mail.", |       "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" |       "code": "Code" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| @@ -1253,7 +1272,7 @@ | |||||||
|     "list-modified-nodes": "Afficher les flux modifiés", |     "list-modified-nodes": "Afficher les flux modifiés", | ||||||
|     "list-hidden-flows": "Afficher les flux cachés", |     "list-hidden-flows": "Afficher les flux cachés", | ||||||
|     "list-flows": "Lister les flux", |     "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-previous-location": "Aller à l'emplacement précédent", | ||||||
|     "go-to-next-location": "Aller à l'emplacement suivant", |     "go-to-next-location": "Aller à l'emplacement suivant", | ||||||
|     "copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers", |     "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-bottom": "Aligner la sélection vers le bas", | ||||||
|     "align-selection-to-middle": "Aligner la sélection au centre verticalement", |     "align-selection-to-middle": "Aligner la sélection au centre verticalement", | ||||||
|     "align-selection-to-center": "Aligner la sélection au centre horizontalement", |     "align-selection-to-center": "Aligner la sélection au centre horizontalement", | ||||||
|     "distribute-selection-horizontally": "Distribuer la sélection horizontalement", |     "distribute-selection-horizontally": "Répartir la sélection horizontalement", | ||||||
|     "distribute-selection-vertical": "Distribuer la sélection verticalement", |     "distribute-selection-vertical": "Répartir la sélection verticalement", | ||||||
|     "wire-series-of-nodes": "Connecter les noeuds en série", |     "wire-series-of-nodes": "Connecter les noeuds en série", | ||||||
|     "wire-node-to-multiple": "Connecter les noeuds à plusieurs", |     "wire-node-to-multiple": "Connecter les noeuds à plusieurs", | ||||||
|     "wire-multiple-to-node": "Connecter plusieurs au noeud", |     "wire-multiple-to-node": "Connecter plusieurs au noeud", | ||||||
|   | |||||||
| @@ -27,7 +27,8 @@ | |||||||
|             "lock": "固定", |             "lock": "固定", | ||||||
|             "unlock": "固定を解除", |             "unlock": "固定を解除", | ||||||
|             "locked": "固定済み", |             "locked": "固定済み", | ||||||
|             "unlocked": "固定なし" |             "unlocked": "固定なし", | ||||||
|  |             "format": "形式" | ||||||
|         }, |         }, | ||||||
|         "type": { |         "type": { | ||||||
|             "string": "文字列", |             "string": "文字列", | ||||||
| @@ -281,8 +282,8 @@ | |||||||
|             "selected": "選択したフロー", |             "selected": "選択したフロー", | ||||||
|             "current": "現在のタブ", |             "current": "現在のタブ", | ||||||
|             "all": "全てのタブ", |             "all": "全てのタブ", | ||||||
|             "compact": "インデントのないJSONフォーマット", |             "compact": "インデントなし", | ||||||
|             "formatted": "インデント付きのJSONフォーマット", |             "formatted": "インデント付き", | ||||||
|             "copy": "書き出し", |             "copy": "書き出し", | ||||||
|             "export": "ライブラリに書き出し", |             "export": "ライブラリに書き出し", | ||||||
|             "exportAs": "書き出し先", |             "exportAs": "書き出し先", | ||||||
| @@ -303,7 +304,8 @@ | |||||||
|                 "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" |                 "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" | ||||||
|             }, |             }, | ||||||
|             "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", |             "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", | ||||||
|             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。" |             "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。", | ||||||
|  |             "alreadyExists": "本ノードは既に存在" | ||||||
|         }, |         }, | ||||||
|         "copyMessagePath": "パスをコピーしました", |         "copyMessagePath": "パスをコピーしました", | ||||||
|         "copyMessageValue": "値をコピーしました", |         "copyMessageValue": "値をコピーしました", | ||||||
| @@ -371,6 +373,7 @@ | |||||||
|             "deleted": "削除", |             "deleted": "削除", | ||||||
|             "flowDeleted": "削除されたフロー", |             "flowDeleted": "削除されたフロー", | ||||||
|             "flowAdded": "追加されたフロー", |             "flowAdded": "追加されたフロー", | ||||||
|  |             "moved": "移動", | ||||||
|             "movedTo": "__id__ へ移動", |             "movedTo": "__id__ へ移動", | ||||||
|             "movedFrom": "__id__ から移動" |             "movedFrom": "__id__ から移動" | ||||||
|         }, |         }, | ||||||
| @@ -613,6 +616,8 @@ | |||||||
|             }, |             }, | ||||||
|             "nodeCount": "__label__ 個のノード", |             "nodeCount": "__label__ 個のノード", | ||||||
|             "nodeCount_plural": "__label__ 個のノード", |             "nodeCount_plural": "__label__ 個のノード", | ||||||
|  |             "pluginCount": "__count__ 個のプラグイン", | ||||||
|  |             "pluginCount_plural": "__count__ 個のプラグイン", | ||||||
|             "moduleCount": "__count__ 個のモジュール", |             "moduleCount": "__count__ 個のモジュール", | ||||||
|             "moduleCount_plural": "__count__ 個のモジュール", |             "moduleCount_plural": "__count__ 個のモジュール", | ||||||
|             "inuse": "使用中", |             "inuse": "使用中", | ||||||
| @@ -640,6 +645,7 @@ | |||||||
|             "errors": { |             "errors": { | ||||||
|                 "catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>", |                 "catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>", | ||||||
|                 "installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</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>", |                 "removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>", | ||||||
|                 "updateFailed": "<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>", |                 "enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>", | ||||||
| @@ -654,6 +660,9 @@ | |||||||
|                     "body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>", |                     "body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>", | ||||||
|                     "title": "ノードを削除" |                     "title": "ノードを削除" | ||||||
|                 }, |                 }, | ||||||
|  |                 "removePlugin": { | ||||||
|  |                     "body": "<p>プラグイン __module__ を削除しました。ブラウザを再読み込みして残った表示を消してください。</p>" | ||||||
|  |                 }, | ||||||
|                 "update": { |                 "update": { | ||||||
|                     "body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>", |                     "body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>", | ||||||
|                     "title": "ノードの更新" |                     "title": "ノードの更新" | ||||||
| @@ -665,7 +674,8 @@ | |||||||
|                     "review": "ノードの情報を参照", |                     "review": "ノードの情報を参照", | ||||||
|                     "install": "追加", |                     "install": "追加", | ||||||
|                     "remove": "削除", |                     "remove": "削除", | ||||||
|                     "update": "更新" |                     "update": "更新", | ||||||
|  |                     "understood": "了解" | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -718,6 +728,7 @@ | |||||||
|             "nodeHelp": "ノードヘルプ", |             "nodeHelp": "ノードヘルプ", | ||||||
|             "showHelp": "ヘルプを表示", |             "showHelp": "ヘルプを表示", | ||||||
|             "showInOutline": "アウトラインに表示", |             "showInOutline": "アウトラインに表示", | ||||||
|  |             "hideTopics": "トピックを非表示", | ||||||
|             "showTopics": "トピックを表示", |             "showTopics": "トピックを表示", | ||||||
|             "noHelp": "ヘルプのトピックが未選択", |             "noHelp": "ヘルプのトピックが未選択", | ||||||
|             "changeLog": "更新履歴" |             "changeLog": "更新履歴" | ||||||
| @@ -913,6 +924,8 @@ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "typedInput": { |     "typedInput": { | ||||||
|  |         "selected": "__count__個を選択", | ||||||
|  |         "selected_plural": "__count__個を選択", | ||||||
|         "type": { |         "type": { | ||||||
|             "str": "文字列", |             "str": "文字列", | ||||||
|             "num": "数値", |             "num": "数値", | ||||||
| @@ -923,7 +936,14 @@ | |||||||
|             "date": "日時", |             "date": "日時", | ||||||
|             "jsonata": "JSONata式", |             "jsonata": "JSONata式", | ||||||
|             "env": "環境変数", |             "env": "環境変数", | ||||||
|             "cred": "認証情報" |             "cred": "認証情報", | ||||||
|  |             "conf-types": "設定ノード" | ||||||
|  |         }, | ||||||
|  |         "date": { | ||||||
|  |             "format": { | ||||||
|  |                 "timestamp": "エポックからの経過ミリ秒", | ||||||
|  |                 "object": "JavaScript日付オブジェクト" | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "editableList": { |     "editableList": { | ||||||
| @@ -1229,7 +1249,7 @@ | |||||||
|     }, |     }, | ||||||
|     "env-var": { |     "env-var": { | ||||||
|         "environment": "環境変数", |         "environment": "環境変数", | ||||||
|         "header": "大域環境変数", |         "header": "グローバル環境変数", | ||||||
|         "revert": "破棄" |         "revert": "破棄" | ||||||
|     }, |     }, | ||||||
|     "action-list": { |     "action-list": { | ||||||
| @@ -1381,7 +1401,7 @@ | |||||||
|         "copy-item-edit-url": "要素の編集URLをコピー", |         "copy-item-edit-url": "要素の編集URLをコピー", | ||||||
|         "move-flow-to-start": "フローを先頭に移動", |         "move-flow-to-start": "フローを先頭に移動", | ||||||
|         "move-flow-to-end": "フローを末尾に移動", |         "move-flow-to-end": "フローを末尾に移動", | ||||||
|         "show-global-env": "大域環境変数を表示", |         "show-global-env": "グローバル環境変数を表示", | ||||||
|         "lock-flow": "フローを固定", |         "lock-flow": "フローを固定", | ||||||
|         "unlock-flow": "フローの固定を解除", |         "unlock-flow": "フローの固定を解除", | ||||||
|         "show-node-help": "ノードのヘルプを表示" |         "show-node-help": "ノードのヘルプを表示" | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|     "name": "@node-red/editor-client", |     "name": "@node-red/editor-client", | ||||||
|     "version": "4.0.0-dev", |     "version": "4.0.2", | ||||||
|     "license": "Apache-2.0", |     "license": "Apache-2.0", | ||||||
|     "repository": { |     "repository": { | ||||||
|         "type": "git", |         "type": "git", | ||||||
|   | |||||||
| @@ -26,6 +26,15 @@ RED.comms = (function() { | |||||||
|     var reconnectAttempts = 0; |     var reconnectAttempts = 0; | ||||||
|     var active = false; |     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() { |     function connectWS() { | ||||||
|         active = true; |         active = true; | ||||||
|         var wspath; |         var wspath; | ||||||
| @@ -56,6 +65,7 @@ RED.comms = (function() { | |||||||
|                     ws.send(JSON.stringify({subscribe:t})); |                     ws.send(JSON.stringify({subscribe:t})); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             emit('connect') | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ws = new WebSocket(wspath); |         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 { |     return { | ||||||
|         connect: connectWS, |         connect: connectWS, | ||||||
|         subscribe: subscribe, |         subscribe: subscribe, | ||||||
|         unsubscribe:unsubscribe |         unsubscribe:unsubscribe, | ||||||
|  |         on, | ||||||
|  |         off, | ||||||
|  |         send | ||||||
|     } |     } | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -29,7 +29,14 @@ RED.history = (function() { | |||||||
|         } |         } | ||||||
|         return RED.nodes.junction(id); |         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) { |     function undoEvent(ev) { | ||||||
|         var i; |         var i; | ||||||
|         var len; |         var len; | ||||||
| @@ -59,18 +66,46 @@ RED.history = (function() { | |||||||
|                         t: 'replace', |                         t: 'replace', | ||||||
|                         config: RED.nodes.createCompleteNodeSet(), |                         config: RED.nodes.createCompleteNodeSet(), | ||||||
|                         changed: {}, |                         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(); |                     RED.nodes.clear(); | ||||||
|                     var imported = RED.nodes.import(ev.config); |                     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) { |                     imported.nodes.forEach(function(n) { | ||||||
|                         if (ev.changed[n.id]) { |                         if (ev.changed[n.id]) { | ||||||
|  |                             ensureUnlocked(n.z, flowsToLock) | ||||||
|                             n.changed = true; |                             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.nodes.version(ev.rev); | ||||||
|  |                     RED.view.redraw(true); | ||||||
|  |                     RED.palette.refresh(); | ||||||
|  |                     RED.workspaces.refresh(); | ||||||
|  |                     RED.workspaces.show(selectedTab, true); | ||||||
|  |                     RED.sidebar.config.refresh(); | ||||||
|                 } else { |                 } else { | ||||||
|                     var importMap = {}; |                     var importMap = {}; | ||||||
|                     ev.config.forEach(function(n) { |                     ev.config.forEach(function(n) { | ||||||
|   | |||||||
							
								
								
									
										490
									
								
								packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
									
									
									
									
										vendored
									
									
										Normal 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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | })(); | ||||||
| @@ -91,6 +91,31 @@ RED.nodes = (function() { | |||||||
|             getNodeTypes: function() { |             getNodeTypes: function() { | ||||||
|                 return Object.keys(nodeDefinitions); |                 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) { |             setNodeList: function(list) { | ||||||
|                 nodeList = []; |                 nodeList = []; | ||||||
|                 for(var i=0;i<list.length;i++) { |                 for(var i=0;i<list.length;i++) { | ||||||
| @@ -124,6 +149,8 @@ RED.nodes = (function() { | |||||||
|             }, |             }, | ||||||
|             removeNodeSet: function(id) { |             removeNodeSet: function(id) { | ||||||
|                 var ns = nodeSets[id]; |                 var ns = nodeSets[id]; | ||||||
|  |                 if (!ns) { return {} } | ||||||
|  |  | ||||||
|                 for (var j=0;j<ns.types.length;j++) { |                 for (var j=0;j<ns.types.length;j++) { | ||||||
|                     delete typeToId[ns.types[j]]; |                     delete typeToId[ns.types[j]]; | ||||||
|                 } |                 } | ||||||
| @@ -547,12 +574,16 @@ RED.nodes = (function() { | |||||||
|              * @param {String} z tab id |              * @param {String} z tab id | ||||||
|              */ |              */ | ||||||
|             checkTabState: function (z) { |             checkTabState: function (z) { | ||||||
|                 const ws = workspaces[z] |                 const ws = workspaces[z] || subflows[z] | ||||||
|                 if (ws) { |                 if (ws) { | ||||||
|                     const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0 |                     const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0 | ||||||
|                     if (Boolean(ws.contentsChanged) !== contentsChanged) { |                     if (Boolean(ws.contentsChanged) !== contentsChanged) { | ||||||
|                         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, { |         RED.nodes.registerType("subflow:"+sf.id, { | ||||||
|             defaults:{ |             defaults:{ | ||||||
|                 name:{value:""}, |                 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" }, |             icon: function() { return sf.icon||"subflow.svg" }, | ||||||
|             category: sf.category || "subflows", |             category: sf.category || "subflows", | ||||||
| @@ -2360,6 +2406,13 @@ RED.nodes = (function() { | |||||||
|             } else { |             } else { | ||||||
|                 delete n.g |                 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) { |             for (var d3 in n._def.defaults) { | ||||||
|                 if (n._def.defaults.hasOwnProperty(d3)) { |                 if (n._def.defaults.hasOwnProperty(d3)) { | ||||||
|                     if (n._def.defaults[d3].type) { |                     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++) { |         for (i=0;i<new_subflows.length;i++) { | ||||||
|             n = new_subflows[i]; |             n = new_subflows[i]; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| RED.plugins = (function() { | RED.plugins = (function() { | ||||||
|     var plugins = {}; |     var plugins = {}; | ||||||
|     var pluginsByType = {}; |     var pluginsByType = {}; | ||||||
|  |     var moduleList = {}; | ||||||
|  |  | ||||||
|     function registerPlugin(id,definition) { |     function registerPlugin(id,definition) { | ||||||
|         plugins[id] = definition; |         plugins[id] = definition; | ||||||
| @@ -38,9 +39,43 @@ RED.plugins = (function() { | |||||||
|     function getPluginsByType(type) { |     function getPluginsByType(type) { | ||||||
|         return pluginsByType[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 { |     return { | ||||||
|         registerPlugin: registerPlugin, |         registerPlugin: registerPlugin, | ||||||
|         getPlugin: getPlugin, |         getPlugin: getPlugin, | ||||||
|         getPluginsByType: getPluginsByType |         getPluginsByType: getPluginsByType, | ||||||
|  |  | ||||||
|  |         setPluginList: setPluginList, | ||||||
|  |         addPlugin: addPlugin, | ||||||
|  |         getModule: getModule | ||||||
|     } |     } | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ var RED = (function() { | |||||||
|             cache: false, |             cache: false, | ||||||
|             url: 'plugins', |             url: 'plugins', | ||||||
|             success: function(data) { |             success: function(data) { | ||||||
|  |                 RED.plugins.setPluginList(data); | ||||||
|                 loader.reportProgress(RED._("event.loadPlugins"), 13) |                 loader.reportProgress(RED._("event.loadPlugins"), 13) | ||||||
|                 RED.i18n.loadPluginCatalogs(function() { |                 RED.i18n.loadPluginCatalogs(function() { | ||||||
|                     loadPlugins(function() { |                     loadPlugins(function() { | ||||||
| @@ -297,6 +298,7 @@ var RED = (function() { | |||||||
|                                 RED.workspaces.show(workspaces[0]); |                                 RED.workspaces.show(workspaces[0]); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |                         RED.events.emit('flows:loaded') | ||||||
|                     } catch(err) { |                     } catch(err) { | ||||||
|                         console.warn(err); |                         console.warn(err); | ||||||
|                         RED.notify( |                         RED.notify( | ||||||
| @@ -534,6 +536,41 @@ var RED = (function() { | |||||||
|                 RED.view.redrawStatus(node); |                 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 pendingNodeRemovedNotifications = [] | ||||||
|         let pendingNodeRemovedTimeout |         let pendingNodeRemovedTimeout | ||||||
| @@ -803,6 +840,10 @@ var RED = (function() { | |||||||
|  |  | ||||||
|         RED.nodes.init(); |         RED.nodes.init(); | ||||||
|         RED.runtime.init() |         RED.runtime.init() | ||||||
|  |  | ||||||
|  |         if (RED.settings.theme("multiplayer.enabled",false)) { | ||||||
|  |             RED.multiplayer.init() | ||||||
|  |         } | ||||||
|         RED.comms.connect(); |         RED.comms.connect(); | ||||||
|  |  | ||||||
|         $("#red-ui-main-container").show(); |         $("#red-ui-main-container").show(); | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ RED.clipboard = (function() { | |||||||
|     var currentPopoverError; |     var currentPopoverError; | ||||||
|     var activeTab; |     var activeTab; | ||||||
|     var libraryBrowser; |     var libraryBrowser; | ||||||
|  |     var clipboardTabs; | ||||||
|  |  | ||||||
|     var activeLibraries = {}; |     var activeLibraries = {}; | ||||||
|  |  | ||||||
| @@ -215,6 +216,13 @@ RED.clipboard = (function() { | |||||||
|                 open: function( event, ui ) { |                 open: function( event, ui ) { | ||||||
|                     RED.keyboard.disable(); |                     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) { |                 close: function(e) { | ||||||
|                     RED.keyboard.enable(); |                     RED.keyboard.enable(); | ||||||
|                     if (popover) { |                     if (popover) { | ||||||
| @@ -228,12 +236,23 @@ RED.clipboard = (function() { | |||||||
|  |  | ||||||
|         exportNodesDialog = |         exportNodesDialog = | ||||||
|             '<div class="form-row">'+ |             '<div class="form-row">'+ | ||||||
|                 '<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+ |                 '<div style="display: flex; justify-content: space-between;">'+ | ||||||
|                 '<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+ |                     '<div class="form-row">'+ | ||||||
|                     '<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+ |                         '<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+ | ||||||
|                     '<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+ |                         '<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+ | ||||||
|                     '<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+ |                             '<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+ | ||||||
|                 '</span>'+ |                             '<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>'+ | ||||||
|             '<div class="red-ui-clipboard-dialog-box">'+ |             '<div class="red-ui-clipboard-dialog-box">'+ | ||||||
|                 '<div class="red-ui-clipboard-dialog-tabs">'+ |                 '<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 id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+ | ||||||
|                         '</div>'+ |                         '</div>'+ | ||||||
|                         '<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+ |                         '<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>'+ |                                 '<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+ | ||||||
|                             '</div>'+ |                             '</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>'+ |                     '</div>'+ | ||||||
|                     '<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+ |                     '<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+ | ||||||
| @@ -569,7 +582,7 @@ RED.clipboard = (function() { | |||||||
|  |  | ||||||
|         dialogContainer.empty(); |         dialogContainer.empty(); | ||||||
|         dialogContainer.append($(exportNodesDialog)); |         dialogContainer.append($(exportNodesDialog)); | ||||||
|  |         clipboardTabs = null | ||||||
|         var tabs = RED.tabs.create({ |         var tabs = RED.tabs.create({ | ||||||
|             id: "red-ui-clipboard-dialog-export-tabs", |             id: "red-ui-clipboard-dialog-export-tabs", | ||||||
|             vertical: true, |             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-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)}); | ||||||
|         $("#red-ui-clipboard-dialog-export").button("enable"); |         $("#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", |             id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs", | ||||||
|             onchange: function(tab) { |             onchange: function(tab) { | ||||||
|                 $(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide(); |                 $(".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", |             id: "red-ui-clipboard-dialog-export-tab-clipboard-json", | ||||||
|             label: RED._("editor.types.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({ |         var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({ | ||||||
|             data: [] |             data: [] | ||||||
|   | |||||||
| @@ -174,12 +174,24 @@ | |||||||
|                 this.uiContainer.width(m[1]); |                 this.uiContainer.width(m[1]); | ||||||
|             } |             } | ||||||
|             if (this.options.sortable) { |             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')? |                 var handle = (typeof this.options.sortable === 'string')? | ||||||
|                                 this.options.sortable : |                                 this.options.sortable : | ||||||
|                                 ".red-ui-editableList-item-handle"; |                                 ".red-ui-editableList-item-handle"; | ||||||
|                 var sortOptions = { |                 var sortOptions = { | ||||||
|                     axis: "y", |                     axis: "y", | ||||||
|                     update: function( event, ui ) { |                     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) { |                         if (that.options.sortItems) { | ||||||
|                             that.options.sortItems(that.items()); |                             that.options.sortItems(that.items()); | ||||||
|                         } |                         } | ||||||
| @@ -189,8 +201,32 @@ | |||||||
|                     tolerance: "pointer", |                     tolerance: "pointer", | ||||||
|                     forcePlaceholderSize:true, |                     forcePlaceholderSize:true, | ||||||
|                     placeholder: "red-ui-editabelList-item-placeholder", |                     placeholder: "red-ui-editabelList-item-placeholder", | ||||||
|                     start: function(e, ui){ |                     start: function (event, ui) { | ||||||
|                         ui.placeholder.height(ui.item.height()-4); |                         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) { |                 if (this.options.connectWith) { | ||||||
|   | |||||||
| @@ -211,7 +211,7 @@ RED.popover = (function() { | |||||||
|                         closePopup(true); |                         closePopup(true); | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|                 if (trigger === 'hover' && options.interactive) { |                 if (/*trigger === 'hover' && */options.interactive) { | ||||||
|                     div.on('mouseenter', function(e) { |                     div.on('mouseenter', function(e) { | ||||||
|                         clearTimeout(timer); |                         clearTimeout(timer); | ||||||
|                         active = true; |                         active = true; | ||||||
| @@ -445,9 +445,12 @@ RED.popover = (function() { | |||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         create: createPopover, |         create: createPopover, | ||||||
|         tooltip: function(target,content, action) { |         tooltip: function(target,content, action, interactive) { | ||||||
|             var label = function() { |             var label = function() { | ||||||
|                 var label = content; |                 var label = content; | ||||||
|  |                 if (typeof content === 'function') { | ||||||
|  |                     label = content() | ||||||
|  |                 } | ||||||
|                 if (action) { |                 if (action) { | ||||||
|                     var shortcut = RED.keyboard.getShortcut(action); |                     var shortcut = RED.keyboard.getShortcut(action); | ||||||
|                     if (shortcut && shortcut.key) { |                     if (shortcut && shortcut.key) { | ||||||
| @@ -463,6 +466,7 @@ RED.popover = (function() { | |||||||
|                 size: "small", |                 size: "small", | ||||||
|                 direction: "bottom", |                 direction: "bottom", | ||||||
|                 content: label, |                 content: label, | ||||||
|  |                 interactive, | ||||||
|                 delay: { show: 750, hide: 50 } |                 delay: { show: 750, hide: 50 } | ||||||
|             }); |             }); | ||||||
|             popover.setContent = function(newContent) { |             popover.setContent = function(newContent) { | ||||||
|   | |||||||
| @@ -365,7 +365,10 @@ RED.tabs = (function() { | |||||||
|  |  | ||||||
|             var thisTabA = thisTab.find("a"); |             var thisTabA = thisTab.find("a"); | ||||||
|             if (options.onclick) { |             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); |             activateTab(thisTabA); | ||||||
|             if (fireSelectionChanged) { |             if (fireSelectionChanged) { | ||||||
| @@ -548,6 +551,8 @@ RED.tabs = (function() { | |||||||
|         ul.find("li.red-ui-tab a") |         ul.find("li.red-ui-tab a") | ||||||
|             .on("mousedown", function(evt) { mousedownTab = evt.currentTarget }) |             .on("mousedown", function(evt) { mousedownTab = evt.currentTarget }) | ||||||
|             .on("mouseup",onTabClick) |             .on("mouseup",onTabClick) | ||||||
|  |             // prevent browser-default middle-click behaviour | ||||||
|  |             .on("auxclick", function(evt) { evt.preventDefault() }) | ||||||
|             .on("click", function(evt) {evt.preventDefault(); }) |             .on("click", function(evt) {evt.preventDefault(); }) | ||||||
|             .on("dblclick", function(evt) {evt.stopPropagation(); 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("mousedown", function(evt) { mousedownTab = evt.currentTarget }) | ||||||
|                 link.on("mouseup",onTabClick); |                 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("click", function(evt) { evt.preventDefault(); }) | ||||||
|                 link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); }) |                 link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -358,61 +358,64 @@ | |||||||
|         { value: "_session", source: ["websocket out","tcp out"] }, |         { value: "_session", source: ["websocket out","tcp out"] }, | ||||||
|     ] |     ] | ||||||
|     var allOptions = { |     var allOptions = { | ||||||
|         msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)}, |         msg: { value: "msg", label: "msg.", validate: RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions) }, | ||||||
|         flow: {value:"flow",label:"flow.",hasValue:true, |         flow: { value: "flow", label: "flow.", hasValue: true, | ||||||
|             options:[], |             options: [], | ||||||
|             validate:RED.utils.validatePropertyExpression, |             validate: RED.utils.validatePropertyExpression, | ||||||
|             parse: contextParse, |             parse: contextParse, | ||||||
|             export: contextExport, |             export: contextExport, | ||||||
|             valueLabel: contextLabel, |             valueLabel: contextLabel, | ||||||
|             autoComplete: contextAutoComplete |             autoComplete: contextAutoComplete | ||||||
|         }, |         }, | ||||||
|         global: {value:"global",label:"global.",hasValue:true, |         global: { | ||||||
|             options:[], |             value: "global", label: "global.", hasValue: true, | ||||||
|             validate:RED.utils.validatePropertyExpression, |             options: [], | ||||||
|  |             validate: RED.utils.validatePropertyExpression, | ||||||
|             parse: contextParse, |             parse: contextParse, | ||||||
|             export: contextExport, |             export: contextExport, | ||||||
|             valueLabel: contextLabel, |             valueLabel: contextLabel, | ||||||
|             autoComplete: contextAutoComplete |             autoComplete: contextAutoComplete | ||||||
|         }, |         }, | ||||||
|         str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, |         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) { |         num: { value: "num", label: "number", icon: "red/images/typedInput/09.svg", validate: function (v, o) { | ||||||
|             return (true === RED.utils.validateTypedProperty(v, "num")); |             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: { |         json: { | ||||||
|             value:"json", |             value: "json", | ||||||
|             label:"JSON", |             label: "JSON", | ||||||
|             icon:"red/images/typedInput/json.svg", |             icon: "red/images/typedInput/json.svg", | ||||||
|             validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}, |             validate: function (v, o) { | ||||||
|             expand: function() { |                 return RED.utils.validateTypedProperty(v, "json", o); | ||||||
|  |             }, | ||||||
|  |             expand: function () { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 var value = this.value(); |                 var value = this.value(); | ||||||
|                 try { |                 try { | ||||||
|                     value = JSON.stringify(JSON.parse(value),null,4); |                     value = JSON.stringify(JSON.parse(value), null, 4); | ||||||
|                 } catch(err) { |                 } catch (err) { | ||||||
|                 } |                 } | ||||||
|                 RED.editor.editJSON({ |                 RED.editor.editJSON({ | ||||||
|                     value: value, |                     value: value, | ||||||
|                     stateId: RED.editor.generateViewStateId("typedInput", that, "json"), |                     stateId: RED.editor.generateViewStateId("typedInput", that, "json"), | ||||||
|                     focus: true, |                     focus: true, | ||||||
|                     complete: function(v) { |                     complete: function (v) { | ||||||
|                         var value = v; |                         var value = v; | ||||||
|                         try { |                         try { | ||||||
|                             value = JSON.stringify(JSON.parse(v)); |                             value = JSON.stringify(JSON.parse(v)); | ||||||
|                         } catch(err) { |                         } catch (err) { | ||||||
|                         } |                         } | ||||||
|                         that.value(value); |                         that.value(value); | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"}, |         re: { value: "re", label: "regular expression", icon: "red/images/typedInput/re.svg" }, | ||||||
|         date: { |         date: { | ||||||
|             value:"date", |             value: "date", | ||||||
|             label:"timestamp", |             label: "timestamp", | ||||||
|             icon:"fa fa-clock-o", |             icon: "fa fa-clock-o", | ||||||
|             options:[ |             options: [ | ||||||
|                 { |                 { | ||||||
|                     label: 'milliseconds since epoch', |                     label: 'milliseconds since epoch', | ||||||
|                     value: '' |                     value: '' | ||||||
| @@ -431,15 +434,17 @@ | |||||||
|             value: "jsonata", |             value: "jsonata", | ||||||
|             label: "expression", |             label: "expression", | ||||||
|             icon: "red/images/typedInput/expr.svg", |             icon: "red/images/typedInput/expr.svg", | ||||||
|             validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}}, |             validate: function (v, o) { | ||||||
|             expand:function() { |                 return RED.utils.validateTypedProperty(v, "jsonata", o); | ||||||
|  |             }, | ||||||
|  |             expand: function () { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 RED.editor.editExpression({ |                 RED.editor.editExpression({ | ||||||
|                     value: this.value().replace(/\t/g,"\n"), |                     value: this.value().replace(/\t/g, "\n"), | ||||||
|                     stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"), |                     stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"), | ||||||
|                     focus: true, |                     focus: true, | ||||||
|                     complete: function(v) { |                     complete: function (v) { | ||||||
|                         that.value(v.replace(/\n/g,"\t")); |                         that.value(v.replace(/\n/g, "\t")); | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|             } |             } | ||||||
| @@ -448,13 +453,13 @@ | |||||||
|             value: "bin", |             value: "bin", | ||||||
|             label: "buffer", |             label: "buffer", | ||||||
|             icon: "red/images/typedInput/bin.svg", |             icon: "red/images/typedInput/bin.svg", | ||||||
|             expand: function() { |             expand: function () { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 RED.editor.editBuffer({ |                 RED.editor.editBuffer({ | ||||||
|                     value: this.value(), |                     value: this.value(), | ||||||
|                     stateId: RED.editor.generateViewStateId("typedInput", that, "bin"), |                     stateId: RED.editor.generateViewStateId("typedInput", that, "bin"), | ||||||
|                     focus: true, |                     focus: true, | ||||||
|                     complete: function(v) { |                     complete: function (v) { | ||||||
|                         that.value(v); |                         that.value(v); | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
| @@ -470,9 +475,9 @@ | |||||||
|             value: "node", |             value: "node", | ||||||
|             label: "node", |             label: "node", | ||||||
|             icon: "red/images/typedInput/target.svg", |             icon: "red/images/typedInput/target.svg", | ||||||
|             valueLabel: function(container,value) { |             valueLabel: function (container, value) { | ||||||
|                 var node = RED.nodes.node(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-top": "2px", | ||||||
|                     "margin-left": "3px" |                     "margin-left": "3px" | ||||||
|                 }).appendTo(container); |                 }).appendTo(container); | ||||||
| @@ -481,133 +486,190 @@ | |||||||
|                     "margin-left": "6px" |                     "margin-left": "6px" | ||||||
|                 }).appendTo(container); |                 }).appendTo(container); | ||||||
|                 if (node) { |                 if (node) { | ||||||
|                     var colour = RED.utils.getNodeColor(node.type,node._def); |                     var colour = RED.utils.getNodeColor(node.type, node._def); | ||||||
|                     var icon_url = RED.utils.getNodeIcon(node._def,node); |                     var icon_url = RED.utils.getNodeIcon(node._def, node); | ||||||
|                     if (node.type === 'tab') { |                     if (node.type === 'tab') { | ||||||
|                         colour = "#C0DEED"; |                         colour = "#C0DEED"; | ||||||
|                     } |                     } | ||||||
|                     nodeDiv.css('backgroundColor',colour); |                     nodeDiv.css('backgroundColor', colour); | ||||||
|                     var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); |                     var iconContainer = $('<div/>', { class: "red-ui-palette-icon-container" }).appendTo(nodeDiv); | ||||||
|                     RED.utils.createIconElement(icon_url, iconContainer, true); |                     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); |                     nodeLabel.text(l); | ||||||
|                 } else { |                 } else { | ||||||
|                     nodeDiv.css({ |                     nodeDiv.css({ | ||||||
|                         'backgroundColor': '#eee', |                         'backgroundColor': '#eee', | ||||||
|                         'border-style' : 'dashed' |                         'border-style': 'dashed' | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             expand: function() { |             expand: function () { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 RED.tray.hide(); |                 RED.tray.hide(); | ||||||
|                 RED.view.selectNodes({ |                 RED.view.selectNodes({ | ||||||
|                     single: true, |                     single: true, | ||||||
|                     selected: [that.value()], |                     selected: [that.value()], | ||||||
|                     onselect: function(selection) { |                     onselect: function (selection) { | ||||||
|                         that.value(selection.id); |                         that.value(selection.id); | ||||||
|                         RED.tray.show(); |                         RED.tray.show(); | ||||||
|                     }, |                     }, | ||||||
|                     oncancel: function() { |                     oncancel: function () { | ||||||
|                         RED.tray.show(); |                         RED.tray.show(); | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         cred:{ |         cred: { | ||||||
|             value:"cred", |             value: "cred", | ||||||
|             label:"credential", |             label: "credential", | ||||||
|             icon:"fa fa-lock", |             icon: "fa fa-lock", | ||||||
|             inputType: "password", |             inputType: "password", | ||||||
|             valueLabel: function(container,value) { |             valueLabel: function (container, value) { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 container.css("pointer-events","none"); |                 container.css("pointer-events", "none"); | ||||||
|                 container.css("flex-grow",0); |                 container.css("flex-grow", 0); | ||||||
|                 this.elementDiv.hide(); |                 this.elementDiv.hide(); | ||||||
|                 var buttons = $('<div>').css({ |                 var buttons = $('<div>').css({ | ||||||
|                     position: "absolute", |                     position: "absolute", | ||||||
|                     right:"6px", |                     right: "6px", | ||||||
|                     top: "6px", |                     top: "6px", | ||||||
|                     "pointer-events":"all" |                     "pointer-events": "all" | ||||||
|                 }).appendTo(container); |                 }).appendTo(container); | ||||||
|                 var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({ |                 var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({ | ||||||
|                     width:"20px" |                     width: "20px" | ||||||
|                 }).appendTo(buttons).on("click", function(evt) { |                 }).appendTo(buttons).on("click", function (evt) { | ||||||
|                     evt.preventDefault(); |                     evt.preventDefault(); | ||||||
|                     var cursorPosition = that.input[0].selectionStart; |                     var cursorPosition = that.input[0].selectionStart; | ||||||
|                     var currentType = that.input.attr("type"); |                     var currentType = that.input.attr("type"); | ||||||
|                     if (currentType === "text") { |                     if (currentType === "text") { | ||||||
|                         that.input.attr("type","password"); |                         that.input.attr("type", "password"); | ||||||
|                         eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); |                         eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); | ||||||
|                         setTimeout(function() { |                         setTimeout(function () { | ||||||
|                             that.input.focus(); |                             that.input.focus(); | ||||||
|                             that.input[0].setSelectionRange(cursorPosition, cursorPosition); |                             that.input[0].setSelectionRange(cursorPosition, cursorPosition); | ||||||
|                         },50); |                         }, 50); | ||||||
|                     } else { |                     } else { | ||||||
|                         that.input.attr("type","text"); |                         that.input.attr("type", "text"); | ||||||
|                         eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); |                         eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); | ||||||
|                         setTimeout(function() { |                         setTimeout(function () { | ||||||
|                             that.input.focus(); |                             that.input.focus(); | ||||||
|                             that.input[0].setSelectionRange(cursorPosition, cursorPosition); |                             that.input[0].setSelectionRange(cursorPosition, cursorPosition); | ||||||
|                         },50); |                         }, 50); | ||||||
|                     } |                     } | ||||||
|                 }).hide(); |                 }).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__") { |                 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({ |                     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", |                         padding: "6px 6px", | ||||||
|                         borderRadius:"4px" |                         borderRadius: "4px" | ||||||
|                     }).addClass("red-ui-typedInput-value-label-inactive").appendTo(container); |                     }).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(); |                         evt.preventDefault(); | ||||||
|                         innerContainer.hide(); |                         innerContainer.hide(); | ||||||
|                         container.css("background","none"); |                         container.css("background", "none"); | ||||||
|                         container.css("pointer-events","none"); |                         container.css("pointer-events", "none"); | ||||||
|                         that.input.val(""); |                         that.input.val(""); | ||||||
|                         that.element.val(""); |                         that.element.val(""); | ||||||
|                         that.elementDiv.show(); |                         that.elementDiv.show(); | ||||||
|                         editButton.hide(); |                         editButton.hide(); | ||||||
|                         cancelButton.show(); |                         cancelButton.show(); | ||||||
|                         eyeButton.show(); |                         eyeButton.show(); | ||||||
|                         setTimeout(function() { |                         setTimeout(function () { | ||||||
|                             that.input.focus(); |                             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(); |                         evt.preventDefault(); | ||||||
|                         innerContainer.show(); |                         innerContainer.show(); | ||||||
|                         container.css("background",""); |                         container.css("background", ""); | ||||||
|                         that.input.val("__PWRD__"); |                         that.input.val("__PWRD__"); | ||||||
|                         that.element.val("__PWRD__"); |                         that.element.val("__PWRD__"); | ||||||
|                         that.elementDiv.hide(); |                         that.elementDiv.hide(); | ||||||
|                         editButton.show(); |                         editButton.show(); | ||||||
|                         cancelButton.hide(); |                         cancelButton.hide(); | ||||||
|                         eyeButton.hide(); |                         eyeButton.hide(); | ||||||
|                         that.input.attr("type","password"); |                         that.input.attr("type", "password"); | ||||||
|                         eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); |                         eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); | ||||||
|  |  | ||||||
|                     }).hide(); |                     }).hide(); | ||||||
|                 } else { |                 } else { | ||||||
|                     container.css("background","none"); |                     container.css("background", "none"); | ||||||
|                     container.css("pointer-events","none"); |                     container.css("pointer-events", "none"); | ||||||
|                     this.elementDiv.show(); |                     this.elementDiv.show(); | ||||||
|                     eyeButton.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 |     // For a type with options, check value is a valid selection | ||||||
|     // If !opt.multiple, returns the valid option object |     // If !opt.multiple, returns the valid option object | ||||||
|     // if opt.multiple, returns an array of valid option objects |     // if opt.multiple, returns an array of valid option objects | ||||||
|     // If not valid, returns null; |     // If not valid, returns null; | ||||||
|  |  | ||||||
|     function isOptionValueValid(opt, currentVal) { |     function isOptionValueValid(opt, currentVal) { | ||||||
|  |         let _options = opt.options | ||||||
|  |         if (typeof _options === "function") { | ||||||
|  |             _options = _options.call(this) | ||||||
|  |         } | ||||||
|         if (!opt.multiple) { |         if (!opt.multiple) { | ||||||
|             for (var i=0;i<opt.options.length;i++) { |             for (var i=0;i<_options.length;i++) { | ||||||
|                 op = opt.options[i]; |                 op = _options[i]; | ||||||
|                 if (typeof op === "string" && op === currentVal) { |                 if (typeof op === "string" && op === currentVal) { | ||||||
|                     return {value:currentVal} |                     return {value:currentVal} | ||||||
|                 } else if (op.value === currentVal) { |                 } else if (op.value === currentVal) { | ||||||
| @@ -624,8 +686,8 @@ | |||||||
|                     currentValues[v] = true; |                     currentValues[v] = true; | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|             for (var i=0;i<opt.options.length;i++) { |             for (var i=0;i<_options.length;i++) { | ||||||
|                 op = opt.options[i]; |                 op = _options[i]; | ||||||
|                 var val = typeof op === "string" ? op : op.value; |                 var val = typeof op === "string" ? op : op.value; | ||||||
|                 if (currentValues.hasOwnProperty(val)) { |                 if (currentValues.hasOwnProperty(val)) { | ||||||
|                     delete currentValues[val]; |                     delete currentValues[val]; | ||||||
| @@ -952,12 +1014,12 @@ | |||||||
|             } |             } | ||||||
|             if (menu.opts.multiple) { |             if (menu.opts.multiple) { | ||||||
|                 var selected = {}; |                 var selected = {}; | ||||||
|                  this.value().split(",").forEach(function(f) { |                 this.value().split(",").forEach(function(f) { | ||||||
|                      selected[f] = true; |                     selected[f] = true; | ||||||
|                  }) |                 }); | ||||||
|                 menu.find('input[type="checkbox"]').each(function() { |                 menu.find('input[type="checkbox"]').each(function() { | ||||||
|                     $(this).prop("checked",selected[$(this).data('value')]) |                     $(this).prop("checked", selected[$(this).data('value')] || false); | ||||||
|                 }) |                 }); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1048,7 +1110,7 @@ | |||||||
|                         this.input.trigger('change',[this.propertyType,this.value()]); |                         this.input.trigger('change',[this.propertyType,this.value()]); | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     this.optionSelectLabel.text(o.length+" selected"); |                     this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length })); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
| @@ -1056,7 +1118,9 @@ | |||||||
|             if (this.optionMenu) { |             if (this.optionMenu) { | ||||||
|                 this.optionMenu.remove(); |                 this.optionMenu.remove(); | ||||||
|             } |             } | ||||||
|             this.menu.remove(); |             if (this.menu) { | ||||||
|  |                 this.menu.remove(); | ||||||
|  |             } | ||||||
|             this.uiSelect.remove(); |             this.uiSelect.remove(); | ||||||
|         }, |         }, | ||||||
|         types: function(types) { |         types: function(types) { | ||||||
| @@ -1089,7 +1153,7 @@ | |||||||
|             this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); |             this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); | ||||||
|             if (currentType && !this.typeMap.hasOwnProperty(currentType)) { |             if (currentType && !this.typeMap.hasOwnProperty(currentType)) { | ||||||
|                 if (!firstCall) { |                 if (!firstCall) { | ||||||
|                     this.type(this.typeList[0].value); |                     this.type(this.typeList[0]?.value || ""); // permit empty typeList | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 this.propertyType = null; |                 this.propertyType = null; | ||||||
| @@ -1126,6 +1190,11 @@ | |||||||
|                 var selectedOption = []; |                 var selectedOption = []; | ||||||
|                 var valueToCheck = value; |                 var valueToCheck = value; | ||||||
|                 if (opt.options) { |                 if (opt.options) { | ||||||
|  |                     let _options = opt.options | ||||||
|  |                     if (typeof opt.options === "function") { | ||||||
|  |                         _options = opt.options.call(this) | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     if (opt.hasValue && opt.parse) { |                     if (opt.hasValue && opt.parse) { | ||||||
|                         var parts = opt.parse(value); |                         var parts = opt.parse(value); | ||||||
|                         if (this.options.debug) { console.log(this.identifier,"new parse",parts) } |                         if (this.options.debug) { console.log(this.identifier,"new parse",parts) } | ||||||
| @@ -1139,8 +1208,8 @@ | |||||||
|                         checkValues = valueToCheck.split(","); |                         checkValues = valueToCheck.split(","); | ||||||
|                     } |                     } | ||||||
|                     checkValues.forEach(function(valueToCheck) { |                     checkValues.forEach(function(valueToCheck) { | ||||||
|                         for (var i=0;i<opt.options.length;i++) { |                         for (var i=0;i<_options.length;i++) { | ||||||
|                             var op = opt.options[i]; |                             var op = _options[i]; | ||||||
|                             if (typeof op === "string") { |                             if (typeof op === "string") { | ||||||
|                                 if (op === valueToCheck || op === ""+valueToCheck) { |                                 if (op === valueToCheck || op === ""+valueToCheck) { | ||||||
|                                     selectedOption.push(that.activeOptions[op]); |                                     selectedOption.push(that.activeOptions[op]); | ||||||
| @@ -1175,7 +1244,7 @@ | |||||||
|         }, |         }, | ||||||
|         type: function(type) { |         type: function(type) { | ||||||
|             if (!arguments.length) { |             if (!arguments.length) { | ||||||
|                 return this.propertyType; |                 return this.propertyType || this.options?.default || ''; | ||||||
|             } else { |             } else { | ||||||
|                 var that = this; |                 var that = this; | ||||||
|                 if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) } |                 if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) } | ||||||
| @@ -1276,6 +1345,10 @@ | |||||||
|                         this.optionMenu = null; |                         this.optionMenu = null; | ||||||
|                     } |                     } | ||||||
|                     if (opt.options) { |                     if (opt.options) { | ||||||
|  |                         let _options = opt.options | ||||||
|  |                         if (typeof _options === "function") { | ||||||
|  |                             _options = opt.options.call(this); | ||||||
|  |                         } | ||||||
|                         if (this.optionExpandButton) { |                         if (this.optionExpandButton) { | ||||||
|                             this.optionExpandButton.hide(); |                             this.optionExpandButton.hide(); | ||||||
|                             this.optionExpandButton.shown = false; |                             this.optionExpandButton.shown = false; | ||||||
| @@ -1292,7 +1365,7 @@ | |||||||
|                                 this.valueLabelContainer.hide(); |                                 this.valueLabelContainer.hide(); | ||||||
|                             } |                             } | ||||||
|                             this.activeOptions = {}; |                             this.activeOptions = {}; | ||||||
|                             opt.options.forEach(function(o) { |                             _options.forEach(function(o) { | ||||||
|                                 if (typeof o === 'string') { |                                 if (typeof o === 'string') { | ||||||
|                                     that.activeOptions[o] = {label:o,value:o}; |                                     that.activeOptions[o] = {label:o,value:o}; | ||||||
|                                 } else { |                                 } else { | ||||||
| @@ -1312,7 +1385,7 @@ | |||||||
|                                     if (validValues) { |                                     if (validValues) { | ||||||
|                                         that._updateOptionSelectLabel(validValues) |                                         that._updateOptionSelectLabel(validValues) | ||||||
|                                     } else { |                                     } else { | ||||||
|                                         op = opt.options[0]; |                                         op = _options[0] || {value:""}; // permit zero options | ||||||
|                                         if (typeof op === "string") { |                                         if (typeof op === "string") { | ||||||
|                                             this.value(op); |                                             this.value(op); | ||||||
|                                             that._updateOptionSelectLabel({value:op}); |                                             that._updateOptionSelectLabel({value:op}); | ||||||
| @@ -1331,7 +1404,7 @@ | |||||||
|                                     that._updateOptionSelectLabel(validValues); |                                     that._updateOptionSelectLabel(validValues); | ||||||
|                                 } |                                 } | ||||||
|                             } else { |                             } else { | ||||||
|                                 var selectedOption = this.optionValue||opt.options[0]; |                                 var selectedOption = this.optionValue||_options[0]; | ||||||
|                                 if (opt.parse) { |                                 if (opt.parse) { | ||||||
|                                     var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption |                                     var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption | ||||||
|                                     var parts = opt.parse(this.input.val(),selectedOptionObj); |                                     var parts = opt.parse(this.input.val(),selectedOptionObj); | ||||||
| @@ -1375,7 +1448,7 @@ | |||||||
|                                     }) |                                     }) | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                             this.optionMenu = this._createMenu(opt.options,opt,function(v){ |                             this.optionMenu = this._createMenu(_options,opt,function(v){ | ||||||
|                                 if (!opt.multiple) { |                                 if (!opt.multiple) { | ||||||
|                                     that._updateOptionSelectLabel(that.activeOptions[v]); |                                     that._updateOptionSelectLabel(that.activeOptions[v]); | ||||||
|                                     if (!opt.hasValue) { |                                     if (!opt.hasValue) { | ||||||
| @@ -1470,26 +1543,48 @@ | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         validate: function() { |         validate: function(options) { | ||||||
|             var result; |             let valid = true; | ||||||
|             var value = this.value(); |             const value = this.value(); | ||||||
|             var type = this.type(); |             const type = this.type(); | ||||||
|             if (this.typeMap[type] && this.typeMap[type].validate) { |             if (this.typeMap[type] && this.typeMap[type].validate) { | ||||||
|                 var val = this.typeMap[type].validate; |                 const validate = this.typeMap[type].validate; | ||||||
|                 if (typeof val === 'function') { |                 if (typeof validate === 'function') { | ||||||
|                     result = val(value); |                     valid = validate(value, {}); | ||||||
|                 } else { |                 } 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 { |             } 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) { |             if (options?.returnErrorMessage === true) { | ||||||
|                 this.uiSelect.removeClass('input-error'); |                 return valid; | ||||||
|             } else { |  | ||||||
|                 this.uiSelect.addClass('input-error'); |  | ||||||
|             } |             } | ||||||
|             return result; |             // Must return a boolean for no 3.x validator | ||||||
|  |             return (typeof valid === "string") ? false : valid; | ||||||
|         }, |         }, | ||||||
|         show: function() { |         show: function() { | ||||||
|             this.uiSelect.show(); |             this.uiSelect.show(); | ||||||
|   | |||||||
| @@ -118,10 +118,16 @@ RED.contextMenu = (function () { | |||||||
|                     onselect: 'core:split-wire-with-link-nodes', |                     onselect: 'core:split-wire-with-link-nodes', | ||||||
|                     disabled: !canEdit || !hasLinks |                     disabled: !canEdit || !hasLinks | ||||||
|                 }, |                 }, | ||||||
|                 null, |                 null | ||||||
|                 { onselect: 'core:show-import-dialog', label: RED._('common.label.import')}, |  | ||||||
|                 { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') } |  | ||||||
|             ) |             ) | ||||||
|  |             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) { |             if (hasSelection && canEdit) { | ||||||
|                 const nodeOptions = [] |                 const nodeOptions = [] | ||||||
|                 if (!hasMultipleSelection && !isGroup) { |                 if (!hasMultipleSelection && !isGroup) { | ||||||
| @@ -194,8 +200,14 @@ RED.contextMenu = (function () { | |||||||
|                 { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() }, |                 { 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', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete }, | ||||||
|                 { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), 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") } | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,6 +34,8 @@ RED.deploy = (function() { | |||||||
|  |  | ||||||
|     var currentDiff = null; |     var currentDiff = null; | ||||||
|  |  | ||||||
|  |     var activeBackgroundDeployNotification; | ||||||
|  |  | ||||||
|     function changeDeploymentType(type) { |     function changeDeploymentType(type) { | ||||||
|         deploymentType = type; |         deploymentType = type; | ||||||
|         $("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img); |         $("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img); | ||||||
| @@ -61,7 +63,7 @@ RED.deploy = (function() { | |||||||
|                  '<img src="red/images/spin.svg"/>'+ |                  '<img src="red/images/spin.svg"/>'+ | ||||||
|                 '</span>'+ |                 '</span>'+ | ||||||
|               '</a>'+ |               '</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"); |               '</span></li>').prependTo(".red-ui-header-toolbar"); | ||||||
|             const mainMenuItems = [ |             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")}}}, |                     {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); }); |             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) { |         RED.events.on('workspace:dirty',function(state) { | ||||||
|  |             if (RED.settings.user?.permissions === 'read') { | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|             if (state.dirty) { |             if (state.dirty) { | ||||||
|                 window.onbeforeunload = function() { |                 // window.onbeforeunload = function() { | ||||||
|                     return RED._("deploy.confirm.undeployedChanges"); |                 //     return  | ||||||
|                 } |                 // } | ||||||
|                 $("#red-ui-header-button-deploy").removeClass("disabled"); |                 $("#red-ui-header-button-deploy").removeClass("disabled"); | ||||||
|             } else { |             } else { | ||||||
|                 window.onbeforeunload = null; |                 // window.onbeforeunload = null; | ||||||
|                 $("#red-ui-header-button-deploy").addClass("disabled"); |                 $("#red-ui-header-button-deploy").addClass("disabled"); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         var activeNotifyMessage; |  | ||||||
|         RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) { |         RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) { | ||||||
|             if (!activeNotifyMessage) { |             var currentRev = RED.nodes.version(); | ||||||
|                 var currentRev = RED.nodes.version(); |             if (currentRev === null || deployInflight || currentRev === msg.revision) { | ||||||
|                 if (currentRev === null || deployInflight || currentRev === msg.revision) { |                 return; | ||||||
|                     return; |             } | ||||||
|                 } |             if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) { | ||||||
|                 var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); |                 activeBackgroundDeployNotification.showNotification() | ||||||
|                 activeNotifyMessage = RED.notify(message,{ |                 return | ||||||
|                     modal: true, |             } | ||||||
|                     fixed: true, |             const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); | ||||||
|                     buttons: [ |             const options = { | ||||||
|                         { |                 id: 'background-update', | ||||||
|                             text: RED._('deploy.confirm.button.ignore'), |                 type: 'compact', | ||||||
|                             click: function() { |                 modal: false, | ||||||
|                                 activeNotifyMessage.close(); |                 fixed: true, | ||||||
|                                 activeNotifyMessage = null; |                 timeout: 10000, | ||||||
|                             } |                 buttons: [ | ||||||
|                         }, |                     { | ||||||
|                         { |                         text: RED._('deploy.confirm.button.review'), | ||||||
|                             text: RED._('deploy.confirm.button.review'), |                         class: "primary", | ||||||
|                             class: "primary", |                         click: function() { | ||||||
|                             click: function() { |                             activeBackgroundDeployNotification.hideNotification(); | ||||||
|                                 activeNotifyMessage.close(); |                             var nns = RED.nodes.createCompleteNodeSet(); | ||||||
|                                 var nns = RED.nodes.createCompleteNodeSet(); |                             resolveConflict(nns,false); | ||||||
|                                 resolveConflict(nns,false); |  | ||||||
|                                 activeNotifyMessage = null; |  | ||||||
|                             } |  | ||||||
|                         } |                         } | ||||||
|                     ] |                     } | ||||||
|                 }); |                 ] | ||||||
|  |             } | ||||||
|  |             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) { |     function getNodeInfo(node) { | ||||||
| @@ -213,7 +242,11 @@ RED.deploy = (function() { | |||||||
|                 class: "primary disabled", |                 class: "primary disabled", | ||||||
|                 click: function() { |                 click: function() { | ||||||
|                     if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) { |                     if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) { | ||||||
|                         RED.diff.showRemoteDiff(); |                         RED.diff.showRemoteDiff(null, { | ||||||
|  |                             onmerge: function () { | ||||||
|  |                                 activeBackgroundDeployNotification.close() | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|                         conflictNotification.close(); |                         conflictNotification.close(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @@ -226,6 +259,7 @@ RED.deploy = (function() { | |||||||
|                     if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) { |                     if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) { | ||||||
|                         RED.diff.mergeDiff(currentDiff); |                         RED.diff.mergeDiff(currentDiff); | ||||||
|                         conflictNotification.close(); |                         conflictNotification.close(); | ||||||
|  |                         activeBackgroundDeployNotification.close() | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -238,6 +272,7 @@ RED.deploy = (function() { | |||||||
|                 click: function() { |                 click: function() { | ||||||
|                     save(true,activeDeploy); |                     save(true,activeDeploy); | ||||||
|                     conflictNotification.close(); |                     conflictNotification.close(); | ||||||
|  |                     activeBackgroundDeployNotification.close() | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| @@ -248,21 +283,17 @@ RED.deploy = (function() { | |||||||
|             buttons: buttons |             buttons: buttons | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         var now = Date.now(); |  | ||||||
|         RED.diff.getRemoteDiff(function(diff) { |         RED.diff.getRemoteDiff(function(diff) { | ||||||
|             var ellapsed = Math.max(1000 - (Date.now()-now), 0); |  | ||||||
|             currentDiff = diff; |             currentDiff = diff; | ||||||
|             setTimeout(function() { |             conflictCheck.hide(); | ||||||
|                 conflictCheck.hide(); |             var d = Object.keys(diff.conflicts); | ||||||
|                 var d = Object.keys(diff.conflicts); |             if (d.length === 0) { | ||||||
|                 if (d.length === 0) { |                 conflictAutoMerge.show(); | ||||||
|                     conflictAutoMerge.show(); |                 $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') | ||||||
|                     $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') |             } else { | ||||||
|                 } else { |                 conflictManualMerge.show(); | ||||||
|                     conflictManualMerge.show(); |             } | ||||||
|                 } |             $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') | ||||||
|                 $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') |  | ||||||
|             },ellapsed); |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|     function cropList(list) { |     function cropList(list) { | ||||||
| @@ -612,7 +643,10 @@ RED.deploy = (function() { | |||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|             RED.nodes.eachSubflow(function (subflow) { |             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) { |             RED.nodes.eachWorkspace(function (ws) { | ||||||
|                 if (ws.changed || ws.added) { |                 if (ws.changed || ws.added) { | ||||||
| @@ -628,6 +662,7 @@ RED.deploy = (function() { | |||||||
|             // Once deployed, cannot undo back to a clean state |             // Once deployed, cannot undo back to a clean state | ||||||
|             RED.history.markAllDirty(); |             RED.history.markAllDirty(); | ||||||
|             RED.view.redraw(); |             RED.view.redraw(); | ||||||
|  |             RED.sidebar.config.refresh(); | ||||||
|             RED.events.emit("deploy"); |             RED.events.emit("deploy"); | ||||||
|         }).fail(function (xhr, textStatus, err) { |         }).fail(function (xhr, textStatus, err) { | ||||||
|             RED.nodes.dirty(true); |             RED.nodes.dirty(true); | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| RED.diff = (function() { | RED.diff = (function() { | ||||||
|  |  | ||||||
|     var currentDiff = {}; |     var currentDiff = {}; | ||||||
|     var diffVisible = false; |     var diffVisible = false; | ||||||
|     var diffList; |     var diffList; | ||||||
| @@ -62,12 +61,14 @@ RED.diff = (function() { | |||||||
|                         addedCount:0, |                         addedCount:0, | ||||||
|                         deletedCount:0, |                         deletedCount:0, | ||||||
|                         changedCount:0, |                         changedCount:0, | ||||||
|  |                         movedCount:0, | ||||||
|                         unchangedCount: 0 |                         unchangedCount: 0 | ||||||
|                     }, |                     }, | ||||||
|                     remote: { |                     remote: { | ||||||
|                         addedCount:0, |                         addedCount:0, | ||||||
|                         deletedCount:0, |                         deletedCount:0, | ||||||
|                         changedCount:0, |                         changedCount:0, | ||||||
|  |                         movedCount:0, | ||||||
|                         unchangedCount: 0 |                         unchangedCount: 0 | ||||||
|                     }, |                     }, | ||||||
|                     conflicts: 0 |                     conflicts: 0 | ||||||
| @@ -138,7 +139,7 @@ RED.diff = (function() { | |||||||
|                             $(this).parent().toggleClass('collapsed'); |                             $(this).parent().toggleClass('collapsed'); | ||||||
|                         }); |                         }); | ||||||
|  |  | ||||||
|                         createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div); |                         createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div); | ||||||
|                         selectState = ""; |                         selectState = ""; | ||||||
|                         if (conflicts[tab.id]) { |                         if (conflicts[tab.id]) { | ||||||
|                             flowStats.conflicts++; |                             flowStats.conflicts++; | ||||||
| @@ -208,19 +209,26 @@ RED.diff = (function() { | |||||||
|                         var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell); |                         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); |                         $('<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); |                             $('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats); | ||||||
|                             if (flowStats.conflicts > 0) { |                             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); |                                 $('<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) { |                             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) { |                             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) { |                             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); |                             $('<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); |                             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); |                             $('<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); |                                 $('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats); | ||||||
|                                 if (flowStats.conflicts > 0) { |                                 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); |                                     $('<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) { |                                 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) { |                                 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) { |                                 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); |                                 $('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats); | ||||||
|                             } |                             } | ||||||
| @@ -293,7 +308,7 @@ RED.diff = (function() { | |||||||
|         if (options.mode === "merge") { |         if (options.mode === "merge") { | ||||||
|             diffPanel.addClass("red-ui-diff-panel-merge"); |             diffPanel.addClass("red-ui-diff-panel-merge"); | ||||||
|         } |         } | ||||||
|         var diffList = createDiffTable(diffPanel, diff); |         var diffList = createDiffTable(diffPanel, diff, options); | ||||||
|  |  | ||||||
|         var localDiff = diff.localDiff; |         var localDiff = diff.localDiff; | ||||||
|         var remoteDiff = diff.remoteDiff; |         var remoteDiff = diff.remoteDiff; | ||||||
| @@ -516,7 +531,6 @@ RED.diff = (function() { | |||||||
|  |  | ||||||
|         var hasChanges = false; // exists in original and local/remote but with changes |         var hasChanges = false; // exists in original and local/remote but with changes | ||||||
|         var unChanged = true; // existing in original,local,remote unchanged |         var unChanged = true; // existing in original,local,remote unchanged | ||||||
|         var localChanged = false; |  | ||||||
|  |  | ||||||
|         if (localDiff.added[node.id]) { |         if (localDiff.added[node.id]) { | ||||||
|             stats.local.addedCount++; |             stats.local.addedCount++; | ||||||
| @@ -535,12 +549,20 @@ RED.diff = (function() { | |||||||
|             unChanged = false; |             unChanged = false; | ||||||
|         } |         } | ||||||
|         if (localDiff.changed[node.id]) { |         if (localDiff.changed[node.id]) { | ||||||
|             stats.local.changedCount++; |             if (localDiff.positionChanged[node.id]) { | ||||||
|  |                 stats.local.movedCount++ | ||||||
|  |             } else { | ||||||
|  |                 stats.local.changedCount++; | ||||||
|  |             } | ||||||
|             hasChanges = true; |             hasChanges = true; | ||||||
|             unChanged = false; |             unChanged = false; | ||||||
|         } |         } | ||||||
|         if (remoteDiff && remoteDiff.changed[node.id]) { |         if (remoteDiff && remoteDiff.changed[node.id]) { | ||||||
|             stats.remote.changedCount++; |             if (remoteDiff.positionChanged[node.id]) { | ||||||
|  |                 stats.remote.movedCount++ | ||||||
|  |             } else { | ||||||
|  |                 stats.remote.changedCount++; | ||||||
|  |             } | ||||||
|             hasChanges = true; |             hasChanges = true; | ||||||
|             unChanged = false; |             unChanged = false; | ||||||
|         } |         } | ||||||
| @@ -605,27 +627,32 @@ RED.diff = (function() { | |||||||
|                     localNodeDiv.addClass("red-ui-diff-status-moved"); |                     localNodeDiv.addClass("red-ui-diff-status-moved"); | ||||||
|                     var localMovedMessage = ""; |                     var localMovedMessage = ""; | ||||||
|                     if (node.z === localN.z) { |                     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 { |                     } 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); |                     $('<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]) { |             } else if (localDiff.deleted[node.z]) { | ||||||
|                 localNodeDiv.addClass("red-ui-diff-empty"); |                 localNodeDiv.addClass("red-ui-diff-empty"); | ||||||
|                 localChanged = true; |  | ||||||
|             } else if (localDiff.deleted[node.id]) { |             } else if (localDiff.deleted[node.id]) { | ||||||
|                 localNodeDiv.addClass("red-ui-diff-status-deleted"); |                 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); |                 $('<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]) { |             } else if (localDiff.changed[node.id]) { | ||||||
|                 if (localDiff.newConfig.all[node.id].z !== node.z) { |                 if (localDiff.newConfig.all[node.id].z !== node.z) { | ||||||
|                     localNodeDiv.addClass("red-ui-diff-empty"); |                     localNodeDiv.addClass("red-ui-diff-empty"); | ||||||
|                 } else { |                 } else { | ||||||
|                     localNodeDiv.addClass("red-ui-diff-status-changed"); |                     if (localDiff.positionChanged[node.id]) { | ||||||
|                     $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv); |                         localNodeDiv.addClass("red-ui-diff-status-moved"); | ||||||
|                     localChanged = true; |                         $('<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 { |             } else { | ||||||
|                 if (localDiff.newConfig.all[node.id].z !== node.z) { |                 if (localDiff.newConfig.all[node.id].z !== node.z) { | ||||||
| @@ -646,9 +673,13 @@ RED.diff = (function() { | |||||||
|                         remoteNodeDiv.addClass("red-ui-diff-status-moved"); |                         remoteNodeDiv.addClass("red-ui-diff-status-moved"); | ||||||
|                         var remoteMovedMessage = ""; |                         var remoteMovedMessage = ""; | ||||||
|                         if (node.z === remoteN.z) { |                         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 { |                         } 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); |                         $('<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) { |                     if (remoteDiff.newConfig.all[node.id].z !== node.z) { | ||||||
|                         remoteNodeDiv.addClass("red-ui-diff-empty"); |                         remoteNodeDiv.addClass("red-ui-diff-empty"); | ||||||
|                     } else { |                     } else { | ||||||
|                         remoteNodeDiv.addClass("red-ui-diff-status-changed"); |                         if (remoteDiff.positionChanged[node.id]) { | ||||||
|                         $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv); |                             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 { |                 } else { | ||||||
|                     if (remoteDiff.newConfig.all[node.id].z !== node.z) { |                     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); |             $("<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); |             localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row); | ||||||
|             if (localNode) { |             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); |                 $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell); | ||||||
|                 element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell); |                 element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell); | ||||||
|                 var localPosition = {x:localNode.x,y:localNode.y}; |                 var localPosition = {x:localNode.x,y:localNode.y}; | ||||||
| @@ -813,7 +849,7 @@ RED.diff = (function() { | |||||||
|  |  | ||||||
|             if (remoteNode !== undefined) { |             if (remoteNode !== undefined) { | ||||||
|                 remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row); |                 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) { |                 if (remoteNode) { | ||||||
|                     $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell); |                     $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell); | ||||||
|                     element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell); |                     element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell); | ||||||
| @@ -1099,11 +1135,11 @@ RED.diff = (function() { | |||||||
|     //     var diff = generateDiff(originalFlow,nns); |     //     var diff = generateDiff(originalFlow,nns); | ||||||
|     //     showDiff(diff); |     //     showDiff(diff); | ||||||
|     // } |     // } | ||||||
|     function showRemoteDiff(diff) { |     function showRemoteDiff(diff, options = {}) { | ||||||
|         if (diff === undefined) { |         if (!diff) { | ||||||
|             getRemoteDiff(showRemoteDiff); |             getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options)); | ||||||
|         } else { |         } else { | ||||||
|             showDiff(diff,{mode:'merge'}); |             showDiff(diff,{...options, mode:'merge'}); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     function parseNodes(nodeList) { |     function parseNodes(nodeList) { | ||||||
| @@ -1144,23 +1180,53 @@ RED.diff = (function() { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     function generateDiff(currentNodes,newNodes) { |     function generateDiff(currentNodes,newNodes) { | ||||||
|         var currentConfig = parseNodes(currentNodes); |         const currentConfig = parseNodes(currentNodes); | ||||||
|         var newConfig = parseNodes(newNodes); |         const newConfig = parseNodes(newNodes); | ||||||
|         var added = {}; |         const added = {}; | ||||||
|         var deleted = {}; |         const deleted = {}; | ||||||
|         var changed = {}; |         const changed = {}; | ||||||
|         var moved = {}; |         const positionChanged = {}; | ||||||
|  |         const moved = {}; | ||||||
|  |  | ||||||
|         Object.keys(currentConfig.all).forEach(function(id) { |         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)) { |             if (!newConfig.all.hasOwnProperty(id)) { | ||||||
|                 deleted[id] = true; |                 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; |                 changed[id] = true; | ||||||
|  |  | ||||||
|                 if (currentConfig.all[id].z !== newConfig.all[id].z) { |                 if (currentConfig.all[id].z !== newConfig.all[id].z) { | ||||||
|                     moved[id] = true; |                     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) { |         Object.keys(newConfig.all).forEach(function(id) { | ||||||
| @@ -1169,13 +1235,14 @@ RED.diff = (function() { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         var diff = { |         const diff = { | ||||||
|             currentConfig: currentConfig, |             currentConfig, | ||||||
|             newConfig: newConfig, |             newConfig, | ||||||
|             added: added, |             added, | ||||||
|             deleted: deleted, |             deleted, | ||||||
|             changed: changed, |             changed, | ||||||
|             moved: moved |             positionChanged, | ||||||
|  |             moved | ||||||
|         }; |         }; | ||||||
|         return diff; |         return diff; | ||||||
|     } |     } | ||||||
| @@ -1240,12 +1307,14 @@ RED.diff = (function() { | |||||||
|         return diff; |         return diff; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function showDiff(diff,options) { |     function showDiff(diff, options) { | ||||||
|         if (diffVisible) { |         if (diffVisible) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         options = options || {}; |         options = options || {}; | ||||||
|         var mode = options.mode || 'merge'; |         var mode = options.mode || 'merge'; | ||||||
|  |          | ||||||
|  |         options.hidePositionChanges = true | ||||||
|  |  | ||||||
|         var localDiff = diff.localDiff; |         var localDiff = diff.localDiff; | ||||||
|         var remoteDiff = diff.remoteDiff; |         var remoteDiff = diff.remoteDiff; | ||||||
| @@ -1315,6 +1384,9 @@ RED.diff = (function() { | |||||||
|                         if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) { |                         if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) { | ||||||
|                             refreshConflictHeader(diff); |                             refreshConflictHeader(diff); | ||||||
|                             mergeDiff(diff); |                             mergeDiff(diff); | ||||||
|  |                             if (options.onmerge) { | ||||||
|  |                                 options.onmerge() | ||||||
|  |                             } | ||||||
|                             RED.tray.close(); |                             RED.tray.close(); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -1345,6 +1417,7 @@ RED.diff = (function() { | |||||||
|         var newConfig = []; |         var newConfig = []; | ||||||
|         var node; |         var node; | ||||||
|         var nodeChangedStates = {}; |         var nodeChangedStates = {}; | ||||||
|  |         var nodeMovedStates = {}; | ||||||
|         var localChangedStates = {}; |         var localChangedStates = {}; | ||||||
|         for (id in localDiff.newConfig.all) { |         for (id in localDiff.newConfig.all) { | ||||||
|             if (localDiff.newConfig.all.hasOwnProperty(id)) { |             if (localDiff.newConfig.all.hasOwnProperty(id)) { | ||||||
| @@ -1352,12 +1425,14 @@ RED.diff = (function() { | |||||||
|                 if (resolutions[id] === 'local') { |                 if (resolutions[id] === 'local') { | ||||||
|                     if (node) { |                     if (node) { | ||||||
|                         nodeChangedStates[id] = node.changed; |                         nodeChangedStates[id] = node.changed; | ||||||
|  |                         nodeMovedStates[id] = node.moved; | ||||||
|                     } |                     } | ||||||
|                     newConfig.push(localDiff.newConfig.all[id]); |                     newConfig.push(localDiff.newConfig.all[id]); | ||||||
|                 } else if (resolutions[id] === 'remote') { |                 } else if (resolutions[id] === 'remote') { | ||||||
|                     if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) { |                     if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) { | ||||||
|                         if (node) { |                         if (node) { | ||||||
|                             nodeChangedStates[id] = node.changed; |                             nodeChangedStates[id] = node.changed; | ||||||
|  |                             nodeMovedStates[id] = node.moved; | ||||||
|                         } |                         } | ||||||
|                         localChangedStates[id] = 1; |                         localChangedStates[id] = 1; | ||||||
|                         newConfig.push(remoteDiff.newConfig.all[id]); |                         newConfig.push(remoteDiff.newConfig.all[id]); | ||||||
| @@ -1381,8 +1456,9 @@ RED.diff = (function() { | |||||||
|         } |         } | ||||||
|         return { |         return { | ||||||
|             config: newConfig, |             config: newConfig, | ||||||
|             nodeChangedStates: nodeChangedStates, |             nodeChangedStates, | ||||||
|             localChangedStates: localChangedStates |             nodeMovedStates, | ||||||
|  |             localChangedStates | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1393,6 +1469,7 @@ RED.diff = (function() { | |||||||
|  |  | ||||||
|         var newConfig = appliedDiff.config; |         var newConfig = appliedDiff.config; | ||||||
|         var nodeChangedStates = appliedDiff.nodeChangedStates; |         var nodeChangedStates = appliedDiff.nodeChangedStates; | ||||||
|  |         var nodeMovedStates = appliedDiff.nodeMovedStates; | ||||||
|         var localChangedStates = appliedDiff.localChangedStates; |         var localChangedStates = appliedDiff.localChangedStates; | ||||||
|  |  | ||||||
|         var isDirty = RED.nodes.dirty(); |         var isDirty = RED.nodes.dirty(); | ||||||
| @@ -1401,33 +1478,56 @@ RED.diff = (function() { | |||||||
|             t:"replace", |             t:"replace", | ||||||
|             config: RED.nodes.createCompleteNodeSet(), |             config: RED.nodes.createCompleteNodeSet(), | ||||||
|             changed: nodeChangedStates, |             changed: nodeChangedStates, | ||||||
|  |             moved: nodeMovedStates, | ||||||
|  |             complete: true, | ||||||
|             dirty: isDirty, |             dirty: isDirty, | ||||||
|             rev: RED.nodes.version() |             rev: RED.nodes.version() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         RED.history.push(historyEvent); |         RED.history.push(historyEvent); | ||||||
|  |  | ||||||
|         var originalFlow = RED.nodes.originalFlow(); |         // var originalFlow = RED.nodes.originalFlow(); | ||||||
|         // originalFlow is what the editor things it loaded |         // // originalFlow is what the editor thinks it loaded | ||||||
|         //  - add any newly added nodes from remote diff as they are now part of the record |         // //  - add any newly added nodes from remote diff as they are now part of the record | ||||||
|         for (var id in diff.remoteDiff.added) { |         // for (var id in diff.remoteDiff.added) { | ||||||
|             if (diff.remoteDiff.added.hasOwnProperty(id)) { |         //     if (diff.remoteDiff.added.hasOwnProperty(id)) { | ||||||
|                 if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { |         //         if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { | ||||||
|                     originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); |         //             originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); | ||||||
|                 } |         //         } | ||||||
|             } |         //     } | ||||||
|         } |         // } | ||||||
|  |  | ||||||
|         RED.nodes.clear(); |         RED.nodes.clear(); | ||||||
|         var imported = RED.nodes.import(newConfig); |         var imported = RED.nodes.import(newConfig); | ||||||
|  |  | ||||||
|         // Restore the original flow so subsequent merge resolutions can properly |         // // Restore the original flow so subsequent merge resolutions can properly | ||||||
|         // identify new-vs-old |         // // identify new-vs-old | ||||||
|         RED.nodes.originalFlow(originalFlow); |         // 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) { |         imported.nodes.forEach(function(n) { | ||||||
|             if (nodeChangedStates[n.id] || localChangedStates[n.id]) { |             if (nodeChangedStates[n.id]) { | ||||||
|  |                 ensureUnlocked(n.z) | ||||||
|                 n.changed = true; |                 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); |         RED.nodes.version(diff.remoteDiff.rev); | ||||||
|   | |||||||
| @@ -157,6 +157,12 @@ RED.editor = (function() { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (valid && "validate" in definition[property]) { |         if (valid && "validate" in definition[property]) { | ||||||
|  |             if (definition[property].hasOwnProperty("required") && | ||||||
|  |                 definition[property].required === false) { | ||||||
|  |                 if (value === "") { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             try { |             try { | ||||||
|                 var opt = {}; |                 var opt = {}; | ||||||
|                 if (label) { |                 if (label) { | ||||||
| @@ -183,6 +189,11 @@ RED.editor = (function() { | |||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         } else if (valid) { |         } 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 the validator is not provided in node property => Check if the input has a validator | ||||||
|             if ("category" in node._def) { |             if ("category" in node._def) { | ||||||
|                 const isConfig = node._def.category === "config"; |                 const isConfig = node._def.category === "config"; | ||||||
| @@ -190,7 +201,10 @@ RED.editor = (function() { | |||||||
|                 const input = $("#"+prefix+"-"+property); |                 const input = $("#"+prefix+"-"+property); | ||||||
|                 const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0; |                 const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0; | ||||||
|                 if (isTypedInput) { |                 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(); |             var value = input.val(); | ||||||
|             if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { |             if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { | ||||||
|                 value = input.text(); |                 value = input.text(); | ||||||
|  |             } else if (input.attr("type") === "checkbox") { | ||||||
|  |                 value = input.prop("checked"); | ||||||
|             } |             } | ||||||
|             var valid = validateNodeProperty(node, defaults, property,value); |             var valid = validateNodeProperty(node, defaults, property,value); | ||||||
|             if (((typeof valid) === "string") || !valid) { |             if (((typeof valid) === "string") || !valid) { | ||||||
| @@ -326,49 +342,101 @@ RED.editor = (function() { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Create a config-node select box for this property |      * Create a config-node select box for this property | ||||||
|      * @param node - the node being edited |      * @param  {Object} node - the node being edited | ||||||
|      * @param property - the name of the field |      * @param {String} property - the name of the node property | ||||||
|      * @param type - the type of the config-node |      * @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) { |     function prepareConfigNodeSelect(node, property, type, prefix, filter, env) { | ||||||
|         var input = $("#"+prefix+"-"+property); |         let nodeValue | ||||||
|         if (input.length === 0 ) { |         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; |             return; | ||||||
|         } |         } | ||||||
|         var newWidth = input.width(); |         const attrStyle = input.attr('style'); | ||||||
|         var attrStyle = input.attr('style'); |         let newWidth; | ||||||
|         var m; |         let m; | ||||||
|         if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) { |         if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) { | ||||||
|             newWidth = m[2].trim(); |             newWidth = m[2].trim(); | ||||||
|         } else { |         } else { | ||||||
|             newWidth = "70%"; |             newWidth = "70%"; | ||||||
|         } |         } | ||||||
|         var outerWrap = $("<div></div>").css({ |         const outerWrap = $("<div></div>").css({ | ||||||
|             width: newWidth, |             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); |         input.replaceWith(outerWrap); | ||||||
|         // set the style attr directly - using width() on FF causes a value of 114%... |         // set the style attr directly - using width() on FF causes a value of 114%... | ||||||
|         select.css({ |         select.css({ | ||||||
|             'flex-grow': 1 |             '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>') |         updateConfigNodeSelect(property, type, nodeValue, prefix, filter); | ||||||
|             .css({"margin-left":"10px"}) |  | ||||||
|  |         // 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); |             .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(); |             e.preventDefault(); | ||||||
|         }); |         }); | ||||||
|         var label = ""; |  | ||||||
|         var configNode = RED.nodes.node(node[property]); |  | ||||||
|         var node_def = RED.nodes.getType(type); |  | ||||||
|  |  | ||||||
|         if (configNode) { |         // dont permit the user to click the button if the selected option is an env var | ||||||
|             label = RED.utils.getNodeLabel(configNode,configNode.id); |         select.on("change", function () { | ||||||
|         } |             const selectedOpt = select.find(":selected"); | ||||||
|         input.val(label); |             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 { |             try { | ||||||
|                 var rc = editing_node._def.oneditsave.call(editing_node); |                 const rc = editing_node._def.oneditsave.call(editing_node); | ||||||
|                 if (rc === true) { |                 if (rc === true) { | ||||||
|                     editState.changed = 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) { |             } catch(err) { | ||||||
|                 console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); |                 console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); | ||||||
| @@ -768,12 +843,9 @@ RED.editor = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function defaultConfigNodeSort(A,B) { |     function defaultConfigNodeSort(A,B) { | ||||||
|         if (A.__label__ < B.__label__) { |         // sort case insensitive so that `[env] node-name` items are at the top and | ||||||
|             return -1; |         // not mixed inbetween the the lower and upper case items | ||||||
|         } else if (A.__label__ > B.__label__) { |         return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'}) | ||||||
|             return 1; |  | ||||||
|         } |  | ||||||
|         return 0; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function updateConfigNodeSelect(name,type,value,prefix,filter) { |     function updateConfigNodeSelect(name,type,value,prefix,filter) { | ||||||
| @@ -788,7 +860,7 @@ RED.editor = (function() { | |||||||
|                 } |                 } | ||||||
|                 $("#"+prefix+"-"+name).val(value); |                 $("#"+prefix+"-"+name).val(value); | ||||||
|             } else { |             } else { | ||||||
|  |                 let inclSubflowEnvvars = false | ||||||
|                 var select = $("#"+prefix+"-"+name); |                 var select = $("#"+prefix+"-"+name); | ||||||
|                 var node_def = RED.nodes.getType(type); |                 var node_def = RED.nodes.getType(type); | ||||||
|                 select.children().remove(); |                 select.children().remove(); | ||||||
| @@ -796,6 +868,7 @@ RED.editor = (function() { | |||||||
|                 var activeWorkspace = RED.nodes.workspace(RED.workspaces.active()); |                 var activeWorkspace = RED.nodes.workspace(RED.workspaces.active()); | ||||||
|                 if (!activeWorkspace) { |                 if (!activeWorkspace) { | ||||||
|                     activeWorkspace = RED.nodes.subflow(RED.workspaces.active()); |                     activeWorkspace = RED.nodes.subflow(RED.workspaces.active()); | ||||||
|  |                     inclSubflowEnvvars = true | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 var configNodes = []; |                 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; |                 var configSortFn = defaultConfigNodeSort; | ||||||
|                 if (typeof node_def.sort == "function") { |                 if (typeof node_def.sort == "function") { | ||||||
|                     configSortFn = node_def.sort; |                     configSortFn = node_def.sort; | ||||||
| @@ -822,7 +920,10 @@ RED.editor = (function() { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 configNodes.forEach(function(cn) { |                 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__; |                     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); |                 window.setTimeout(function() { select.trigger("change");},50); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -914,6 +1022,17 @@ RED.editor = (function() { | |||||||
|                             dirty: startDirty |                             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.nodes.dirty(true); | ||||||
|                         RED.view.redraw(true); |                         RED.view.redraw(true); | ||||||
|                         RED.history.push(historyEvent); |                         RED.history.push(historyEvent); | ||||||
| @@ -1015,7 +1134,7 @@ RED.editor = (function() { | |||||||
|                                     } |                                     } | ||||||
|                                 }); |                                 }); | ||||||
|                             } |                             } | ||||||
|                             var historyEvent = { |                             let historyEvent = { | ||||||
|                                 t:'edit', |                                 t:'edit', | ||||||
|                                 node:editing_node, |                                 node:editing_node, | ||||||
|                                 changes:editState.changes, |                                 changes:editState.changes, | ||||||
| @@ -1031,6 +1150,15 @@ RED.editor = (function() { | |||||||
|                                     instances:subflowInstances |                                     instances:subflowInstances | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  |                             if (editState.history) { | ||||||
|  |                                 historyEvent = { | ||||||
|  |                                     t: 'multi', | ||||||
|  |                                     events: [ historyEvent, ...editState.history ], | ||||||
|  |                                     dirty: wasDirty | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |  | ||||||
|                             RED.history.push(historyEvent); |                             RED.history.push(historyEvent); | ||||||
|                         } |                         } | ||||||
|                         editing_node.dirty = true; |                         editing_node.dirty = true; | ||||||
| @@ -1483,9 +1611,16 @@ RED.editor = (function() { | |||||||
|                     } |                     } | ||||||
|                     RED.tray.close(function() { |                     RED.tray.close(function() { | ||||||
|                         var filter = null; |                         var filter = null; | ||||||
|                         if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') { |                         // when editing a config via subflow edit panel, the `configProperty` will not | ||||||
|                             filter = function(n) { |                         // necessarily be a property of the editContext._def.defaults object | ||||||
|                                 return editContext._def.defaults[configProperty].filter.call(editContext,n); |                         // 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); |                         updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter); | ||||||
| @@ -1546,7 +1681,7 @@ RED.editor = (function() { | |||||||
|                     RED.history.push(historyEvent); |                     RED.history.push(historyEvent); | ||||||
|                     RED.tray.close(function() { |                     RED.tray.close(function() { | ||||||
|                         var filter = null; |                         var filter = null; | ||||||
|                         if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') { |                         if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') { | ||||||
|                             filter = function(n) { |                             filter = function(n) { | ||||||
|                                 return editContext._def.defaults[configProperty].filter.call(editContext,n); |                                 return editContext._def.defaults[configProperty].filter.call(editContext,n); | ||||||
|                             } |                             } | ||||||
| @@ -1623,8 +1758,8 @@ RED.editor = (function() { | |||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         if (!isSameObj(old_env, new_env)) { |                         if (!isSameObj(old_env, new_env)) { | ||||||
|                             editing_node.env = new_env; |  | ||||||
|                             editState.changes.env = editing_node.env; |                             editState.changes.env = editing_node.env; | ||||||
|  |                             editing_node.env = new_env; | ||||||
|                             editState.changed = true; |                             editState.changed = true; | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
| @@ -2132,6 +2267,7 @@ RED.editor = (function() { | |||||||
|                 filteredEditPanes[type] = filter |                 filteredEditPanes[type] = filter | ||||||
|             } |             } | ||||||
|             editPanes[type] = definition; |             editPanes[type] = definition; | ||||||
|         } |         }, | ||||||
|  |         prepareConfigNodeSelect: prepareConfigNodeSelect, | ||||||
|     } |     } | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -165,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() { | |||||||
|         //Handles orphaned models |         //Handles orphaned models | ||||||
|         //ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed |         //ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed | ||||||
|         RED.events.on("editor:close",function() { |         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) { |             if(models && models.length) { | ||||||
|                 console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().") |                 console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().") | ||||||
|                 for (let index = 0; index < models.length; index++) { |                 for (let index = 0; index < models.length; index++) { | ||||||
| @@ -514,7 +520,7 @@ RED.editor.codeEditor.monaco = (function() { | |||||||
|                 _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions); |                 _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions); | ||||||
|                 if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); } |                 if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); } | ||||||
|             } catch (error) { |             } 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(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); } | ||||||
|                 if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); } |                 if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); } | ||||||
|             } catch (error) { |             } 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(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); } | ||||||
|                 if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); } |                 if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); } | ||||||
|             } catch (error) { |             } 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("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("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("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});', |                     createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});', | ||||||
|                         ["```typescript", |                         ["```typescript", | ||||||
|                         "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T", |                         "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T", | ||||||
| @@ -1124,6 +1130,7 @@ RED.editor.codeEditor.monaco = (function() { | |||||||
|  |  | ||||||
|             $(el).remove(); |             $(el).remove(); | ||||||
|             $(toolbarRow).remove(); |             $(toolbarRow).remove(); | ||||||
|  |             ed.dispose(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ed.resize = function resize() { |         ed.resize = function resize() { | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| RED.editor.envVarList = (function() { | RED.editor.envVarList = (function() { | ||||||
|  |  | ||||||
|     var currentLocale = 'en-US'; |     var currentLocale = 'en-US'; | ||||||
|     var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; |     const 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_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 |      * Create env var edit interface | ||||||
| @@ -10,8 +11,8 @@ RED.editor.envVarList = (function() { | |||||||
|      * @param node - subflow node |      * @param node - subflow node | ||||||
|      */ |      */ | ||||||
|     function buildPropertiesList(envContainer, node) { |     function buildPropertiesList(envContainer, node) { | ||||||
|  |         if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) } | ||||||
|         var isTemplateNode = (node.type === "subflow"); |         const isTemplateNode = (node.type === "subflow"); | ||||||
|  |  | ||||||
|         envContainer |         envContainer | ||||||
|             .css({ |             .css({ | ||||||
| @@ -83,7 +84,14 @@ RED.editor.envVarList = (function() { | |||||||
|                         // if `opt.ui` does not exist, then apply defaults. If these |                         // if `opt.ui` does not exist, then apply defaults. If these | ||||||
|                         // defaults do not change then they will get stripped off |                         // defaults do not change then they will get stripped off | ||||||
|                         // before saving. |                         // 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 || { |                             opt.ui = opt.ui || { | ||||||
|                                 icon: "", |                                 icon: "", | ||||||
|                                 type: "cred" |                                 type: "cred" | ||||||
| @@ -119,7 +127,7 @@ RED.editor.envVarList = (function() { | |||||||
|                             } |                             } | ||||||
|                         }); |                         }); | ||||||
|  |  | ||||||
|                         buildEnvEditRow(uiRow, opt.ui, nameField, valueField); |                         buildEnvEditRow(uiRow, opt, nameField, valueField); | ||||||
|                         nameField.trigger('change'); |                         nameField.trigger('change'); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
| @@ -181,21 +189,23 @@ RED.editor.envVarList = (function() { | |||||||
|      * @param nameField - name field of env var |      * @param nameField - name field of env var | ||||||
|      * @param valueField - value 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") |          container.addClass("red-ui-editor-subflow-env-ui-row") | ||||||
|          var topRow = $('<div></div>').appendTo(container); |          var topRow = $('<div></div>').appendTo(container); | ||||||
|          $('<div></div>').appendTo(topRow); |          $('<div></div>').appendTo(topRow); | ||||||
|          $('<div>').text(RED._("editor.icon")).appendTo(topRow); |          $('<div>').text(RED._("editor.icon")).appendTo(topRow); | ||||||
|          $('<div>').text(RED._("editor.label")).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); |          var row = $('<div></div>').appendTo(container); | ||||||
|          $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row); |          $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row); | ||||||
|          var typeOptions = { |          var typeOptions = { | ||||||
|              'input': {types:DEFAULT_ENV_TYPE_LIST}, |             'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES}, | ||||||
|              'select': {opts:[]}, |             'select': {opts:[]}, | ||||||
|              'spinner': {}, |             'spinner': {}, | ||||||
|              'cred': {} |             'cred': {} | ||||||
|          }; |          }; | ||||||
|          if (ui.opts) { |          if (ui.opts) { | ||||||
|              typeOptions[ui.type] = ui.opts; |              typeOptions[ui.type] = ui.opts; | ||||||
| @@ -260,15 +270,16 @@ RED.editor.envVarList = (function() { | |||||||
|             labelInput.attr("placeholder",$(this).val()) |             labelInput.attr("placeholder",$(this).val()) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         var inputCell = $('<div></div>').appendTo(row); |         var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row); | ||||||
|         var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell); |         var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell); | ||||||
|         if (ui.type === "input") { |         if (ui.type === "input") { | ||||||
|             inputCellInput.val(ui.opts.types.join(",")); |             uiInputTypeInput.val(ui.opts.types.join(",")); | ||||||
|         } |         } | ||||||
|         var checkbox; |         var checkbox; | ||||||
|         var selectBox; |         var selectBox; | ||||||
|  |  | ||||||
|         inputCellInput.typedInput({ |         // the options presented in the UI section for an "input" type selection | ||||||
|  |         uiInputTypeInput.typedInput({ | ||||||
|             types: [ |             types: [ | ||||||
|                 { |                 { | ||||||
|                     value:"input", |                     value:"input", | ||||||
| @@ -429,7 +440,7 @@ RED.editor.envVarList = (function() { | |||||||
|                                         } |                                         } | ||||||
|                                     }); |                                     }); | ||||||
|                                     ui.opts.opts = vals; |                                     ui.opts.opts = vals; | ||||||
|                                     inputCellInput.typedInput('value',Date.now()) |                                     uiInputTypeInput.typedInput('value',Date.now()) | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @@ -496,12 +507,13 @@ RED.editor.envVarList = (function() { | |||||||
|                                     } else { |                                     } else { | ||||||
|                                         delete ui.opts.max; |                                         delete ui.opts.max; | ||||||
|                                     } |                                     } | ||||||
|                                     inputCellInput.typedInput('value',Date.now()) |                                     uiInputTypeInput.typedInput('value',Date.now()) | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  |                 'conf-types', | ||||||
|                 { |                 { | ||||||
|                     value:"none", |                     value:"none", | ||||||
|                     label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false |                     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 |                 // 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 |                 // mode. Its value needs to be set to a comma-separately list of the | ||||||
|                 // selected options. |                 // 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 { |             } else { | ||||||
|                 // No other type cares about `value`, but doing this will |                 // No other type cares about `value`, but doing this will | ||||||
|                 // force a refresh of the label now that `ui.opts` has |                 // force a refresh of the label now that `ui.opts` has | ||||||
|                 // been updated. |                 // 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) { |             switch (ui.type) { | ||||||
|                 case 'input': |                 case 'input': | ||||||
|                     valueField.typedInput('types',ui.opts.types); |                     valueField.typedInput('types',ui.opts.types); | ||||||
| @@ -544,7 +562,7 @@ RED.editor.envVarList = (function() { | |||||||
|                     valueField.typedInput('types',['cred']); |                     valueField.typedInput('types',['cred']); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST) |                     valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST); | ||||||
|             } |             } | ||||||
|             if (ui.type === 'checkbox') { |             if (ui.type === 'checkbox') { | ||||||
|                 valueField.typedInput('type','bool'); |                 valueField.typedInput('type','bool'); | ||||||
| @@ -556,8 +574,46 @@ RED.editor.envVarList = (function() { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|         }).on("change", function(evt,type) { |         }).on("change", function(evt,type) { | ||||||
|             if (ui.type === 'input') { |             const selectedType = $(this).typedInput('type') // the UI typedInput type | ||||||
|                 var types = inputCellInput.typedInput('value'); |             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(","); |                 ui.opts.types = (types === "") ? ["str"] : types.split(","); | ||||||
|                 valueField.typedInput('types',ui.opts.types); |                 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' |         // Set the input to the right type. This will trigger the 'typedinputtypechange' | ||||||
|         // event handler (just above ^^) to update the value if needed |         // event handler (just above ^^) to update the value if needed | ||||||
|         inputCellInput.typedInput('type',ui.type) |         uiInputTypeInput.typedInput('type',ui.type) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function setLocale(l, list) { |     function setLocale(l, list) { | ||||||
|   | |||||||
| @@ -11,9 +11,22 @@ RED.editor.mermaid = (function () { | |||||||
|              |              | ||||||
|             if (!initializing) { |             if (!initializing) { | ||||||
|                 initializing = true |                 initializing = true | ||||||
|                 $.getScript( |                 // Find the cache-buster: | ||||||
|                     'vendor/mermaid/mermaid.min.js', |                 let cacheBuster | ||||||
|                     function (data, stat, jqxhr) { |                 $('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({ |                         mermaid.initialize({ | ||||||
|                             startOnLoad: false, |                             startOnLoad: false, | ||||||
|                             theme: RED.settings.get('mermaid', {}).theme |                             theme: RED.settings.get('mermaid', {}).theme | ||||||
| @@ -24,7 +37,7 @@ RED.editor.mermaid = (function () { | |||||||
|                             render(pending) |                             render(pending) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 ) |                 }); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             const nodes = document.querySelectorAll(selector) |             const nodes = document.querySelectorAll(selector) | ||||||
|   | |||||||
| @@ -153,10 +153,6 @@ RED.envVar = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function init(done) { |     function init(done) { | ||||||
|         if (!RED.user.hasPermission("settings.write")) { |  | ||||||
|             RED.notify(RED._("user.errors.settings"),"error"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         RED.userSettings.add({ |         RED.userSettings.add({ | ||||||
|             id:'envvar', |             id:'envvar', | ||||||
|             title: RED._("env-var.environment"), |             title: RED._("env-var.environment"), | ||||||
|   | |||||||
| @@ -221,12 +221,12 @@ RED.notifications = (function() { | |||||||
|                     if (newType) { |                     if (newType) { | ||||||
|                         n.className = "red-ui-notification red-ui-notification-"+newType; |                         n.className = "red-ui-notification red-ui-notification-"+newType; | ||||||
|                     } |                     } | ||||||
|  |                     newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout | ||||||
|                     if (!fixed || newOptions.fixed === false) { |                     if (!fixed || newOptions.fixed === false) { | ||||||
|                         newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000; |                         newTimeout = newTimeout || 5000 | ||||||
|                     } |                     } | ||||||
|                     if (newOptions.buttons) { |                     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) { |                         newOptions.buttons.forEach(function(buttonDef) { | ||||||
|                             var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet); |                             var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet); | ||||||
|                             if (buttonDef.id) { |                             if (buttonDef.id) { | ||||||
| @@ -272,6 +272,15 @@ RED.notifications = (function() { | |||||||
|                 }; |                 }; | ||||||
|             })()); |             })()); | ||||||
|             n.timeoutid = window.setTimeout(n.close,timeout||5000); |             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); |         currentNotifications.push(n); | ||||||
|         if (options.id) { |         if (options.id) { | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ RED.palette.editor = (function() { | |||||||
|         }).done(function(data,textStatus,xhr) { |         }).done(function(data,textStatus,xhr) { | ||||||
|             callback(); |             callback(); | ||||||
|         }).fail(function(xhr,textStatus,err) { |         }).fail(function(xhr,textStatus,err) { | ||||||
|             callback(xhr); |             callback(xhr,textStatus,err); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     function removeNodeModule(id,callback) { |     function removeNodeModule(id,callback) { | ||||||
| @@ -248,86 +248,106 @@ RED.palette.editor = (function() { | |||||||
|             var moduleInfo = nodeEntries[module].info; |             var moduleInfo = nodeEntries[module].info; | ||||||
|             var nodeEntry = nodeEntries[module].elements; |             var nodeEntry = nodeEntries[module].elements; | ||||||
|             if (nodeEntry) { |             if (nodeEntry) { | ||||||
|                 var activeTypeCount = 0; |                 if (moduleInfo.plugin) { | ||||||
|                 var typeCount = 0; |                     nodeEntry.enableButton.hide(); | ||||||
|                 var errorCount = 0; |                     nodeEntry.removeButton.show(); | ||||||
|                 nodeEntry.errorList.empty(); |  | ||||||
|                 nodeEntries[module].totalUseCount = 0; |  | ||||||
|                 nodeEntries[module].setUseCount = {}; |  | ||||||
|  |  | ||||||
|                 for (var setName in moduleInfo.sets) { |                     let pluginCount = 0; | ||||||
|                     if (moduleInfo.sets.hasOwnProperty(setName)) { |                     for (let setName in moduleInfo.sets) { | ||||||
|                         var inUseCount = 0; |                         if (moduleInfo.sets.hasOwnProperty(setName)) { | ||||||
|                         var set = moduleInfo.sets[setName]; |                             let set = moduleInfo.sets[setName]; | ||||||
|                         var setElements = nodeEntry.sets[setName]; |                             if (set.plugins) { | ||||||
|                         if (set.err) { |                                 pluginCount += set.plugins.length; | ||||||
|                             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) { |                     } | ||||||
|                             activeTypeCount += set.types.length; |                      | ||||||
|                         } |                     nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount})); | ||||||
|                         typeCount += set.types.length; |  | ||||||
|                         for (var i=0;i<moduleInfo.sets[setName].types.length;i++) { |                 } else { | ||||||
|                             var t = moduleInfo.sets[setName].types[i]; |                     var activeTypeCount = 0; | ||||||
|                             inUseCount += (typesInUse[t]||0); |                     var typeCount = 0; | ||||||
|                             var swatch = setElements.swatches[t]; |                     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) { |                             if (set.enabled) { | ||||||
|                                 var def = RED.nodes.getType(t); |                                 activeTypeCount += set.types.length; | ||||||
|                                 if (def && def.color) { |                             } | ||||||
|                                     swatch.css({background:RED.utils.getNodeColor(t,def)}); |                             typeCount += set.types.length; | ||||||
|                                     swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))}) |                             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].setUseCount[setName] = inUseCount; |                             nodeEntries[module].totalUseCount += inUseCount; | ||||||
|                         nodeEntries[module].totalUseCount += inUseCount; |  | ||||||
|  |  | ||||||
|                         if (inUseCount > 0) { |                             if (setElements) { | ||||||
|                             setElements.enableButton.text(RED._('palette.editor.inuse')); |                                 if (inUseCount > 0) { | ||||||
|                             setElements.enableButton.addClass('disabled'); |                                     setElements.enableButton.text(RED._('palette.editor.inuse')); | ||||||
|                         } else { |                                     setElements.enableButton.addClass('disabled'); | ||||||
|                             setElements.enableButton.removeClass('disabled'); |                                 } else { | ||||||
|                             if (set.enabled) { |                                     setElements.enableButton.removeClass('disabled'); | ||||||
|                                 setElements.enableButton.text(RED._('palette.editor.disable')); |                                     if (set.enabled) { | ||||||
|                             } else { |                                         setElements.enableButton.text(RED._('palette.editor.disable')); | ||||||
|                                 setElements.enableButton.text(RED._('palette.editor.enable')); |                                     } 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) { |                     if (errorCount === 0) { | ||||||
|                     nodeEntry.errorRow.hide() |                         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')); |  | ||||||
|                     } else { |                     } 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) { |             if (moduleInfo.pending_version) { | ||||||
| @@ -678,6 +698,33 @@ 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); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             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; |     var settingsPane; | ||||||
| @@ -804,6 +851,7 @@ RED.palette.editor = (function() { | |||||||
|                         errorRow: errorRow, |                         errorRow: errorRow, | ||||||
|                         errorList: errorList, |                         errorList: errorList, | ||||||
|                         setCount: setCount, |                         setCount: setCount, | ||||||
|  |                         setButton: setButton, | ||||||
|                         container: container, |                         container: container, | ||||||
|                         shade: shade, |                         shade: shade, | ||||||
|                         versionSpan: versionSpan, |                         versionSpan: versionSpan, | ||||||
| @@ -814,49 +862,88 @@ RED.palette.editor = (function() { | |||||||
|                         if (container.hasClass('expanded')) { |                         if (container.hasClass('expanded')) { | ||||||
|                             container.removeClass('expanded'); |                             container.removeClass('expanded'); | ||||||
|                             contentRow.slideUp(); |                             contentRow.slideUp(); | ||||||
|  |                             setTimeout(() => { | ||||||
|  |                                 contentRow.empty() | ||||||
|  |                             }, 200) | ||||||
|  |                             object.elements.sets = {} | ||||||
|                         } else { |                         } else { | ||||||
|                             container.addClass('expanded'); |                             container.addClass('expanded'); | ||||||
|  |                             populateSetList() | ||||||
|                             contentRow.slideDown(); |                             contentRow.slideDown(); | ||||||
|                         } |                         } | ||||||
|                     }) |                     }) | ||||||
|  |                     const populateSetList = function () { | ||||||
|                     var setList = Object.keys(entry.sets) |                         var setList = Object.keys(entry.sets) | ||||||
|                     setList.sort(function(A,B) { |                         setList.sort(function(A,B) { | ||||||
|                         return A.toLowerCase().localeCompare(B.toLowerCase()); |                             return A.toLowerCase().localeCompare(B.toLowerCase()); | ||||||
|                     }); |                         }); | ||||||
|                     setList.forEach(function(setName) { |                         setList.forEach(function(setName) { | ||||||
|                         var set = entry.sets[setName]; |                             var set = entry.sets[setName]; | ||||||
|                         var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow); |                             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 buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow); | ||||||
|                         var typeSwatches = {}; |                             var typeSwatches = {}; | ||||||
|                         set.types.forEach(function(t) { |                             let enableButton; | ||||||
|                             var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow); |                             if (set.types) { | ||||||
|                             typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); |                                 set.types.forEach(function(t) { | ||||||
|                             $('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv); |                                     var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow); | ||||||
|                         }) |                                     typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); | ||||||
|                         var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup); |                                     if (set.enabled) { | ||||||
|                         enableButton.on("click", function(evt) { |                                         var def = RED.nodes.getType(t); | ||||||
|                             evt.preventDefault(); |                                         if (def && def.color) { | ||||||
|                             if (object.setUseCount[setName] === 0) { |                                             typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)}); | ||||||
|                                 var currentSet = RED.nodes.registry.getNodeSet(set.id); |                                             typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))}) | ||||||
|                                 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})); |  | ||||||
|                                         } |                                         } | ||||||
|                                     } |                                     } | ||||||
|                                 }); |                                     $('<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] = { |                                 if (object.setUseCount[setName] > 0) { | ||||||
|                             setRow: setRow, |                                     enableButton.text(RED._('palette.editor.inuse')); | ||||||
|                             enableButton: enableButton, |                                     enableButton.addClass('disabled'); | ||||||
|                             swatches: typeSwatches |                                 } 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) { |                     enableButton.on("click", function(evt) { | ||||||
|                         evt.preventDefault(); |                         evt.preventDefault(); | ||||||
|                         if (object.totalUseCount === 0) { |                         if (object.totalUseCount === 0) { | ||||||
| @@ -1226,7 +1313,55 @@ 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]; | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     // 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(); |                         notification.close(); | ||||||
| @@ -1270,9 +1405,28 @@ RED.palette.editor = (function() { | |||||||
|                     RED.actions.invoke("core:show-event-log"); |                     RED.actions.invoke("core:show-event-log"); | ||||||
|                 }); |                 }); | ||||||
|                 RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version); |                 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(); |                     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) { |                          if (xhr.responseJSON) { | ||||||
|                              var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{ |                              var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{ | ||||||
|                                  type: 'error', |                                  type: 'error', | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ RED.palette = (function() { | |||||||
|     var categoryContainers = {}; |     var categoryContainers = {}; | ||||||
|     var sidebarControls; |     var sidebarControls; | ||||||
|  |  | ||||||
|  |     let paletteState = { filter: "", collapsed: [] }; | ||||||
|  |  | ||||||
|  |     let filterRefreshTimeout | ||||||
|  |  | ||||||
|     function createCategory(originalCategory,rootCategory,category,ns) { |     function createCategory(originalCategory,rootCategory,category,ns) { | ||||||
|         if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) { |         if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) { | ||||||
|             createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory); |             createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory); | ||||||
| @@ -60,20 +64,57 @@ RED.palette = (function() { | |||||||
|         catDiv.data('label',label); |         catDiv.data('label',label); | ||||||
|         categoryContainers[category] = { |         categoryContainers[category] = { | ||||||
|             container: catDiv, |             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.removeClass("red-ui-palette-open"); | ||||||
|                 catDiv.addClass("red-ui-palette-closed"); |                 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"); |                 $("#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.addClass("red-ui-palette-open"); | ||||||
|                 catDiv.removeClass("red-ui-palette-closed"); |                 catDiv.removeClass("red-ui-palette-closed"); | ||||||
|                 $("#red-ui-palette-base-category-"+category).slideDown(); |                 $("#red-ui-palette-base-category-"+category).slideDown(); | ||||||
|                 $("#red-ui-palette-header-"+category+" i").addClass("expanded"); |                 $("#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() { |             toggle: function() { | ||||||
|                 if (catDiv.hasClass("red-ui-palette-open")) { |                 if (categoryContainers[category].isOpen()) { | ||||||
|                     categoryContainers[category].close(); |                     categoryContainers[category].close(); | ||||||
|                 } else { |                 } else { | ||||||
|                     categoryContainers[category].open(); |                     categoryContainers[category].open(); | ||||||
| @@ -415,8 +456,16 @@ RED.palette = (function() { | |||||||
|  |  | ||||||
|             var categoryNode = $("#red-ui-palette-container-"+rootCategory); |             var categoryNode = $("#red-ui-palette-container-"+rootCategory); | ||||||
|             if (categoryNode.find(".red-ui-palette-node").length === 1) { |             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); |         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'); |         var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i'); | ||||||
|         $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) { |         $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) { | ||||||
|             var currentLabel = $(el).attr("data-palette-label"); |             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.hasOwnProperty(category)) { | ||||||
|                 if (categoryContainers[category].container |                 const categorySection = categoryContainers[category] | ||||||
|                         .find(".red-ui-palette-node") |                 if (categorySection.getNodeCount(true) === 0) { | ||||||
|                         .filter(function() { return $(this).css('display') !== 'none'}).length === 0) { |                     categorySection.hide() | ||||||
|                     categoryContainers[category].close(); |  | ||||||
|                     categoryContainers[category].container.slideUp(); |  | ||||||
|                 } else { |                 } else { | ||||||
|                     categoryContainers[category].open(); |                     categorySection.show() | ||||||
|                     categoryContainers[category].container.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-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) { |         RED.events.on('registry:node-type-added', function(nodeType) { | ||||||
|             var def = RED.nodes.getType(nodeType); |             var def = RED.nodes.getType(nodeType); | ||||||
| @@ -596,14 +659,14 @@ RED.palette = (function() { | |||||||
|  |  | ||||||
|         RED.events.on("subflows:change",refreshSubflow); |         RED.events.on("subflows:change",refreshSubflow); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         $("#red-ui-palette-search input").searchBox({ |         $("#red-ui-palette-search input").searchBox({ | ||||||
|             delay: 100, |             delay: 100, | ||||||
|             change: function() { |             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")); |         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"); |         RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette"); | ||||||
| @@ -669,7 +732,23 @@ RED.palette = (function() { | |||||||
|                 togglePalette(state); |                 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) { |     function togglePalette(state) { | ||||||
|         if (!state) { |         if (!state) { | ||||||
|             $("#red-ui-main-container").addClass("red-ui-palette-closed"); |             $("#red-ui-main-container").addClass("red-ui-palette-closed"); | ||||||
| @@ -689,6 +768,15 @@ RED.palette = (function() { | |||||||
|         }) |         }) | ||||||
|         return categories; |         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 { |     return { | ||||||
|         init: init, |         init: init, | ||||||
|         add:addNodeType, |         add:addNodeType, | ||||||
|   | |||||||
| @@ -287,7 +287,7 @@ RED.projects.settings = (function() { | |||||||
|         var notInstalledCount = 0; |         var notInstalledCount = 0; | ||||||
|  |  | ||||||
|         for (var m in modulesInUse) { |         for (var m in modulesInUse) { | ||||||
|             if (modulesInUse.hasOwnProperty(m)) { |             if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) { | ||||||
|                 depsList.editableList('addItem',{ |                 depsList.editableList('addItem',{ | ||||||
|                     id: modulesInUse[m].module, |                     id: modulesInUse[m].module, | ||||||
|                     version: modulesInUse[m].version, |                     version: modulesInUse[m].version, | ||||||
| @@ -307,8 +307,8 @@ RED.projects.settings = (function() { | |||||||
|  |  | ||||||
|         if (activeProject.dependencies) { |         if (activeProject.dependencies) { | ||||||
|             for (var m in activeProject.dependencies) { |             for (var m in activeProject.dependencies) { | ||||||
|                 if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) { |                 if (activeProject.dependencies.hasOwnProperty(m)) { | ||||||
|                     var installed = !!RED.nodes.registry.getModule(m); |                     var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version; | ||||||
|                     depsList.editableList('addItem',{ |                     depsList.editableList('addItem',{ | ||||||
|                         id: m, |                         id: m, | ||||||
|                         version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version, |                         version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version, | ||||||
|   | |||||||
| @@ -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({ |         var tabs = RED.tabs.create({ | ||||||
|             id: "subflow-env-tabs", |             id: "subflow-env-tabs", | ||||||
|             onchange: function(tab) { |             onchange: function(tab) { | ||||||
|                 if (tab.id === "subflow-env-tab-preview") { |                 if (tab.id === "subflow-env-tab-preview") { | ||||||
|                     var inputContainer = $("#subflow-input-ui"); |                     var inputContainer = $("#subflow-input-ui"); | ||||||
|                     var list = envList.editableList("items"); |                     var list = uiContainer.editableList("items"); | ||||||
|                     var exportedEnv = exportEnvList(list, true); |                     var exportedEnv = exportEnvList(list, true); | ||||||
|                     buildEnvUI(inputContainer, exportedEnv,node); |                     buildEnvUI(inputContainer, exportedEnv, node); | ||||||
|                 } |                 } | ||||||
|                 $("#subflow-env-tabs-content").children().hide(); |                 $("#subflow-env-tabs-content").children().hide(); | ||||||
|                 $("#" + tab.id).show(); |                 $("#" + tab.id).show(); | ||||||
| @@ -957,12 +959,33 @@ RED.subflow = (function() { | |||||||
|         RED.editor.envVarList.setLocale(locale); |         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||{}; |         ui.label = ui.label||{}; | ||||||
|         if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { |         if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { | ||||||
|             ui.type = "cred"; |             ui.type = "cred"; | ||||||
|             ui.opts = {}; |             ui.opts = {}; | ||||||
|  |         } else if (tenv.type === "conf-types") { | ||||||
|  |             ui.type = "conf-types" | ||||||
|  |             ui.opts = { types: ['conf-types'] } | ||||||
|         } else if (!ui.type) { |         } else if (!ui.type) { | ||||||
|             ui.type = "input"; |             ui.type = "input"; | ||||||
|             ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST } |             ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST } | ||||||
| @@ -1006,9 +1029,10 @@ RED.subflow = (function() { | |||||||
|         if (tenv.hasOwnProperty('type')) { |         if (tenv.hasOwnProperty('type')) { | ||||||
|             val.type = tenv.type; |             val.type = tenv.type; | ||||||
|         } |         } | ||||||
|  |         const elId = getSubflowEnvPropertyName(tenv.name) | ||||||
|         switch(ui.type) { |         switch(ui.type) { | ||||||
|             case "input": |             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) { |                 if (ui.opts.types && ui.opts.types.length > 0) { | ||||||
|                     var inputType = val.type; |                     var inputType = val.type; | ||||||
|                     if (ui.opts.types.indexOf(inputType) === -1) { |                     if (ui.opts.types.indexOf(inputType) === -1) { | ||||||
| @@ -1035,7 +1059,7 @@ RED.subflow = (function() { | |||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case "select": |             case "select": | ||||||
|                 input = $('<select>').css('width','70%').appendTo(row); |                 input = $('<select>').css('width','70%').attr('id', elId).appendTo(row); | ||||||
|                 if (ui.opts.opts) { |                 if (ui.opts.opts) { | ||||||
|                     ui.opts.opts.forEach(function(o) { |                     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); |                         $('<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": |             case "checkbox": | ||||||
|                 label.css("cursor","default"); |                 label.css("cursor","default"); | ||||||
|                 var cblabel = $('<label>').css('width','70%').appendTo(row); |                 var cblabel = $('<label>').css('width','70%').appendTo(row); | ||||||
|                 input = $('<input type="checkbox">').css({ |                 input = $('<input type="checkbox">').attr('id', elId).css({ | ||||||
|                     marginTop: 0, |                     marginTop: 0, | ||||||
|                     width: 'auto', |                     width: 'auto', | ||||||
|                     height: '34px' |                     height: '34px' | ||||||
| @@ -1064,7 +1088,7 @@ RED.subflow = (function() { | |||||||
|                 input.prop("checked",boolVal); |                 input.prop("checked",boolVal); | ||||||
|                 break; |                 break; | ||||||
|             case "spinner": |             case "spinner": | ||||||
|                 input = $('<input>').css('width','70%').appendTo(row); |                 input = $('<input>').css('width','70%').attr('id', elId).appendTo(row); | ||||||
|                 var spinnerOpts = {}; |                 var spinnerOpts = {}; | ||||||
|                 if (ui.opts.hasOwnProperty('min')) { |                 if (ui.opts.hasOwnProperty('min')) { | ||||||
|                     spinnerOpts.min = ui.opts.min; |                     spinnerOpts.min = ui.opts.min; | ||||||
| @@ -1076,7 +1100,7 @@ RED.subflow = (function() { | |||||||
|                 input.val(val.value); |                 input.val(val.value); | ||||||
|                 break; |                 break; | ||||||
|             case "cred": |             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) { | ||||||
|                     if (node.credentials[tenv.name]) { |                     if (node.credentials[tenv.name]) { | ||||||
|                         input.val(node.credentials[tenv.name]); |                         input.val(node.credentials[tenv.name]); | ||||||
| @@ -1093,18 +1117,25 @@ RED.subflow = (function() { | |||||||
|                     default: 'cred' |                     default: 'cred' | ||||||
|                 }) |                 }) | ||||||
|                 break; |                 break; | ||||||
|         } |             case "conf-types": | ||||||
|         if (input) { |                 // let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000); | ||||||
|             input.attr('id',getSubflowEnvPropertyName(tenv.name)) |                 // 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 uiContainer - container for UI | ||||||
|      * @param envList - env var definitions of template |      * @param envList - env var definitions of template | ||||||
|      */ |      */ | ||||||
|     function buildEnvUI(uiContainer, envList, node) { |     function buildEnvUI(uiContainer, envList, node) { | ||||||
|  |         if(RED.subflow.debug) { console.log("buildEnvUI",envList) } | ||||||
|         uiContainer.empty(); |         uiContainer.empty(); | ||||||
|         for (var i = 0; i < envList.length; i++) { |         for (var i = 0; i < envList.length; i++) { | ||||||
|             var tenv = envList[i]; |             var tenv = envList[i]; | ||||||
| @@ -1112,7 +1143,7 @@ RED.subflow = (function() { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer); |             var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer); | ||||||
|             buildEnvUIRow(row,tenv, tenv.ui || {}, node); |             buildEnvUIRow(row, tenv, node); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     // buildEnvUI |     // buildEnvUI | ||||||
| @@ -1185,6 +1216,9 @@ RED.subflow = (function() { | |||||||
|                                         delete ui.opts |                                         delete ui.opts | ||||||
|                                     } |                                     } | ||||||
|                                     break; |                                     break; | ||||||
|  |                                 case "conf-types": | ||||||
|  |                                     delete ui.opts; | ||||||
|  |                                     break; | ||||||
|                                 default: |                                 default: | ||||||
|                                     delete ui.opts; |                                     delete ui.opts; | ||||||
|                             } |                             } | ||||||
| @@ -1207,8 +1241,9 @@ RED.subflow = (function() { | |||||||
|         if (/^subflow:/.test(node.type)) { |         if (/^subflow:/.test(node.type)) { | ||||||
|             var subflowDef = RED.nodes.subflow(node.type.substring(8)); |             var subflowDef = RED.nodes.subflow(node.type.substring(8)); | ||||||
|             if (subflowDef.env) { |             if (subflowDef.env) { | ||||||
|                 subflowDef.env.forEach(function(env) { |                 subflowDef.env.forEach(function(env, i) { | ||||||
|                     var item = { |                     var item = { | ||||||
|  |                         index: i, | ||||||
|                         name:env.name, |                         name:env.name, | ||||||
|                         parent: { |                         parent: { | ||||||
|                             type: env.type, |                             type: env.type, | ||||||
| @@ -1245,14 +1280,20 @@ RED.subflow = (function() { | |||||||
|                     var nodePropValue = nodeProp; |                     var nodePropValue = nodeProp; | ||||||
|                     if (prop.ui && prop.ui.type === "cred") { |                     if (prop.ui && prop.ui.type === "cred") { | ||||||
|                         nodePropType = "cred"; |                         nodePropType = "cred"; | ||||||
|  |                     } else if (prop.ui && prop.ui.type === "conf-types") { | ||||||
|  |                         nodePropType = prop.value.type | ||||||
|                     } else { |                     } else { | ||||||
|                         switch(typeof nodeProp) { |                         switch(typeof nodeProp) { | ||||||
|                             case "string": nodePropType = "str"; break; |                             case "string": nodePropType = "str"; break; | ||||||
|                             case "number": nodePropType = "num"; break; |                             case "number": nodePropType = "num"; break; | ||||||
|                             case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break; |                             case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break; | ||||||
|                             default: |                             default: | ||||||
|                             nodePropType = nodeProp.type; |                                 if (nodeProp) { | ||||||
|                             nodePropValue = nodeProp.value; |                                     nodePropType = nodeProp.type; | ||||||
|  |                                     nodePropValue = nodeProp.value; | ||||||
|  |                                 } else { | ||||||
|  |                                     nodePropType = 'str' | ||||||
|  |                                 } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     var item = { |                     var item = { | ||||||
| @@ -1273,6 +1314,7 @@ RED.subflow = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function exportSubflowInstanceEnv(node) { |     function exportSubflowInstanceEnv(node) { | ||||||
|  |         if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) } | ||||||
|         var env = []; |         var env = []; | ||||||
|         // First, get the values for the SubflowTemplate defined properties |         // First, get the values for the SubflowTemplate defined properties | ||||||
|         //  - these are the ones with custom UI elements |         //  - these are the ones with custom UI elements | ||||||
| @@ -1304,7 +1346,7 @@ RED.subflow = (function() { | |||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
|                     case "cred": |                     case "cred": | ||||||
|                         item.value = input.val(); |                         item.value = input.typedInput('value'); | ||||||
|                         item.type = 'cred'; |                         item.type = 'cred'; | ||||||
|                         break; |                         break; | ||||||
|                     case "spinner": |                     case "spinner": | ||||||
| @@ -1319,6 +1361,9 @@ RED.subflow = (function() { | |||||||
|                         item.type = 'bool'; |                         item.type = 'bool'; | ||||||
|                         item.value = ""+input.prop("checked"); |                         item.value = ""+input.prop("checked"); | ||||||
|                         break; |                         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) { |                 if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { | ||||||
|                     env.push(item); |                     env.push(item); | ||||||
| @@ -1332,8 +1377,15 @@ RED.subflow = (function() { | |||||||
|         return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_"); |         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) { |     function buildEditForm(type,node) { | ||||||
|  |         if(RED.subflow.debug) { console.log("buildEditForm",type,node) } | ||||||
|         if (type === "subflow-template") { |         if (type === "subflow-template") { | ||||||
|             // This is the tabbed UI that offers the env list - with UI options |             // This is the tabbed UI that offers the env list - with UI options | ||||||
|             // plus the preview tab |             // plus the preview tab | ||||||
|   | |||||||
| @@ -382,9 +382,11 @@ RED.sidebar.config = (function() { | |||||||
|                 refreshConfigNodeList(); |                 refreshConfigNodeList(); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes")); |         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-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) { |     function flashConfigNode(el) { | ||||||
|   | |||||||
| @@ -36,7 +36,13 @@ RED.sidebar.help = (function() { | |||||||
|         toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); |         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) |         $('<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') |         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) { |         showTOCButton.on("click",function(e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             if ($(this).hasClass('selected')) { |             if ($(this).hasClass('selected')) { | ||||||
| @@ -158,8 +164,10 @@ RED.sidebar.help = (function() { | |||||||
|  |  | ||||||
|     function refreshSubflow(sf) { |     function refreshSubflow(sf) { | ||||||
|         var item = treeList.treeList('get',"node-type:subflow:"+sf.id); |         var item = treeList.treeList('get',"node-type:subflow:"+sf.id); | ||||||
|         item.subflowLabel = sf._def.label().toLowerCase(); |         if (item) { | ||||||
|         item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); |             item.subflowLabel = sf._def.label().toLowerCase(); | ||||||
|  |             item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function hideTOC() { |     function hideTOC() { | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ RED.sidebar.info.outliner = (function() { | |||||||
|                 evt.stopPropagation(); |                 evt.stopPropagation(); | ||||||
|                 RED.search.show("type:subflow:"+n.id); |                 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") { |         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) { |             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) { | ||||||
|   | |||||||
| @@ -435,10 +435,15 @@ RED.tourGuide = (function() { | |||||||
|  |  | ||||||
|     function listTour() { |     function listTour() { | ||||||
|         return [ |         return [ | ||||||
|  |             { | ||||||
|  |                 id: "4_0", | ||||||
|  |                 label: "4.0", | ||||||
|  |                 path: "./tours/welcome.js" | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|                 id: "3_1", |                 id: "3_1", | ||||||
|                 label: "3.1", |                 label: "3.1", | ||||||
|                 path: "./tours/welcome.js" |                 path: "./tours/3.1/welcome.js" | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 id: "3_0", |                 id: "3_0", | ||||||
|   | |||||||
| @@ -264,6 +264,7 @@ | |||||||
|                 setTimeout(function() { |                 setTimeout(function() { | ||||||
|                     oldTray.tray.detach(); |                     oldTray.tray.detach(); | ||||||
|                     showTray(options); |                     showTray(options); | ||||||
|  |                     RED.events.emit('editor:change') | ||||||
|                 },250) |                 },250) | ||||||
|             } else { |             } else { | ||||||
|                 if (stack.length > 0) { |                 if (stack.length > 0) { | ||||||
| @@ -333,6 +334,7 @@ | |||||||
|                         RED.view.focus(); |                         RED.view.focus(); | ||||||
|                     } else { |                     } else { | ||||||
|                         stack[stack.length-1].tray.css("z-index", "auto"); |                         stack[stack.length-1].tray.css("z-index", "auto"); | ||||||
|  |                         RED.events.emit('editor:change') | ||||||
|                     } |                     } | ||||||
|                 },250) |                 },250) | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -483,6 +483,16 @@ RED.utils = (function() { | |||||||
|                 $('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e); |                 $('<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') { |         } else if (typeof obj === 'number') { | ||||||
|             e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj); |             e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj); | ||||||
|  |  | ||||||
| @@ -589,6 +599,7 @@ RED.utils = (function() { | |||||||
|                                     exposeApi: exposeApi, |                                     exposeApi: exposeApi, | ||||||
|                                     // tools: tools // Do not pass tools down as we |                                     // tools: tools // Do not pass tools down as we | ||||||
|                                                     // keep them attached to the top-level header |                                                     // keep them attached to the top-level header | ||||||
|  |                                     nodeSelector: options.nodeSelector, | ||||||
|                                 } |                                 } | ||||||
|                             ).appendTo(row); |                             ).appendTo(row); | ||||||
|                         } |                         } | ||||||
| @@ -619,6 +630,7 @@ RED.utils = (function() { | |||||||
|                                                 exposeApi: exposeApi, |                                                 exposeApi: exposeApi, | ||||||
|                                                 // tools: tools // Do not pass tools down as we |                                                 // tools: tools // Do not pass tools down as we | ||||||
|                                                                 // keep them attached to the top-level header |                                                                 // keep them attached to the top-level header | ||||||
|  |                                                 nodeSelector: options.nodeSelector, | ||||||
|                                             } |                                             } | ||||||
|                                         ).appendTo(row); |                                         ).appendTo(row); | ||||||
|                                     } |                                     } | ||||||
| @@ -675,6 +687,7 @@ RED.utils = (function() { | |||||||
|                                 exposeApi: exposeApi, |                                 exposeApi: exposeApi, | ||||||
|                                 // tools: tools // Do not pass tools down as we |                                 // tools: tools // Do not pass tools down as we | ||||||
|                                                 // keep them attached to the top-level header |                                                 // keep them attached to the top-level header | ||||||
|  |                                 nodeSelector: options.nodeSelector, | ||||||
|                             } |                             } | ||||||
|                         ).appendTo(row); |                         ).appendTo(row); | ||||||
|                     } |                     } | ||||||
| @@ -888,11 +901,25 @@ RED.utils = (function() { | |||||||
|         return parts; |         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 { |         try { | ||||||
|             var parts = normalisePropertyExpression(str); |             const parts = normalisePropertyExpression(str); | ||||||
|             return true; |             return true; | ||||||
|         } catch(err) { |         } 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; |             return false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -906,23 +933,28 @@ RED.utils = (function() { | |||||||
|      * @returns true if valid, String if invalid |      * @returns true if valid, String if invalid | ||||||
|      */ |      */ | ||||||
|     function validateTypedProperty(propertyValue, propertyType, opt) { |     function validateTypedProperty(propertyValue, propertyType, opt) { | ||||||
|  |         if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) { | ||||||
|         let error |             // Allow ${ENV_VAR} value | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |         let error; | ||||||
|         if (propertyType === 'json') { |         if (propertyType === 'json') { | ||||||
|             try { |             try { | ||||||
|                 JSON.parse(propertyValue); |                 JSON.parse(propertyValue); | ||||||
|             } catch(err) { |             } catch(err) { | ||||||
|                 error = RED._("validator.errors.invalid-json", { |                 error = RED._("validator.errors.invalid-json", { | ||||||
|                     error: err.message |                     error: err.message | ||||||
|                 }) |                 }); | ||||||
|             } |             } | ||||||
|         } else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) { |         } else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) { | ||||||
|             if (!RED.utils.validatePropertyExpression(propertyValue)) { |             // To avoid double label | ||||||
|                 error = RED._("validator.errors.invalid-prop") |             const valid = RED.utils.validatePropertyExpression(propertyValue, opt ? {} : null); | ||||||
|  |             if (valid !== true) { | ||||||
|  |                 error = opt ? valid : RED._("validator.errors.invalid-prop"); | ||||||
|             } |             } | ||||||
|         } else if (propertyType === 'num') { |         } 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)) { |             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') { |         } else if (propertyType === 'jsonata') { | ||||||
|             try {  |             try {  | ||||||
| @@ -930,16 +962,16 @@ RED.utils = (function() { | |||||||
|             } catch(err) { |             } catch(err) { | ||||||
|                 error = RED._("validator.errors.invalid-expr", { |                 error = RED._("validator.errors.invalid-expr", { | ||||||
|                     error: err.message |                     error: err.message | ||||||
|                 }) |                 }); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (error) { |         if (error) { | ||||||
|             if (opt && opt.label) { |             if (opt && opt.label) { | ||||||
|                 return opt.label+': '+error |                 return opt.label + ': ' + error; | ||||||
|             } |             } | ||||||
|             return error |             return error; | ||||||
|         } |         } | ||||||
|         return true |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getMessageProperty(msg,expr) { |     function getMessageProperty(msg,expr) { | ||||||
|   | |||||||
| @@ -9,14 +9,27 @@ RED.view.annotations = (function() { | |||||||
|                     addAnnotation(evt.node.__pendingAnnotation__,evt); |                     addAnnotation(evt.node.__pendingAnnotation__,evt); | ||||||
|                     delete evt.node.__pendingAnnotation__; |                     delete evt.node.__pendingAnnotation__; | ||||||
|                 } |                 } | ||||||
|                 var badgeDX = 0; |                 let badgeRDX = 0; | ||||||
|                 var controlDX = 0; |                 let badgeLDX = 0; | ||||||
|                 for (var i=0,l=evt.el.__annotations__.length;i<l;i++) { |                  | ||||||
|                     var annotation = evt.el.__annotations__[i]; |                 for (let i=0,l=evt.el.__annotations__.length;i<l;i++) { | ||||||
|  |                     const annotation = evt.el.__annotations__[i]; | ||||||
|                     if (annotations.hasOwnProperty(annotation.id)) { |                     if (annotations.hasOwnProperty(annotation.id)) { | ||||||
|                         var opts = annotations[annotation.id]; |                         const opts = annotations[annotation.id]; | ||||||
|                         var showAnnotation = true; |                         let showAnnotation = true; | ||||||
|                         var isBadge = opts.type === 'badge'; |                         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 (opts.show !== undefined) { | ||||||
|                             if (typeof opts.show === "string") { |                             if (typeof opts.show === "string") { | ||||||
|                                 showAnnotation = !!evt.node[opts.show] |                                 showAnnotation = !!evt.node[opts.show] | ||||||
| @@ -29,17 +42,24 @@ RED.view.annotations = (function() { | |||||||
|                         } |                         } | ||||||
|                         if (isBadge) { |                         if (isBadge) { | ||||||
|                             if (showAnnotation) { |                             if (showAnnotation) { | ||||||
|                                 var rect = annotation.element.getBoundingClientRect(); |                                 const rect = annotation.element.getBoundingClientRect(); | ||||||
|                                 badgeDX += rect.width; |                                 let annotationX | ||||||
|                                 annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)"); |                                 if (!opts.align || opts.align === 'right') { | ||||||
|                                 badgeDX += 4; |                                     annotationX = evt.node.w - 3 - badgeRDX - rect.width | ||||||
|                             } |                                     badgeRDX += rect.width + 4; | ||||||
|                         } else { |  | ||||||
|                             if (showAnnotation) { |                                 } else if (opts.align === 'left') { | ||||||
|                                 var rect = annotation.element.getBoundingClientRect(); |                                     annotationX = 3 + badgeLDX | ||||||
|                                 annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)"); |                                     badgeLDX += rect.width + 4; | ||||||
|                                 controlDX += 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 { |                     } else { | ||||||
|                         annotation.element.parentNode.removeChild(annotation.element); |                         annotation.element.parentNode.removeChild(annotation.element); | ||||||
| @@ -95,15 +115,25 @@ RED.view.annotations = (function() { | |||||||
|         annotationGroup.setAttribute("class",opts.class || ""); |         annotationGroup.setAttribute("class",opts.class || ""); | ||||||
|         evt.el.__annotations__.push({ |         evt.el.__annotations__.push({ | ||||||
|             id:id, |             id:id, | ||||||
|  |             node: evt.node, | ||||||
|             element: annotationGroup |             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) { |         if (opts.tooltip) { | ||||||
|             annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip)); |             annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip)); | ||||||
|             annotation.addEventListener("mouseleave", annotationMouseLeave); |             annotation.addEventListener("mouseleave", annotationMouseLeave); | ||||||
|         } |         } | ||||||
|  |         if (annotationGroup.hasChildNodes()) { | ||||||
|  |             annotationGroup.removeChild(annotationGroup.firstChild) | ||||||
|  |         } | ||||||
|         annotationGroup.appendChild(annotation); |         annotationGroup.appendChild(annotation); | ||||||
|         evt.el.appendChild(annotationGroup); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -646,120 +646,128 @@ RED.view = (function() { | |||||||
|                 } |                 } | ||||||
|                 d3.event = event; |                 d3.event = event; | ||||||
|                 var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); |                 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 { |                 try { | ||||||
|                     var isLink = (nn.type === "link in" || nn.type === "link out") |                     var result = createNode(selected_tool); | ||||||
|                     var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; |                     if (!result) { | ||||||
|  |                         return; | ||||||
|                     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 historyEvent = result.historyEvent; | ||||||
|                 } |                     var nn = RED.nodes.add(result.node); | ||||||
|  |  | ||||||
|                 mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); |                     var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); | ||||||
|                 mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); |                     if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { | ||||||
|                 mousePos[1] /= scaleFactor; |                         nn.l = showLabel; | ||||||
|                 mousePos[0] /= scaleFactor; |                     } | ||||||
|  |  | ||||||
|                 nn.x = mousePos[0]; |                     var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); | ||||||
|                 nn.y = mousePos[1]; |                     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; |                     try { | ||||||
|                 if (nn.x < minX) { |                         var isLink = (nn.type === "link in" || nn.type === "link out") | ||||||
|                     nn.x = minX; |                         var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; | ||||||
|                 } |  | ||||||
|                 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 label = RED.utils.getNodeLabel(nn, nn.type); | ||||||
|                     var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); |                         var labelParts = getLabelParts(label, "red-ui-flow-node-label"); | ||||||
|                     nn.x -= gridOffset.x; |                         if (hideLabel) { | ||||||
|                     nn.y -= gridOffset.y; |                             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"); |                     mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); | ||||||
|                 if (linkToSplice) { |                     mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); | ||||||
|                     spliceLink(linkToSplice, nn, historyEvent) |                     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], |  | ||||||
|  |  | ||||||
|                     }; |                     RED.history.push(historyEvent); | ||||||
|                     if (moveEvent) { |                     RED.editor.validateNode(nn); | ||||||
|                         historyEvent.events.push(moveEvent) |                     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); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @@ -1182,6 +1190,7 @@ RED.view = (function() { | |||||||
|  |  | ||||||
|         if (d3.event.button === 1) { |         if (d3.event.button === 1) { | ||||||
|             // Middle Click pan |             // Middle Click pan | ||||||
|  |             d3.event.preventDefault(); | ||||||
|             mouse_mode = RED.state.PANNING; |             mouse_mode = RED.state.PANNING; | ||||||
|             mouse_position = [d3.event.pageX,d3.event.pageY] |             mouse_position = [d3.event.pageX,d3.event.pageY] | ||||||
|             scroll_position = [chart.scrollLeft(),chart.scrollTop()]; |             scroll_position = [chart.scrollLeft(),chart.scrollTop()]; | ||||||
| @@ -4156,7 +4165,7 @@ RED.view = (function() { | |||||||
|                     } |                     } | ||||||
|                     var width = img.width * scaleFactor; |                     var width = img.width * scaleFactor; | ||||||
|                     if (width > 20) { |                     if (width > 20) { | ||||||
|                         scalefactor *= 20/width; |                         scaleFactor *= 20/width; | ||||||
|                         width = 20; |                         width = 20; | ||||||
|                     } |                     } | ||||||
|                     var height = img.height * scaleFactor; |                     var height = img.height * scaleFactor; | ||||||
| @@ -6063,14 +6072,19 @@ RED.view = (function() { | |||||||
|      function createNode(type, x, y, z) { |      function createNode(type, x, y, z) { | ||||||
|         const wasDirty = RED.nodes.dirty() |         const wasDirty = RED.nodes.dirty() | ||||||
|         var m = /^subflow:(.+)$/.exec(type); |         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) { |         if (activeSubflow && m) { | ||||||
|             var subflowId = m[1]; |             var subflowId = m[1]; | ||||||
|  |             let err | ||||||
|             if (subflowId === activeSubflow.id) { |             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)) { |             if (err) { | ||||||
|                 throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) |                 err.code = 'NODE_RED' | ||||||
|  |                 throw err | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -359,11 +359,17 @@ RED.workspaces = (function() { | |||||||
|                 RED.sidebar.config.refresh(); |                 RED.sidebar.config.refresh(); | ||||||
|                 RED.view.focus(); |                 RED.view.focus(); | ||||||
|             }, |             }, | ||||||
|             onclick: function(tab) { |             onclick: function(tab, evt) { | ||||||
|                 if (tab.id !== activeWorkspace) { |                 if(evt.which === 2) { | ||||||
|                     addToViewStack(activeWorkspace); |                     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) { |             ondblclick: function(tab) { | ||||||
|                 if (tab.type != "subflow") { |                 if (tab.type != "subflow") { | ||||||
| @@ -401,6 +407,7 @@ RED.workspaces = (function() { | |||||||
|                 if (tab.type === "tab") { |                 if (tab.type === "tab") { | ||||||
|                     workspaceTabCount--; |                     workspaceTabCount--; | ||||||
|                 } else { |                 } else { | ||||||
|  |                     RED.events.emit("workspace:close",{workspace: tab.id}) | ||||||
|                     hideStack.push(tab.id); |                     hideStack.push(tab.id); | ||||||
|                 } |                 } | ||||||
|                 RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); |                 RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1); | ||||||
| @@ -491,6 +498,11 @@ RED.workspaces = (function() { | |||||||
|         createWorkspaceTabs(); |         createWorkspaceTabs(); | ||||||
|         RED.events.on("sidebar:resize",workspace_tabs.resize); |         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() { |         RED.actions.add("core:show-next-tab",function() { | ||||||
|             var oldActive = activeWorkspace; |             var oldActive = activeWorkspace; | ||||||
|             workspace_tabs.nextTab(); |             workspace_tabs.nextTab(); | ||||||
| @@ -657,6 +669,9 @@ RED.workspaces = (function() { | |||||||
|         RED.events.on("flows:change", (ws) => { |         RED.events.on("flows:change", (ws) => { | ||||||
|             $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added)); |             $("#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(); |         hideWorkspace(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -187,6 +187,7 @@ RED.user = (function() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     function logout() { |     function logout() { | ||||||
|  |         RED.events.emit('logout') | ||||||
|         var tokens = RED.settings.get("auth-tokens"); |         var tokens = RED.settings.get("auth-tokens"); | ||||||
|         var token = tokens?tokens.access_token:""; |         var token = tokens?tokens.access_token:""; | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
| @@ -211,6 +212,8 @@ RED.user = (function() { | |||||||
|  |  | ||||||
|     function updateUserMenu() { |     function updateUserMenu() { | ||||||
|         $("#red-ui-header-button-user-submenu li").remove(); |         $("#red-ui-header-button-user-submenu li").remove(); | ||||||
|  |         const userMenu = $("#red-ui-header-button-user") | ||||||
|  |         userMenu.empty() | ||||||
|         if (RED.settings.user.anonymous) { |         if (RED.settings.user.anonymous) { | ||||||
|             RED.menu.addItem("red-ui-header-button-user",{ |             RED.menu.addItem("red-ui-header-button-user",{ | ||||||
|                 id:"usermenu-item-login", |                 id:"usermenu-item-login", | ||||||
| @@ -238,7 +241,8 @@ RED.user = (function() { | |||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |         const userIcon = generateUserIcon(RED.settings.user) | ||||||
|  |         userIcon.appendTo(userMenu); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function init() { |     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>') |                 var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>') | ||||||
|                     .prependTo(".red-ui-header-toolbar"); |                     .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", |                 RED.menu.init({id:"red-ui-header-button-user", | ||||||
|                     options: [] |                     options: [] | ||||||
|                 }); |                 }); | ||||||
| @@ -317,12 +313,30 @@ RED.user = (function() { | |||||||
|         return false; |         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 { |     return { | ||||||
|         init: init, |         init: init, | ||||||
|         login: login, |         login: login, | ||||||
|         logout: logout, |         logout: logout, | ||||||
|         hasPermission: hasPermission |         hasPermission: hasPermission, | ||||||
|  |         generateUserIcon | ||||||
|     } |     } | ||||||
|  |  | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -16,8 +16,20 @@ | |||||||
| RED.validators = { | RED.validators = { | ||||||
|     number: function(blankAllowed,mopt){ |     number: function(blankAllowed,mopt){ | ||||||
|         return function(v, opt) { |         return function(v, opt) { | ||||||
|             if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) { |             if (blankAllowed && (v === '' || v === undefined)) { | ||||||
|                 return true; |                 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) { |             if (opt && opt.label) { | ||||||
|                 return RED._("validator.errors.invalid-num-prop", { |                 return RED._("validator.errors.invalid-num-prop", { | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ body { | |||||||
| } | } | ||||||
| #red-ui-main-container { | #red-ui-main-container { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     top:40px; left:0; bottom: 0; right:0; |     top: var(--red-ui-header-height); left:0; bottom: 0; right:0; | ||||||
|     overflow:hidden; |     overflow:hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -259,7 +259,8 @@ $deploy-button-background-disabled-hover: #555; | |||||||
|  |  | ||||||
| $header-background: #000; | $header-background: #000; | ||||||
| $header-button-background-active: #121212; | $header-button-background-active: #121212; | ||||||
| $header-menu-color: #C7C7C7; | $header-accent: #C02020; | ||||||
|  | $header-menu-color: #eee; | ||||||
| $header-menu-color-disabled: #666; | $header-menu-color-disabled: #666; | ||||||
| $header-menu-heading-color: #fff; | $header-menu-heading-color: #fff; | ||||||
| $header-menu-sublabel-color: #aeaeae; | $header-menu-sublabel-color: #aeaeae; | ||||||
| @@ -313,6 +314,16 @@ $spinner-color: #999; | |||||||
|  |  | ||||||
| $tab-icon-color: #dedede; | $tab-icon-color: #dedede; | ||||||
|  |  | ||||||
|  | // Anonymous User Colors | ||||||
|  |  | ||||||
|  | $user-profile-colors: ( | ||||||
|  |     1: #822e81, | ||||||
|  |     2: #955e42, | ||||||
|  |     3: #9c914f, | ||||||
|  |     4: #748e54, | ||||||
|  |     5: #06bcc1 | ||||||
|  | ); | ||||||
|  |  | ||||||
| // Deprecated | // Deprecated | ||||||
| $text-color-green: $text-color-success; | $text-color-green: $text-color-success; | ||||||
| $info-text-code-color: $text-color-code; | $info-text-code-color: $text-color-code; | ||||||
|   | |||||||
| @@ -23,16 +23,20 @@ | |||||||
|     top: 0; |     top: 0; | ||||||
|     left: 0; |     left: 0; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 40px; |     height: var(--red-ui-header-height); | ||||||
|     background: var(--red-ui-header-background); |     background: var(--red-ui-header-background); | ||||||
|     box-sizing: border-box; |     box-sizing: border-box; | ||||||
|     padding: 0px 0px 0px 20px; |     padding: 0px 0px 0px 20px; | ||||||
|     color: var(--red-ui-header-menu-color); |     color: var(--red-ui-header-menu-color); | ||||||
|     font-size: 14px; |     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 { |     span.red-ui-header-logo { | ||||||
|         float: left; |         float: left; | ||||||
|         margin-top: 5px; |  | ||||||
|         font-size: 30px; |         font-size: 30px; | ||||||
|         line-height: 30px; |         line-height: 30px; | ||||||
|         text-decoration: none; |         text-decoration: none; | ||||||
| @@ -42,7 +46,7 @@ | |||||||
|             vertical-align: middle; |             vertical-align: middle; | ||||||
|             font-size: 16px !important; |             font-size: 16px !important; | ||||||
|             &:not(:first-child) { |             &:not(:first-child) { | ||||||
|                 margin-left: 5px; |                 margin-left: 8px; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         img { |         img { | ||||||
| @@ -59,25 +63,29 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     .red-ui-header-toolbar { |     .red-ui-header-toolbar { | ||||||
|  |         display: flex; | ||||||
|  |         align-items: stretch; | ||||||
|         padding: 0; |         padding: 0; | ||||||
|         margin: 0; |         margin: 0; | ||||||
|         list-style: none; |         list-style: none; | ||||||
|         float: right; |         float: right; | ||||||
|  |  | ||||||
|         > li { |         > li { | ||||||
|             display: inline-block; |             display: inline-flex; | ||||||
|  |             align-items: stretch; | ||||||
|             padding: 0; |             padding: 0; | ||||||
|             margin: 0; |             margin: 0; | ||||||
|             position: relative; |             position: relative; | ||||||
|  |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .button { |     .button { | ||||||
|  |         height: 100%; | ||||||
|  |         display: inline-flex; | ||||||
|  |         align-items: center; | ||||||
|  |         justify-content: center; | ||||||
|         min-width: 20px; |         min-width: 20px; | ||||||
|         text-align: center; |         text-align: center; | ||||||
|         line-height: 40px; |  | ||||||
|         display: inline-block; |  | ||||||
|         font-size: 20px; |         font-size: 20px; | ||||||
|         padding: 0px 12px; |         padding: 0px 12px; | ||||||
|         text-decoration: none; |         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 { |     li.open .button { | ||||||
|         background: var(--red-ui-header-button-background-active); |         background: var(--red-ui-header-button-background-active); | ||||||
|         border-color: 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 { |     #usermenu-item-username > .red-ui-menu-label { | ||||||
|         color: var(--red-ui-header-menu-heading-color); |         color: var(--red-ui-header-menu-heading-color); | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|     #red-ui-header-button-user .user-profile { |  | ||||||
|         background-position: center center; | .red-ui-user-profile { | ||||||
|         background-repeat: no-repeat; |     background-color: var(--red-ui-header-background); | ||||||
|         background-size: contain; |     border: 2px solid var(--red-ui-header-menu-color); | ||||||
|         display: inline-block; |     border-radius: 30px; | ||||||
|         width: 40px; |     overflow: hidden; | ||||||
|         height: 35px; |  | ||||||
|         vertical-align: middle; |     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) { | @media only screen and (max-width: 450px) { | ||||||
|     span.red-ui-header-logo > span { |     span.red-ui-header-logo > span { | ||||||
|         display: none; |         display: none; | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								packages/node_modules/@node-red/editor-client/src/sass/multiplayer.scss
									
									
									
									
										vendored
									
									
										Normal 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; | ||||||
|  |         }      | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								packages/node_modules/@node-red/editor-client/src/sass/sizes.scss
									
									
									
									
										vendored
									
									
										Normal 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; | ||||||
| @@ -15,4 +15,5 @@ | |||||||
| **/ | **/ | ||||||
|  |  | ||||||
| @import "colors"; | @import "colors"; | ||||||
|  | @import "sizes"; | ||||||
| @import "variables"; | @import "variables"; | ||||||
| @@ -15,6 +15,7 @@ | |||||||
| **/ | **/ | ||||||
|  |  | ||||||
| @import "colors"; | @import "colors"; | ||||||
|  | @import "sizes"; | ||||||
| @import "variables"; | @import "variables"; | ||||||
| @import "mixins"; | @import "mixins"; | ||||||
|  |  | ||||||
| @@ -72,3 +73,5 @@ | |||||||
| @import "radialMenu"; | @import "radialMenu"; | ||||||
|  |  | ||||||
| @import "tourGuide"; | @import "tourGuide"; | ||||||
|  |  | ||||||
|  | @import "multiplayer"; | ||||||
|   | |||||||
| @@ -37,7 +37,6 @@ ul.red-ui-sidebar-node-config-list { | |||||||
|     } |     } | ||||||
|     .red-ui-palette-node { |     .red-ui-palette-node { | ||||||
|         // overflow: hidden; |         // overflow: hidden; | ||||||
|         cursor: default; |  | ||||||
|         &.selected { |         &.selected { | ||||||
|             border-color: transparent; |             border-color: transparent; | ||||||
|             box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); |             box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); | ||||||
|   | |||||||
| @@ -16,6 +16,9 @@ | |||||||
|  |  | ||||||
|     --red-ui-shadow: #{$shadow}; |     --red-ui-shadow: #{$shadow}; | ||||||
|  |  | ||||||
|  |     // Header Height | ||||||
|  |     --red-ui-header-height: #{$header-height}; | ||||||
|  |  | ||||||
| // Main body text | // Main body text | ||||||
|     --red-ui-primary-text-color: #{$primary-text-color}; |     --red-ui-primary-text-color: #{$primary-text-color}; | ||||||
| // UI control label text | // UI control label text | ||||||
| @@ -240,6 +243,7 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|     --red-ui-header-background: #{$header-background}; |     --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-button-background-active: #{$header-button-background-active}; | ||||||
|     --red-ui-header-menu-color: #{$header-menu-color}; |     --red-ui-header-menu-color: #{$header-menu-color}; | ||||||
|     --red-ui-header-menu-color-disabled: #{$header-menu-color-disabled}; |     --red-ui-header-menu-color-disabled: #{$header-menu-color-disabled}; | ||||||
| @@ -295,4 +299,7 @@ | |||||||
|  |  | ||||||
|     --red-ui-tab-icon-color: #{$tab-icon-color}; |     --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)}; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB | 
| Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB | 
| Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB | 
| Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB | 
							
								
								
									
										231
									
								
								packages/node_modules/@node-red/editor-client/src/tours/3.1/welcome.js
									
									
									
									
										vendored
									
									
										Normal 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>` | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-auto-complete.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-config-select.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-plugins.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-sf-config.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/tours/images/nr4-timestamp-formatting.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
| @@ -1,12 +1,12 @@ | |||||||
| export default { | export default { | ||||||
|     version: "3.1.0", |     version: "4.0.0", | ||||||
|     steps: [ |     steps: [ | ||||||
|         { |         { | ||||||
|             titleIcon: "fa fa-map-o", |             titleIcon: "fa fa-map-o", | ||||||
|             title: { |             title: { | ||||||
|                 "en-US": "Welcome to Node-RED 3.1!", |                 "en-US": "Welcome to Node-RED 4.0!", | ||||||
|                 "ja": "Node-RED 3.1へようこそ!", |                 "ja": "Node-RED 4.0 へようこそ!", | ||||||
|                 "fr": "Bienvenue dans Node-RED 3.1!" |                 "fr": "Bienvenue dans Node-RED 4.0!" | ||||||
|             }, |             }, | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": "<p>Let's take a moment to discover the new features in this release.</p>", |                 "en-US": "<p>Let's take a moment to discover the new features in this release.</p>", | ||||||
| @@ -16,202 +16,184 @@ export default { | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             title: { |             title: { | ||||||
|                 "en-US": "New ways to work with groups", |                 "en-US": "Multiplayer Mode", | ||||||
|                 "ja": "グループの新たな操作方法", |                 "ja": "複数ユーザ同時利用モード", | ||||||
|                 "fr": "De nouvelles façons de travailler avec les groupes" |                 "fr": "Mode Multi-utilisateur" | ||||||
|             }, |             }, | ||||||
|  |             image: 'images/nr4-multiplayer-location.png', | ||||||
|             description: { |             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 d’autres 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> |                 <ul> | ||||||
|                     <li>They don't get in the way when clicking on a node</li> |                     <li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li> | ||||||
|                     <li>They can be reordered using the Moving Forwards and Move Backwards actions</li> |                     <li>ISO 8601 - a common format used by many systems</li> | ||||||
|                     <li>Multiple nodes can be dragged into a group in one go</li> |                     <li>JavaScript Date Object</li> | ||||||
|                     <li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li> |  | ||||||
|                 </ul>`, |                 </ul>`, | ||||||
|                 "ja": `<p>エディタ上のグループの操作が変更されました。</p> |                 "ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p> | ||||||
|  |                 <p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p> | ||||||
|                 <ul> |                 <ul> | ||||||
|                     <li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li> |                     <li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li> | ||||||
|                     <li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li> |                     <li>ISO 8601 - 多くのシステムで使用されている共通の形式</li> | ||||||
|                     <li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li> |                     <li>JavaScript日付オブジェクト</li> | ||||||
|                     <li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li> |  | ||||||
|                 </ul>`, |                 </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> |                 <ul> | ||||||
|                     <li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li> |                     <li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li> | ||||||
|                     <li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li> |                     <li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li> | ||||||
|                     <li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li> |                     <li>Objet Date JavaScript</li> | ||||||
|                     <li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li> |  | ||||||
|                 </ul>` |                 </ul>` | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             title: { |             title: { | ||||||
|                 "en-US": "Change notification on tabs", |                 "en-US": "Auto-complete of flow/global and env types", | ||||||
|                 "ja": "タブ上の変更通知", |                 "ja": "フロー/グローバル、環境変数の型の自動補完", | ||||||
|                 "fr": "Notification de changement sur les onglets" |                 "fr": "Saisie automatique des types de flux/global et env" | ||||||
|             }, |             }, | ||||||
|             image: 'images/tab-changes.png', |             image: 'images/nr4-auto-complete.png', | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": `<p>When a tab contains undeployed changes it now shows the |                 "en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input | ||||||
|                     same style of change icon used by nodes.</p> |                 now all include auto-complete suggestions based on the live state of your flows.</p> | ||||||
|                     <p>This will make it much easier to track down changes when you're |                 `, | ||||||
|                     working across multiple flows.</p>`, |                 "ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p> | ||||||
|                 "ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p> |                 `, | ||||||
|                        <p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`, |                 "fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code> | ||||||
|                 "fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le |                 incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p> | ||||||
|                     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: { |             title: { | ||||||
|                 "en-US": "A bigger canvas to work with", |                 "en-US": "Config node customisation in Subflows", | ||||||
|                 "ja": "より広くなった作業キャンバス", |                 "ja": "サブフローでの設定ノードのカスタマイズ", | ||||||
|                 "fr": "Un canevas plus grand pour travailler" |                 "fr": "Personnalisation du noeud de configuration dans les sous-flux" | ||||||
|             }, |             }, | ||||||
|  |             image: 'images/nr4-sf-config.png', | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": `<p>The default canvas size has been increased so you can fit more |                 "en-US": `<p>Subflows can now be customised to allow each instance to use a different | ||||||
|                 into one flow.</p> |                 config node of a selected type.</p> | ||||||
|                 <p>We still recommend using tools such as subflows and Link Nodes to help |                 <p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing | ||||||
|                    keep things organised, but now you have more room to work in.</p>`, |                 of the messages received can be pointed at a different broker.</p> | ||||||
|                 "ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p> |                 `, | ||||||
|                        <p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`, |                 "ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p> | ||||||
|                 "fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus |                 <p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p> | ||||||
|                 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 |                 "fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un | ||||||
|                    à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>` |                 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: { |             title: { | ||||||
|                 "en-US": "Finding help", |                 "en-US": "Remembering palette state", | ||||||
|                 "ja": "ヘルプを見つける", |                 "ja": "パレットの状態を維持", | ||||||
|                 "fr": "Trouver de l'aide" |                 "fr": "Mémorisation de l'état de la palette" | ||||||
|             }, |             }, | ||||||
|             image: 'images/node-help.png', |  | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": `<p>All node edit dialogs now include a link to that node's help |                 "en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any | ||||||
|                 in the footer.</p> |                 filter you have applied.</p>`, | ||||||
|                 <p>Clicking it will open up the Help sidebar showing the help for that node.</p>`, |                 "ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`, | ||||||
|                 "ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p> |                 "fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements, | ||||||
|                        <p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`, |                 ainsi que le filtre que vous avez appliqué.</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: { |             title: { | ||||||
|                 "en-US": "Improved Context Menu", |                 "en-US": "Plugins shown in the Palette Manager", | ||||||
|                 "ja": "コンテキストメニューの改善", |                 "ja": "パレット管理にプラグインを表示", | ||||||
|                 "fr": "Menu contextuel amélioré" |                 "fr": "Affichage des Plugins dans le gestionnaire de palettes" | ||||||
|             }, |             }, | ||||||
|             image: 'images/context-menu.png', |             image: 'images/nr4-plugins.png', | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": `<p>The editor's context menu has been expanded to make lots more of |                 "en-US": `<p>The palette manager now shows any plugin modules you have installed, such as | ||||||
|                         the built-in actions available.</p> |                 <code>node-red-debugger</code>. Previously they would only be shown if the plugins include | ||||||
|                         <p>Adding nodes, working with groups and plenty |                 nodes for the palette.</p>`, | ||||||
|                         of other useful tools are now just a click away.</p> |                 "ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`, | ||||||
|                         <p>The flow tab bar also has its own context menu to make working |                 "fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés, | ||||||
|                         with your flows much easier.</p>`, |                 tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient | ||||||
|                 "ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p> |                 des noeuds pour la palette.</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: '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: { |             title: { | ||||||
|                 "en-US": "Node Updates", |                 "en-US": "Node Updates", | ||||||
| @@ -221,10 +203,28 @@ export default { | |||||||
|             // image: "images/", |             // image: "images/", | ||||||
|             description: { |             description: { | ||||||
|                 "en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and |                 "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>`, |                           small enhancements. Check the full changelog in the Help sidebar for a full list.</p> | ||||||
|                 "ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`, |                           <ul> | ||||||
|                 "fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et |                             <li>A fully RFC4180 compliant CSV mode</li> | ||||||
|                 petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>` |                             <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>` | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|   | |||||||
| @@ -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 { | interface NodeMessage { | ||||||
|     topic?: string; |     topic?: string; | ||||||
|     payload?: any; |     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 |     [other: string]: any; //permit other properties | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -19,15 +19,15 @@ declare const promisify:typeof import('util').promisify | |||||||
| /** | /** | ||||||
|  * @typedef NodeStatus |  * @typedef NodeStatus | ||||||
|  * @type {object} |  * @type {object} | ||||||
|  * @property {string} [fill] The fill property can be: red, green, yellow, blue or grey. |  * @property {'red'|'green'|'yellow'|'blue'|'grey'|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 {'ring'|'dot'|string} [shape] The shape property can be: ring or dot. | ||||||
|  * @property {string} [text] The text to display |  * @property {string|boolean|number} [text] The text to display | ||||||
|  */ |  */ | ||||||
| interface NodeStatus { | interface NodeStatus { | ||||||
|     /** The fill property can be: red, green, yellow, blue or grey */ |     /** 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 */ |     /** The shape property can be: ring or dot */ | ||||||
|     shape?: string, |     shape?: 'ring'|'dot'|string, | ||||||
|     /** The text to display */ |     /** The text to display */ | ||||||
|     text?: string|boolean|number |     text?: string|boolean|number | ||||||
| } | } | ||||||
| @@ -37,25 +37,24 @@ declare class node { | |||||||
|     * Send 1 or more messages asynchronously |     * Send 1 or more messages asynchronously | ||||||
|     * @param {object | object[]} msg  The msg object |     * @param {object | object[]} msg  The msg object | ||||||
|     * @param {Boolean} [clone=true]  Flag to indicate the `msg` should be cloned. Default = `true` |     * @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 */ |     /** Inform runtime this instance has completed its operation */ | ||||||
|     static done(); |     static done(); | ||||||
|     /** Send an error to the console and debug side bar. Include `msg` in the 2nd parameter to trigger the catch node.  */ |     /** 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 */ |     /** Log a warn message to the console and debug sidebar */ | ||||||
|     static warn(warning:string|object); |     static warn(warning:string|object); | ||||||
|     /** Log an info message to the console (not sent to sidebar)' */ |     /** Log an info message to the console (not sent to sidebar)' */ | ||||||
|     static log(info:string|object); |     static log(info:string|object); | ||||||
|     /** Sets the status icon and text underneath the node. |     /** Sets the status icon and text underneath the node. | ||||||
|     * @param {NodeStatus} status - The status object `{fill, shape, text}` |     * @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); |     static status(status:NodeStatus); | ||||||
|     /** Sets the status text underneath the node. |     /** 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); |     static status(status:string|boolean|number); | ||||||
|     /** the id of this node */ |     /** the id of this node */ | ||||||
| @@ -264,9 +263,12 @@ declare class global { | |||||||
|     /** Get an array of the keys in the context store */ |     /** Get an array of the keys in the context store */ | ||||||
|     static keys(store: string, callback: Function); |     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 { | 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...   |      * Predefined node-red variables...   | ||||||
|      *   * `NR_NODE_ID` - the ID of the node |      *   * `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_GROUP_NAME` - the Name of the containing group | ||||||
|      *   * `NR_FLOW_ID` - the ID of the flow the node is on |      *   * `NR_FLOW_ID` - the ID of the flow the node is on | ||||||
|      *   * `NR_FLOW_NAME` - the Name 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  |      * @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; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 |  * The `assert` module provides a set of assertion functions for verifying | ||||||
|  * invariants. |  * 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' { | declare module 'assert' { | ||||||
|     /** |     /** | ||||||
| @@ -290,8 +290,8 @@ declare module 'assert' { | |||||||
|          * > Stability: 3 - Legacy: Use {@link strictEqual} instead. |          * > Stability: 3 - Legacy: Use {@link strictEqual} instead. | ||||||
|          * |          * | ||||||
|          * Tests shallow, coercive equality between the `actual` and `expected` parameters |          * 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 |          * using the [`==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality). `NaN` is specially handled | ||||||
|          * and treated as being identical in case both sides are `NaN`. |          * and treated as being identical if both sides are `NaN`. | ||||||
|          * |          * | ||||||
|          * ```js |          * ```js | ||||||
|          * import assert from 'assert'; |          * import assert from 'assert'; | ||||||
| @@ -323,9 +323,8 @@ declare module 'assert' { | |||||||
|          * |          * | ||||||
|          * > Stability: 3 - Legacy: Use {@link notStrictEqual} instead. |          * > 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 |          * Tests shallow, coercive inequality with the [`!=` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality). `NaN` is | ||||||
|          * being identical in case both |          * specially handled and treated as being identical if both sides are `NaN`. | ||||||
|          * sides are `NaN`. |  | ||||||
|          * |          * | ||||||
|          * ```js |          * ```js | ||||||
|          * import assert from 'assert'; |          * import assert from 'assert'; | ||||||
| @@ -415,7 +414,7 @@ declare module 'assert' { | |||||||
|         function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void; |         function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void; | ||||||
|         /** |         /** | ||||||
|          * Tests strict equality between the `actual` and `expected` parameters as |          * 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 |          * ```js | ||||||
|          * import assert from 'assert/strict'; |          * 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; |         function strictEqual<T>(actual: unknown, expected: T, message?: string | Error): asserts actual is T; | ||||||
|         /** |         /** | ||||||
|          * Tests strict inequality between the `actual` and `expected` parameters as |          * 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 |          * ```js | ||||||
|          * import assert from 'assert/strict'; |          * import assert from 'assert/strict'; | ||||||
|   | |||||||
| @@ -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' { | declare module 'assert/strict' { | ||||||
|     import { strict } from 'node:assert'; |     import { strict } from 'node:assert'; | ||||||
|   | |||||||
| @@ -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 |  * The `async_hooks` module provides an API to track asynchronous resources. It | ||||||
| @@ -9,7 +9,7 @@ | |||||||
|  * import async_hooks from 'async_hooks'; |  * import async_hooks from 'async_hooks'; | ||||||
|  * ``` |  * ``` | ||||||
|  * @experimental |  * @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' { | declare module 'async_hooks' { | ||||||
|     /** |     /** | ||||||
| @@ -367,7 +367,7 @@ declare module 'async_hooks' { | |||||||
|      * |      * | ||||||
|      * Each instance of `AsyncLocalStorage` maintains an independent storage context. |      * Each instance of `AsyncLocalStorage` maintains an independent storage context. | ||||||
|      * Multiple instances can safely exist simultaneously without risk of interfering |      * 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 |      * @since v13.10.0, v12.17.0 | ||||||
|      */ |      */ | ||||||
|     class AsyncLocalStorage<T> { |     class AsyncLocalStorage<T> { | ||||||
| @@ -398,8 +398,9 @@ declare module 'async_hooks' { | |||||||
|         getStore(): T | undefined; |         getStore(): T | undefined; | ||||||
|         /** |         /** | ||||||
|          * Runs a function synchronously within a context and returns its |          * Runs a function synchronously within a context and returns its | ||||||
|          * return value. The store is not accessible outside of the callback function or |          * return value. The store is not accessible outside of the callback function. | ||||||
|          * the asynchronous operations created within the callback. |          * The store is accessible to any asynchronous operations created within the | ||||||
|  |          * callback. | ||||||
|          * |          * | ||||||
|          * The optional `args` are passed to the callback function. |          * The optional `args` are passed to the callback function. | ||||||
|          * |          * | ||||||
| @@ -413,6 +414,9 @@ declare module 'async_hooks' { | |||||||
|          * try { |          * try { | ||||||
|          *   asyncLocalStorage.run(store, () => { |          *   asyncLocalStorage.run(store, () => { | ||||||
|          *     asyncLocalStorage.getStore(); // Returns the store object |          *     asyncLocalStorage.getStore(); // Returns the store object | ||||||
|  |          *     setTimeout(() => { | ||||||
|  |          *       asyncLocalStorage.getStore(); // Returns the store object | ||||||
|  |          *     }, 200); | ||||||
|          *     throw new Error(); |          *     throw new Error(); | ||||||
|          *   }); |          *   }); | ||||||
|          * } catch (e) { |          * } catch (e) { | ||||||
|   | |||||||
| @@ -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 |  * `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]. |  * // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74]. | ||||||
|  * const buf7 = Buffer.from('tést', 'latin1'); |  * 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' { | declare module 'buffer' { | ||||||
|     import { BinaryLike } from 'node:crypto'; |     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 |      * 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. |      * multiple worker threads. | ||||||
|      * @since v15.7.0 |      * @since v15.7.0, v14.18.0 | ||||||
|      * @experimental |  | ||||||
|      */ |      */ | ||||||
|     export class Blob { |     export class Blob { | ||||||
|         /** |         /** | ||||||
|          * The total size of the `Blob` in bytes. |          * The total size of the `Blob` in bytes. | ||||||
|          * @since v15.7.0 |          * @since v15.7.0, v14.18.0 | ||||||
|          */ |          */ | ||||||
|         readonly size: number; |         readonly size: number; | ||||||
|         /** |         /** | ||||||
|          * The content-type of the `Blob`. |          * The content-type of the `Blob`. | ||||||
|          * @since v15.7.0 |          * @since v15.7.0, v14.18.0 | ||||||
|          */ |          */ | ||||||
|         readonly type: string; |         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 |          * 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. |          * the `Blob` data. | ||||||
|          * @since v15.7.0 |          * @since v15.7.0, v14.18.0 | ||||||
|          */ |          */ | ||||||
|         arrayBuffer(): Promise<ArrayBuffer>; |         arrayBuffer(): Promise<ArrayBuffer>; | ||||||
|         /** |         /** | ||||||
|          * Creates and returns a new `Blob` containing a subset of this `Blob` objects |          * Creates and returns a new `Blob` containing a subset of this `Blob` objects | ||||||
|          * data. The original `Blob` is not altered. |          * data. The original `Blob` is not altered. | ||||||
|          * @since v15.7.0 |          * @since v15.7.0, v14.18.0 | ||||||
|          * @param start The starting index. |          * @param start The starting index. | ||||||
|          * @param end The ending index. |          * @param end The ending index. | ||||||
|          * @param type The content-type for the new `Blob` |          * @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 |          * Returns a promise that fulfills with the contents of the `Blob` decoded as a | ||||||
|          * UTF-8 string. |          * UTF-8 string. | ||||||
|          * @since v15.7.0 |          * @since v15.7.0, v14.18.0 | ||||||
|          */ |          */ | ||||||
|         text(): Promise<string>; |         text(): Promise<string>; | ||||||
|         /** |         /** | ||||||
| @@ -169,6 +168,12 @@ declare module 'buffer' { | |||||||
|     } |     } | ||||||
|     export import atob = globalThis.atob; |     export import atob = globalThis.atob; | ||||||
|     export import btoa = globalThis.btoa; |     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 { |     global { | ||||||
|         // Buffer class |         // Buffer class | ||||||
|         type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex'; |         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 |              * @since v0.11.13 | ||||||
|              * @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details. |              * @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. |              * 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. |              * 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_ |              * 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 |              * ```js | ||||||
|              * import { Buffer } from 'buffer'; |              * import { Buffer } from 'buffer'; | ||||||
| @@ -485,7 +490,7 @@ declare module 'buffer' { | |||||||
|              * if `size` is 0. |              * if `size` is 0. | ||||||
|              * |              * | ||||||
|              * The underlying memory for `Buffer` instances created in this way is _not_ |              * 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. |              * such `Buffer` instances with zeroes. | ||||||
|              * |              * | ||||||
|              * When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, |              * 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 [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). |              * @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`. |              * 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 |              * Returns a new `Buffer` that references the same memory as the original, but | ||||||
|              * offset and cropped by the `start` and `end` indices. |              * 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()`, |              * This method is not compatible with the `Uint8Array.prototype.slice()`, | ||||||
|              * which is a superclass of `Buffer`. To copy the slice, use`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()); |              * console.log(buf.toString()); | ||||||
|              * // Prints: buffer |              * // 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 |              * @since v0.3.0 | ||||||
|  |              * @deprecated Use `subarray` instead. | ||||||
|              * @param [start=0] Where the new `Buffer` will start. |              * @param [start=0] Where the new `Buffer` will start. | ||||||
|              * @param [end=buf.length] Where the new `Buffer` will end (not inclusive). |              * @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 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. |              * * 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 |              * * a number, `value` will be interpreted as an unsigned 8-bit integer | ||||||
|              * value between `0` and `255`. |              * value between `0` and `255`. | ||||||
|              * |              * | ||||||
| @@ -2208,7 +2220,7 @@ declare module 'buffer' { | |||||||
|          * **binary data and predate the introduction of typed arrays in JavaScript.** |          * **binary data and predate the introduction of typed arrays in JavaScript.** | ||||||
|          * **For code running using Node.js APIs, converting between base64-encoded strings** |          * **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')`.** |          * **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. |          * @deprecated Use `Buffer.from(data, 'base64')` instead. | ||||||
|          * @param data The Base64-encoded input string. |          * @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.** |          * **binary data and predate the introduction of typed arrays in JavaScript.** | ||||||
|          * **For code running using Node.js APIs, converting between base64-encoded strings** |          * **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')`.** |          * **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. |          * @deprecated Use `buf.toString('base64')` instead. | ||||||
|          * @param data An ASCII (Latin1) string. |          * @param data An ASCII (Latin1) string. | ||||||
|          */ |          */ | ||||||
|         function btoa(data: string): 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' { | declare module 'node:buffer' { | ||||||
|   | |||||||
| @@ -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 |  * 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. |  * 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 |  * The command lookup is performed using the `options.env.PATH` environment | ||||||
|  * variable if it is in the `options` object. Otherwise, `process.env.PATH` is |  * variable if `env` is in the `options` object. Otherwise, `process.env.PATH` is | ||||||
|  * used. |  * 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 |  * On Windows, environment variables are case-insensitive. Node.js | ||||||
|  * lexicographically sorts the `env` keys and uses the first one that |  * 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, |  * 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 |  * the synchronous methods can have significant impact on performance due to | ||||||
|  * stalling the event loop while spawned processes complete. |  * 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' { | declare module 'child_process' { | ||||||
|     import { ObjectEncodingOptions } from 'node:fs'; |     import { ObjectEncodingOptions } from 'node:fs'; | ||||||
| @@ -624,7 +627,7 @@ declare module 'child_process' { | |||||||
|     } |     } | ||||||
|     interface CommonOptions extends ProcessEnvOptions { |     interface CommonOptions extends ProcessEnvOptions { | ||||||
|         /** |         /** | ||||||
|          * @default false |          * @default true | ||||||
|          */ |          */ | ||||||
|         windowsHide?: boolean | undefined; |         windowsHide?: boolean | undefined; | ||||||
|         /** |         /** | ||||||
|   | |||||||
| @@ -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 |  * Clusters of Node.js processes can be used to run multiple instances of Node.js | ||||||
|  * multi-core systems, the user will sometimes want to launch a cluster of Node.js |  * that can distribute workloads among their application threads. When process | ||||||
|  * processes to handle the load. |  * 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 |  * The cluster module allows easy creation of child processes that all share | ||||||
|  * server ports. |  * server ports. | ||||||
| @@ -52,7 +53,7 @@ | |||||||
|  * ``` |  * ``` | ||||||
|  * |  * | ||||||
|  * On Windows, it is not yet possible to set up a named pipe server in a worker. |  * 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' { | declare module 'cluster' { | ||||||
|     import * as child from 'node:child_process'; |     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. |          * 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: |          * 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, callback?: (error: Error | null) => void): boolean; | ||||||
|         send(message: child.Serializable, sendHandle: child.SendHandle, options?: child.MessageOptions, 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 |          * This function will kill the worker. In the primary worker, it does this by | ||||||
|          * by disconnecting the `worker.process`, and once disconnected, killing |          * disconnecting the `worker.process`, and once disconnected, killing with`signal`. In the worker, it does it by killing the process with `signal`. | ||||||
|          * with `signal`. In the worker, it does it by disconnecting the channel, |  | ||||||
|          * and then exiting with code `0`. |  | ||||||
|          * |          * | ||||||
|          * Because `kill()` attempts to gracefully disconnect the worker process, it is |          * The `kill()` function kills the worker process without waiting for a graceful | ||||||
|          * susceptible to waiting indefinitely for the disconnect to complete. For example, |          * disconnect, it has the same behavior as `worker.process.kill()`. | ||||||
|          * 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()`. |  | ||||||
|          * |          * | ||||||
|          * Causes `.exitedAfterDisconnect` to be set. |          * This method is aliased as `worker.destroy()` for backwards compatibility. | ||||||
|          * |  | ||||||
|          * This method is aliased as `worker.destroy()` for backward compatibility. |  | ||||||
|          * |          * | ||||||
|          * In a worker, `process.kill()` exists, but it is not this function; |          * In a worker, `process.kill()` exists, but it is not this function; | ||||||
|          * it is `kill()`. |          * it is `kill()`. | ||||||
| @@ -256,7 +251,8 @@ declare module 'cluster' { | |||||||
|          */ |          */ | ||||||
|         isDead(): boolean; |         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`. |          * worker has not exited, it is `undefined`. | ||||||
|          * |          * | ||||||
|          * The boolean `worker.exitedAfterDisconnect` allows distinguishing between |          * The boolean `worker.exitedAfterDisconnect` allows distinguishing between | ||||||
|   | |||||||
| @@ -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 |  * The `console` module provides a simple debugging console that is similar to the | ||||||
| @@ -56,7 +56,7 @@ | |||||||
|  * myConsole.warn(`Danger ${name}! Danger!`); |  * myConsole.warn(`Danger ${name}! Danger!`); | ||||||
|  * // Prints: Danger Will Robinson! Danger!, to err |  * // 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' { | declare module 'console' { | ||||||
|     import console = require('node:console'); |     import console = require('node:console'); | ||||||
|   | |||||||
| @@ -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 |  * The `crypto` module provides cryptographic functionality that includes a set of | ||||||
| @@ -16,12 +16,64 @@ | |||||||
|  * // Prints: |  * // Prints: | ||||||
|  * //   c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e |  * //   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' { | declare module 'crypto' { | ||||||
|     import * as stream from 'node:stream'; |     import * as stream from 'node:stream'; | ||||||
|     import { PeerCertificate } from 'node:tls'; |     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 |          * @deprecated | ||||||
|          * @param spkac |          * @param spkac | ||||||
| @@ -45,31 +97,6 @@ declare module 'crypto' { | |||||||
|          */ |          */ | ||||||
|         verifySpkac(spkac: NodeJS.ArrayBufferView): boolean; |         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 { |     namespace constants { | ||||||
|         // https://nodejs.org/dist/latest-v10.x/docs/api/crypto.html#crypto_crypto_constants |         // https://nodejs.org/dist/latest-v10.x/docs/api/crypto.html#crypto_crypto_constants | ||||||
|         const OPENSSL_VERSION_NUMBER: number; |         const OPENSSL_VERSION_NUMBER: number; | ||||||
| @@ -175,7 +202,7 @@ declare module 'crypto' { | |||||||
|      * |      * | ||||||
|      * The `algorithm` is dependent on the available algorithms supported by the |      * The `algorithm` is dependent on the available algorithms supported by the | ||||||
|      * version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc. |      * 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. |      * display the available digest algorithms. | ||||||
|      * |      * | ||||||
|      * Example: generating the sha256 sum of a file |      * 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 |      * The `algorithm` is dependent on the available algorithms supported by the | ||||||
|      * version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc. |      * 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. |      * display the available digest algorithms. | ||||||
|      * |      * | ||||||
|      * The `key` is the HMAC key used to generate the cryptographic HMAC hash. If it is |      * 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; |     function createHmac(algorithm: string, key: BinaryLike | KeyObject, options?: stream.TransformOptions): Hmac; | ||||||
|     // https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings |     // 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 CharacterEncoding = 'utf8' | 'utf-8' | 'utf16le' | 'latin1'; | ||||||
|     type LegacyCharacterEncoding = 'ascii' | 'binary' | 'ucs2' | 'ucs-2'; |     type LegacyCharacterEncoding = 'ascii' | 'binary' | 'ucs2' | 'ucs-2'; | ||||||
|     type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding; |     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`. |      * 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 |      * 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 |      * 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. |      * 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 |      * 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. |      * display the available cipher algorithms. | ||||||
|      * |      * | ||||||
|      * The `password` is used to derive the cipher key and initialization vector (IV). |      * The `password` is used to derive the cipher key and initialization vector (IV). | ||||||
| @@ -700,12 +728,13 @@ declare module 'crypto' { | |||||||
|      * initialization vector (`iv`). |      * initialization vector (`iv`). | ||||||
|      * |      * | ||||||
|      * The `options` argument controls stream behavior and is optional except when a |      * 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 |      * 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. |      * 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 |      * 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. |      * 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 |      * 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). |      * 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 |      * 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`. |      * 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 |      * 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 |      * 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`). |      * 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 |      * 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 |      * 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. |      * 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 |      * 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. |      * 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 |      * 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; |         format?: KeyFormat | undefined; | ||||||
|         type?: 'pkcs1' | 'pkcs8' | 'sec1' | undefined; |         type?: 'pkcs1' | 'pkcs8' | 'sec1' | undefined; | ||||||
|         passphrase?: string | Buffer | undefined; |         passphrase?: string | Buffer | undefined; | ||||||
|         encoding?: string | undefined; |  | ||||||
|     } |     } | ||||||
|     interface PublicKeyInput { |     interface PublicKeyInput { | ||||||
|         key: string | Buffer; |         key: string | Buffer; | ||||||
|         format?: KeyFormat | undefined; |         format?: KeyFormat | undefined; | ||||||
|         type?: 'pkcs1' | 'spki' | 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`. |      * 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 { |     interface VerifyKeyObjectInput extends SigningOptions { | ||||||
|         key: KeyObject; |         key: KeyObject; | ||||||
|     } |     } | ||||||
|     interface VerifyJsonWebKeyInput extends JsonWebKeyInput, SigningOptions {} |  | ||||||
|     type KeyLike = string | Buffer | KeyObject; |     type KeyLike = string | Buffer | KeyObject; | ||||||
|     /** |     /** | ||||||
|      * The `Sign` class is a utility for generating signatures. It can be used in one |      * 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. |          * be passed instead of a public key. | ||||||
|          * @since v0.1.92 |          * @since v0.1.92 | ||||||
|          */ |          */ | ||||||
|         verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: NodeJS.ArrayBufferView): boolean; |         verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: NodeJS.ArrayBufferView): boolean; | ||||||
|         verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, signature: string, signature_format?: BinaryToTextEncoding): boolean; |         verify(object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: string, signature_format?: BinaryToTextEncoding): boolean; | ||||||
|     } |     } | ||||||
|     /** |     /** | ||||||
|      * Creates a `DiffieHellman` key exchange object using the supplied `prime` and an |      * Creates a `DiffieHellman` key exchange object using the supplied `prime` and an | ||||||
| @@ -1453,10 +1481,10 @@ declare module 'crypto' { | |||||||
|      * @param [generator=2] |      * @param [generator=2] | ||||||
|      * @param generatorEncoding The `encoding` of the `generator` string. |      * @param generatorEncoding The `encoding` of the `generator` string. | ||||||
|      */ |      */ | ||||||
|     function createDiffieHellman(primeLength: number, generator?: number | NodeJS.ArrayBufferView): DiffieHellman; |     function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman; | ||||||
|     function createDiffieHellman(prime: NodeJS.ArrayBufferView): DiffieHellman; |     function createDiffieHellman(prime: ArrayBuffer | NodeJS.ArrayBufferView, generator?: number | ArrayBuffer | NodeJS.ArrayBufferView): DiffieHellman; | ||||||
|     function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding): DiffieHellman; |     function createDiffieHellman(prime: ArrayBuffer | NodeJS.ArrayBufferView, generator: string, generatorEncoding: BinaryToTextEncoding): DiffieHellman; | ||||||
|     function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator: number | NodeJS.ArrayBufferView): DiffieHellman; |     function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator?: number | ArrayBuffer | NodeJS.ArrayBufferView): DiffieHellman; | ||||||
|     function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator: string, generatorEncoding: BinaryToTextEncoding): DiffieHellman; |     function createDiffieHellman(prime: string, primeEncoding: BinaryToTextEncoding, generator: string, generatorEncoding: BinaryToTextEncoding): DiffieHellman; | ||||||
|     /** |     /** | ||||||
|      * The `DiffieHellman` class is a utility for creating Diffie-Hellman key |      * 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 |      * 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). |      * 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). |      * 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 |      * 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 |          * If `encoding` is specified, a string is returned; otherwise a `Buffer` is | ||||||
|          * returned. |          * returned. | ||||||
|          * @since v0.11.14 |          * @since v0.11.14 | ||||||
|          * @param encoding The `encoding` of the return value. |          * @param [encoding] The `encoding` of the return value. | ||||||
|          * @param [format='uncompressed'] |          * @param [format='uncompressed'] | ||||||
|          * @return The EC Diffie-Hellman public key in the specified `encoding` and `format`. |          * @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; |         getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; | ||||||
|         /** |         /** | ||||||
|          * Sets the EC Diffie-Hellman private key. |          * 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/). |      * 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 |      * `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 |      * 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 |      * entry, such as `Uint16Array`, the result will be computed using the platform | ||||||
| @@ -2331,7 +2360,7 @@ declare module 'crypto' { | |||||||
|     /** @deprecated since v10.0.0 */ |     /** @deprecated since v10.0.0 */ | ||||||
|     const DEFAULT_ENCODING: BufferEncoding; |     const DEFAULT_ENCODING: BufferEncoding; | ||||||
|     type KeyType = 'rsa' | 'rsa-pss' | 'dsa' | 'ec' | 'ed25519' | 'ed448' | 'x25519' | 'x448'; |     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> { |     interface BasePrivateKeyEncodingOptions<T extends KeyFormat> { | ||||||
|         format: T; |         format: T; | ||||||
|         cipher?: string | undefined; |         cipher?: string | undefined; | ||||||
| @@ -2942,16 +2971,11 @@ declare module 'crypto' { | |||||||
|      * If the `callback` function is provided this function uses libuv's threadpool. |      * If the `callback` function is provided this function uses libuv's threadpool. | ||||||
|      * @since v12.0.0 |      * @since v12.0.0 | ||||||
|      */ |      */ | ||||||
|  |     function verify(algorithm: string | null | undefined, data: NodeJS.ArrayBufferView, key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, signature: NodeJS.ArrayBufferView): boolean; | ||||||
|     function verify( |     function verify( | ||||||
|         algorithm: string | null | undefined, |         algorithm: string | null | undefined, | ||||||
|         data: NodeJS.ArrayBufferView, |         data: NodeJS.ArrayBufferView, | ||||||
|         key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput, |         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, |         signature: NodeJS.ArrayBufferView, | ||||||
|         callback: (error: Error | null, result: boolean) => void |         callback: (error: Error | null, result: boolean) => void | ||||||
|     ): void; |     ): void; | ||||||
| @@ -3100,34 +3124,33 @@ declare module 'crypto' { | |||||||
|          */ |          */ | ||||||
|         disableEntropyCache?: boolean | undefined; |         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 |      * 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. |      * 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 { |     interface X509CheckOptions { | ||||||
|         /** |         /** | ||||||
|          * @default 'always' |          * @default 'always' | ||||||
|          */ |          */ | ||||||
|         subject?: 'always' | 'default' | 'never'; |         subject: 'always' | 'never'; | ||||||
|         /** |         /** | ||||||
|          * @default true |          * @default true | ||||||
|          */ |          */ | ||||||
|         wildcards?: boolean; |         wildcards: boolean; | ||||||
|         /** |         /** | ||||||
|          * @default true |          * @default true | ||||||
|          */ |          */ | ||||||
|         partialWildcards?: boolean; |         partialWildcards: boolean; | ||||||
|         /** |         /** | ||||||
|          * @default false |          * @default false | ||||||
|          */ |          */ | ||||||
|         multiLabelWildcards?: boolean; |         multiLabelWildcards: boolean; | ||||||
|         /** |         /** | ||||||
|          * @default false |          * @default false | ||||||
|          */ |          */ | ||||||
|         singleLabelSubdomains?: boolean; |         singleLabelSubdomains: boolean; | ||||||
|     } |     } | ||||||
|     /** |     /** | ||||||
|      * Encapsulates an X509 certificate and provides read-only access to |      * Encapsulates an X509 certificate and provides read-only access to | ||||||
| @@ -3144,12 +3167,16 @@ declare module 'crypto' { | |||||||
|      */ |      */ | ||||||
|     class X509Certificate { |     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 |          * @since v15.6.0 | ||||||
|          */ |          */ | ||||||
|         readonly ca: boolean; |         readonly ca: boolean; | ||||||
|         /** |         /** | ||||||
|          * The SHA-1 fingerprint of this certificate. |          * 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 |          * @since v15.6.0 | ||||||
|          */ |          */ | ||||||
|         readonly fingerprint: string; |         readonly fingerprint: string; | ||||||
| @@ -3208,6 +3235,10 @@ declare module 'crypto' { | |||||||
|         readonly raw: Buffer; |         readonly raw: Buffer; | ||||||
|         /** |         /** | ||||||
|          * The serial number of this certificate. |          * 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 |          * @since v15.6.0 | ||||||
|          */ |          */ | ||||||
|         readonly serialNumber: string; |         readonly serialNumber: string; | ||||||
| @@ -3224,18 +3255,50 @@ declare module 'crypto' { | |||||||
|         constructor(buffer: BinaryLike); |         constructor(buffer: BinaryLike); | ||||||
|         /** |         /** | ||||||
|          * Checks whether the certificate matches the given email address. |          * 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 |          * @since v15.6.0 | ||||||
|          * @return Returns `email` if the certificate matches, `undefined` if it does not. |          * @return Returns `email` if the certificate matches, `undefined` if it does not. | ||||||
|          */ |          */ | ||||||
|         checkEmail(email: string, options?: Pick<X509CheckOptions, 'subject'>): string | undefined; |         checkEmail(email: string, options?: Pick<X509CheckOptions, 'subject'>): string | undefined; | ||||||
|         /** |         /** | ||||||
|          * Checks whether the certificate matches the given host name. |          * 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 |          * @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; |         checkHost(name: string, options?: X509CheckOptions): string | undefined; | ||||||
|         /** |         /** | ||||||
|          * Checks whether the certificate matches the given IP address (IPv4 or IPv6). |          * 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 |          * @since v15.6.0 | ||||||
|          * @return Returns `ip` if the certificate matches, `undefined` if it does not. |          * @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] |      * @param [flags=crypto.constants.ENGINE_METHOD_ALL] | ||||||
|      */ |      */ | ||||||
|     function setEngine(engine: string, flags?: number): void; |     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. |      * 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. |              * The UUID is generated using a cryptographic pseudorandom number generator. | ||||||
|              * @since v16.7.0 |              * @since v16.7.0 | ||||||
|              */ |              */ | ||||||
|             randomUUID(): UUID; |             randomUUID(): string; | ||||||
|             CryptoKey: CryptoKeyConstructor; |             CryptoKey: CryptoKeyConstructor; | ||||||
|         } |         } | ||||||
|         // This constructor throws ILLEGAL_CONSTRUCTOR so it should not be newable. |         // 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`, |              * Using the method and parameters specified in `algorithm` and the keying material provided by `baseKey`, | ||||||
|              * `subtle.deriveBits()` attempts to generate `length` bits. |              * `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. |              * If successful, the returned promise will be resolved with an `<ArrayBuffer>` containing the generated data. | ||||||
|              * |              * | ||||||
|              * The algorithms currently supported include: |              * The algorithms currently supported include: | ||||||
|              * |              * | ||||||
|              * - `'ECDH'` |              * - `'ECDH'` | ||||||
|  |              * - `'X25519'` | ||||||
|  |              * - `'X448'` | ||||||
|              * - `'HKDF'` |              * - `'HKDF'` | ||||||
|              * - `'PBKDF2'` |              * - `'PBKDF2'` | ||||||
|              * @since v15.0.0 |              * @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`, |              * 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`. |              * `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: |              * The algorithms currently supported include: | ||||||
|              * |              * | ||||||
|              * - `'ECDH'` |              * - `'ECDH'` | ||||||
|  |              * - `'X25519'` | ||||||
|  |              * - `'X448'` | ||||||
|              * - `'HKDF'` |              * - `'HKDF'` | ||||||
|              * - `'PBKDF2'` |              * - `'PBKDF2'` | ||||||
|              * @param keyUsages See {@link https://nodejs.org/docs/latest/api/webcrypto.html#cryptokeyusages Key usages}. |              * @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-PSS'` | ||||||
|              * - `'RSA-OAEP'` |              * - `'RSA-OAEP'` | ||||||
|              * - `'ECDSA'` |              * - `'ECDSA'` | ||||||
|  |              * - `'Ed25519'` | ||||||
|  |              * - `'Ed448'` | ||||||
|              * - `'ECDH'` |              * - `'ECDH'` | ||||||
|  |              * - `'X25519'` | ||||||
|  |              * - `'X448'` | ||||||
|              * The `<CryptoKey>` (secret key) generating algorithms supported include: |              * The `<CryptoKey>` (secret key) generating algorithms supported include: | ||||||
|              * |              * | ||||||
|              * - `'HMAC'` |              * - `'HMAC'` | ||||||
| @@ -3787,6 +3874,8 @@ declare module 'crypto' { | |||||||
|              * - `'RSASSA-PKCS1-v1_5'` |              * - `'RSASSA-PKCS1-v1_5'` | ||||||
|              * - `'RSA-PSS'` |              * - `'RSA-PSS'` | ||||||
|              * - `'ECDSA'` |              * - `'ECDSA'` | ||||||
|  |              * - `'Ed25519'` | ||||||
|  |              * - `'Ed448'` | ||||||
|              * - `'HMAC'` |              * - `'HMAC'` | ||||||
|              * @since v15.0.0 |              * @since v15.0.0 | ||||||
|              */ |              */ | ||||||
| @@ -3812,7 +3901,11 @@ declare module 'crypto' { | |||||||
|              * - `'RSA-PSS'` |              * - `'RSA-PSS'` | ||||||
|              * - `'RSA-OAEP'` |              * - `'RSA-OAEP'` | ||||||
|              * - `'ECDSA'` |              * - `'ECDSA'` | ||||||
|  |              * - `'Ed25519'` | ||||||
|  |              * - `'Ed448'` | ||||||
|              * - `'ECDH'` |              * - `'ECDH'` | ||||||
|  |              * - `'X25519'` | ||||||
|  |              * - `'X448'` | ||||||
|              * - `'HMAC'` |              * - `'HMAC'` | ||||||
|              * - `'AES-CTR'` |              * - `'AES-CTR'` | ||||||
|              * - `'AES-CBC'` |              * - `'AES-CBC'` | ||||||
| @@ -3841,6 +3934,8 @@ declare module 'crypto' { | |||||||
|              * - `'RSASSA-PKCS1-v1_5'` |              * - `'RSASSA-PKCS1-v1_5'` | ||||||
|              * - `'RSA-PSS'` |              * - `'RSA-PSS'` | ||||||
|              * - `'ECDSA'` |              * - `'ECDSA'` | ||||||
|  |              * - `'Ed25519'` | ||||||
|  |              * - `'Ed448'` | ||||||
|              * - `'HMAC'` |              * - `'HMAC'` | ||||||
|              * @since v15.0.0 |              * @since v15.0.0 | ||||||
|              */ |              */ | ||||||
|   | |||||||
| @@ -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 `dgram` module provides an implementation of UDP datagram sockets. |  * The `dgram` module provides an implementation of UDP datagram sockets. | ||||||
| @@ -26,7 +26,7 @@ | |||||||
|  * server.bind(41234); |  * server.bind(41234); | ||||||
|  * // Prints: server listening 0.0.0.0:41234 |  * // Prints: server listening 0.0.0.0:41234 | ||||||
|  * ``` |  * ``` | ||||||
|  * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/dgram.js) |  * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/dgram.js) | ||||||
|  */ |  */ | ||||||
| declare module 'dgram' { | declare module 'dgram' { | ||||||
|     import { AddressInfo } from 'node:net'; |     import { AddressInfo } from 'node:net'; | ||||||
| @@ -263,7 +263,7 @@ declare module 'dgram' { | |||||||
|          * |          * | ||||||
|          * The `address` argument is a string. If the value of `address` is a host name, |          * The `address` argument is a string. If the value of `address` is a host name, | ||||||
|          * DNS will be used to resolve the address of the host. If `address` is not |          * DNS will be used to resolve the address of the host. If `address` is not | ||||||
|          * provided or otherwise falsy, `'127.0.0.1'` (for `udp4` sockets) or `'::1'`(for `udp6` sockets) will be used by default. |          * provided or otherwise nullish, `'127.0.0.1'` (for `udp4` sockets) or `'::1'`(for `udp6` sockets) will be used by default. | ||||||
|          * |          * | ||||||
|          * If the socket has not been previously bound with a call to `bind`, the socket |          * If the socket has not been previously bound with a call to `bind`, the socket | ||||||
|          * is assigned a random port number and is bound to the "all interfaces" address |          * is assigned a random port number and is bound to the "all interfaces" address | ||||||
| @@ -454,7 +454,7 @@ declare module 'dgram' { | |||||||
|          * TTL. If the TTL is decremented to 0 by a router, it will not be forwarded. |          * TTL. If the TTL is decremented to 0 by a router, it will not be forwarded. | ||||||
|          * Changing TTL values is typically done for network probes or when multicasting. |          * Changing TTL values is typically done for network probes or when multicasting. | ||||||
|          * |          * | ||||||
|          * The `ttl` argument may be between between 1 and 255\. The default on most systems |          * The `ttl` argument may be between 1 and 255\. The default on most systems | ||||||
|          * is 64. |          * is 64. | ||||||
|          * |          * | ||||||
|          * This method throws `EBADF` if called on an unbound socket. |          * This method throws `EBADF` if called on an unbound socket. | ||||||
|   | |||||||
| @@ -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 `diagnostics_channel` module provides an API to create named channels |  * The `diagnostics_channel` module provides an API to create named channels | ||||||
| @@ -23,7 +23,7 @@ | |||||||
|  * should generally include the module name to avoid collisions with data from |  * should generally include the module name to avoid collisions with data from | ||||||
|  * other modules. |  * other modules. | ||||||
|  * @experimental |  * @experimental | ||||||
|  * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/diagnostics_channel.js) |  * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/diagnostics_channel.js) | ||||||
|  */ |  */ | ||||||
| declare module 'diagnostics_channel' { | declare module 'diagnostics_channel' { | ||||||
|     /** |     /** | ||||||
| @@ -44,7 +44,7 @@ declare module 'diagnostics_channel' { | |||||||
|      * @param name The channel name |      * @param name The channel name | ||||||
|      * @return If there are active subscribers |      * @return If there are active subscribers | ||||||
|      */ |      */ | ||||||
|     function hasSubscribers(name: string | symbol): boolean; |     function hasSubscribers(name: string): boolean; | ||||||
|     /** |     /** | ||||||
|      * This is the primary entry-point for anyone wanting to interact with a named |      * This is the primary entry-point for anyone wanting to interact with a named | ||||||
|      * channel. It produces a channel object which is optimized to reduce overhead at |      * channel. It produces a channel object which is optimized to reduce overhead at | ||||||
| @@ -59,8 +59,8 @@ declare module 'diagnostics_channel' { | |||||||
|      * @param name The channel name |      * @param name The channel name | ||||||
|      * @return The named channel object |      * @return The named channel object | ||||||
|      */ |      */ | ||||||
|     function channel(name: string | symbol): Channel; |     function channel(name: string): Channel; | ||||||
|     type ChannelListener = (message: unknown, name: string | symbol) => void; |     type ChannelListener = (message: unknown, name: string) => void; | ||||||
|     /** |     /** | ||||||
|      * The class `Channel` represents an individual named channel within the data |      * The class `Channel` represents an individual named channel within the data | ||||||
|      * pipeline. It is use to track subscribers and to publish messages when there |      * pipeline. It is use to track subscribers and to publish messages when there | ||||||
| @@ -71,7 +71,7 @@ declare module 'diagnostics_channel' { | |||||||
|      * @since v15.1.0, v14.17.0 |      * @since v15.1.0, v14.17.0 | ||||||
|      */ |      */ | ||||||
|     class Channel { |     class Channel { | ||||||
|         readonly name: string | symbol; |         readonly name: string; | ||||||
|         /** |         /** | ||||||
|          * Check if there are active subscribers to this channel. This is helpful if |          * Check if there are active subscribers to this channel. This is helpful if | ||||||
|          * the message you want to send might be expensive to prepare. |          * the message you want to send might be expensive to prepare. | ||||||
| @@ -91,7 +91,7 @@ declare module 'diagnostics_channel' { | |||||||
|          * @since v15.1.0, v14.17.0 |          * @since v15.1.0, v14.17.0 | ||||||
|          */ |          */ | ||||||
|         readonly hasSubscribers: boolean; |         readonly hasSubscribers: boolean; | ||||||
|         private constructor(name: string | symbol); |         private constructor(name: string); | ||||||
|         /** |         /** | ||||||
|          * Publish a message to any subscribers to the channel. This will |          * Publish a message to any subscribers to the channel. This will | ||||||
|          * trigger message handlers synchronously so they will execute within |          * trigger message handlers synchronously so they will execute within | ||||||
| @@ -146,6 +146,7 @@ declare module 'diagnostics_channel' { | |||||||
|          * ``` |          * ``` | ||||||
|          * @since v15.1.0, v14.17.0 |          * @since v15.1.0, v14.17.0 | ||||||
|          * @param onMessage The previous subscribed handler to remove |          * @param onMessage The previous subscribed handler to remove | ||||||
|  |          * @return `true` if the handler was found, `false` otherwise. | ||||||
|          */ |          */ | ||||||
|         unsubscribe(onMessage: ChannelListener): void; |         unsubscribe(onMessage: ChannelListener): void; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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 `dns` module enables name resolution. For example, use it to look up IP |  * The `dns` module enables name resolution. For example, use it to look up IP | ||||||
| @@ -45,7 +45,7 @@ | |||||||
|  * ``` |  * ``` | ||||||
|  * |  * | ||||||
|  * See the `Implementation considerations section` for more information. |  * See the `Implementation considerations section` for more information. | ||||||
|  * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/dns.js) |  * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/dns.js) | ||||||
|  */ |  */ | ||||||
| declare module 'dns' { | declare module 'dns' { | ||||||
|     import * as dnsPromises from 'node:dns/promises'; |     import * as dnsPromises from 'node:dns/promises'; | ||||||
| @@ -61,6 +61,9 @@ declare module 'dns' { | |||||||
|         family?: number | undefined; |         family?: number | undefined; | ||||||
|         hints?: number | undefined; |         hints?: number | undefined; | ||||||
|         all?: boolean | undefined; |         all?: boolean | undefined; | ||||||
|  |         /** | ||||||
|  |          * @default true | ||||||
|  |          */ | ||||||
|         verbatim?: boolean | undefined; |         verbatim?: boolean | undefined; | ||||||
|     } |     } | ||||||
|     export interface LookupOneOptions extends LookupOptions { |     export interface LookupOneOptions extends LookupOptions { | ||||||
| @@ -174,7 +177,7 @@ declare module 'dns' { | |||||||
|         type: 'AAAA'; |         type: 'AAAA'; | ||||||
|     } |     } | ||||||
|     export interface CaaRecord { |     export interface CaaRecord { | ||||||
|         critical: number; |         critial: number; | ||||||
|         issue?: string | undefined; |         issue?: string | undefined; | ||||||
|         issuewild?: string | undefined; |         issuewild?: string | undefined; | ||||||
|         iodef?: string | undefined; |         iodef?: string | undefined; | ||||||
| @@ -317,7 +320,7 @@ declare module 'dns' { | |||||||
|      * Uses the DNS protocol to resolve `CAA` records for the `hostname`. The`addresses` argument passed to the `callback` function |      * Uses the DNS protocol to resolve `CAA` records for the `hostname`. The`addresses` argument passed to the `callback` function | ||||||
|      * will contain an array of certification authority authorization records |      * will contain an array of certification authority authorization records | ||||||
|      * available for the `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`). |      * available for the `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`). | ||||||
|      * @since v15.0.0 |      * @since v15.0.0, v14.17.0 | ||||||
|      */ |      */ | ||||||
|     export function resolveCaa(hostname: string, callback: (err: NodeJS.ErrnoException | null, records: CaaRecord[]) => void): void; |     export function resolveCaa(hostname: string, callback: (err: NodeJS.ErrnoException | null, records: CaaRecord[]) => void): void; | ||||||
|     export namespace resolveCaa { |     export namespace resolveCaa { | ||||||
| @@ -530,14 +533,16 @@ declare module 'dns' { | |||||||
|      */ |      */ | ||||||
|     export function getServers(): string[]; |     export function getServers(): string[]; | ||||||
|     /** |     /** | ||||||
|      * Set the default value of `verbatim` in {@link lookup}. The value could be: |      * Set the default value of `verbatim` in {@link lookup} and `dnsPromises.lookup()`. The value could be: | ||||||
|      * - `ipv4first`: sets default `verbatim` `false`. |  | ||||||
|      * - `verbatim`: sets default `verbatim` `true`. |  | ||||||
|      * |      * | ||||||
|      * The default is `ipv4first` and {@link setDefaultResultOrder} have higher priority than `--dns-result-order`. |      * * `ipv4first`: sets default `verbatim` `false`. | ||||||
|      * When using worker threads, {@link setDefaultResultOrder} from the main thread won't affect the default dns orders in workers. |      * * `verbatim`: sets default `verbatim` `true`. | ||||||
|      * @since v14.18.0 |      * | ||||||
|      * @param order must be 'ipv4first' or 'verbatim'. |      * The default is `ipv4first` and {@link setDefaultResultOrder} have higher | ||||||
|  |      * priority than `--dns-result-order`. When using `worker threads`,{@link setDefaultResultOrder} from the main thread won't affect the default | ||||||
|  |      * dns orders in workers. | ||||||
|  |      * @since v16.4.0, v14.18.0 | ||||||
|  |      * @param order must be `'ipv4first'` or `'verbatim'`. | ||||||
|      */ |      */ | ||||||
|     export function setDefaultResultOrder(order: 'ipv4first' | 'verbatim'): void; |     export function setDefaultResultOrder(order: 'ipv4first' | 'verbatim'): void; | ||||||
|     // Error codes |     // Error codes | ||||||
| @@ -643,7 +648,7 @@ declare module 'dns' { | |||||||
|          * The resolver will use the v4 local address when making requests to IPv4 DNS |          * The resolver will use the v4 local address when making requests to IPv4 DNS | ||||||
|          * servers, and the v6 local address when making requests to IPv6 DNS servers. |          * servers, and the v6 local address when making requests to IPv6 DNS servers. | ||||||
|          * The `rrtype` of resolution requests has no impact on the local address used. |          * The `rrtype` of resolution requests has no impact on the local address used. | ||||||
|          * @since v15.1.0 |          * @since v15.1.0, v14.17.0 | ||||||
|          * @param [ipv4='0.0.0.0'] A string representation of an IPv4 address. |          * @param [ipv4='0.0.0.0'] A string representation of an IPv4 address. | ||||||
|          * @param [ipv6='::0'] A string representation of an IPv6 address. |          * @param [ipv6='::0'] A string representation of an IPv6 address. | ||||||
|          */ |          */ | ||||||
|   | |||||||
| @@ -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 `dns.promises` API provides an alternative set of asynchronous DNS methods |  * The `dns.promises` API provides an alternative set of asynchronous DNS methods | ||||||
| @@ -192,7 +192,7 @@ declare module 'dns/promises' { | |||||||
|      * Uses the DNS protocol to resolve `CAA` records for the `hostname`. On success, |      * Uses the DNS protocol to resolve `CAA` records for the `hostname`. On success, | ||||||
|      * the `Promise` is resolved with an array of objects containing available |      * the `Promise` is resolved with an array of objects containing available | ||||||
|      * certification authority authorization records available for the `hostname`(e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'},{critical: 128, issue: 'pki.example.com'}]`). |      * certification authority authorization records available for the `hostname`(e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'},{critical: 128, issue: 'pki.example.com'}]`). | ||||||
|      * @since v15.0.0 |      * @since v15.0.0, v14.17.0 | ||||||
|      */ |      */ | ||||||
|     function resolveCaa(hostname: string): Promise<CaaRecord[]>; |     function resolveCaa(hostname: string): Promise<CaaRecord[]>; | ||||||
|     /** |     /** | ||||||
| @@ -335,14 +335,16 @@ declare module 'dns/promises' { | |||||||
|      */ |      */ | ||||||
|     function setServers(servers: ReadonlyArray<string>): void; |     function setServers(servers: ReadonlyArray<string>): void; | ||||||
|     /** |     /** | ||||||
|      * Set the default value of `verbatim` in {@link lookup}. The value could be: |      * Set the default value of `verbatim` in `dns.lookup()` and `dnsPromises.lookup()`. The value could be: | ||||||
|      * - `ipv4first`: sets default `verbatim` `false`. |  | ||||||
|      * - `verbatim`: sets default `verbatim` `true`. |  | ||||||
|      * |      * | ||||||
|      * The default is `ipv4first` and {@link setDefaultResultOrder} have higher priority than `--dns-result-order`. |      * * `ipv4first`: sets default `verbatim` `false`. | ||||||
|      * When using worker threads, {@link setDefaultResultOrder} from the main thread won't affect the default dns orders in workers. |      * * `verbatim`: sets default `verbatim` `true`. | ||||||
|      * @since v14.18.0 |      * | ||||||
|      * @param order must be 'ipv4first' or 'verbatim'. |      * The default is `ipv4first` and `dnsPromises.setDefaultResultOrder()` have | ||||||
|  |      * higher priority than `--dns-result-order`. When using `worker threads`,`dnsPromises.setDefaultResultOrder()` from the main thread won't affect the | ||||||
|  |      * default dns orders in workers. | ||||||
|  |      * @since v16.4.0, v14.18.0 | ||||||
|  |      * @param order must be `'ipv4first'` or `'verbatim'`. | ||||||
|      */ |      */ | ||||||
|     function setDefaultResultOrder(order: 'ipv4first' | 'verbatim'): void; |     function setDefaultResultOrder(order: 'ipv4first' | 'verbatim'): void; | ||||||
|     class Resolver { |     class Resolver { | ||||||
|   | |||||||