mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Compare commits
	
		
			754 Commits
		
	
	
		
			delay-node
			...
			Json-try-p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6ae42eb787 | ||
| 
						 | 
					7cd3e49f04 | ||
| 
						 | 
					173e75175e | ||
| 
						 | 
					800006dd76 | ||
| 
						 | 
					a824b6910a | ||
| 
						 | 
					dcea382b38 | ||
| 
						 | 
					d9f976baea | ||
| 
						 | 
					1fa13efe19 | ||
| 
						 | 
					33af5cd7c6 | ||
| 
						 | 
					7cb8f97ef1 | ||
| 
						 | 
					bf965a9cde | ||
| 
						 | 
					17ffff685a | ||
| 
						 | 
					ae76271cff | ||
| 
						 | 
					8f3a96d615 | ||
| 
						 | 
					30e750dfe5 | ||
| 
						 | 
					682dff7c6f | ||
| 
						 | 
					85415eb8a8 | ||
| 
						 | 
					68a80b9244 | ||
| 
						 | 
					c331da7323 | ||
| 
						 | 
					04ffa06221 | ||
| 
						 | 
					1f0690c6ec | ||
| 
						 | 
					711467abcd | ||
| 
						 | 
					9439cd0e3d | ||
| 
						 | 
					314c19650d | ||
| 
						 | 
					ed6afcd802 | ||
| 
						 | 
					082d4fe8e1 | ||
| 
						 | 
					cd23b44506 | ||
| 
						 | 
					46b6b024b9 | ||
| 
						 | 
					cb88cc35e5 | ||
| 
						 | 
					75c0c44809 | ||
| 
						 | 
					a091b82ba9 | ||
| 
						 | 
					a3b8f022e6 | ||
| 
						 | 
					279fcb7c51 | ||
| 
						 | 
					49a9376073 | ||
| 
						 | 
					96840ede56 | ||
| 
						 | 
					7e7f481f99 | ||
| 
						 | 
					3edbf52bc6 | ||
| 
						 | 
					fba6e801fc | ||
| 
						 | 
					720a163273 | ||
| 
						 | 
					6ac0c0a367 | ||
| 
						 | 
					300402d253 | ||
| 
						 | 
					0d9bfae503 | ||
| 
						 | 
					bfe0d3b8a3 | ||
| 
						 | 
					5fdd9c0546 | ||
| 
						 | 
					b6570a16b8 | ||
| 
						 | 
					8e2d3ea16f | ||
| 
						 | 
					bc2c81f058 | ||
| 
						 | 
					3e0f080ea7 | ||
| 
						 | 
					679e07189d | ||
| 
						 | 
					a38ebef100 | ||
| 
						 | 
					b8ad6475e1 | ||
| 
						 | 
					0f0cb3ac6d | ||
| 
						 | 
					d3efb9d7cc | ||
| 
						 | 
					84a237d3f5 | ||
| 
						 | 
					e6de52eede | ||
| 
						 | 
					98aee964d7 | ||
| 
						 | 
					570e5442e0 | ||
| 
						 | 
					b77a2dc353 | ||
| 
						 | 
					87af31de20 | ||
| 
						 | 
					cfe201dbe1 | ||
| 
						 | 
					6ccdab35e0 | ||
| 
						 | 
					55b9f36b45 | ||
| 
						 | 
					fbcb1130c9 | ||
| 
						 | 
					d4f7a6d2bc | ||
| 
						 | 
					8a19f71abe | ||
| 
						 | 
					fb153757b5 | ||
| 
						 | 
					4f175fc93e | ||
| 
						 | 
					2d4ca7cec0 | ||
| 
						 | 
					bf0ea89969 | ||
| 
						 | 
					073f0c2a20 | ||
| 
						 | 
					ba83be9062 | ||
| 
						 | 
					2e7188ea4f | ||
| 
						 | 
					5a012182d9 | ||
| 
						 | 
					b855438af6 | ||
| 
						 | 
					2ffea143e7 | ||
| 
						 | 
					61d85b49e6 | ||
| 
						 | 
					35f617e96c | ||
| 
						 | 
					6b6ad47c35 | ||
| 
						 | 
					e57183ed0e | ||
| 
						 | 
					ecfd61a822 | ||
| 
						 | 
					153f87704b | ||
| 
						 | 
					836f7d2163 | ||
| 
						 | 
					d4d6f71cf4 | ||
| 
						 | 
					42a9da006e | ||
| 
						 | 
					2bd5c4f527 | ||
| 
						 | 
					6a49b5c106 | ||
| 
						 | 
					23e14d1b72 | ||
| 
						 | 
					f4f11c8884 | ||
| 
						 | 
					2b220abdb7 | ||
| 
						 | 
					c1d947ebe3 | ||
| 
						 | 
					d695cf392e | ||
| 
						 | 
					21304a695c | ||
| 
						 | 
					fa51b06c46 | ||
| 
						 | 
					7560bb8d7b | ||
| 
						 | 
					fc9d65abcc | ||
| 
						 | 
					a7413cccd0 | ||
| 
						 | 
					7610353f07 | ||
| 
						 | 
					d3f978c90c | ||
| 
						 | 
					b55a8ef62a | ||
| 
						 | 
					8d79deffb5 | ||
| 
						 | 
					8158487c3e | ||
| 
						 | 
					0cc061196d | ||
| 
						 | 
					d0ec055222 | ||
| 
						 | 
					ae12ddd32b | ||
| 
						 | 
					31da3adaa9 | ||
| 
						 | 
					9fd5213f13 | ||
| 
						 | 
					de882f5849 | ||
| 
						 | 
					fded1e0021 | ||
| 
						 | 
					6cb06c146d | ||
| 
						 | 
					b8f1386ad0 | ||
| 
						 | 
					2b38b5ea50 | ||
| 
						 | 
					2f707a6b16 | ||
| 
						 | 
					d4c2fcd559 | ||
| 
						 | 
					082970cdb7 | ||
| 
						 | 
					fe97c78977 | ||
| 
						 | 
					79394aa69f | ||
| 
						 | 
					21fd6e3c21 | ||
| 
						 | 
					de4944cd83 | ||
| 
						 | 
					3fde5c27ed | ||
| 
						 | 
					e1d492813e | ||
| 
						 | 
					48d0ee3b6d | ||
| 
						 | 
					eebb64901c | ||
| 
						 | 
					60e0ed2af6 | ||
| 
						 | 
					f030694ef4 | ||
| 
						 | 
					e9ed13459a | ||
| 
						 | 
					af1e38fdf7 | ||
| 
						 | 
					b12900e680 | ||
| 
						 | 
					44aa1f4a5e | ||
| 
						 | 
					9425548a85 | ||
| 
						 | 
					bfd4fc81fe | ||
| 
						 | 
					439af2a325 | ||
| 
						 | 
					3204b04455 | ||
| 
						 | 
					bed1be14ba | ||
| 
						 | 
					7cd92faf0d | ||
| 
						 | 
					be7e28af5d | ||
| 
						 | 
					8eaa762ec5 | ||
| 
						 | 
					953a9f7cd4 | ||
| 
						 | 
					36f099d68b | ||
| 
						 | 
					c8fd5090bd | ||
| 
						 | 
					155e1be494 | ||
| 
						 | 
					cf5e125cb3 | ||
| 
						 | 
					764fc8477d | ||
| 
						 | 
					d35e62f8cf | ||
| 
						 | 
					904babdd13 | ||
| 
						 | 
					154d3842a8 | ||
| 
						 | 
					edb8a120bd | ||
| 
						 | 
					cdfeba0b82 | ||
| 
						 | 
					8ce1465e9f | ||
| 
						 | 
					a296b1c9c8 | ||
| 
						 | 
					bb8d7058a4 | ||
| 
						 | 
					816cfa1c7e | ||
| 
						 | 
					53938200fc | ||
| 
						 | 
					3885bb039d | ||
| 
						 | 
					4adad6e424 | ||
| 
						 | 
					5fb9531338 | ||
| 
						 | 
					273d9c76a7 | ||
| 
						 | 
					79a1d6c561 | ||
| 
						 | 
					4b0eb8475d | ||
| 
						 | 
					3dc874b517 | ||
| 
						 | 
					8a3da1ce8d | ||
| 
						 | 
					42d90542b5 | ||
| 
						 | 
					690a93d82d | ||
| 
						 | 
					8042fe4e2b | ||
| 
						 | 
					a27ce375db | ||
| 
						 | 
					db3688799d | ||
| 
						 | 
					a88be35292 | ||
| 
						 | 
					e2d7fcbfc2 | ||
| 
						 | 
					421d155586 | ||
| 
						 | 
					7f9e318214 | ||
| 
						 | 
					57386edb7c | ||
| 
						 | 
					94d5ba4550 | ||
| 
						 | 
					2a0b4ea828 | ||
| 
						 | 
					893ef227d4 | ||
| 
						 | 
					1fe6e5a00d | ||
| 
						 | 
					6c96cde73c | ||
| 
						 | 
					2b12834d53 | ||
| 
						 | 
					8a2e74b3b8 | ||
| 
						 | 
					aa1721ab3d | ||
| 
						 | 
					c0a256306b | ||
| 
						 | 
					f0b03b4ada | ||
| 
						 | 
					5503f53af2 | ||
| 
						 | 
					a89d294b27 | ||
| 
						 | 
					012e1cbcc5 | ||
| 
						 | 
					3759e0f778 | ||
| 
						 | 
					1c18641699 | ||
| 
						 | 
					e50e2201b1 | ||
| 
						 | 
					1419729458 | ||
| 
						 | 
					c14177b0e8 | ||
| 
						 | 
					126df969b3 | ||
| 
						 | 
					f8ee92ba06 | ||
| 
						 | 
					81a278dd8c | ||
| 
						 | 
					a98013806c | ||
| 
						 | 
					0171ffac6a | ||
| 
						 | 
					7544241316 | ||
| 
						 | 
					061afb3a94 | ||
| 
						 | 
					8a5eda9c1f | ||
| 
						 | 
					f62040f0ec | ||
| 
						 | 
					f2e51779e4 | ||
| 
						 | 
					da114fa3a5 | ||
| 
						 | 
					3775a1657b | ||
| 
						 | 
					2bd7c4bc81 | ||
| 
						 | 
					253c489a33 | ||
| 
						 | 
					ac84b6fe3f | ||
| 
						 | 
					8761e61439 | ||
| 
						 | 
					29e903e1c8 | ||
| 
						 | 
					ec27e19e3f | ||
| 
						 | 
					5df0dae11a | ||
| 
						 | 
					7fffc1a36d | ||
| 
						 | 
					f3d0179834 | ||
| 
						 | 
					8bf69c598a | ||
| 
						 | 
					aa5fad6628 | ||
| 
						 | 
					b0f1fad4e2 | ||
| 
						 | 
					3b6d0995b4 | ||
| 
						 | 
					0cbf4ac37d | ||
| 
						 | 
					9ccffee82c | ||
| 
						 | 
					1b38e2eedf | ||
| 
						 | 
					ce87abe96e | ||
| 
						 | 
					becbda8483 | ||
| 
						 | 
					da210e2ae4 | ||
| 
						 | 
					d4fc6feeba | ||
| 
						 | 
					f1cbca8d76 | ||
| 
						 | 
					dfd9364061 | ||
| 
						 | 
					1931395fdb | ||
| 
						 | 
					b01fd24e15 | ||
| 
						 | 
					c9d1329fc2 | ||
| 
						 | 
					ab2d3bfd80 | ||
| 
						 | 
					36bb172f29 | ||
| 
						 | 
					01e64be39d | ||
| 
						 | 
					4ebe160f6c | ||
| 
						 | 
					b427eca21f | ||
| 
						 | 
					3eb438c8d2 | ||
| 
						 | 
					068f425833 | ||
| 
						 | 
					b3c84242dc | ||
| 
						 | 
					24672d91d8 | ||
| 
						 | 
					4422af26ec | ||
| 
						 | 
					5329e803e2 | ||
| 
						 | 
					ad542b91fa | ||
| 
						 | 
					e9e03c945b | ||
| 
						 | 
					e20cfb3dae | ||
| 
						 | 
					48baac916c | ||
| 
						 | 
					adadf38b08 | ||
| 
						 | 
					d4e1469450 | ||
| 
						 | 
					2c456f044f | ||
| 
						 | 
					228c15ace3 | ||
| 
						 | 
					a0d15e6e7b | ||
| 
						 | 
					2fe78cf971 | ||
| 
						 | 
					5ec3544340 | ||
| 
						 | 
					5443a17775 | ||
| 
						 | 
					40d60e4eb3 | ||
| 
						 | 
					f3312a6403 | ||
| 
						 | 
					e638b55b30 | ||
| 
						 | 
					5caa76a8b3 | ||
| 
						 | 
					901a5ce9d2 | ||
| 
						 | 
					6ab74951f4 | ||
| 
						 | 
					c2625d696d | ||
| 
						 | 
					77fb5ef2ab | ||
| 
						 | 
					c20ca3399e | ||
| 
						 | 
					5825da9c76 | ||
| 
						 | 
					e3853ae402 | ||
| 
						 | 
					bd142a9710 | ||
| 
						 | 
					4f23847546 | ||
| 
						 | 
					85820c571d | ||
| 
						 | 
					d9bed03025 | ||
| 
						 | 
					d6e05962c9 | ||
| 
						 | 
					d32636ed6b | ||
| 
						 | 
					bbf066f030 | ||
| 
						 | 
					a3d2f6592e | ||
| 
						 | 
					87b6327c5e | ||
| 
						 | 
					abaebb329d | ||
| 
						 | 
					8970fe412d | ||
| 
						 | 
					9dc5ae21c4 | ||
| 
						 | 
					4132fb79a6 | ||
| 
						 | 
					9a4dc30604 | ||
| 
						 | 
					192b542fe4 | ||
| 
						 | 
					490547cd3d | ||
| 
						 | 
					17f9829498 | ||
| 
						 | 
					234e77fd06 | ||
| 
						 | 
					87ac831c8a | ||
| 
						 | 
					4e92492165 | ||
| 
						 | 
					c3d0b1114f | ||
| 
						 | 
					4463a7d4ba | ||
| 
						 | 
					741fe3dd90 | ||
| 
						 | 
					e910f3915d | ||
| 
						 | 
					39aafc5007 | ||
| 
						 | 
					9b83afae42 | ||
| 
						 | 
					bdf54f6cff | ||
| 
						 | 
					f2a9887a12 | ||
| 
						 | 
					4f4d78bfab | ||
| 
						 | 
					3b460fb8fa | ||
| 
						 | 
					ee15e9acc5 | ||
| 
						 | 
					48fce35fb3 | ||
| 
						 | 
					78899378c2 | ||
| 
						 | 
					67404a327d | ||
| 
						 | 
					702dfa4b79 | ||
| 
						 | 
					2144407e41 | ||
| 
						 | 
					b36dd62c50 | ||
| 
						 | 
					7026df7d96 | ||
| 
						 | 
					ed8e7afdf6 | ||
| 
						 | 
					d78e5932f9 | ||
| 
						 | 
					26d83bb9ea | ||
| 
						 | 
					8e89b1bdf2 | ||
| 
						 | 
					c880cc0987 | ||
| 
						 | 
					0874ba7a03 | ||
| 
						 | 
					7962278475 | ||
| 
						 | 
					c8949f5eeb | ||
| 
						 | 
					8108b93c5f | ||
| 
						 | 
					9dbe531bf7 | ||
| 
						 | 
					46e2ff1001 | ||
| 
						 | 
					d2cdc67ec7 | ||
| 
						 | 
					56121203bf | ||
| 
						 | 
					e13133fd2b | ||
| 
						 | 
					f20565fd16 | ||
| 
						 | 
					cf2d5841f5 | ||
| 
						 | 
					630d2ca926 | ||
| 
						 | 
					34cb93794c | ||
| 
						 | 
					122b5ba468 | ||
| 
						 | 
					401466d6c0 | ||
| 
						 | 
					7f2627dbc8 | ||
| 
						 | 
					6aecc3915c | ||
| 
						 | 
					ef1b3aa7f5 | ||
| 
						 | 
					1aaab2a814 | ||
| 
						 | 
					e93734b209 | ||
| 
						 | 
					9e5218f6b4 | ||
| 
						 | 
					711ec39327 | ||
| 
						 | 
					08049252f2 | ||
| 
						 | 
					f1e7ec0c6b | ||
| 
						 | 
					23765d9139 | ||
| 
						 | 
					43febe269c | ||
| 
						 | 
					40233c7702 | ||
| 
						 | 
					27ed81614b | ||
| 
						 | 
					889d23e9bd | ||
| 
						 | 
					f8571023f6 | ||
| 
						 | 
					6364e00202 | ||
| 
						 | 
					a76c6f86c6 | ||
| 
						 | 
					555e815402 | ||
| 
						 | 
					8a1d81989b | ||
| 
						 | 
					ee9234b2c6 | ||
| 
						 | 
					735b9c5844 | ||
| 
						 | 
					064f3eb3bc | ||
| 
						 | 
					f1775d4fd1 | ||
| 
						 | 
					a9bc111c4f | ||
| 
						 | 
					c100612473 | ||
| 
						 | 
					26087f8dc7 | ||
| 
						 | 
					36e75cb728 | ||
| 
						 | 
					d7a2fc2be4 | ||
| 
						 | 
					142176f194 | ||
| 
						 | 
					c5892fc17e | ||
| 
						 | 
					6e69cfbca4 | ||
| 
						 | 
					775181f761 | ||
| 
						 | 
					36e83d628e | ||
| 
						 | 
					5f6fcb2bc0 | ||
| 
						 | 
					7b106e5650 | ||
| 
						 | 
					79d9c83a2d | ||
| 
						 | 
					269669ba28 | ||
| 
						 | 
					4ef7240598 | ||
| 
						 | 
					efdf689c31 | ||
| 
						 | 
					f7606e92ca | ||
| 
						 | 
					6750be3ec9 | ||
| 
						 | 
					68fb5089f8 | ||
| 
						 | 
					a8d093bacd | ||
| 
						 | 
					233a1995b3 | ||
| 
						 | 
					8ef3baaffb | ||
| 
						 | 
					c9597b9447 | ||
| 
						 | 
					b2dc1d8b23 | ||
| 
						 | 
					859c0c7f6c | ||
| 
						 | 
					aaf18e2416 | ||
| 
						 | 
					fd679ef117 | ||
| 
						 | 
					6cc611b3f1 | ||
| 
						 | 
					77ee726f66 | ||
| 
						 | 
					dc603b76a4 | ||
| 
						 | 
					bcb3371acc | ||
| 
						 | 
					d14ce7e476 | ||
| 
						 | 
					4d26b806dd | ||
| 
						 | 
					a2b95dbb39 | ||
| 
						 | 
					47f7b43bcc | ||
| 
						 | 
					77fd8c120c | ||
| 
						 | 
					a1a6f40158 | ||
| 
						 | 
					ed09cd7489 | ||
| 
						 | 
					eb3330d145 | ||
| 
						 | 
					5ba0588c7b | ||
| 
						 | 
					d4a199f0e1 | ||
| 
						 | 
					32dd186f4d | ||
| 
						 | 
					d820f55358 | ||
| 
						 | 
					81f0fb3c74 | ||
| 
						 | 
					972c83cd52 | ||
| 
						 | 
					66a704af55 | ||
| 
						 | 
					31c5d6e1c1 | ||
| 
						 | 
					bf0ab95c09 | ||
| 
						 | 
					c1d85f760d | ||
| 
						 | 
					88ad2f4c18 | ||
| 
						 | 
					be9f9e7b0c | ||
| 
						 | 
					2cc1973f62 | ||
| 
						 | 
					eb4625a0b9 | ||
| 
						 | 
					5bfb01254b | ||
| 
						 | 
					bb80fa4a2d | ||
| 
						 | 
					ddb715d88d | ||
| 
						 | 
					395b499856 | ||
| 
						 | 
					cce6a47f11 | ||
| 
						 | 
					7fd17b4ec0 | ||
| 
						 | 
					e16ab2a0fd | ||
| 
						 | 
					15f5364c30 | ||
| 
						 | 
					65081767bf | ||
| 
						 | 
					c7c595e5fa | ||
| 
						 | 
					5b24e8b69c | ||
| 
						 | 
					e6a845e606 | ||
| 
						 | 
					ec8b8a7b87 | ||
| 
						 | 
					51a9205105 | ||
| 
						 | 
					ed5567fc73 | ||
| 
						 | 
					4b3f5d74a0 | ||
| 
						 | 
					b01c5a05e7 | ||
| 
						 | 
					36eddabc1c | ||
| 
						 | 
					ea11aa7a0d | ||
| 
						 | 
					e7efa76e6d | ||
| 
						 | 
					41c8ca8ab4 | ||
| 
						 | 
					4624079be7 | ||
| 
						 | 
					c6f6042271 | ||
| 
						 | 
					e9e3b9b7c6 | ||
| 
						 | 
					becbb09a29 | ||
| 
						 | 
					6f6ab50995 | ||
| 
						 | 
					d8ee766860 | ||
| 
						 | 
					108c26d8af | ||
| 
						 | 
					ed8d3088ca | ||
| 
						 | 
					46c4e2d212 | ||
| 
						 | 
					94891d45f9 | ||
| 
						 | 
					7448ad109e | ||
| 
						 | 
					6211dfe024 | ||
| 
						 | 
					9b85200954 | ||
| 
						 | 
					94ee739d91 | ||
| 
						 | 
					e81a6db9a3 | ||
| 
						 | 
					b2f5a259ab | ||
| 
						 | 
					c8a0d3c10d | ||
| 
						 | 
					97df964051 | ||
| 
						 | 
					66dd05f8bc | ||
| 
						 | 
					19589d9117 | ||
| 
						 | 
					8147b2e0b1 | ||
| 
						 | 
					be22f8cd14 | ||
| 
						 | 
					868be9b7ff | ||
| 
						 | 
					5011281104 | ||
| 
						 | 
					42992c64ec | ||
| 
						 | 
					2baff243ed | ||
| 
						 | 
					83440a6b0f | ||
| 
						 | 
					87c9a1c06c | ||
| 
						 | 
					b848fe249f | ||
| 
						 | 
					1e804d97ce | ||
| 
						 | 
					218d3c144b | ||
| 
						 | 
					05a4905490 | ||
| 
						 | 
					75103da378 | ||
| 
						 | 
					9db9b53c81 | ||
| 
						 | 
					0e4787f3e8 | ||
| 
						 | 
					f8d8d4b186 | ||
| 
						 | 
					45e0a1ffea | ||
| 
						 | 
					75c58093f1 | ||
| 
						 | 
					cc708e9fb4 | ||
| 
						 | 
					2ce0e38827 | ||
| 
						 | 
					5b980e8c13 | ||
| 
						 | 
					21b602650c | ||
| 
						 | 
					fa4b7a1a69 | ||
| 
						 | 
					977dfe700b | ||
| 
						 | 
					48ac50e1c9 | ||
| 
						 | 
					1a817947eb | ||
| 
						 | 
					be64603097 | ||
| 
						 | 
					f6b90c8271 | ||
| 
						 | 
					26e4be87c7 | ||
| 
						 | 
					cddbb8d80d | ||
| 
						 | 
					58023b4bf0 | ||
| 
						 | 
					4f18a5f1c3 | ||
| 
						 | 
					56df8d8bd3 | ||
| 
						 | 
					211ec104c2 | ||
| 
						 | 
					3fb573247d | ||
| 
						 | 
					6aac44db14 | ||
| 
						 | 
					3255e11cfc | ||
| 
						 | 
					844bf29de1 | ||
| 
						 | 
					04d91d1422 | ||
| 
						 | 
					db90e1f801 | ||
| 
						 | 
					7f30748a41 | ||
| 
						 | 
					a4e0abb48f | ||
| 
						 | 
					3f27dc89d8 | ||
| 
						 | 
					d6f6efc189 | ||
| 
						 | 
					2cda49fc38 | ||
| 
						 | 
					04f4a76b41 | ||
| 
						 | 
					0a8f7085f3 | ||
| 
						 | 
					7ae48d7390 | ||
| 
						 | 
					c908502644 | ||
| 
						 | 
					2f0631809d | ||
| 
						 | 
					91ab3bd972 | ||
| 
						 | 
					672636313c | ||
| 
						 | 
					79875ef50d | ||
| 
						 | 
					aea5445495 | ||
| 
						 | 
					754a36fbc9 | ||
| 
						 | 
					85dafc0b3c | ||
| 
						 | 
					b516ab9b4f | ||
| 
						 | 
					1a27e60e55 | ||
| 
						 | 
					2c710736e8 | ||
| 
						 | 
					69b9ff69be | ||
| 
						 | 
					a3a4fc0cc2 | ||
| 
						 | 
					ae686bb15d | ||
| 
						 | 
					68a5325849 | ||
| 
						 | 
					75e3bddfa9 | ||
| 
						 | 
					bd3a8db438 | ||
| 
						 | 
					102868bf74 | ||
| 
						 | 
					1a73a27102 | ||
| 
						 | 
					a9cf34ab56 | ||
| 
						 | 
					46d17c3314 | ||
| 
						 | 
					40f816c311 | ||
| 
						 | 
					13f1c12912 | ||
| 
						 | 
					93c25f5d1b | ||
| 
						 | 
					aa6ec60c34 | ||
| 
						 | 
					ac159bb52e | ||
| 
						 | 
					919aee64f9 | ||
| 
						 | 
					553bec1a1f | ||
| 
						 | 
					bcb6d1cf93 | ||
| 
						 | 
					7d24e5b279 | ||
| 
						 | 
					12253e23b5 | ||
| 
						 | 
					4acb66fb7a | ||
| 
						 | 
					68ef85b64b | ||
| 
						 | 
					b73efe6bb4 | ||
| 
						 | 
					89c84522d2 | ||
| 
						 | 
					98172764ac | ||
| 
						 | 
					448e881104 | ||
| 
						 | 
					f16134ab1f | ||
| 
						 | 
					f5dc1564a4 | ||
| 
						 | 
					133df75bd4 | ||
| 
						 | 
					440be0653a | ||
| 
						 | 
					d721a40ca5 | ||
| 
						 | 
					a9b252b8fa | ||
| 
						 | 
					8a5b3ddee7 | ||
| 
						 | 
					d83e543a98 | ||
| 
						 | 
					bcd6e8fd63 | ||
| 
						 | 
					d5c5738aab | ||
| 
						 | 
					9e4dfe081f | ||
| 
						 | 
					090852b72b | ||
| 
						 | 
					ff5e038c49 | ||
| 
						 | 
					5cc2e5f6e1 | ||
| 
						 | 
					4e8c0573c4 | ||
| 
						 | 
					ce905ba2c4 | ||
| 
						 | 
					3104c17fb3 | ||
| 
						 | 
					7651941722 | ||
| 
						 | 
					7bf938901a | ||
| 
						 | 
					f8b61d2926 | ||
| 
						 | 
					4edea59ab1 | ||
| 
						 | 
					c8bcd2818d | ||
| 
						 | 
					9b46dbaff1 | ||
| 
						 | 
					17a139f27f | ||
| 
						 | 
					bd00c728d1 | ||
| 
						 | 
					9d510b514c | ||
| 
						 | 
					00dcc5ecda | ||
| 
						 | 
					dbbdd3f799 | ||
| 
						 | 
					3541b4b968 | ||
| 
						 | 
					5b1bf35a23 | ||
| 
						 | 
					591b61945f | ||
| 
						 | 
					bd1943626b | ||
| 
						 | 
					f152cdef51 | ||
| 
						 | 
					33f8c9747d | ||
| 
						 | 
					714a5e26b3 | ||
| 
						 | 
					7f2c6e40d3 | ||
| 
						 | 
					db676ec223 | ||
| 
						 | 
					ffb3e511a7 | ||
| 
						 | 
					e9e64f6a44 | ||
| 
						 | 
					a7b8adb0e1 | ||
| 
						 | 
					4140ff03d7 | ||
| 
						 | 
					e042ef05a4 | ||
| 
						 | 
					7c02e4d66a | ||
| 
						 | 
					711794cfe1 | ||
| 
						 | 
					c0e4cf2358 | ||
| 
						 | 
					a92f8f36c1 | ||
| 
						 | 
					12698dc347 | ||
| 
						 | 
					3e6a55f78e | ||
| 
						 | 
					7585f14b89 | ||
| 
						 | 
					01b5fc4d49 | ||
| 
						 | 
					0fb7d3bfc8 | ||
| 
						 | 
					2cd74d355c | ||
| 
						 | 
					3d405f8c63 | ||
| 
						 | 
					a92f0c4c6e | ||
| 
						 | 
					de142ac9d6 | ||
| 
						 | 
					468ef7ecff | ||
| 
						 | 
					4d768fd236 | ||
| 
						 | 
					bfc1f95190 | ||
| 
						 | 
					bc17ebd90e | ||
| 
						 | 
					bb1b3727cb | ||
| 
						 | 
					4dbebefb45 | ||
| 
						 | 
					e1c5764fbf | ||
| 
						 | 
					70f975e4f0 | ||
| 
						 | 
					845567d1ba | ||
| 
						 | 
					f570447000 | ||
| 
						 | 
					9d7b8f1f2f | ||
| 
						 | 
					bae6bfc32d | ||
| 
						 | 
					8a63390464 | ||
| 
						 | 
					0b52cd8b31 | ||
| 
						 | 
					f97569dd34 | ||
| 
						 | 
					a9164e63ab | ||
| 
						 | 
					8c95067ec4 | ||
| 
						 | 
					4f77bbeb2b | ||
| 
						 | 
					8bbed2c831 | ||
| 
						 | 
					6b43a23c4b | ||
| 
						 | 
					be9521f659 | ||
| 
						 | 
					90761fd840 | ||
| 
						 | 
					d49d9a783c | ||
| 
						 | 
					d7dc7c4eda | ||
| 
						 | 
					fe64c6a841 | ||
| 
						 | 
					2bbdc85a29 | ||
| 
						 | 
					74628b7034 | ||
| 
						 | 
					15aa249f64 | ||
| 
						 | 
					fdf58e1225 | ||
| 
						 | 
					866f305686 | ||
| 
						 | 
					1550e5343c | ||
| 
						 | 
					add3dd1077 | ||
| 
						 | 
					79a142fb19 | ||
| 
						 | 
					1a30fe4a1a | ||
| 
						 | 
					4ff991764e | ||
| 
						 | 
					001f066769 | ||
| 
						 | 
					c47b553a8e | ||
| 
						 | 
					319af51f84 | ||
| 
						 | 
					5dbaaae68e | ||
| 
						 | 
					8c1a749a5a | ||
| 
						 | 
					fc8643f238 | ||
| 
						 | 
					c8653f19bf | ||
| 
						 | 
					b01100d818 | ||
| 
						 | 
					d4096a9026 | ||
| 
						 | 
					b9e780cdcd | ||
| 
						 | 
					b77cd56a01 | ||
| 
						 | 
					9cdec156dc | ||
| 
						 | 
					6aa5968863 | ||
| 
						 | 
					8f7686cd7b | ||
| 
						 | 
					d8d384a979 | ||
| 
						 | 
					ade318bb78 | ||
| 
						 | 
					ed3aa8189f | ||
| 
						 | 
					3e43597617 | ||
| 
						 | 
					4c8e895ac7 | ||
| 
						 | 
					f6a3671366 | ||
| 
						 | 
					e641b0a965 | ||
| 
						 | 
					eddddc6c9b | ||
| 
						 | 
					f249d6306f | ||
| 
						 | 
					5c31bd54e4 | ||
| 
						 | 
					71ba73b38f | ||
| 
						 | 
					db0ff74857 | ||
| 
						 | 
					1acb073737 | ||
| 
						 | 
					251dda3652 | ||
| 
						 | 
					22db24509d | ||
| 
						 | 
					54c9d27fd8 | ||
| 
						 | 
					01888ff078 | ||
| 
						 | 
					ffbd140a97 | ||
| 
						 | 
					dedf5c52d9 | ||
| 
						 | 
					10465c5d68 | ||
| 
						 | 
					1f4f64a7c0 | ||
| 
						 | 
					a6f116b57b | ||
| 
						 | 
					0a80186a92 | ||
| 
						 | 
					635bdf15cb | ||
| 
						 | 
					a72bdfdacc | ||
| 
						 | 
					dc3e04456c | ||
| 
						 | 
					b0e4fb7602 | ||
| 
						 | 
					df7aa3339b | ||
| 
						 | 
					c475536388 | ||
| 
						 | 
					cc7def89af | ||
| 
						 | 
					58da87898e | ||
| 
						 | 
					bded5490d2 | ||
| 
						 | 
					c3715a2a3d | ||
| 
						 | 
					abf084f6c2 | ||
| 
						 | 
					37ba409dc3 | ||
| 
						 | 
					f29488b24f | ||
| 
						 | 
					71bdade7b9 | ||
| 
						 | 
					60d97c887d | ||
| 
						 | 
					5bba50f01f | ||
| 
						 | 
					1f7884dc70 | ||
| 
						 | 
					69dafd6c68 | ||
| 
						 | 
					64b79cd5ac | ||
| 
						 | 
					1af21735a9 | ||
| 
						 | 
					9886af3cec | ||
| 
						 | 
					1eb8f9ad97 | ||
| 
						 | 
					08e73d9d7d | ||
| 
						 | 
					b0e349b215 | ||
| 
						 | 
					caa98b08da | ||
| 
						 | 
					00caa13a12 | ||
| 
						 | 
					cfc0135e86 | ||
| 
						 | 
					9ee8c1c791 | ||
| 
						 | 
					cd3aba2b89 | ||
| 
						 | 
					a150d8e289 | ||
| 
						 | 
					6da8e92f20 | ||
| 
						 | 
					1d4dd4be96 | ||
| 
						 | 
					7df1a03b4b | ||
| 
						 | 
					ad316ffd37 | ||
| 
						 | 
					91f5542a57 | ||
| 
						 | 
					d47a8aa562 | ||
| 
						 | 
					676f790933 | ||
| 
						 | 
					70433f3d05 | ||
| 
						 | 
					9f2a2b9869 | ||
| 
						 | 
					a0c09fc617 | ||
| 
						 | 
					19d391fa05 | ||
| 
						 | 
					d1aa1fd4d8 | ||
| 
						 | 
					0e02d03d9a | ||
| 
						 | 
					4133f9c56f | ||
| 
						 | 
					53055064e1 | ||
| 
						 | 
					06090d8de1 | ||
| 
						 | 
					d6ccae38f8 | ||
| 
						 | 
					f7210effec | ||
| 
						 | 
					8e7efd98b2 | ||
| 
						 | 
					ea50ba16f9 | ||
| 
						 | 
					b62e4f6662 | ||
| 
						 | 
					f5a1c8bc49 | ||
| 
						 | 
					4cb8e99430 | ||
| 
						 | 
					bbac49ff38 | ||
| 
						 | 
					1d12017f11 | ||
| 
						 | 
					a480919ec3 | ||
| 
						 | 
					f8abf9fce1 | ||
| 
						 | 
					62f2a552ea | ||
| 
						 | 
					b053e02174 | ||
| 
						 | 
					3798167908 | ||
| 
						 | 
					56fe2014e1 | ||
| 
						 | 
					be2e64433f | ||
| 
						 | 
					8732e89e55 | ||
| 
						 | 
					fdd0a93bad | ||
| 
						 | 
					dd12572b1d | ||
| 
						 | 
					e23f20227a | ||
| 
						 | 
					5cc791690b | ||
| 
						 | 
					9f1deb0c36 | ||
| 
						 | 
					4cebbf8d22 | ||
| 
						 | 
					e57ebdb583 | ||
| 
						 | 
					372122037f | ||
| 
						 | 
					23a5cb1917 | ||
| 
						 | 
					f8d5fef3c4 | ||
| 
						 | 
					250005ad16 | ||
| 
						 | 
					0e06da6c63 | ||
| 
						 | 
					b4a03a56b4 | ||
| 
						 | 
					9eb668ab30 | ||
| 
						 | 
					233a74c146 | ||
| 
						 | 
					ff00afb5d7 | ||
| 
						 | 
					3f43dc1855 | ||
| 
						 | 
					4a4e7fc7cb | ||
| 
						 | 
					0253dc9623 | ||
| 
						 | 
					374ef3902c | ||
| 
						 | 
					235690064f | ||
| 
						 | 
					0167c25e08 | ||
| 
						 | 
					d2432716ea | ||
| 
						 | 
					52ef85cba3 | ||
| 
						 | 
					8140057bea | ||
| 
						 | 
					22df59e229 | ||
| 
						 | 
					ed351eee54 | ||
| 
						 | 
					04a3c4bb22 | ||
| 
						 | 
					b5fda5642f | ||
| 
						 | 
					b0955705be | ||
| 
						 | 
					a4a624d537 | ||
| 
						 | 
					6a8cf1b768 | ||
| 
						 | 
					aac2a8f830 | ||
| 
						 | 
					39274b0c5d | ||
| 
						 | 
					55c2430671 | ||
| 
						 | 
					023486e175 | ||
| 
						 | 
					8227643741 | ||
| 
						 | 
					e44131f97a | ||
| 
						 | 
					5028377d45 | ||
| 
						 | 
					51aaf1b150 | ||
| 
						 | 
					13406e76de | ||
| 
						 | 
					4672d98e8a | ||
| 
						 | 
					858b3d640a | ||
| 
						 | 
					6087002562 | ||
| 
						 | 
					ad788fbed1 | 
							
								
								
									
										35
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,35 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
## Before you hit that Submit button....
 | 
			
		||||
 | 
			
		||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
 | 
			
		||||
 | 
			
		||||
If your issue is:
 | 
			
		||||
  - a general 'how-to' type question,
 | 
			
		||||
  - a feature request or suggestion for a change,
 | 
			
		||||
  - or problems with 3rd party (`node-red-contrib-`) nodes
 | 
			
		||||
 | 
			
		||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
 | 
			
		||||
 | 
			
		||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
 | 
			
		||||
 | 
			
		||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
 | 
			
		||||
 | 
			
		||||
## So you have a real issue to raise...
 | 
			
		||||
 | 
			
		||||
To help us understand the issue, please fill-in as much of the following information as you can:
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
### What are the steps to reproduce?
 | 
			
		||||
 | 
			
		||||
### What happens?
 | 
			
		||||
 | 
			
		||||
### What do you expect to happen?
 | 
			
		||||
 | 
			
		||||
### Please tell us about your environment:
 | 
			
		||||
 | 
			
		||||
- [ ] Node-RED version:
 | 
			
		||||
- [ ] Node.js version:
 | 
			
		||||
- [ ] npm version:
 | 
			
		||||
- [ ] Platform/OS:
 | 
			
		||||
- [ ] Browser:
 | 
			
		||||
- [ ] running in Docker:
 | 
			
		||||
							
								
								
									
										39
									
								
								.github/ISSUE_TEMPLATE/--bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/ISSUE_TEMPLATE/--bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,39 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Bug report
 | 
			
		||||
about: Reproducible software issues in the core of Node-RED
 | 
			
		||||
title: ''
 | 
			
		||||
labels: ''
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
 | 
			
		||||
 | 
			
		||||
If your issue is:
 | 
			
		||||
  - a general 'how-to' type question,
 | 
			
		||||
  - a feature request or suggestion for a change,
 | 
			
		||||
  - or problems with 3rd party (`node-red-contrib-`) nodes
 | 
			
		||||
 | 
			
		||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
 | 
			
		||||
 | 
			
		||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
 | 
			
		||||
 | 
			
		||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
 | 
			
		||||
 | 
			
		||||
To help us understand the issue, please fill-in as much of the following information as you can:
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
### What are the steps to reproduce?
 | 
			
		||||
 | 
			
		||||
### What happens?
 | 
			
		||||
 | 
			
		||||
### What do you expect to happen?
 | 
			
		||||
 | 
			
		||||
### Please tell us about your environment:
 | 
			
		||||
 | 
			
		||||
- [ ] Node-RED version:
 | 
			
		||||
- [ ] Node.js version:
 | 
			
		||||
- [ ] npm version:
 | 
			
		||||
- [ ] Platform/OS:
 | 
			
		||||
- [ ] Browser:
 | 
			
		||||
							
								
								
									
										17
									
								
								.github/ISSUE_TEMPLATE/-anything-else.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/ISSUE_TEMPLATE/-anything-else.md
									
									
									
									
										vendored
									
									
								
							@@ -1,17 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Anything Else
 | 
			
		||||
about: Something that is not a bug report
 | 
			
		||||
title: ''
 | 
			
		||||
labels: ''
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Please DO NOT raise an issue.
 | 
			
		||||
 | 
			
		||||
We DO NOT use the issue tracker for general support or feature requests. Only bug reports should be raised here using the 'Bug report' template.
 | 
			
		||||
 | 
			
		||||
For general support, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack). You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`. 
 | 
			
		||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
 | 
			
		||||
 | 
			
		||||
For feature requests, please use the Node-RED Forum](https://discourse.nodered.org). Many ideas have already been discussed there and you should search that for your request before starting a new discussion.
 | 
			
		||||
							
								
								
									
										61
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
name: 🐞 Report a bug
 | 
			
		||||
description: File a bug/issue on the core of Node-RED
 | 
			
		||||
labels: [needs-triage]
 | 
			
		||||
body:
 | 
			
		||||
- type: markdown
 | 
			
		||||
  attributes:
 | 
			
		||||
    value: |
 | 
			
		||||
        This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
 | 
			
		||||
 | 
			
		||||
        If your issue is:
 | 
			
		||||
          - a general 'how-to' type question,
 | 
			
		||||
          - a feature request or suggestion for a change,
 | 
			
		||||
          - or problems with 3rd party (`node-red-contrib-`) nodes
 | 
			
		||||
 | 
			
		||||
        please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
 | 
			
		||||
 | 
			
		||||
        You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
 | 
			
		||||
 | 
			
		||||
        That way the whole Node-RED user community can help, rather than rely on the core development team.
 | 
			
		||||
 | 
			
		||||
        To help us understand the issue, please fill-in as much of the following information as you can:
 | 
			
		||||
- type: textarea
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Current Behavior
 | 
			
		||||
    description: A clear & concise description of what you're experiencing.
 | 
			
		||||
  validations:
 | 
			
		||||
    required: false
 | 
			
		||||
- type: textarea
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Expected Behavior
 | 
			
		||||
    description: A clear & concise description of what you expected to happen.
 | 
			
		||||
  validations:
 | 
			
		||||
    required: false
 | 
			
		||||
- type: textarea
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Steps To Reproduce
 | 
			
		||||
    description: Steps to reproduce the behavior.
 | 
			
		||||
  validations:
 | 
			
		||||
    required: false
 | 
			
		||||
- type: textarea
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Example flow
 | 
			
		||||
    description: If you have a minimal example flow that demonstrates the issue, share it here.
 | 
			
		||||
    value: |
 | 
			
		||||
      ```
 | 
			
		||||
      paste your flow here
 | 
			
		||||
      ```
 | 
			
		||||
  validations:
 | 
			
		||||
    required: false
 | 
			
		||||
- type: textarea
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Environment
 | 
			
		||||
    description: Please tell us about your environment. Include any relevant information on how you are running Node-RED.
 | 
			
		||||
    value: |
 | 
			
		||||
        - Node-RED version:
 | 
			
		||||
        - Node.js version:
 | 
			
		||||
        - npm version:
 | 
			
		||||
        - Platform/OS:
 | 
			
		||||
        - Browser:
 | 
			
		||||
  validations:
 | 
			
		||||
    required: false
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
blank_issues_enabled: true
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: ❓ Questions
 | 
			
		||||
    url: https://discourse.nodered.org
 | 
			
		||||
    about: Ask your question on the Node-RED forum
 | 
			
		||||
  - name: ⭐️ Feature Request
 | 
			
		||||
    url: https://discourse.nodered.org/c/development/feature-requests
 | 
			
		||||
    about: Discuss your request with the community
 | 
			
		||||
  - name: 🗂 Documentation
 | 
			
		||||
    url: https://nodered.org/docs
 | 
			
		||||
    about: Go straight to the documentation
 | 
			
		||||
  - name: 💬 Slack
 | 
			
		||||
    url: https://nodered.org/slack
 | 
			
		||||
    about: Chat about the project on our slack team
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
name: PublishDockerImage
 | 
			
		||||
name: Publish Release
 | 
			
		||||
env:
 | 
			
		||||
  ACTIONS_ALLOW_UNSECURE_COMMANDS: true
 | 
			
		||||
on:
 | 
			
		||||
							
								
								
									
										30
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
name: Run tests
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ master, dev ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [ master, dev ]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        node-version: [12, 14, 16]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - name: Use Node.js ${{ matrix.node-version }}
 | 
			
		||||
      uses: actions/setup-node@v2
 | 
			
		||||
      with:
 | 
			
		||||
        node-version: ${{ matrix.node-version }}
 | 
			
		||||
    - name: Install Dependencies
 | 
			
		||||
      run: npm install
 | 
			
		||||
    - name: Run tests
 | 
			
		||||
      run: |
 | 
			
		||||
        npm run test
 | 
			
		||||
    - name: Publish to coveralls.io
 | 
			
		||||
      if: ${{ matrix.node-version == 14 }}
 | 
			
		||||
      uses: coverallsapp/github-action@v1.1.2
 | 
			
		||||
      with:
 | 
			
		||||
        github-token: ${{ github.token }}
 | 
			
		||||
							
								
								
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -1,18 +0,0 @@
 | 
			
		||||
sudo: false
 | 
			
		||||
addons:
 | 
			
		||||
  chrome: stable
 | 
			
		||||
language: node_js
 | 
			
		||||
matrix:
 | 
			
		||||
  include:
 | 
			
		||||
    - node_js: "14"
 | 
			
		||||
      script:
 | 
			
		||||
        - ./node_modules/.bin/grunt && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
 | 
			
		||||
        #    - scripts/install-ui-test-dependencies.sh && grunt test-ui
 | 
			
		||||
      before_script:
 | 
			
		||||
        - npm install -g coveralls
 | 
			
		||||
    - node_js: "12"
 | 
			
		||||
      script:
 | 
			
		||||
        - ./node_modules/.bin/grunt no-coverage
 | 
			
		||||
    - node_js: "16"
 | 
			
		||||
      script:
 | 
			
		||||
        - ./node_modules/.bin/grunt no-coverage
 | 
			
		||||
							
								
								
									
										14
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								API.md
									
									
									
									
									
								
							@@ -1,8 +1,12 @@
 | 
			
		||||
Node-RED Modules
 | 
			
		||||
---
 | 
			
		||||
Node-RED consists of 6 node modules under the `@node-red` scope, which are pulled together
 | 
			
		||||
by the top-level `node-red` module. The typical scenario is where you are embedding Node-RED into your
 | 
			
		||||
own application, in which case you would use the `node-red` module rather than any of the
 | 
			
		||||
internal modules directly.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
let RED = require("node-red");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Node-RED provides a set of node modules that implement different parts of the
 | 
			
		||||
application.
 | 
			
		||||
 | 
			
		||||
Module | Description
 | 
			
		||||
-------|-------
 | 
			
		||||
@@ -11,5 +15,5 @@ Module | Description
 | 
			
		||||
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
 | 
			
		||||
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
 | 
			
		||||
[@node-red/registry](@node-red_registry.html) | the internal node registry
 | 
			
		||||
@node-red/nodes | the default set of core nodes
 | 
			
		||||
@node-red/nodes | the default set of core nodes. This module only contains the Node-RED nodes - it does not expose any APIs.
 | 
			
		||||
@node-red/editor-client | the client-side resources of the Node-RED editor application
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										496
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										496
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,499 @@
 | 
			
		||||
#### 2.2.0-beta.1: Beta Release
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 2.1.4: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - fix env var access using $parent for groups (#3278) @HiroyasuNishiyama
 | 
			
		||||
 - Add proper error handling for 404 errors when serving debug files (#3277) @knolleary
 | 
			
		||||
 - Add Japanese translations for Node-RED v2.1.0-beta.1 (#3179) @kazuhitoyokoi
 | 
			
		||||
 - Include full user object on login audit events (#3269) @knolleary
 | 
			
		||||
 - Remove styling from de locale files (#3237) @knolleary
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Change tab hide button icon to an eye and add search option (#3282) @knolleary
 | 
			
		||||
 - Fix i18n handling of namespaces with spaces in (#3281) @knolleary
 | 
			
		||||
 - Trigger change event when autoComplete fills in input (#3280) @knolleary
 | 
			
		||||
 - Apply CN i18n fix (#3279) @knolleary
 | 
			
		||||
 - fix select menu label of config node to use paletteLabel (#3273) @HiroyasuNishiyama
 | 
			
		||||
 - fix removed tab not to cause node conflict (#3275) @HiroyasuNishiyama
 | 
			
		||||
 - Group diff fix (#3239) @knolleary
 | 
			
		||||
 - Only toggle disabled workspace flag if on activeWorkspace (#3252) @knolleary
 | 
			
		||||
 - Do not show status for disabled nodes (#3253) @knolleary
 | 
			
		||||
 - Set dimension value for tour guide (#3265) @kazuhitoyokoi
 | 
			
		||||
 - Avoid redundant initialisation of TypedInput type (#3263) @knolleary
 | 
			
		||||
 - Don't let themes change flow port label color (#3270) @bonanitech
 | 
			
		||||
 - Fix treeList gutter calculation to handle floating gutters (#3238) @knolleary
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
- Debug: Handle RegExp types in Debug sidebar (#3251) @knolleary
 | 
			
		||||
- Delay: fix 2nd output when in rate limit per topic modes (#3261) @dceejay
 | 
			
		||||
- Link: fix to show link target when selected (#3267) @HiroyasuNishiyama
 | 
			
		||||
- Inject: Do not modify inject node props in oneditprepare (#3242) @knolleary
 | 
			
		||||
- HTTP Request: HTTP Basic Auth should always add : between username and password even if empty (#3236) @hardillb
 | 
			
		||||
 | 
			
		||||
#### 2.1.3: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Update gen-publish script to update 'next' tag for main releases
 | 
			
		||||
 - Add environment variable to enable/disable tours (#3221) @hardillb
 | 
			
		||||
 - Fix loading non-default language files leaving runtime in wrong locale (#3225) @knolleary
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Refresh editor settings whenever a node is added or enabled (#3227) @knolleary
 | 
			
		||||
 - Revert spinner css change that made it shrink in some cases (#3229) @knolleary
 | 
			
		||||
 - Fix import notification message when importing config nodes (#3224) @knolleary
 | 
			
		||||
 - Handle changing types of TypedInput repeatedly (#3223) @knolleary
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 2.1.2: Maintenance Release
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - node-red-pi: Remove bash dependency (#3216) @a16bitsysop
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Improved regex for markdown renderer (#3213) @GerwinvBeek
 | 
			
		||||
 - Fix TypedInput initialisation (#3220) @knolleary
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl
 | 
			
		||||
 | 
			
		||||
#### 2.1.1: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary
 | 
			
		||||
 - Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary
 | 
			
		||||
 - Add cache-busting query params to index.mst (#3211) @knolleary
 | 
			
		||||
 - Fix TypedInput validation of type without options (#3207) @knolleary
 | 
			
		||||
 | 
			
		||||
#### 2.1.0: Milestone Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Position popover properly on a scrolled page
 | 
			
		||||
 - Fixes from 2.1.0-beta.2 (#3202) @knolleary
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
- Link Out: Fix saving link out node links (#3201) @knolleary
 | 
			
		||||
 - Switch: Refix #3170 - copy switch rule type when adding new rule
 | 
			
		||||
 - TCP Request: Add string option to TCP request node output (#3204) @dceejay
 | 
			
		||||
 | 
			
		||||
#### 2.1.0-beta.2: Beta Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix switching projects (#3199) @knolleary
 | 
			
		||||
 - Use locale setting when installing/enabling node (#3198) @knolleary
 | 
			
		||||
 - Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary
 | 
			
		||||
 - Fix converting selection to subflow (#3196) @knolleary
 | 
			
		||||
 - Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary
 | 
			
		||||
 - Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary
 | 
			
		||||
 - Ensure tab state updates properly when toggling enable state (#3175) @knolleary
 | 
			
		||||
 - Improve handling of long labels in TreeList (#3176) @knolleary
 | 
			
		||||
 - Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Update package dependencies
 | 
			
		||||
 - Update to latest node-red-admin
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Dynamic MQTT connections (#3189)
 | 
			
		||||
 - Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187
 | 
			
		||||
 - Link: Fix link call label (#3200) @knolleary
 | 
			
		||||
 - Debug: Redesign debug filter options and make them persistant (#3183) @knolleary
 | 
			
		||||
 - Inject: Widen Inject interval box for >1 digit (#3184) @knolleary
 | 
			
		||||
 - Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary
 | 
			
		||||
 | 
			
		||||
#### 2.1.0-beta.1: Beta Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Add Tour Guide component (#3136) @knolleary
 | 
			
		||||
 - Allow tabs to be hidden (#3120) @knolleary
 | 
			
		||||
 - Add align actions to editor (#3110) @knolleary
 | 
			
		||||
 - Add support of environment variable for tab & group (#3112) @HiroyasuNishiyama
 | 
			
		||||
 - Add autoComplete widget and add to TypedInput for msg. props (#3171) @knolleary
 | 
			
		||||
 - Render node documentation to node-red style guide when written in markdown. (#3169) @Steve-Mcl
 | 
			
		||||
 - Allow colouring of tab icon svg (#3140) @harmonic7
 | 
			
		||||
 - Restore tab selection after merging conflicts (#3151) @GerwinvBeek
 | 
			
		||||
 - Fix serving of theme files on Windows (#3154) @knolleary
 | 
			
		||||
 - Ensure config-node select inherits width properly from input (#3155) @knolleary
 | 
			
		||||
 - Do better remembering TypedInput values whilst switching types (#3159) @knolleary
 | 
			
		||||
 - Update monaco to 0.28.1 (#3153) @knolleary
 | 
			
		||||
 - Improve themeing of tourGuide (#3161) @knolleary
 | 
			
		||||
 - Allow a node to specify a filter for the config nodes it can pick from (#3160) @knolleary
 | 
			
		||||
 - Allow RED.notify.update to modify any notification setting (#3163) @knolleary
 | 
			
		||||
 - Fix typo in ko editor.json Fixes #3119
 | 
			
		||||
 - Improve RED.actions api to ensure actions cannot be overridden
 | 
			
		||||
 - Ensure treeList row has suitable min-height when no content Fixes #3109
 | 
			
		||||
 - Refactor edit dialogs to use separate edit panes
 | 
			
		||||
 - Ensure type select button is not focussable when TypedInput only has one type
 | 
			
		||||
 - Place close tab link in front of fade
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Improve error reporting with oauth login strategies (#3148) @knolleary
 | 
			
		||||
 - Add allowUpdate feature to externalModules.palette (#3143) @knolleary
 | 
			
		||||
 - Improve node install error reporting (#3158) @knolleary
 | 
			
		||||
 - Improve unit test coverage (#3168) @knolleary
 | 
			
		||||
 - Allow coreNodesDir to be set to false (#3149) @hardillb
 | 
			
		||||
 - Update package dependencies
 | 
			
		||||
 - uncaughtException debug improvements (#3146) @renatojuniorrs
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Change: Add option to deep-clone properties in Change node (#3156) @knolleary
 | 
			
		||||
 - Delay: Add push to front of rate limit queue. (#3069) @dceejay
 | 
			
		||||
 - File: Add paletteLabel to file nodes to make read/write more obvious (#3157) @knolleary
 | 
			
		||||
 - HTTP Request: Extend HTTP request node to log detailed timing information (#3116) @k-toumura
 | 
			
		||||
 - HTTP Response: Fix sizing of HTTP Response header fields (#3164) @knolleary
 | 
			
		||||
 - Join: Support for msg.restartTimeout (#3121) @magma1447
 | 
			
		||||
 - Link Call: Add Link Call node (#3152) @knolleary
 | 
			
		||||
 - Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary
 | 
			
		||||
 - Delay node: add option to send intermediate messages on separate output (#3166) @knolleary
 | 
			
		||||
 - Typo in http request set method translation (#3173) @mailsvb
 | 
			
		||||
 | 
			
		||||
#### 2.0.6: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix typo in ko editor.json Fixes #3119
 | 
			
		||||
 - Change fade color when hovering an inactive tab (#3106) @bonanitech
 | 
			
		||||
 - Ensure treeList row has suitable min-height when no content Fixes #3109
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Update tar to latest (#3128) @aksswami
 | 
			
		||||
 - Give passport verify callback the same arity as the original callback (#3117) @dschmidt
 | 
			
		||||
 - Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers
 | 
			
		||||
 | 
			
		||||
#### 2.0.5: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Remove default ctrl-enter keybinding from monaco editor Fixes #3093
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Update tar dependency
 | 
			
		||||
 - Add support for maintenance streams in generate-publish-script
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Fix regression in Join node when manual joining array with msg.parts present Fixes #3096
 | 
			
		||||
 | 
			
		||||
#### 2.0.4: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix tab fade CSS for when using themes (#3085) @bonanitech
 | 
			
		||||
 - Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Filter: Fix RBE node handling of default topi property Fixes #3087
 | 
			
		||||
 - HTTP Request: Handle partially encoded url query strings in request node
 | 
			
		||||
 - HTTP Request: Fix support for supplied CA certs (#3089) @hardillb
 | 
			
		||||
 - HTTP Request: Ensure TLS Cert is used (#3092) @hardillb
 | 
			
		||||
 - Inject: Fix inject now button unable to send empty props
 | 
			
		||||
 - Inject: Inject now button success notification should use label with updated props
 | 
			
		||||
 | 
			
		||||
#### 2.0.3: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - HTML: Fix HTML parsing when body is included in the select tag Fixes #3079
 | 
			
		||||
 - HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081
 | 
			
		||||
 - HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083
 | 
			
		||||
 - HTTP Request: Add unit tests for HTTP Request encodeURI and error response
 | 
			
		||||
 - HTTP Request: Do not throw HTTP errors in request node Fixes #3082
 | 
			
		||||
 - HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080
 | 
			
		||||
 | 
			
		||||
#### 2.0.2: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Use file:// url with dynamic import
 | 
			
		||||
 - Detect if agent-base has patched https.request and undo it Fixes #3072
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix tab fade css because Safari Fixes #3073
 | 
			
		||||
 - Fix error closing library dialog with monaco
 | 
			
		||||
 - Handle other error types in Manage Palette view
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - HTTP Request node - ignore invalid cookies rather than fail request Fixes #3075
 | 
			
		||||
 - Fix msg.reset handling in Delay node Fixes #3074
 | 
			
		||||
 | 
			
		||||
#### 2.0.1: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Function: Ensure default module export is exposed in Function node
 | 
			
		||||
 | 
			
		||||
#### 2.0.0: Milestone Release
 | 
			
		||||
 | 
			
		||||
**Migration from 1.x**
 | 
			
		||||
 | 
			
		||||
 - Node-RED now requires Node.js 12.x or later.
 | 
			
		||||
 | 
			
		||||
 - The following nodes have had significant dependency updates. Unless stated,
 | 
			
		||||
   they should be fully backward compatible.
 | 
			
		||||
 | 
			
		||||
   - RBE:  Relabelled as 'filter' to make it more discoverable and made part of
 | 
			
		||||
     the core palette, rather than as a separate module.
 | 
			
		||||
   - Tail: This node has been removed from the default palette. You can reinstall it
 | 
			
		||||
     from node-red-node-tail
 | 
			
		||||
   - HTTP Request: Reimplemented with a different underlying module. We have
 | 
			
		||||
     tried to maintain 100% functional compatibility, but it is possible
 | 
			
		||||
     some edge cases remain.
 | 
			
		||||
   - JSON: The schema validation option no longer supports JSON-Schema draft-04
 | 
			
		||||
   - HTML: Its underlying module has had a major version update. Should be fully
 | 
			
		||||
     backward compatible.
 | 
			
		||||
 | 
			
		||||
 - `functionExternalModules` is now enabled by default for new installs.
 | 
			
		||||
   If you have an existing settings file that contains this setting, you will
 | 
			
		||||
   need to set it to `true` yourself.
 | 
			
		||||
 | 
			
		||||
   The external modules will now get installed in your Node-RED user directory,
 | 
			
		||||
   (`~/.node-red`) rather than in a subdirectory. This means all dependencies will
 | 
			
		||||
   be listed in your top-level `package.json`. If you have existing external modules,
 | 
			
		||||
   they will get reinstalled to the new location when you first run Node-RED 2.0.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Fix missing dependencies (#3052, #2057) @kazuhitoyokoi
 | 
			
		||||
 - Ensure node.types is defined if node html file missing
 | 
			
		||||
 - Fix reporting of type_already_registered error
 | 
			
		||||
 - Move install location of external modules (#3064) @knolleary
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Update translations (#3063) @kazuhitoyokoi
 | 
			
		||||
 - Add a slight fade to tab labels that overflow
 | 
			
		||||
 - Show config node details when selected in outliner
 | 
			
		||||
 - Fix layout of info outliner for subflow entries
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay
 | 
			
		||||
 - Function: external modules is now enabled by default (#3065) @knolleary
 | 
			
		||||
 - Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary
 | 
			
		||||
 - WebSocket: add option for client node to send automatic pings (#3056) @knolleary
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##### 2.0.0-beta.2: Beta Release
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Add `node-red admin init` (via `node-red-admin@2.1.0`)
 | 
			
		||||
 - Move to GH Actions rather than Travis for build (#3042) @knolleary
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Include hasUser=false config nodes when exporting whole flow (#3048)
 | 
			
		||||
 - Emit nodes:change for any updated config node when node deleted/added
 | 
			
		||||
 - Fix padding of compact notification Closes #3045
 | 
			
		||||
 - Ensure any html in changelog is escaped before displaying
 | 
			
		||||
 - Add support for Map/Set property types on Debug (#3040) @knolleary
 | 
			
		||||
 - Add 'theme' to default settings file
 | 
			
		||||
 - Add RED.view.annotations api (#3032) @knolleary
 | 
			
		||||
 - Update monaco editor to V0.25.2 (#3031) @Steve-Mcl
 | 
			
		||||
 - Lower tray zIndex when overlay tray being opened Fixes #3019
 | 
			
		||||
 - Reduce z-Index of Function expand buttons to prevent overlap Part of #3019
 | 
			
		||||
 - Ensure RED.clipboard.import displays the right library Fixes #3021
 | 
			
		||||
 - Batch messages sent over comms to prevent flooding (#3025) @knolleary
 | 
			
		||||
 - Allow RED.popover.panel to specify a closeButton to ignore click events on
 | 
			
		||||
 - Use browser default language for initial page load
 | 
			
		||||
 - Add css var for node font color
 | 
			
		||||
 - Fix label padding of toggleButton
 | 
			
		||||
 - Give sidebar open tab a bit more room for its label
 | 
			
		||||
 - Various Monaco updates (#3015) @Steve-Mcl
 | 
			
		||||
 - Log readOnly on startup (#3024) @sammachin
 | 
			
		||||
 - Translation updates (#3020 #3022) @HiroyasuNishiyama @kazuhitoyokoi
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - HTTP Request: Fix proxy handling (#3044) @hardillb
 | 
			
		||||
 - HTTP Request: Handle basic auth with @ in username (#3017) @hardillb
 | 
			
		||||
 - Add Japanese translation for file-in node (#3037 #3039) @kazuhitoyokoi
 | 
			
		||||
 - File In: Add option for file-in node to include all properties (default off) (#3035) @dceejay
 | 
			
		||||
 - Exec: add windowsHide option to hide windows under Windows (#3026) @natcl
 | 
			
		||||
 - Support loading external module sub path Fixes #3023
 | 
			
		||||
 | 
			
		||||
##### 2.0.0-beta.1: Beta Release
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - [MAJOR] Set minimum node version to 12.
 | 
			
		||||
 - [MAJOR] Fix flowfile name to flows.json in settings (#2951) @dceejay
 | 
			
		||||
 - [MAJOR] Update to latest i18n in editor and runtime (#2940) @knolleary
 | 
			
		||||
 - [MAJOR] Deprecate usage of httpRoot (#2953) @knolleary
 | 
			
		||||
 - Add pre/postInstall hooks to npm install handling (#2936) @knolleary
 | 
			
		||||
 - Add engine-strict flag to npm install args (#2965) @nileio
 | 
			
		||||
 - Restructure default settings.js to be more organised (#3012) @knolleary
 | 
			
		||||
 - Ensure httpServerOptions gets applied to ALL the express apps
 | 
			
		||||
 - Allow RED.settings.set to replace string property with object property
 | 
			
		||||
 - Update debug tests to handle compact comms format
 | 
			
		||||
 - Updates to encode/decode message when passed over debug comms link
 | 
			
		||||
 - Remove all input event listeners on a node once it is closed
 | 
			
		||||
 - Move hooks to util package
 | 
			
		||||
 - Rework hooks structure to be a linkedlist
 | 
			
		||||
 - Update dependencies (#2922) @knolleary
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - [MAJOR] Change node id generation to give fixed length values without '.' (#2987) @knolleary
 | 
			
		||||
 - [MAJOR] Add Monaco code editor (#2971) @Steve-Mcl
 | 
			
		||||
 - Update to latest Monaco (#3007) @Steve-Mcl
 | 
			
		||||
 - Update Node-RED Function typings in Monaco (#3008) @Steve-Mcl
 | 
			
		||||
 - Add css named variables for certain key colours (#2994) @knolleary
 | 
			
		||||
 - Improve contrast of export dialog JSON font color
 | 
			
		||||
 - Switch editableList buttons from <a> to <button> elements
 | 
			
		||||
 - Add option to RED.nodes.createCompleteNodeSet to include node dimensions
 | 
			
		||||
 - Fix css of node help table of contents elements
 | 
			
		||||
 - Improve red-ui-node-icon css and add red-ui-node-icon-small modifier class
 | 
			
		||||
 - Add RED.hooks to editor
 | 
			
		||||
 - Add viewAddPort viewRemovePort viewAddNode viewRemoveNode hooks to view
 | 
			
		||||
 - Use paletteLabel if set in help sidebar
 | 
			
		||||
 - Add missing args from JSONata $now signature
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - [MAJOR] Relabel RBE node as 'filter' and move into core. Also remove tail (#2944) @dceejay
 | 
			
		||||
 - [MAJOR] HTTP Request: migrate to 'got' module (#2952) @knolleary
 | 
			
		||||
 - [MAJOR] Move Inject node to CronosJS module (#2959) @knolleary
 | 
			
		||||
 - [MAJOR] JSON: Update ajv to 8.2.0 - drop support for JSON-Schema draft-04 (#2969) @knolleary
 | 
			
		||||
 - [MAJOR] HTML node: cheerio update to 1.x (#3011) @knolleary
 | 
			
		||||
 - Join: change default manual mode to object (#2931) @knolleary
 | 
			
		||||
 - File node: Add fileWorkingDirectory (#2932) @knolleary
 | 
			
		||||
 - Delay node enhancements (#2294) @kazuhitoyokoi (#2949) @dceejay
 | 
			
		||||
 - Add Japanese translations for delay node enhancements (#2958) @kazuhitoyokoi
 | 
			
		||||
 - Inject node: reorder TypedInput options (#2961) @dceejay
 | 
			
		||||
 - HTTP Request: update to work with proxies (#2983) @hardillb (#3009) @hardillb
 | 
			
		||||
 - HTTP Request: fix msg.responseUrl (#2986) @hardillb
 | 
			
		||||
 - TLS: Add ALPN support to TLS node (#2988) @hardillb
 | 
			
		||||
 - Inject: add "Inject now" button to edit dialog (#2990) @Steve-Mcl
 | 
			
		||||
 | 
			
		||||
### 1.3.5 Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Open subflow tab next to active tab rather than at the end
 | 
			
		||||
 - Shrink default notification box
 | 
			
		||||
 - Support mousewheel scroll in tab bar
 | 
			
		||||
 - Revert some of #2967 to fix treeList gutter width calculation
 | 
			
		||||
 - Prevent unknown node from breaking editor
 | 
			
		||||
 - Stop module with missing types from preventing editor load
 | 
			
		||||
 - Handle sidebar tab that no longer exists when setting first active
 | 
			
		||||
 - Fix plugin loading when browser sends unrecognised lang
 | 
			
		||||
 - Prevent error whilst drag/drop importing from leaving dropTarget visible Fixes #2982
 | 
			
		||||
 - Fix scaling issues when dragging nodes into scaled workspace
 | 
			
		||||
 - Fix incorrect shortcut keys in info tips (#2980) @kazuhitoyokoi
 | 
			
		||||
 - Reduce code duplication around node/label generation
 | 
			
		||||
 - Fix theme handling when no editorTheme.page setting
 | 
			
		||||
 - Fix jshint error in treeList
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Fix error handling in runtime/lib/api/nodes
 | 
			
		||||
 - Add Node 16 with sass fixed
 | 
			
		||||
 - Migrate from node-sass to sass (#2984)
 | 
			
		||||
 - Fix "installRetry" was declared a constant and changed (#2974) @aheissenberger
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Function: Fix 'SyntaxError' in Function node when last line of on-stop is a comment
 | 
			
		||||
 - Function: Fix Function tab label names in the node help text Closes #2978
 | 
			
		||||
 - Function: Update Japanese info text of function node (#2985) @HiroyasuNishiyama
 | 
			
		||||
 | 
			
		||||
### 1.3.4 Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 - Allow nodes to access resolved theme files Fixes #2968
 | 
			
		||||
 - Fix importing node to currently flow rather than match its old z value
 | 
			
		||||
 - Don't let 'escape' whilst moving nodes interrupt things Fixes #2960
 | 
			
		||||
 - Sort context stores in TypedInput and ensure default first Fixes #2954
 | 
			
		||||
 - Fix margin between nodes on palette (#2947) @kazuhitoyokoi
 | 
			
		||||
 - Ensure typedInput option is selected in dropdown menu Part of #2945
 | 
			
		||||
 - Ensure typedInput without value has focus class removed Closes #2945
 | 
			
		||||
 - TreeList: Fix remove item when depth=0 and wrong gutter calc (#2967) @hanc2006
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 - Handle subflow modules that contain subflows
 | 
			
		||||
 - Timeout http upgrade requests that are not otherwise handled Fixes #2956
 | 
			
		||||
 - Fix error on auto commit for no flow change (#2957) @HiroyasuNishiyama
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - CSV: Fix CSV handling of special chars as separators
 | 
			
		||||
 - Delay: Give delay node random mina nd max more space so you can see complete value
 | 
			
		||||
 - Exec: fix grunt fail on exec node test (#2964) @HiroyasuNishiyama
 | 
			
		||||
 - Function: Ensure function expand button is above vertical scrollbar Fixes #2955
 | 
			
		||||
 - Inject: Fix inject node output tooltip extra property count
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.3.3: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix package semver comparison to allow >1 version increment
 | 
			
		||||
 - Prevent TypedInput label overflowing element Fixes #2941
 | 
			
		||||
 - Remove TypedInput from tab focus when only one type available
 | 
			
		||||
 - Make typedInput.disable more consistent in behaviour Fixes #2942
 | 
			
		||||
 - Fix project credential secret reset handling Part of #2868
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Export package version in Grunt file so docs template can access
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - CSV: ensure CSV node can send false as string
 | 
			
		||||
 - HTTPIn: handle application/x-protobuf as Buffer type (#2935 #2938) @hardillb
 | 
			
		||||
 - MQTT: Ensure mqtt-close message is published when closing mqtt nodes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.3.2: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 - Handle package.json without dependencies section
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - Fix variable reference error in editableList Fixes #2933
 | 
			
		||||
 - Fix handling of user-provided keymap Fixes #2926
 | 
			
		||||
 - Ensure theme login image is passed through to api response Fixes #2929
 | 
			
		||||
 - Add Japanese translations for Node-RED v1.3.1 (#2930) @kazuhitoyokoi
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - CSV: Fix CSV parsing with other than , separator
 | 
			
		||||
 - File out: Fix timing of msg.send to be after close
 | 
			
		||||
 - Function: describe `node.outputCount` in help text
 | 
			
		||||
 - MQTT: Fix MQTT Broker TLS config row layout Fixes #2927
 | 
			
		||||
 - Split: add comment to info re $N being number of messages arriving
 | 
			
		||||
 | 
			
		||||
### 1.3.1: Maintenance Release
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,14 @@ If you raise a pull-request without having signed the CLA, you will be prompted
 | 
			
		||||
to do so automatically.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Code Branches
 | 
			
		||||
 | 
			
		||||
When raising a PR for a fix or a new feature, it is important to target the right branch.
 | 
			
		||||
 | 
			
		||||
 - `master` - this is the main branch for the latest stable release of Node-RED. All bug fixes for that release should target this branch.
 | 
			
		||||
 - `v1.x` - this is the maintenance branch for the 1.x stream. If a fix *only* applies to 1.x, then it should target this branch. If it applies to the current stable release as well, target `master` first. We will then decide if it needs to be back ported to the 1.x stream.
 | 
			
		||||
 - `dev` - this is the branch for new feature development targeting the next milestone release.
 | 
			
		||||
 | 
			
		||||
### Coding standards
 | 
			
		||||
 | 
			
		||||
Please ensure you follow the coding standards used through-out the existing
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								Gruntfile.js
									
									
									
									
									
								
							@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
var path = require("path");
 | 
			
		||||
var fs = require("fs-extra");
 | 
			
		||||
var sass = require("node-sass");
 | 
			
		||||
var sass = require("sass");
 | 
			
		||||
 | 
			
		||||
module.exports = function(grunt) {
 | 
			
		||||
 | 
			
		||||
@@ -40,8 +40,10 @@ module.exports = function(grunt) {
 | 
			
		||||
    if (nonHeadless) {
 | 
			
		||||
        process.env.NODE_RED_NON_HEADLESS = true;
 | 
			
		||||
    }
 | 
			
		||||
    const pkg = grunt.file.readJSON('package.json');
 | 
			
		||||
    process.env.NODE_RED_PACKAGE_VERSION = pkg.version;
 | 
			
		||||
    grunt.initConfig({
 | 
			
		||||
        pkg: grunt.file.readJSON('package.json'),
 | 
			
		||||
        pkg: pkg,
 | 
			
		||||
        paths: {
 | 
			
		||||
            dist: ".dist"
 | 
			
		||||
        },
 | 
			
		||||
@@ -135,6 +137,7 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/red.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/events.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/hooks.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/i18n.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/settings.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/user.js",
 | 
			
		||||
@@ -159,7 +162,7 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
 | 
			
		||||
@@ -167,6 +170,7 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
 | 
			
		||||
@@ -178,7 +182,9 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
 | 
			
		||||
@@ -194,7 +200,8 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
 | 
			
		||||
                    "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"
 | 
			
		||||
                ],
 | 
			
		||||
                dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
 | 
			
		||||
            },
 | 
			
		||||
@@ -283,7 +290,9 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/index.html",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/favicon.ico",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/icons",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/vendor"
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/vendor",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/types/node",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/types/node-red",
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            release: {
 | 
			
		||||
@@ -319,6 +328,12 @@ module.exports = function(grunt) {
 | 
			
		||||
                ],
 | 
			
		||||
                tasks: ['jsonlint:keymaps','copy:build']
 | 
			
		||||
            },
 | 
			
		||||
            tours: {
 | 
			
		||||
                files: [
 | 
			
		||||
                    'packages/node_modules/@node-red/editor-client/src/tours/**/*.js'
 | 
			
		||||
                ],
 | 
			
		||||
                tasks: ['copy:build']
 | 
			
		||||
            },
 | 
			
		||||
            misc: {
 | 
			
		||||
                files: [
 | 
			
		||||
                    'CHANGELOG.md'
 | 
			
		||||
@@ -373,11 +388,24 @@ module.exports = function(grunt) {
 | 
			
		||||
                        src: [
 | 
			
		||||
                            'ace/**',
 | 
			
		||||
                            'jquery/css/base/**',
 | 
			
		||||
                            'font-awesome/**'
 | 
			
		||||
                            'font-awesome/**',
 | 
			
		||||
                            'monaco/dist/**',
 | 
			
		||||
                            'monaco/types/extraLibs.js',
 | 
			
		||||
                            'monaco/style.css',
 | 
			
		||||
                            'monaco/monaco-bootstrap.js'
 | 
			
		||||
                        ],
 | 
			
		||||
                        expand: true,
 | 
			
		||||
                        dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        cwd: 'packages/node_modules/@node-red/editor-client/src',
 | 
			
		||||
                        src: [
 | 
			
		||||
                            'types/node/*.ts',
 | 
			
		||||
                            'types/node-red/*.ts',
 | 
			
		||||
                        ],
 | 
			
		||||
                        expand: true,
 | 
			
		||||
                        dest: 'packages/node_modules/@node-red/editor-client/public/'
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
 | 
			
		||||
                        src: '**',
 | 
			
		||||
@@ -403,6 +431,12 @@ module.exports = function(grunt) {
 | 
			
		||||
                        src: '**',
 | 
			
		||||
                        expand: true,
 | 
			
		||||
                        dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        cwd: 'packages/node_modules/@node-red/editor-client/src/tours',
 | 
			
		||||
                        src: '**',
 | 
			
		||||
                        expand: true,
 | 
			
		||||
                        dest: 'packages/node_modules/@node-red/editor-client/public/red/tours/'
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
@@ -469,7 +503,8 @@ module.exports = function(grunt) {
 | 
			
		||||
                ],
 | 
			
		||||
                options: {
 | 
			
		||||
                    destination: 'docs',
 | 
			
		||||
                    configure: './jsdoc.json'
 | 
			
		||||
                    configure: './jsdoc.json',
 | 
			
		||||
                    fred: "hi there"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            _editor: {
 | 
			
		||||
@@ -548,7 +583,7 @@ module.exports = function(grunt) {
 | 
			
		||||
    grunt.registerMultiTask('attachCopyright', function() {
 | 
			
		||||
        var files = this.data.src;
 | 
			
		||||
        var copyright = "/**\n"+
 | 
			
		||||
            " * Copyright JS Foundation and other contributors, http://js.foundation\n"+
 | 
			
		||||
            " * Copyright OpenJS Foundation and other contributors, https://openjsf.org/\n"+
 | 
			
		||||
            " *\n"+
 | 
			
		||||
            " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
 | 
			
		||||
            " * you may not use this file except in compliance with the License.\n"+
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "node-red",
 | 
			
		||||
    "version": "2.0.0-beta.1",
 | 
			
		||||
    "version": "2.2.0-beta.1",
 | 
			
		||||
    "description": "Low-code programming for event-driven applications",
 | 
			
		||||
    "homepage": "http://nodered.org",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
@@ -26,68 +26,74 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "ajv": "6.12.6",
 | 
			
		||||
        "async-mutex": "0.3.1",
 | 
			
		||||
        "acorn": "8.6.0",
 | 
			
		||||
        "acorn-walk": "8.2.0",
 | 
			
		||||
        "ajv": "8.8.2",
 | 
			
		||||
        "async-mutex": "0.3.2",
 | 
			
		||||
        "basic-auth": "2.0.1",
 | 
			
		||||
        "bcryptjs": "2.4.3",
 | 
			
		||||
        "body-parser": "1.19.0",
 | 
			
		||||
        "cheerio": "0.22.0",
 | 
			
		||||
        "cheerio": "1.0.0-rc.10",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "content-type": "1.0.4",
 | 
			
		||||
        "cookie": "0.4.1",
 | 
			
		||||
        "cookie-parser": "1.4.5",
 | 
			
		||||
        "cookie-parser": "1.4.6",
 | 
			
		||||
        "cors": "2.8.5",
 | 
			
		||||
        "cron": "1.7.2",
 | 
			
		||||
        "denque": "1.5.0",
 | 
			
		||||
        "cronosjs": "1.7.1",
 | 
			
		||||
        "denque": "2.0.1",
 | 
			
		||||
        "express": "4.17.1",
 | 
			
		||||
        "express-session": "1.17.1",
 | 
			
		||||
        "fs-extra": "9.1.0",
 | 
			
		||||
        "express-session": "1.17.2",
 | 
			
		||||
        "form-data": "4.0.0",
 | 
			
		||||
        "fs-extra": "10.0.0",
 | 
			
		||||
        "fs.notify": "0.0.4",
 | 
			
		||||
        "got": "11.8.3",
 | 
			
		||||
        "hash-sum": "2.0.0",
 | 
			
		||||
        "hpagent": "0.1.2",
 | 
			
		||||
        "https-proxy-agent": "5.0.0",
 | 
			
		||||
        "i18next": "20.2.1",
 | 
			
		||||
        "iconv-lite": "0.6.2",
 | 
			
		||||
        "i18next": "21.5.4",
 | 
			
		||||
        "iconv-lite": "0.6.3",
 | 
			
		||||
        "is-utf8": "0.2.1",
 | 
			
		||||
        "js-yaml": "3.14.0",
 | 
			
		||||
        "js-yaml": "3.14.1",
 | 
			
		||||
        "json-stringify-safe": "5.0.1",
 | 
			
		||||
        "jsonata": "1.8.4",
 | 
			
		||||
        "jsonata": "1.8.5",
 | 
			
		||||
        "lodash.clonedeep": "^4.5.0",
 | 
			
		||||
        "media-typer": "1.1.0",
 | 
			
		||||
        "memorystore": "1.6.6",
 | 
			
		||||
        "mime": "2.5.2",
 | 
			
		||||
        "moment-timezone": "0.5.33",
 | 
			
		||||
        "mqtt": "4.2.6",
 | 
			
		||||
        "multer": "1.4.2",
 | 
			
		||||
        "moment-timezone": "0.5.34",
 | 
			
		||||
        "mqtt": "4.2.8",
 | 
			
		||||
        "multer": "1.4.3",
 | 
			
		||||
        "mustache": "4.2.0",
 | 
			
		||||
        "node-red-admin": "^0.2.6",
 | 
			
		||||
        "node-red-admin": "^2.2.1",
 | 
			
		||||
        "nopt": "5.0.0",
 | 
			
		||||
        "oauth2orize": "1.11.0",
 | 
			
		||||
        "oauth2orize": "1.11.1",
 | 
			
		||||
        "on-headers": "1.0.2",
 | 
			
		||||
        "passport": "0.4.1",
 | 
			
		||||
        "passport": "0.5.0",
 | 
			
		||||
        "passport-http-bearer": "1.0.1",
 | 
			
		||||
        "passport-oauth2-client-password": "0.1.2",
 | 
			
		||||
        "raw-body": "2.4.1",
 | 
			
		||||
        "request": "2.88.0",
 | 
			
		||||
        "raw-body": "2.4.2",
 | 
			
		||||
        "semver": "7.3.5",
 | 
			
		||||
        "tar": "6.1.0",
 | 
			
		||||
        "uglify-js": "3.13.3",
 | 
			
		||||
        "ws": "6.2.1",
 | 
			
		||||
        "tar": "6.1.11",
 | 
			
		||||
        "tough-cookie": "4.0.0",
 | 
			
		||||
        "uglify-js": "3.14.4",
 | 
			
		||||
        "uuid": "8.3.2",
 | 
			
		||||
        "ws": "7.5.1",
 | 
			
		||||
        "xml2js": "0.4.23"
 | 
			
		||||
    },
 | 
			
		||||
    "optionalDependencies": {
 | 
			
		||||
        "bcrypt": "5.0.1"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "dompurify": "2.2.7",
 | 
			
		||||
        "grunt": "1.3.0",
 | 
			
		||||
        "dompurify": "2.3.3",
 | 
			
		||||
        "grunt": "1.4.1",
 | 
			
		||||
        "grunt-chmod": "~1.1.1",
 | 
			
		||||
        "grunt-cli": "~1.4.2",
 | 
			
		||||
        "grunt-cli": "~1.4.3",
 | 
			
		||||
        "grunt-concurrent": "3.0.0",
 | 
			
		||||
        "grunt-contrib-clean": "~2.0.0",
 | 
			
		||||
        "grunt-contrib-compress": "2.0.0",
 | 
			
		||||
        "grunt-contrib-concat": "~1.0.1",
 | 
			
		||||
        "grunt-contrib-copy": "~1.0.0",
 | 
			
		||||
        "grunt-contrib-jshint": "3.0.0",
 | 
			
		||||
        "grunt-contrib-jshint": "3.1.1",
 | 
			
		||||
        "grunt-contrib-uglify": "5.0.1",
 | 
			
		||||
        "grunt-contrib-watch": "~1.1.0",
 | 
			
		||||
        "grunt-jsdoc": "2.4.1",
 | 
			
		||||
@@ -98,20 +104,20 @@
 | 
			
		||||
        "grunt-sass": "~3.1.0",
 | 
			
		||||
        "grunt-simple-mocha": "~0.4.1",
 | 
			
		||||
        "grunt-simple-nyc": "^3.0.1",
 | 
			
		||||
        "http-proxy": "1.18.1",
 | 
			
		||||
        "i18next-http-backend": "1.2.1",
 | 
			
		||||
        "i18next-http-backend": "1.3.1",
 | 
			
		||||
        "jquery-i18next": "1.2.1",
 | 
			
		||||
        "jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
 | 
			
		||||
        "marked": "2.0.1",
 | 
			
		||||
        "marked": "3.0.7",
 | 
			
		||||
        "minami": "1.2.3",
 | 
			
		||||
        "mocha": "8.3.2",
 | 
			
		||||
        "mocha": "9.1.3",
 | 
			
		||||
        "node-red-node-test-helper": "^0.2.7",
 | 
			
		||||
        "node-sass": "^5.0.0",
 | 
			
		||||
        "nodemon": "2.0.7",
 | 
			
		||||
        "nodemon": "2.0.15",
 | 
			
		||||
        "proxy": "^1.0.2",
 | 
			
		||||
        "sass": "1.44.0",
 | 
			
		||||
        "should": "13.2.3",
 | 
			
		||||
        "sinon": "10.0.1",
 | 
			
		||||
        "sinon": "11.1.2",
 | 
			
		||||
        "stoppable": "^1.1.0",
 | 
			
		||||
        "supertest": "6.1.3"
 | 
			
		||||
        "supertest": "6.1.6"
 | 
			
		||||
    },
 | 
			
		||||
    "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,15 @@ module.exports = {
 | 
			
		||||
 | 
			
		||||
        var adminApp = express();
 | 
			
		||||
 | 
			
		||||
        var defaultServerSettings = {
 | 
			
		||||
            "x-powered-by": false
 | 
			
		||||
        }
 | 
			
		||||
        var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
 | 
			
		||||
        for (var eOption in serverSettings) {
 | 
			
		||||
            adminApp.set(eOption, serverSettings[eOption]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Flows
 | 
			
		||||
        adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
 | 
			
		||||
        adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,8 @@ module.exports = {
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
 | 
			
		||||
            if (/[^a-z\-\*]/i.test(opts.lang)) {
 | 
			
		||||
                res.json({});
 | 
			
		||||
                return;
 | 
			
		||||
            if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
 | 
			
		||||
                opts.lang = "en-US";
 | 
			
		||||
            }
 | 
			
		||||
            runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) {
 | 
			
		||||
                res.send(configs);
 | 
			
		||||
@@ -32,9 +31,8 @@ module.exports = {
 | 
			
		||||
            lang: req.query.lng,
 | 
			
		||||
            req: apiUtils.getRequestLogObject(req)
 | 
			
		||||
        }
 | 
			
		||||
        if (/[^a-z\-\*]/i.test(opts.lang)) {
 | 
			
		||||
            res.json({});
 | 
			
		||||
            return;
 | 
			
		||||
        if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
 | 
			
		||||
            opts.lang = "en-US";
 | 
			
		||||
        }
 | 
			
		||||
        runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) {
 | 
			
		||||
            res.json(result);
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ function getToken(req,res,next) {
 | 
			
		||||
    return server.token()(req,res,next);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function login(req,res) {
 | 
			
		||||
async function login(req,res) {
 | 
			
		||||
    var response = {};
 | 
			
		||||
    if (settings.adminAuth) {
 | 
			
		||||
        var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
 | 
			
		||||
@@ -116,8 +116,9 @@ function login(req,res) {
 | 
			
		||||
                response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (theme.context().login && theme.context().login.image) {
 | 
			
		||||
            response.image = theme.context().login.image;
 | 
			
		||||
        let themeContext = await theme.context();
 | 
			
		||||
        if (themeContext.login && themeContext.login.image) {
 | 
			
		||||
            response.image = themeContext.login.image;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    res.json(response);
 | 
			
		||||
@@ -140,7 +141,7 @@ function completeVerify(profile,done) {
 | 
			
		||||
    Users.authenticate(profile).then(function(user) {
 | 
			
		||||
        if (user) {
 | 
			
		||||
            Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) {
 | 
			
		||||
                log.audit({event: "auth.login",username:user.username,scope:user.permissions});
 | 
			
		||||
                log.audit({event: "auth.login",user,username:user.username,scope:user.permissions});
 | 
			
		||||
                user.tokens = tokens;
 | 
			
		||||
                done(null,user);
 | 
			
		||||
            });
 | 
			
		||||
@@ -172,31 +173,38 @@ function genericStrategy(adminApp,strategy) {
 | 
			
		||||
    adminApp.use(passport.session());
 | 
			
		||||
 | 
			
		||||
    var options = strategy.options;
 | 
			
		||||
    var verify = function() {
 | 
			
		||||
        var originalDone = arguments[arguments.length-1];
 | 
			
		||||
        if (options.verify) {
 | 
			
		||||
            var args = Array.from(arguments);
 | 
			
		||||
            args[args.length-1] = function(err,profile) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    return originalDone(err);
 | 
			
		||||
                } else {
 | 
			
		||||
                    return completeVerify(profile,originalDone);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
    passport.use(new strategy.strategy(options,
 | 
			
		||||
        function() {
 | 
			
		||||
            var originalDone = arguments[arguments.length-1];
 | 
			
		||||
            if (options.verify) {
 | 
			
		||||
                var args = Array.from(arguments);
 | 
			
		||||
                args[args.length-1] = function(err,profile) {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        return originalDone(err);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return completeVerify(profile,originalDone);
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                options.verify.apply(null,args);
 | 
			
		||||
            } else {
 | 
			
		||||
                var profile = arguments[arguments.length - 2];
 | 
			
		||||
                return completeVerify(profile,originalDone);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            options.verify.apply(null,args);
 | 
			
		||||
        } else {
 | 
			
		||||
            var profile = arguments[arguments.length - 2];
 | 
			
		||||
            return completeVerify(profile,originalDone);
 | 
			
		||||
        }
 | 
			
		||||
    ));
 | 
			
		||||
    };
 | 
			
		||||
    // Give our callback the same arity as the original one from options
 | 
			
		||||
    if (options.verify) {
 | 
			
		||||
        Object.defineProperty(verify, "length", { value: options.verify.length })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    passport.use(new strategy.strategy(options, verify));
 | 
			
		||||
 | 
			
		||||
    adminApp.get('/auth/strategy',
 | 
			
		||||
        passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
 | 
			
		||||
        completeGenerateStrategyAuth
 | 
			
		||||
        passport.authenticate(strategy.name, {session:false,
 | 
			
		||||
            failureMessage: true,
 | 
			
		||||
            failureRedirect: settings.httpAdminRoot
 | 
			
		||||
        }),
 | 
			
		||||
        completeGenerateStrategyAuth,
 | 
			
		||||
        handleStrategyError
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    var callbackMethodFunc = adminApp.get;
 | 
			
		||||
@@ -204,8 +212,13 @@ function genericStrategy(adminApp,strategy) {
 | 
			
		||||
        callbackMethodFunc = adminApp.post;
 | 
			
		||||
    }
 | 
			
		||||
    callbackMethodFunc.call(adminApp,'/auth/strategy/callback',
 | 
			
		||||
        passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
 | 
			
		||||
        completeGenerateStrategyAuth
 | 
			
		||||
        passport.authenticate(strategy.name, {
 | 
			
		||||
            session:false,
 | 
			
		||||
            failureMessage: true,
 | 
			
		||||
            failureRedirect: settings.httpAdminRoot
 | 
			
		||||
        }),
 | 
			
		||||
        completeGenerateStrategyAuth,
 | 
			
		||||
        handleStrategyError
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -215,6 +228,13 @@ function completeGenerateStrategyAuth(req,res) {
 | 
			
		||||
    // Successful authentication, redirect home.
 | 
			
		||||
    res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
 | 
			
		||||
}
 | 
			
		||||
function handleStrategyError(err, req, res, next) {
 | 
			
		||||
    if (res.headersSent) {
 | 
			
		||||
        return next(err)
 | 
			
		||||
    }
 | 
			
		||||
    log.audit({event: "auth.login.fail.oauth",error:err.toString()});
 | 
			
		||||
    res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    init: init,
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,7 @@ var passwordTokenExchange = function(client, username, password, scope, done) {
 | 
			
		||||
                    return logEntry.user !== username;
 | 
			
		||||
                });
 | 
			
		||||
                Tokens.create(username,client.id,scope).then(function(tokens) {
 | 
			
		||||
                    log.audit({event: "auth.login",username:username,client:client.id,scope:scope});
 | 
			
		||||
                    log.audit({event: "auth.login",user,username:username,client:client.id,scope:scope});
 | 
			
		||||
                    done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -158,25 +158,31 @@ function CommsConnection(ws, user) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CommsConnection.prototype.send = function(topic,data) {
 | 
			
		||||
    var self = this;
 | 
			
		||||
    if (topic && data) {
 | 
			
		||||
        this.stack.push({topic:topic,data:data});
 | 
			
		||||
    }
 | 
			
		||||
    this._queueSend();
 | 
			
		||||
}
 | 
			
		||||
CommsConnection.prototype._queueSend = function() {
 | 
			
		||||
    var self = this;
 | 
			
		||||
    if (!this._xmitTimer) {
 | 
			
		||||
        this._xmitTimer = setTimeout(function() {
 | 
			
		||||
            try {
 | 
			
		||||
                self.ws.send(JSON.stringify(self.stack));
 | 
			
		||||
                self.ws.send(JSON.stringify(self.stack.splice(0,50)));
 | 
			
		||||
                self.lastSentTime = Date.now();
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                removeActiveConnection(self);
 | 
			
		||||
                log.warn(log._("comms.error-send",{message:err.toString()}));
 | 
			
		||||
            }
 | 
			
		||||
            delete self._xmitTimer;
 | 
			
		||||
            self.stack = [];
 | 
			
		||||
            if (self.stack.length > 0) {
 | 
			
		||||
                self._queueSend();
 | 
			
		||||
            }
 | 
			
		||||
        },50);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CommsConnection.prototype.subscribe = function(topic) {
 | 
			
		||||
    runtimeAPI.comms.subscribe({
 | 
			
		||||
        user: this.user,
 | 
			
		||||
 
 | 
			
		||||
@@ -64,10 +64,12 @@ module.exports = {
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            if (settings.httpServerOptions) {
 | 
			
		||||
                for (var eOption in settings.httpServerOptions) {
 | 
			
		||||
                    editorApp.set(eOption, settings.httpServerOptions[eOption]);
 | 
			
		||||
                }
 | 
			
		||||
            var defaultServerSettings = {
 | 
			
		||||
                "x-powered-by": false
 | 
			
		||||
            }
 | 
			
		||||
            var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
 | 
			
		||||
            for (var eOption in serverSettings) {
 | 
			
		||||
                editorApp.set(eOption, serverSettings[eOption]);
 | 
			
		||||
            }
 | 
			
		||||
            editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,10 @@ module.exports = {
 | 
			
		||||
        var prevLang = i18n.i.language;
 | 
			
		||||
        // Trigger a load from disk of the language if it is not the default
 | 
			
		||||
        i18n.i.changeLanguage(lang, function(){
 | 
			
		||||
            var catalog = loadResource(lang, namespace);
 | 
			
		||||
            res.json(catalog||{});
 | 
			
		||||
            i18n.i.changeLanguage(prevLang, function() {
 | 
			
		||||
                var catalog = loadResource(lang, namespace);
 | 
			
		||||
                res.json(catalog||{});
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        i18n.i.changeLanguage(prevLang);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,6 @@ var apiUtils = require("../util");
 | 
			
		||||
var express = require("express");
 | 
			
		||||
var runtimeAPI;
 | 
			
		||||
 | 
			
		||||
function getUsername(userObj) {
 | 
			
		||||
    var username = '__default';
 | 
			
		||||
    if ( userObj && userObj.name ) {
 | 
			
		||||
        username = userObj.name;
 | 
			
		||||
    }
 | 
			
		||||
    return username;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    init: function(_runtimeAPI) {
 | 
			
		||||
        runtimeAPI = _runtimeAPI;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,16 +24,20 @@ var defaultContext = {
 | 
			
		||||
    page: {
 | 
			
		||||
        title: "Node-RED",
 | 
			
		||||
        favicon: "favicon.ico",
 | 
			
		||||
        tabicon: "red/images/node-red-icon-black.svg"
 | 
			
		||||
        tabicon: {
 | 
			
		||||
            icon: "red/images/node-red-icon-black.svg",
 | 
			
		||||
            colour: "#8f0000"
 | 
			
		||||
        },
 | 
			
		||||
        version: require(path.join(__dirname,"../../package.json")).version
 | 
			
		||||
    },
 | 
			
		||||
    header: {
 | 
			
		||||
        title: "Node-RED",
 | 
			
		||||
        image: "red/images/node-red.svg"
 | 
			
		||||
    },
 | 
			
		||||
    asset: {
 | 
			
		||||
        red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js",
 | 
			
		||||
        main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js",
 | 
			
		||||
 | 
			
		||||
        red: "red/red.min.js",
 | 
			
		||||
        main: "red/main.min.js",
 | 
			
		||||
        vendorMonaco: ""
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -74,7 +78,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
 | 
			
		||||
            let fullPath = array[i];
 | 
			
		||||
            if (baseDirectory) {
 | 
			
		||||
                fullPath = path.resolve(baseDirectory,array[i]);
 | 
			
		||||
                if (fullPath.indexOf(baseDirectory) !== 0) {
 | 
			
		||||
                if (fullPath.indexOf(path.resolve(baseDirectory)) !== 0) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -91,8 +95,13 @@ module.exports = {
 | 
			
		||||
    init: function(settings, _runtimeAPI) {
 | 
			
		||||
        runtimeAPI = _runtimeAPI;
 | 
			
		||||
        themeContext = clone(defaultContext);
 | 
			
		||||
        if (process.env.NODE_ENV == "development") {
 | 
			
		||||
            themeContext.asset.red = "red/red.js";
 | 
			
		||||
            themeContext.asset.main = "red/main.js";
 | 
			
		||||
        }
 | 
			
		||||
        themeSettings = null;
 | 
			
		||||
        theme = settings.editorTheme || {};
 | 
			
		||||
        themeContext.asset.vendorMonaco = ((theme.codeEditor || {}).lib === "monaco") ? "vendor/monaco/monaco-bootstrap.js" : "";
 | 
			
		||||
        activeTheme = theme.theme;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@@ -122,13 +131,25 @@ module.exports = {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (theme.page.tabicon) {
 | 
			
		||||
                url = serveFile(themeApp,"/tabicon/",theme.page.tabicon)
 | 
			
		||||
                let icon = theme.page.tabicon.icon || theme.page.tabicon
 | 
			
		||||
                url = serveFile(themeApp,"/tabicon/", icon)
 | 
			
		||||
                if (url) {
 | 
			
		||||
                    themeContext.page.tabicon = url;
 | 
			
		||||
                    themeContext.page.tabicon.icon = url;
 | 
			
		||||
                }
 | 
			
		||||
                if (theme.page.tabicon.colour) {
 | 
			
		||||
                    themeContext.page.tabicon.colour = theme.page.tabicon.colour
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            themeContext.page.title = theme.page.title || themeContext.page.title;
 | 
			
		||||
 | 
			
		||||
            // Store the resolved urls to these resources so nodes (such as Debug)
 | 
			
		||||
            // can access them
 | 
			
		||||
            theme.page._ = {
 | 
			
		||||
                css: themeContext.page.css,
 | 
			
		||||
                scripts: themeContext.page.scripts,
 | 
			
		||||
                favicon: themeContext.page.favicon
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (theme.header) {
 | 
			
		||||
@@ -207,6 +228,11 @@ module.exports = {
 | 
			
		||||
        if (theme.theme) {
 | 
			
		||||
            themeSettings.theme = theme.theme;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (theme.hasOwnProperty("tours")) {
 | 
			
		||||
            themeSettings.tours = theme.tours;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return themeApp;
 | 
			
		||||
    },
 | 
			
		||||
    context: async function() {
 | 
			
		||||
@@ -223,6 +249,8 @@ module.exports = {
 | 
			
		||||
                        themePlugin.path
 | 
			
		||||
                    );
 | 
			
		||||
                    themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
 | 
			
		||||
                    theme.page = theme.page || {_:{}}
 | 
			
		||||
                    theme.page._.css = cssFiles.concat(theme.page._.css || [])
 | 
			
		||||
                }
 | 
			
		||||
                if (themePlugin.scripts) {
 | 
			
		||||
                    const scriptFiles = serveFilesFromTheme(
 | 
			
		||||
@@ -232,6 +260,11 @@ module.exports = {
 | 
			
		||||
                        themePlugin.path
 | 
			
		||||
                    )
 | 
			
		||||
                    themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
 | 
			
		||||
                    theme.page = theme.page || {_:{}}
 | 
			
		||||
                    theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
 | 
			
		||||
                }
 | 
			
		||||
                if(theme.codeEditor) {
 | 
			
		||||
                    theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            activeThemeInitialised = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -91,7 +91,16 @@ module.exports = {
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    editor: async function(req,res) {
 | 
			
		||||
        res.send(Mustache.render(editorTemplate,await theme.context()));
 | 
			
		||||
 | 
			
		||||
        let sessionMessages;
 | 
			
		||||
        if (req.session && req.session.messages) {
 | 
			
		||||
            sessionMessages = JSON.stringify(req.session.messages);
 | 
			
		||||
            delete req.session.messages
 | 
			
		||||
        }
 | 
			
		||||
        res.send(Mustache.render(editorTemplate,{
 | 
			
		||||
            sessionMessages,
 | 
			
		||||
            ...await theme.context()
 | 
			
		||||
        }));
 | 
			
		||||
    },
 | 
			
		||||
    editorResources: express.static(path.join(editorClientDir,'public'))
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,14 @@ function init(settings,_server,storage,runtimeAPI) {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var defaultServerSettings = {
 | 
			
		||||
            "x-powered-by": false
 | 
			
		||||
        }
 | 
			
		||||
        var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
 | 
			
		||||
        for (var eOption in serverSettings) {
 | 
			
		||||
            adminApp.set(eOption, serverSettings[eOption]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auth.init(settings,storage);
 | 
			
		||||
 | 
			
		||||
        var maxApiRequestSize = settings.apiMaxLength || '5mb';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/editor-api",
 | 
			
		||||
    "version": "2.0.0-beta.1",
 | 
			
		||||
    "version": "2.2.0-beta.1",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "main": "./lib/index.js",
 | 
			
		||||
    "repository": {
 | 
			
		||||
@@ -16,23 +16,23 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@node-red/util": "2.0.0-beta.1",
 | 
			
		||||
        "@node-red/editor-client": "2.0.0-beta.1",
 | 
			
		||||
        "@node-red/util": "2.2.0-beta.1",
 | 
			
		||||
        "@node-red/editor-client": "2.2.0-beta.1",
 | 
			
		||||
        "bcryptjs": "2.4.3",
 | 
			
		||||
        "body-parser": "1.19.0",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "cors": "2.8.5",
 | 
			
		||||
        "express-session": "1.17.1",
 | 
			
		||||
        "express-session": "1.17.2",
 | 
			
		||||
        "express": "4.17.1",
 | 
			
		||||
        "memorystore": "1.6.6",
 | 
			
		||||
        "mime": "2.5.2",
 | 
			
		||||
        "multer": "1.4.2",
 | 
			
		||||
        "multer": "1.4.3",
 | 
			
		||||
        "mustache": "4.2.0",
 | 
			
		||||
        "oauth2orize": "1.11.0",
 | 
			
		||||
        "oauth2orize": "1.11.1",
 | 
			
		||||
        "passport-http-bearer": "1.0.1",
 | 
			
		||||
        "passport-oauth2-client-password": "0.1.2",
 | 
			
		||||
        "passport": "0.4.1",
 | 
			
		||||
        "ws": "6.2.1"
 | 
			
		||||
        "passport": "0.5.0",
 | 
			
		||||
        "ws": "7.5.1"
 | 
			
		||||
    },
 | 
			
		||||
    "optionalDependencies": {
 | 
			
		||||
        "bcrypt": "5.0.1"
 | 
			
		||||
 
 | 
			
		||||
@@ -53,8 +53,17 @@
 | 
			
		||||
        "confirmDelete": "Confirm delete",
 | 
			
		||||
        "delete": "Are you sure you want to delete '__label__'?",
 | 
			
		||||
        "dropFlowHere": "Drop the flow here",
 | 
			
		||||
        "addFlow": "Add Flow",
 | 
			
		||||
        "listFlows": "List Flows",
 | 
			
		||||
        "addFlow": "Add flow",
 | 
			
		||||
        "addFlowToRight": "Add flow to the right",
 | 
			
		||||
        "hideFlow": "Hide flow",
 | 
			
		||||
        "hideOtherFlows": "Hide other flows",
 | 
			
		||||
        "showAllFlows": "Show all flows",
 | 
			
		||||
        "hideAllFlows": "Hide all flows",
 | 
			
		||||
        "hiddenFlows": "List __count__ hidden flow",
 | 
			
		||||
        "hiddenFlows_plural": "List __count__ hidden flows",
 | 
			
		||||
        "showLastHiddenFlow": "Show last hidden flow",
 | 
			
		||||
        "listFlows": "List flows",
 | 
			
		||||
        "listSubflows": "List subflows",
 | 
			
		||||
        "status": "Status",
 | 
			
		||||
        "enabled": "Enabled",
 | 
			
		||||
        "disabled":"Disabled",
 | 
			
		||||
@@ -83,6 +92,7 @@
 | 
			
		||||
            "palette": {
 | 
			
		||||
                "show": "Show palette"
 | 
			
		||||
            },
 | 
			
		||||
            "edit": "Edit",
 | 
			
		||||
            "settings": "Settings",
 | 
			
		||||
            "userSettings": "User Settings",
 | 
			
		||||
            "nodes": "Nodes",
 | 
			
		||||
@@ -105,17 +115,32 @@
 | 
			
		||||
            "editPalette":"Manage palette",
 | 
			
		||||
            "other": "Other",
 | 
			
		||||
            "showTips": "Show tips",
 | 
			
		||||
            "showWelcomeTours": "Show guided tours for new versions",
 | 
			
		||||
            "help": "Node-RED website",
 | 
			
		||||
            "projects": "Projects",
 | 
			
		||||
            "projects-new": "New",
 | 
			
		||||
            "projects-open": "Open",
 | 
			
		||||
            "projects-settings": "Project Settings",
 | 
			
		||||
            "showNodeLabelDefault": "Show label of newly added nodes",
 | 
			
		||||
            "codeEditor": "Code Editor",
 | 
			
		||||
            "groups": "Groups",
 | 
			
		||||
            "groupSelection": "Group selection",
 | 
			
		||||
            "ungroupSelection": "Ungroup selection",
 | 
			
		||||
            "groupMergeSelection": "Merge selection",
 | 
			
		||||
            "groupRemoveSelection": "Remove from group"
 | 
			
		||||
            "groupRemoveSelection": "Remove from group",
 | 
			
		||||
            "arrange":"Arrange",
 | 
			
		||||
            "alignLeft":"Align to left",
 | 
			
		||||
            "alignCenter":"Align to center",
 | 
			
		||||
            "alignRight":"Align to right",
 | 
			
		||||
            "alignTop":"Align to top",
 | 
			
		||||
            "alignMiddle":"Align to middle",
 | 
			
		||||
            "alignBottom":"Align to bottom",
 | 
			
		||||
            "distributeHorizontally":"Distribute horizontally",
 | 
			
		||||
            "distributeVertically":"Distribute vertically",
 | 
			
		||||
            "moveToBack":"Move to back",
 | 
			
		||||
            "moveToFront":"Move to front",
 | 
			
		||||
            "moveBackwards":"Move backwards",
 | 
			
		||||
            "moveForwards":"Move forwards"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
@@ -449,8 +474,9 @@
 | 
			
		||||
        "unassigned": "Unassigned",
 | 
			
		||||
        "global": "global",
 | 
			
		||||
        "workspace": "workspace",
 | 
			
		||||
        "selectAll": "Select all nodes",
 | 
			
		||||
        "selectAllConnected": "Select all connected nodes",
 | 
			
		||||
        "selectAll": "Select all",
 | 
			
		||||
        "selectNone": "Select none",
 | 
			
		||||
        "selectAllConnected": "Select connected",
 | 
			
		||||
        "addRemoveNode": "Add/remove node from selection",
 | 
			
		||||
        "editSelected": "Edit selected node",
 | 
			
		||||
        "deleteSelected": "Delete selected nodes or link",
 | 
			
		||||
@@ -463,7 +489,10 @@
 | 
			
		||||
        "copyNode": "Copy selected nodes",
 | 
			
		||||
        "cutNode": "Cut selected nodes",
 | 
			
		||||
        "pasteNode": "Paste nodes",
 | 
			
		||||
        "undoChange": "Undo the last change performed",
 | 
			
		||||
        "copyGroupStyle": "Copy group style",
 | 
			
		||||
        "pasteGroupStyle": "Paste group style",
 | 
			
		||||
        "undoChange": "Undo",
 | 
			
		||||
        "redoChange": "Redo",
 | 
			
		||||
        "searchBox": "Open search box",
 | 
			
		||||
        "managePalette": "Manage palette",
 | 
			
		||||
        "actionList":"Action list"
 | 
			
		||||
@@ -518,7 +547,8 @@
 | 
			
		||||
            "nodeEnabled_plural": "Nodes enabled:",
 | 
			
		||||
            "nodeDisabled": "Node disabled:",
 | 
			
		||||
            "nodeDisabled_plural": "Nodes disabled:",
 | 
			
		||||
            "nodeUpgraded": "Node module __module__ upgraded to version __version__"
 | 
			
		||||
            "nodeUpgraded": "Node module __module__ upgraded to version __version__",
 | 
			
		||||
            "unknownNodeRegistered": "Error loading node: <ul><li>__type__<br>__error__</li></ul>"
 | 
			
		||||
        },
 | 
			
		||||
        "editor": {
 | 
			
		||||
            "title": "Manage palette",
 | 
			
		||||
@@ -641,7 +671,8 @@
 | 
			
		||||
                "unusedConfigNodes": "Unused configuration nodes",
 | 
			
		||||
                "invalidNodes": "Invalid nodes",
 | 
			
		||||
                "uknownNodes": "Unknown nodes",
 | 
			
		||||
                "unusedSubflows": "Unused subflows"
 | 
			
		||||
                "unusedSubflows": "Unused subflows",
 | 
			
		||||
                "hiddenFlows": "Hidden flows"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "help": {
 | 
			
		||||
@@ -885,6 +916,9 @@
 | 
			
		||||
            "eval": "Error evaluating expression:\n  __message__"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "monaco": {
 | 
			
		||||
        "setTheme": "Set theme"
 | 
			
		||||
    },
 | 
			
		||||
    "jsEditor": {
 | 
			
		||||
        "title": "JavaScript editor"
 | 
			
		||||
    },
 | 
			
		||||
@@ -1104,6 +1138,11 @@
 | 
			
		||||
        "preview": "UI Preview",
 | 
			
		||||
        "defaultValue": "Default value"
 | 
			
		||||
    },
 | 
			
		||||
    "tourGuide": {
 | 
			
		||||
        "takeATour": "Take a tour",
 | 
			
		||||
        "start": "Start",
 | 
			
		||||
        "next": "Next"
 | 
			
		||||
    },
 | 
			
		||||
    "languages" : {
 | 
			
		||||
        "de": "German",
 | 
			
		||||
        "en-US": "English",
 | 
			
		||||
 
 | 
			
		||||
@@ -52,8 +52,8 @@
 | 
			
		||||
        "desc": "Finds occurrences of `pattern` within `str` and replaces them with `replacement`.\n\nThe optional `limit` parameter is the maximum number of replacements."
 | 
			
		||||
    },
 | 
			
		||||
    "$now": {
 | 
			
		||||
        "args":"",
 | 
			
		||||
        "desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string."
 | 
			
		||||
        "args":"$[picture [, timezone]]",
 | 
			
		||||
        "desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string. If the optional picture and timezone parameters are supplied, then the current timestamp is formatted as described by the `$fromMillis()` function"
 | 
			
		||||
    },
 | 
			
		||||
    "$base64encode": {
 | 
			
		||||
        "args":"string",
 | 
			
		||||
@@ -200,8 +200,8 @@
 | 
			
		||||
        "desc": "Returns a copy of the `string` with extra padding, if necessary, so that its total number of characters is at least the absolute value of the `width` parameter.\n\nIf `width` is a positive number, then the string is padded to the right; if negative, it is padded to the left.\n\nThe optional `char` argument specifies the padding character(s) to use. If not specified, it defaults to the space character."
 | 
			
		||||
    },
 | 
			
		||||
    "$fromMillis": {
 | 
			
		||||
        "args": "number",
 | 
			
		||||
        "desc": "Convert a number representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a timestamp string in the ISO 8601 format."
 | 
			
		||||
        "args": "number, [, picture [, timezone]]",
 | 
			
		||||
        "desc": "Convert the `number` representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a formatted string representation of the timestamp as specified by the picture string.\n\nIf the optional `picture` parameter is omitted, then the timestamp is formatted in the ISO 8601 format.\n\nIf the optional `picture` string is supplied, then the timestamp is formatted occording to the representation specified in that string. The behaviour of this function is consistent with the two-argument version of the XPath/XQuery function `format-dateTime` as defined in the XPath F&O 3.1 specification. The picture string parameter defines how the timestamp is formatted and has the same syntax as `format-dateTime`.\n\nIf the optional `timezone` string is supplied, then the formatted timestamp will be in that timezone. The `timezone` string should be in the format '±HHMM', where ± is either the plus or minus sign and HHMM is the offset in hours and minutes from UTC. Positive offset for timezones east of UTC, negative offset for timezones west of UTC."
 | 
			
		||||
    },
 | 
			
		||||
    "$formatNumber": {
 | 
			
		||||
        "args": "number, picture [, options]",
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,14 @@
 | 
			
		||||
        "delete": "本当に '__label__' を削除しますか?",
 | 
			
		||||
        "dropFlowHere": "ここにフローをドロップしてください",
 | 
			
		||||
        "addFlow": "フローの追加",
 | 
			
		||||
        "addFlowToRight": "右側にフローを追加",
 | 
			
		||||
        "hideFlow": "フローを非表示",
 | 
			
		||||
        "hideOtherFlows": "他のフローを非表示",
 | 
			
		||||
        "showAllFlows": "全てのフローを表示",
 | 
			
		||||
        "hideAllFlows": "全てのフローを非表示",
 | 
			
		||||
        "showLastHiddenFlow": "最後に非表示にしたフローを表示",
 | 
			
		||||
        "listFlows": "フロー一覧",
 | 
			
		||||
        "listSubflows": "サブフロー一覧",
 | 
			
		||||
        "status": "状態",
 | 
			
		||||
        "enabled": "有効",
 | 
			
		||||
        "disabled": "無効",
 | 
			
		||||
@@ -83,6 +90,7 @@
 | 
			
		||||
            "palette": {
 | 
			
		||||
                "show": "パレットを表示"
 | 
			
		||||
            },
 | 
			
		||||
            "edit": "編集",
 | 
			
		||||
            "settings": "設定",
 | 
			
		||||
            "userSettings": "ユーザ設定",
 | 
			
		||||
            "nodes": "ノード",
 | 
			
		||||
@@ -105,17 +113,32 @@
 | 
			
		||||
            "editPalette": "パレットの管理",
 | 
			
		||||
            "other": "その他",
 | 
			
		||||
            "showTips": "ヒントを表示",
 | 
			
		||||
            "showWelcomeTours": "新バージョンのガイドツアーを表示",
 | 
			
		||||
            "help": "Node-REDウェブサイト",
 | 
			
		||||
            "projects": "プロジェクト",
 | 
			
		||||
            "projects-new": "新規",
 | 
			
		||||
            "projects-open": "開く",
 | 
			
		||||
            "projects-settings": "設定",
 | 
			
		||||
            "showNodeLabelDefault": "追加したノードのラベルを表示",
 | 
			
		||||
            "codeEditor": "コードエディタ",
 | 
			
		||||
            "groups": "グループ",
 | 
			
		||||
            "groupSelection": "選択部分をグループ化",
 | 
			
		||||
            "ungroupSelection": "選択部分をグループ解除",
 | 
			
		||||
            "groupMergeSelection": "選択部分をマージ",
 | 
			
		||||
            "groupRemoveSelection": "グループから削除"
 | 
			
		||||
            "groupRemoveSelection": "グループから削除",
 | 
			
		||||
            "arrange": "配置",
 | 
			
		||||
            "alignLeft": "左揃え",
 | 
			
		||||
            "alignCenter": "左右中央揃え",
 | 
			
		||||
            "alignRight": "右揃え",
 | 
			
		||||
            "alignTop": "上揃え",
 | 
			
		||||
            "alignMiddle": "上下中央揃え",
 | 
			
		||||
            "alignBottom": "下揃え",
 | 
			
		||||
            "distributeHorizontally": "左右に整列",
 | 
			
		||||
            "distributeVertically": "上下に整列",
 | 
			
		||||
            "moveToBack": "最背面へ移動",
 | 
			
		||||
            "moveToFront": "最前面へ移動",
 | 
			
		||||
            "moveBackwards": "背面へ移動",
 | 
			
		||||
            "moveForwards": "前面へ移動"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
@@ -450,7 +473,8 @@
 | 
			
		||||
        "global": "グローバル",
 | 
			
		||||
        "workspace": "ワークスペース",
 | 
			
		||||
        "selectAll": "全てのノードを選択",
 | 
			
		||||
        "selectAllConnected": "接続された全てのノードを選択",
 | 
			
		||||
        "selectNone": "選択を外す",
 | 
			
		||||
        "selectAllConnected": "接続されたノードを選択",
 | 
			
		||||
        "addRemoveNode": "ノードの選択、選択解除",
 | 
			
		||||
        "editSelected": "選択したノードを編集",
 | 
			
		||||
        "deleteSelected": "選択したノードや接続を削除",
 | 
			
		||||
@@ -460,10 +484,13 @@
 | 
			
		||||
        "moveNode": "選択したノードを移動(移動量大)",
 | 
			
		||||
        "toggleSidebar": "サイドバーの表示/非表示",
 | 
			
		||||
        "togglePalette": "パレットの表示/非表示",
 | 
			
		||||
        "copyNode": "選択したノードをコピー",
 | 
			
		||||
        "cutNode": "選択したノードを切り取り",
 | 
			
		||||
        "copyNode": "ノードをコピー",
 | 
			
		||||
        "cutNode": "ノードを切り取り",
 | 
			
		||||
        "pasteNode": "ノードを貼り付け",
 | 
			
		||||
        "copyGroupStyle": "グループ様式をコピー",
 | 
			
		||||
        "pasteGroupStyle": "グループ様式を貼り付け",
 | 
			
		||||
        "undoChange": "変更操作を戻す",
 | 
			
		||||
        "redoChange": "変更操作をやり直し",
 | 
			
		||||
        "searchBox": "ノードを検索",
 | 
			
		||||
        "managePalette": "パレットの管理",
 | 
			
		||||
        "actionList": "動作一覧"
 | 
			
		||||
@@ -518,14 +545,15 @@
 | 
			
		||||
            "nodeEnabled_plural": "ノードを有効化しました:",
 | 
			
		||||
            "nodeDisabled": "ノードを無効化しました:",
 | 
			
		||||
            "nodeDisabled_plural": "ノードを無効化しました:",
 | 
			
		||||
            "nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました"
 | 
			
		||||
            "nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました",
 | 
			
		||||
            "unknownNodeRegistered": "ノードの読み込みエラー: <ul><li>__type__<br>__error__</li></ul>"
 | 
			
		||||
        },
 | 
			
		||||
        "editor": {
 | 
			
		||||
            "title": "パレットの管理",
 | 
			
		||||
            "palette": "パレット",
 | 
			
		||||
            "times": {
 | 
			
		||||
                "seconds": "秒前",
 | 
			
		||||
                "minutes": "分前",
 | 
			
		||||
                "seconds": "数秒前",
 | 
			
		||||
                "minutes": "数分前",
 | 
			
		||||
                "minutesV": "__count__ 分前",
 | 
			
		||||
                "hoursV": "__count__ 時間前",
 | 
			
		||||
                "hoursV_plural": "__count__ 時間前",
 | 
			
		||||
@@ -885,6 +913,9 @@
 | 
			
		||||
            "eval": "表現評価エラー:\n  __message__"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "monaco": {
 | 
			
		||||
        "setTheme": "テーマを設定:"
 | 
			
		||||
    },
 | 
			
		||||
    "jsEditor": {
 | 
			
		||||
        "title": "JavaScriptエディタ"
 | 
			
		||||
    },
 | 
			
		||||
@@ -1104,6 +1135,11 @@
 | 
			
		||||
        "preview": "UIプレビュー",
 | 
			
		||||
        "defaultValue": "デフォルト値"
 | 
			
		||||
    },
 | 
			
		||||
    "tourGuide": {
 | 
			
		||||
        "takeATour": "ツアーを開始",
 | 
			
		||||
        "start": "開始",
 | 
			
		||||
        "next": "次へ"
 | 
			
		||||
    },
 | 
			
		||||
    "languages": {
 | 
			
		||||
        "de": "ドイツ語",
 | 
			
		||||
        "en-US": "英語",
 | 
			
		||||
 
 | 
			
		||||
@@ -52,8 +52,8 @@
 | 
			
		||||
        "desc": "文字列 `str` からパターン `pattern` を検索し、置換文字列 `replacement` に置き換えます。\n\n任意の引数 `limit` には、置換回数の上限値を指定します。"
 | 
			
		||||
    },
 | 
			
		||||
    "$now": {
 | 
			
		||||
        "args": "",
 | 
			
		||||
        "desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。"
 | 
			
		||||
        "args": "$[picture [, timezone]]",
 | 
			
		||||
        "desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。pictureおよびtimezoneパラメータが指定されている場合、現在時刻を`$fromMillis()`関数の説明に従ってフォーマットします。"
 | 
			
		||||
    },
 | 
			
		||||
    "$base64encode": {
 | 
			
		||||
        "args": "string",
 | 
			
		||||
@@ -200,8 +200,8 @@
 | 
			
		||||
        "desc": "文字数が引数 `width` の絶対値以上となるよう、必要に応じて追加文字を付け足した `string` のコピーを返します。\n\n`width` が正の値の場合、文字列の右側に追加文字を付け足します。もし負の値の場合、文字列の左側に追加文字を付け足します。\n\n任意の引数 `char` には、本関数で用いる追加文字を指定します。もし追加文字を指定しない場合は、既定値として空白文字を使用します。"
 | 
			
		||||
    },
 | 
			
		||||
    "$fromMillis": {
 | 
			
		||||
        "args": "number",
 | 
			
		||||
        "desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値を、ISO 8601形式のタイムスタンプの文字列に変換します。"
 | 
			
		||||
        "args": "number, [, picture [, timezone]]",
 | 
			
		||||
        "desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値を、`picture`の指定に従ってタイムスタンプの文字列に変換します。\n\n`picture`パラメータが指定されない場合、ISO 8601形式に変換します。\n\n`picture`を指定すると、指定した文字列に従って変換を行います。この変換はXPath F&O 3.1仕様におけるXPath/XQueryの2引数形式`format-dateTime`と同様です。`picture`パラメータはタイムスタンプの変換形式を定義し、その書式は`format-dateTime`と同じです。\n\n`timezone`を指定すると、指定タイムゾーンで変換します。`timezone`は'±HHMM'という形式で指定します。ここで、±は+もしくは-記号を表し、HHMMはUTCからの差分時間と分を表します。正の差分はUTCの東、負の差分は西のタイムゾーンとなります。"
 | 
			
		||||
    },
 | 
			
		||||
    "$formatNumber": {
 | 
			
		||||
        "args": "number, picture [, options]",
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
      "displayConfig": "설정노드 보기",
 | 
			
		||||
      "import": "가져오기",
 | 
			
		||||
      "export": "내보내기",
 | 
			
		||||
      "search": "플로우 겅색",
 | 
			
		||||
      "search": "플로우 검색",
 | 
			
		||||
      "searchInput": "플로우 검색",
 | 
			
		||||
      "subflows": "보조 플로우",
 | 
			
		||||
      "createSubflow": "보조 플로우 생성",
 | 
			
		||||
 
 | 
			
		||||
@@ -225,7 +225,7 @@
 | 
			
		||||
            "compact": "紧凑",
 | 
			
		||||
            "formatted": "已格式化",
 | 
			
		||||
            "copy": "导出到剪贴板",
 | 
			
		||||
            "export": "到处到库",
 | 
			
		||||
            "export": "导出到库",
 | 
			
		||||
            "exportAs": "导出为",
 | 
			
		||||
            "overwrite": "替换",
 | 
			
		||||
            "exists": "<p><b>\"__file__\"</b>已存在</p><p>是否要替换它?</p>"
 | 
			
		||||
@@ -648,7 +648,7 @@
 | 
			
		||||
        },
 | 
			
		||||
        "context": {
 | 
			
		||||
            "name": "上下文数据",
 | 
			
		||||
            "label": "上下午",
 | 
			
		||||
            "label": "上下文",
 | 
			
		||||
            "none": "未选择",
 | 
			
		||||
            "refresh": "刷新以加载",
 | 
			
		||||
            "empty": "空",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/editor-client",
 | 
			
		||||
    "version": "2.0.0-beta.1",
 | 
			
		||||
    "version": "2.2.0-beta.1",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
@@ -14,5 +14,5 @@
 | 
			
		||||
            "name": "Dave Conway-Jones"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "main": "./lib/index.js"
 | 
			
		||||
    "main": "./index.js"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 90 B  | 
							
								
								
									
										1
									
								
								packages/node_modules/@node-red/editor-client/src/images/node-red-256.svg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/node_modules/@node-red/editor-client/src/images/node-red-256.svg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="512" width="512"><g transform="translate(0 -540.36)"><path fill="#8f0000" color="#000" d="M0 540.36h512v392H0z"/><rect ry="0" height="108.23" width="500.23" stroke="#fff" y="938.25" x="5.89" stroke-width="11.77" fill="#fff"/><path style="text-decoration-color:#000;isolation:auto;mix-blend-mode:normal;solid-color:#000;block-progression:tb;text-decoration-line:none;white-space:normal;text-indent:0;text-transform:none;text-decoration-style:solid" d="M122.88 305.82a4.46 4.46 0 0 0-4.38 4.42v.78c-2.23.1-4.04.54-5.33 1.43a10.5 10.5 0 0 0-3.18 3.87c-.71 1.3-1.3 2.41-2.15 3.2-.72.66-1.8 1.12-3.45 1.32a4.37 4.37 0 0 0-4.3-3.95H82.91a4.43 4.43 0 0 0-4.42 4.35v4.24a4.43 4.43 0 0 0 4.42 4.36h17.16a4.4 4.4 0 0 0 4.4-4.36v-1.6c9.72.14 12.46 2.6 15.59 5.33 3 2.62 6.66 5.38 15.43 5.5v.73a4.49 4.49 0 0 0 4.46 4.38h17.09c2.38 0 4.45-2 4.45-4.38v-4.24a4.49 4.49 0 0 0-4.45-4.38h-17.1c-2.38 0-4.45 2-4.45 4.38v.58c-8.1-.06-10.48-2.15-13.5-4.79-2.5-2.19-5.64-4.58-11.94-5.58 1.17-1.18 1.88-2.52 2.51-3.66.68-1.23 1.29-2.2 2.27-2.88.76-.52 1.98-.84 3.66-.94v.55c0 2.39 2 4.34 4.38 4.34h17.24a4.39 4.39 0 0 0 4.38-4.34v-4.24c0-2.38-2-4.42-4.38-4.42zm0 3h17.24c.8 0 1.38.62 1.38 1.42v4.24c0 .81-.57 1.34-1.38 1.34h-17.24c-.8 0-1.38-.53-1.38-1.34v-4.24c0-.8.57-1.42 1.38-1.42zm-39.96 11.02h17.16c.81 0 1.42.6 1.42 1.4v4.24c0 .81-.61 1.41-1.42 1.41H82.92c-.8 0-1.42-.6-1.42-1.4v-4.25c0-.8.61-1.4 1.42-1.4zm57.04 9.98h17.09c.8 0 1.45.57 1.45 1.38v4.17c0 .8-.65 1.45-1.45 1.45h-17.1c-.8 0-1.45-.65-1.45-1.45v-4.17c0-.8.65-1.38 1.46-1.38z" fill="#fff" color="#000" transform="matrix(4 0 0 4 -162 -450.91)"/><g fill="#8f0000"><path d="M91 954.34v8.45l-8 1.45v60.07H69.03l-28.53-47.1-.5.05v37.2l8 1.44v8.41H19v-8.4l7.45-1.45v-50.22L19 962.79v-8.46h21.48l28.37 47.1.15-.04v-37.15l-7-1.45v-8.45h29zM95 997.83q0-11.63 6.49-19.03 6.53-7.45 18.02-7.45 11.53 0 18.02 7.4 6.54 7.4 6.54 19.08v1q0 11.74-6.54 19.14-6.49 7.35-17.93 7.35-11.58 0-18.11-7.35-6.5-7.4-6.5-19.13v-1.01zm14.03 1q0 7.12 2.5 11.45 2.5 4.28 8.08 4.28 5.43 0 7.93-4.33 2.54-4.33 2.54-11.4v-1q0-6.92-2.54-11.3-2.55-4.37-8.03-4.37t-7.98 4.37-2.5 11.3v1zM184.48 1017.96a17.15 17.15 0 0 1-5.82 5.48 15.17 15.17 0 0 1-7.59 1.88c-6.4 0-11.4-2.35-14.95-7.03-3.52-4.67-5.13-10.86-5.13-18.55v-1c0-8.21 1.62-14.83 5.18-19.86 3.56-5.03 8.56-7.54 15-7.54 2.6 0 4.93.57 7.01 1.73a17.94 17.94 0 0 1 5.81 4.8v-18.64l-8-1.45v-8.46h22v65.13l6 1.44v8.43h-18.45l-1.06-6.36zm-19.49-18.22c0 4.55.63 8.14 2.14 10.77 1.54 2.6 4.04 3.9 7.5 3.9 2.05 0 3.83-.43 5.33-1.26 1.5-.83 3.07-2.03 4.03-3.6v-22.06a11.27 11.27 0 0 0-4.03-3.85 9.62 9.62 0 0 0-5.24-1.4c-3.43 0-5.92 1.53-7.5 4.57s-2.23 7.02-2.23 11.92v1.01zM233.7 1025.28c-7.5 0-13.5-2.4-17.98-7.21-4.48-4.81-6.73-10.91-6.73-18.32v-1.92c0-7.72 2.12-14.08 6.35-19.08 4.26-5 9.96-7.46 17.1-7.43 7.03 0 12.47 2.1 16.35 6.33a23.46 23.46 0 0 1 6.2 17.15v7.52h-31.43l-.1.41c.26 3.43 1.4 6.25 3.41 8.46 2.05 2.21 4.83 3.32 8.32 3.32 3.1 0 5.69-.3 7.74-.91 2.05-.64 4.29-1.64 6.73-2.98l3.8 8.65a27.59 27.59 0 0 1-8.37 4.28 35.28 35.28 0 0 1-11.4 1.73zm-1.25-43.16c-2.6 0-4.65.99-6.15 2.98s-2.44 4.6-2.8 7.83l.15.4H241v-1.41c0-2.98-.84-5.35-2.25-7.11-1.37-1.8-3.47-2.7-6.3-2.7zM291.99 1000.32h-27v-11h27zM331.88 954.34c7.95 0 14.18 1.82 18.7 5.47 4.52 3.63 6.4 8.64 6.4 15.05 0 3.52-.57 6.58-2.46 9.18-1.89 2.6-4.66 4.7-8.31 6.3 4.13 1.21 7.1 3.25 8.89 6.1a19.02 19.02 0 0 1 2.89 10.52v3.56c0 1.54.15 2.74.76 3.6.6.84 1.62 1.34 3.03 1.5l1.2.24v8.46h-6.73c-4.58 0-7.8-1.24-9.66-3.7s-2.6-5.66-2.6-9.57v-3.99c0-3.4-1.1-6.05-2.93-7.98-1.8-1.95-4.34-2.98-7.64-3.07H322v18.45l7 1.44v8.42h-29v-8.42l8-1.44v-50.22l-8-1.44v-8.46h31.89zm-9.95 30.85h9.71c3.91 0 6.84-.83 8.8-2.5s2.93-4.07 2.93-7.2c0-3.15-.98-5.65-2.93-7.5-1.92-1.9-4.78-2.84-8.56-2.84h-9.9v20.04zM412.99 993.32h-23v20h22.21l.63-8h10.2v18.99H368v-8.42l8-1.44v-50.22l-8-1.44v-8.47h54.95v19h-10.3l-.63-8H390v17h23v11zM462.48 954.36c8.55 0 15.6 2.71 21.14 8.19 5.55 5.45 8.36 12.42 8.36 20.98v11.58c0 8.59-2.81 15.63-8.36 21.08-5.54 5.41-12.59 8.12-21.14 8.12h-31.5v-8.41l7-1.52v-50.22l-7-1.37v-8.43l7.46-.08 24.04.08zm-10.5 10.76v48.4l9.77.02c5.03.02 8.98-1.7 11.83-5.1 2.85-3.39 4.4-7.8 4.4-13.28v-11.68c0-5.42-1.55-9.84-4.4-13.24-2.85-3.4-6.8-5.1-11.83-5.1l-9.77-.02z"/></g></g></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 4.2 KiB  | 
@@ -66,12 +66,14 @@ RED.history = (function() {
 | 
			
		||||
                    var importedResult = RED.nodes.import(ev.config,{importMap: importMap})
 | 
			
		||||
                    inverseEv = {
 | 
			
		||||
                        t: 'replace',
 | 
			
		||||
                        config: importedResult.removedNodes
 | 
			
		||||
                        config: importedResult.removedNodes,
 | 
			
		||||
                        dirty: RED.nodes.dirty()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else if (ev.t == 'add') {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
                    t: "delete",
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                };
 | 
			
		||||
                if (ev.nodes) {
 | 
			
		||||
                    inverseEv.nodes = [];
 | 
			
		||||
@@ -158,7 +160,8 @@ RED.history = (function() {
 | 
			
		||||
 | 
			
		||||
            } else if (ev.t == "delete") {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
                    t: "add"
 | 
			
		||||
                    t: "add",
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                };
 | 
			
		||||
                if (ev.workspaces) {
 | 
			
		||||
                    inverseEv.workspaces = [];
 | 
			
		||||
@@ -300,11 +303,12 @@ RED.history = (function() {
 | 
			
		||||
            } else if (ev.t == "move") {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
                    t: 'move',
 | 
			
		||||
                    nodes: []
 | 
			
		||||
                    nodes: [],
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                };
 | 
			
		||||
                for (i=0;i<ev.nodes.length;i++) {
 | 
			
		||||
                    var n = ev.nodes[i];
 | 
			
		||||
                    var rn = {n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.moved};
 | 
			
		||||
                    var rn = {n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.n.moved};
 | 
			
		||||
                    inverseEv.nodes.push(rn);
 | 
			
		||||
                    n.n.x = n.ox;
 | 
			
		||||
                    n.n.y = n.oy;
 | 
			
		||||
@@ -336,7 +340,9 @@ RED.history = (function() {
 | 
			
		||||
            } else if (ev.t == "edit") {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
                    t: "edit",
 | 
			
		||||
                    changes: {}
 | 
			
		||||
                    changes: {},
 | 
			
		||||
                    changed: ev.node.changed,
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                };
 | 
			
		||||
                inverseEv.node = ev.node;
 | 
			
		||||
                for (i in ev.changes) {
 | 
			
		||||
@@ -552,10 +558,22 @@ RED.history = (function() {
 | 
			
		||||
            } else if (ev.t == "reorder") {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
                    t: 'reorder',
 | 
			
		||||
                    order: RED.nodes.getWorkspaceOrder()
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                };
 | 
			
		||||
                if (ev.order) {
 | 
			
		||||
                    RED.workspaces.order(ev.order);
 | 
			
		||||
                if (ev.workspaces) {
 | 
			
		||||
                    inverseEv.workspaces = {
 | 
			
		||||
                        from: ev.workspaces.to,
 | 
			
		||||
                        to: ev.workspaces.from
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.workspaces.order(ev.workspaces.from);
 | 
			
		||||
                }
 | 
			
		||||
                if (ev.nodes) {
 | 
			
		||||
                    inverseEv.nodes = {
 | 
			
		||||
                        z: ev.nodes.z,
 | 
			
		||||
                        from: ev.nodes.to,
 | 
			
		||||
                        to: ev.nodes.from
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.nodes.setNodeOrder(ev.nodes.z,ev.nodes.from);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (ev.t == "createGroup") {
 | 
			
		||||
                inverseEv = {
 | 
			
		||||
@@ -651,6 +669,8 @@ RED.history = (function() {
 | 
			
		||||
        push: function(ev) {
 | 
			
		||||
            undoHistory.push(ev);
 | 
			
		||||
            redoHistory = [];
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-undo", false);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-redo", true);
 | 
			
		||||
        },
 | 
			
		||||
        pop: function() {
 | 
			
		||||
            var ev = undoHistory.pop();
 | 
			
		||||
@@ -658,6 +678,8 @@ RED.history = (function() {
 | 
			
		||||
            if (rev) {
 | 
			
		||||
                redoHistory.push(rev);
 | 
			
		||||
            }
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-undo", undoHistory.length === 0);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-redo", redoHistory.length === 0);
 | 
			
		||||
        },
 | 
			
		||||
        peek: function() {
 | 
			
		||||
            return undoHistory[undoHistory.length-1];
 | 
			
		||||
@@ -665,6 +687,8 @@ RED.history = (function() {
 | 
			
		||||
        clear: function() {
 | 
			
		||||
            undoHistory = [];
 | 
			
		||||
            redoHistory = [];
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-undo", true);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-redo", true);
 | 
			
		||||
        },
 | 
			
		||||
        redo: function() {
 | 
			
		||||
            var ev = redoHistory.pop();
 | 
			
		||||
@@ -674,6 +698,8 @@ RED.history = (function() {
 | 
			
		||||
                    undoHistory.push(uev);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-undo", undoHistory.length === 0);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-redo", redoHistory.length === 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										156
									
								
								packages/node_modules/@node-red/editor-client/src/js/hooks.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								packages/node_modules/@node-red/editor-client/src/js/hooks.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
RED.hooks = (function() {
 | 
			
		||||
 | 
			
		||||
    var VALID_HOOKS = [
 | 
			
		||||
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    var hooks = { }
 | 
			
		||||
    var labelledHooks = { }
 | 
			
		||||
 | 
			
		||||
    function add(hookId, callback) {
 | 
			
		||||
        var parts = hookId.split(".");
 | 
			
		||||
        var id = parts[0], label = parts[1];
 | 
			
		||||
 | 
			
		||||
        // if (VALID_HOOKS.indexOf(id) === -1) {
 | 
			
		||||
        //     throw new Error("Invalid hook '"+id+"'");
 | 
			
		||||
        // }
 | 
			
		||||
        if (label && labelledHooks[label] && labelledHooks[label][id]) {
 | 
			
		||||
            throw new Error("Hook "+hookId+" already registered")
 | 
			
		||||
        }
 | 
			
		||||
        var hookItem = {cb:callback, previousHook: null, nextHook: null }
 | 
			
		||||
 | 
			
		||||
        var tailItem = hooks[id];
 | 
			
		||||
        if (tailItem === undefined) {
 | 
			
		||||
            hooks[id] = hookItem;
 | 
			
		||||
        } else {
 | 
			
		||||
            while(tailItem.nextHook !== null) {
 | 
			
		||||
                tailItem = tailItem.nextHook
 | 
			
		||||
            }
 | 
			
		||||
            tailItem.nextHook = hookItem;
 | 
			
		||||
            hookItem.previousHook = tailItem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (label) {
 | 
			
		||||
            labelledHooks[label] = labelledHooks[label]||{};
 | 
			
		||||
            labelledHooks[label][id] = hookItem;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function remove(hookId) {
 | 
			
		||||
        var parts = hookId.split(".");
 | 
			
		||||
        var id = parts[0], label = parts[1];
 | 
			
		||||
        if ( !label) {
 | 
			
		||||
            throw new Error("Cannot remove hook without label: "+hookId)
 | 
			
		||||
        }
 | 
			
		||||
        if (labelledHooks[label]) {
 | 
			
		||||
            if (id === "*") {
 | 
			
		||||
                // Remove all hooks for this label
 | 
			
		||||
                var hookList = Object.keys(labelledHooks[label]);
 | 
			
		||||
                for (var i=0;i<hookList.length;i++) {
 | 
			
		||||
                    removeHook(hookList[i],labelledHooks[label][hookList[i]])
 | 
			
		||||
                }
 | 
			
		||||
                delete labelledHooks[label];
 | 
			
		||||
            } else if (labelledHooks[label][id]) {
 | 
			
		||||
                removeHook(id,labelledHooks[label][id])
 | 
			
		||||
                delete labelledHooks[label][id];
 | 
			
		||||
                if (Object.keys(labelledHooks[label]).length === 0){
 | 
			
		||||
                    delete labelledHooks[label];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeHook(id,hookItem) {
 | 
			
		||||
        var previousHook = hookItem.previousHook;
 | 
			
		||||
        var nextHook = hookItem.nextHook;
 | 
			
		||||
 | 
			
		||||
        if (previousHook) {
 | 
			
		||||
            previousHook.nextHook = nextHook;
 | 
			
		||||
        } else {
 | 
			
		||||
            hooks[id] = nextHook;
 | 
			
		||||
        }
 | 
			
		||||
        if (nextHook) {
 | 
			
		||||
            nextHook.previousHook = previousHook;
 | 
			
		||||
        }
 | 
			
		||||
        hookItem.removed = true;
 | 
			
		||||
        if (!previousHook && !nextHook) {
 | 
			
		||||
            delete hooks[id];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function trigger(hookId, payload, done) {
 | 
			
		||||
        var hookItem = hooks[hookId];
 | 
			
		||||
        if (!hookItem) {
 | 
			
		||||
            if (done) {
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        function callNextHook(err) {
 | 
			
		||||
            if (!hookItem || err) {
 | 
			
		||||
                if (done) { done(err) }
 | 
			
		||||
                return err;
 | 
			
		||||
            }
 | 
			
		||||
            if (hookItem.removed) {
 | 
			
		||||
                hookItem = hookItem.nextHook;
 | 
			
		||||
                return callNextHook();
 | 
			
		||||
            }
 | 
			
		||||
            var callback = hookItem.cb;
 | 
			
		||||
            if (callback.length === 1) {
 | 
			
		||||
                try {
 | 
			
		||||
                    let result = callback(payload);
 | 
			
		||||
                    if (result === false) {
 | 
			
		||||
                        // Halting the flow
 | 
			
		||||
                        if (done) { done(false) }
 | 
			
		||||
                        return result;
 | 
			
		||||
                    }
 | 
			
		||||
                    hookItem = hookItem.nextHook;
 | 
			
		||||
                    return callNextHook();
 | 
			
		||||
                } catch(e) {
 | 
			
		||||
                    console.warn(e);
 | 
			
		||||
                    if (done) { done(e);}
 | 
			
		||||
                    return e;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // There is a done callback
 | 
			
		||||
                try {
 | 
			
		||||
                    callback(payload,function(result) {
 | 
			
		||||
                        if (result === undefined) {
 | 
			
		||||
                            hookItem = hookItem.nextHook;
 | 
			
		||||
                            callNextHook();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (done) { done(result)}
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                } catch(e) {
 | 
			
		||||
                    console.warn(e);
 | 
			
		||||
                    if (done) { done(e) }
 | 
			
		||||
                    return e;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return callNextHook();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clear() {
 | 
			
		||||
        hooks = {}
 | 
			
		||||
        labelledHooks = {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function has(hookId) {
 | 
			
		||||
        var parts = hookId.split(".");
 | 
			
		||||
        var id = parts[0], label = parts[1];
 | 
			
		||||
        if (label) {
 | 
			
		||||
            return !!(labelledHooks[label] && labelledHooks[label][id])
 | 
			
		||||
        }
 | 
			
		||||
        return !!hooks[id]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        has: has,
 | 
			
		||||
        clear: clear,
 | 
			
		||||
        add: add,
 | 
			
		||||
        remove: remove,
 | 
			
		||||
        trigger: trigger
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
@@ -25,8 +25,9 @@ RED.i18n = (function() {
 | 
			
		||||
    return {
 | 
			
		||||
        init: function(options, done) {
 | 
			
		||||
            apiRootUrl = options.apiRootUrl||"";
 | 
			
		||||
            var preferredLanguage = localStorage.getItem("editor-language");
 | 
			
		||||
            var preferredLanguage = localStorage.getItem("editor-language") || detectLanguage();
 | 
			
		||||
            var opts = {
 | 
			
		||||
                compatibilityJSON: 'v3',
 | 
			
		||||
                backend: {
 | 
			
		||||
                    loadPath: apiRootUrl+'locales/__ns__?lng=__lng__',
 | 
			
		||||
                },
 | 
			
		||||
@@ -37,6 +38,8 @@ RED.i18n = (function() {
 | 
			
		||||
                defaultNS: "editor",
 | 
			
		||||
                fallbackLng: ['en-US'],
 | 
			
		||||
                returnObjects: true,
 | 
			
		||||
                keySeparator: ".",
 | 
			
		||||
                nsSeparator: ":",
 | 
			
		||||
                interpolation: {
 | 
			
		||||
                    unescapeSuffix: 'HTML',
 | 
			
		||||
                    escapeValue: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,9 @@
 | 
			
		||||
        "ctrl-alt-o": "core:open-project",
 | 
			
		||||
        "ctrl-g v": "core:show-version-control-tab",
 | 
			
		||||
        "ctrl-shift-l": "core:show-event-log",
 | 
			
		||||
        "ctrl-shift-p":"core:show-action-list"
 | 
			
		||||
        "ctrl-shift-p":"core:show-action-list",
 | 
			
		||||
        "alt-w": "core:hide-flow",
 | 
			
		||||
        "alt-shift-w": "core:show-last-hidden-flow"
 | 
			
		||||
    },
 | 
			
		||||
    "red-ui-sidebar-node-config": {
 | 
			
		||||
        "backspace": "core:delete-config-selection",
 | 
			
		||||
@@ -77,6 +79,15 @@
 | 
			
		||||
        "right": "core:go-to-nearest-node-on-right",
 | 
			
		||||
        "left": "core:go-to-nearest-node-on-left",
 | 
			
		||||
        "up": "core:go-to-nearest-node-above",
 | 
			
		||||
        "down": "core:go-to-nearest-node-below"
 | 
			
		||||
        "down": "core:go-to-nearest-node-below",
 | 
			
		||||
        "alt-a g": "core:align-selection-to-grid",
 | 
			
		||||
        "alt-a l": "core:align-selection-to-left",
 | 
			
		||||
        "alt-a r": "core:align-selection-to-right",
 | 
			
		||||
        "alt-a t": "core:align-selection-to-top",
 | 
			
		||||
        "alt-a b": "core:align-selection-to-bottom",
 | 
			
		||||
        "alt-a m": "core:align-selection-to-middle",
 | 
			
		||||
        "alt-a c": "core:align-selection-to-center",
 | 
			
		||||
        "alt-a h": "core:distribute-selection-horizontally",
 | 
			
		||||
        "alt-a v": "core:distribute-selection-vertically"
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@
 | 
			
		||||
RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    var node_defs = {};
 | 
			
		||||
    var nodes = {};
 | 
			
		||||
    var nodeTabMap = {};
 | 
			
		||||
    var linkTabMap = {};
 | 
			
		||||
 | 
			
		||||
    var configNodes = {};
 | 
			
		||||
@@ -41,6 +39,7 @@ RED.nodes = (function() {
 | 
			
		||||
        RED.events.emit("workspace:dirty",{dirty:dirty});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The registry holds information about all node types.
 | 
			
		||||
    var registry = (function() {
 | 
			
		||||
        var moduleList = {};
 | 
			
		||||
        var nodeList = [];
 | 
			
		||||
@@ -53,7 +52,8 @@ RED.nodes = (function() {
 | 
			
		||||
            defaults: {
 | 
			
		||||
                label: {value:""},
 | 
			
		||||
                disabled: {value: false},
 | 
			
		||||
                info: {value: ""}
 | 
			
		||||
                info: {value: ""},
 | 
			
		||||
                env: {value: []}
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +86,10 @@ RED.nodes = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            addNodeSet: function(ns) {
 | 
			
		||||
                if (!ns.types) {
 | 
			
		||||
                    // A node has been loaded without any types. Ignore it.
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                ns.added = false;
 | 
			
		||||
                nodeSets[ns.id] = ns;
 | 
			
		||||
                for (var j=0;j<ns.types.length;j++) {
 | 
			
		||||
@@ -138,9 +142,21 @@ RED.nodes = (function() {
 | 
			
		||||
                RED.events.emit("registry:node-set-disabled",ns);
 | 
			
		||||
            },
 | 
			
		||||
            registerNodeType: function(nt,def) {
 | 
			
		||||
                nodeDefinitions[nt] = def;
 | 
			
		||||
                def.type = nt;
 | 
			
		||||
                if (nt.substring(0,8) != "subflow:") {
 | 
			
		||||
                    if (!nodeSets[typeToId[nt]]) {
 | 
			
		||||
                        var error = "";
 | 
			
		||||
                        var fullType = nt;
 | 
			
		||||
                        if (RED._loadingModule) {
 | 
			
		||||
                            fullType = "["+RED._loadingModule+"] "+nt;
 | 
			
		||||
                            if (nodeSets[RED._loadingModule]) {
 | 
			
		||||
                                error = nodeSets[RED._loadingModule].err || "";
 | 
			
		||||
                            } else {
 | 
			
		||||
                                error = "Unknown error";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        RED.notify(RED._("palette.event.unknownNodeRegistered",{type:fullType, error:error}), "error");
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    def.set = nodeSets[typeToId[nt]];
 | 
			
		||||
                    nodeSets[typeToId[nt]].added = true;
 | 
			
		||||
                    nodeSets[typeToId[nt]].enabled = true;
 | 
			
		||||
@@ -163,9 +179,13 @@ RED.nodes = (function() {
 | 
			
		||||
                        }
 | 
			
		||||
                        return result;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // TODO: too tightly coupled into palette UI
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                def.type = nt;
 | 
			
		||||
                nodeDefinitions[nt] = def;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if (def.defaults) {
 | 
			
		||||
                    for (var d in def.defaults) {
 | 
			
		||||
                        if (def.defaults.hasOwnProperty(d)) {
 | 
			
		||||
@@ -205,9 +225,291 @@ RED.nodes = (function() {
 | 
			
		||||
        return exports;
 | 
			
		||||
    })();
 | 
			
		||||
 | 
			
		||||
    // allNodes holds information about the Flow nodes.
 | 
			
		||||
    var allNodes = (function() {
 | 
			
		||||
        var nodes = {};
 | 
			
		||||
        var tabMap = {};
 | 
			
		||||
        var api = {
 | 
			
		||||
            addTab: function(id) {
 | 
			
		||||
                tabMap[id] = [];
 | 
			
		||||
            },
 | 
			
		||||
            hasTab: function(z) {
 | 
			
		||||
                return tabMap.hasOwnProperty(z)
 | 
			
		||||
            },
 | 
			
		||||
            removeTab: function(id) {
 | 
			
		||||
                delete tabMap[id];
 | 
			
		||||
            },
 | 
			
		||||
            addNode: function(n) {
 | 
			
		||||
                nodes[n.id] = n;
 | 
			
		||||
                if (tabMap.hasOwnProperty(n.z)) {
 | 
			
		||||
                    tabMap[n.z].push(n);
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.warn("Node added to unknown tab/subflow:",n);
 | 
			
		||||
                    tabMap["_"] = tabMap["_"] || [];
 | 
			
		||||
                    tabMap["_"].push(n);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            removeNode: function(n) {
 | 
			
		||||
                delete nodes[n.id]
 | 
			
		||||
                if (tabMap.hasOwnProperty(n.z)) {
 | 
			
		||||
                    var i = tabMap[n.z].indexOf(n);
 | 
			
		||||
                    if (i > -1) {
 | 
			
		||||
                        tabMap[n.z].splice(i,1);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            hasNode: function(id) {
 | 
			
		||||
                return nodes.hasOwnProperty(id);
 | 
			
		||||
            },
 | 
			
		||||
            getNode: function(id) {
 | 
			
		||||
                return nodes[id]
 | 
			
		||||
            },
 | 
			
		||||
            moveNode: function(n, newZ) {
 | 
			
		||||
                api.removeNode(n);
 | 
			
		||||
                n.z = newZ;
 | 
			
		||||
                api.addNode(n)
 | 
			
		||||
            },
 | 
			
		||||
            moveNodesForwards: function(nodes) {
 | 
			
		||||
                var result = [];
 | 
			
		||||
                if (!Array.isArray(nodes)) {
 | 
			
		||||
                    nodes = [nodes]
 | 
			
		||||
                }
 | 
			
		||||
                // Can only do this for nodes on the same tab.
 | 
			
		||||
                // Use nodes[0] to get the z
 | 
			
		||||
                var tabNodes = tabMap[nodes[0].z];
 | 
			
		||||
                var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
 | 
			
		||||
                var moved = new Set();
 | 
			
		||||
                for (var i = tabNodes.length-1; i >= 0; i--) {
 | 
			
		||||
                    if (toMove.size === 0) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    var n = tabNodes[i];
 | 
			
		||||
                    if (toMove.has(n)) {
 | 
			
		||||
                        // This is a node to move.
 | 
			
		||||
                        if (i < tabNodes.length-1 && !moved.has(tabNodes[i+1])) {
 | 
			
		||||
                            // Remove from current position
 | 
			
		||||
                            tabNodes.splice(i,1);
 | 
			
		||||
                            // Add it back one position higher
 | 
			
		||||
                            tabNodes.splice(i+1,0,n);
 | 
			
		||||
                            n._reordered = true;
 | 
			
		||||
                            result.push(n);
 | 
			
		||||
                        }
 | 
			
		||||
                        toMove.delete(n);
 | 
			
		||||
                        moved.add(n);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (result.length > 0) {
 | 
			
		||||
                    RED.events.emit('nodes:reorder',{
 | 
			
		||||
                        z: nodes[0].z,
 | 
			
		||||
                        nodes: result
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            },
 | 
			
		||||
            moveNodesBackwards: function(nodes) {
 | 
			
		||||
                var result = [];
 | 
			
		||||
                if (!Array.isArray(nodes)) {
 | 
			
		||||
                    nodes = [nodes]
 | 
			
		||||
                }
 | 
			
		||||
                // Can only do this for nodes on the same tab.
 | 
			
		||||
                // Use nodes[0] to get the z
 | 
			
		||||
                var tabNodes = tabMap[nodes[0].z];
 | 
			
		||||
                var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
 | 
			
		||||
                var moved = new Set();
 | 
			
		||||
                for (var i = 0; i < tabNodes.length; i++) {
 | 
			
		||||
                    if (toMove.size === 0) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    var n = tabNodes[i];
 | 
			
		||||
                    if (toMove.has(n)) {
 | 
			
		||||
                        // This is a node to move.
 | 
			
		||||
                        if (i > 0 && !moved.has(tabNodes[i-1])) {
 | 
			
		||||
                            // Remove from current position
 | 
			
		||||
                            tabNodes.splice(i,1);
 | 
			
		||||
                            // Add it back one position lower
 | 
			
		||||
                            tabNodes.splice(i-1,0,n);
 | 
			
		||||
                            n._reordered = true;
 | 
			
		||||
                            result.push(n);
 | 
			
		||||
                        }
 | 
			
		||||
                        toMove.delete(n);
 | 
			
		||||
                        moved.add(n);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (result.length > 0) {
 | 
			
		||||
                    RED.events.emit('nodes:reorder',{
 | 
			
		||||
                        z: nodes[0].z,
 | 
			
		||||
                        nodes: result
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            },
 | 
			
		||||
            moveNodesToFront: function(nodes) {
 | 
			
		||||
                var result = [];
 | 
			
		||||
                if (!Array.isArray(nodes)) {
 | 
			
		||||
                    nodes = [nodes]
 | 
			
		||||
                }
 | 
			
		||||
                // Can only do this for nodes on the same tab.
 | 
			
		||||
                // Use nodes[0] to get the z
 | 
			
		||||
                var tabNodes = tabMap[nodes[0].z];
 | 
			
		||||
                var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
 | 
			
		||||
                var target = tabNodes.length-1;
 | 
			
		||||
                for (var i = tabNodes.length-1; i >= 0; i--) {
 | 
			
		||||
                    if (toMove.size === 0) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    var n = tabNodes[i];
 | 
			
		||||
                    if (toMove.has(n)) {
 | 
			
		||||
                        // This is a node to move.
 | 
			
		||||
                        if (i < target) {
 | 
			
		||||
                            // Remove from current position
 | 
			
		||||
                            tabNodes.splice(i,1);
 | 
			
		||||
                            tabNodes.splice(target,0,n);
 | 
			
		||||
                            n._reordered = true;
 | 
			
		||||
                            result.push(n);
 | 
			
		||||
                        }
 | 
			
		||||
                        target--;
 | 
			
		||||
                        toMove.delete(n);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (result.length > 0) {
 | 
			
		||||
                    RED.events.emit('nodes:reorder',{
 | 
			
		||||
                        z: nodes[0].z,
 | 
			
		||||
                        nodes: result
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            },
 | 
			
		||||
            moveNodesToBack: function(nodes) {
 | 
			
		||||
                var result = [];
 | 
			
		||||
                if (!Array.isArray(nodes)) {
 | 
			
		||||
                    nodes = [nodes]
 | 
			
		||||
                }
 | 
			
		||||
                // Can only do this for nodes on the same tab.
 | 
			
		||||
                // Use nodes[0] to get the z
 | 
			
		||||
                var tabNodes = tabMap[nodes[0].z];
 | 
			
		||||
                var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
 | 
			
		||||
                var target = 0;
 | 
			
		||||
                for (var i = 0; i < tabNodes.length; i++) {
 | 
			
		||||
                    if (toMove.size === 0) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    var n = tabNodes[i];
 | 
			
		||||
                    if (toMove.has(n)) {
 | 
			
		||||
                        // This is a node to move.
 | 
			
		||||
                        if (i > target) {
 | 
			
		||||
                            // Remove from current position
 | 
			
		||||
                            tabNodes.splice(i,1);
 | 
			
		||||
                            // Add it back one position lower
 | 
			
		||||
                            tabNodes.splice(target,0,n);
 | 
			
		||||
                            n._reordered = true;
 | 
			
		||||
                            result.push(n);
 | 
			
		||||
                        }
 | 
			
		||||
                        target++;
 | 
			
		||||
                        toMove.delete(n);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (result.length > 0) {
 | 
			
		||||
                    RED.events.emit('nodes:reorder',{
 | 
			
		||||
                        z: nodes[0].z,
 | 
			
		||||
                        nodes: result
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            },
 | 
			
		||||
            getNodes: function(z) {
 | 
			
		||||
                return tabMap[z];
 | 
			
		||||
            },
 | 
			
		||||
            clear: function() {
 | 
			
		||||
                nodes = {};
 | 
			
		||||
                tabMap = {};
 | 
			
		||||
            },
 | 
			
		||||
            eachNode: function(cb) {
 | 
			
		||||
                var nodeList,i,j;
 | 
			
		||||
                for (i in subflows) {
 | 
			
		||||
                    if (subflows.hasOwnProperty(i)) {
 | 
			
		||||
                        nodeList = tabMap[i];
 | 
			
		||||
                        for (j = 0; j < nodeList.length; j++) {
 | 
			
		||||
                            if (cb(nodeList[j]) === false) {
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                for (i = 0; i < workspacesOrder.length; i++) {
 | 
			
		||||
                    nodeList = tabMap[workspacesOrder[i]];
 | 
			
		||||
                    for (j = 0; j < nodeList.length; j++) {
 | 
			
		||||
                        if (cb(nodeList[j]) === false) {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // Flow nodes that do not have a valid tab/subflow
 | 
			
		||||
                if (tabMap["_"]) {
 | 
			
		||||
                    nodeList = tabMap["_"];
 | 
			
		||||
                    for (j = 0; j < nodeList.length; j++) {
 | 
			
		||||
                        if (cb(nodeList[j]) === false) {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            filterNodes: function(filter) {
 | 
			
		||||
                var result = [];
 | 
			
		||||
                var searchSet = null;
 | 
			
		||||
                var doZFilter = false;
 | 
			
		||||
                if (filter.hasOwnProperty("z")) {
 | 
			
		||||
                    if (tabMap.hasOwnProperty(filter.z)) {
 | 
			
		||||
                        searchSet = tabMap[filter.z];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        doZFilter = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                var objectLookup = false;
 | 
			
		||||
                if (searchSet === null) {
 | 
			
		||||
                    searchSet = Object.keys(nodes);
 | 
			
		||||
                    objectLookup = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                for (var n=0;n<searchSet.length;n++) {
 | 
			
		||||
                    var node = searchSet[n];
 | 
			
		||||
                    if (objectLookup) {
 | 
			
		||||
                        node = nodes[node];
 | 
			
		||||
                    }
 | 
			
		||||
                    if (filter.hasOwnProperty("type") && node.type !== filter.type) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (doZFilter && node.z !== filter.z) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    result.push(node);
 | 
			
		||||
                }
 | 
			
		||||
                return result;
 | 
			
		||||
            },
 | 
			
		||||
            getNodeOrder: function(z) {
 | 
			
		||||
                return tabMap[z].map(function(n) { return n.id })
 | 
			
		||||
            },
 | 
			
		||||
            setNodeOrder: function(z, order) {
 | 
			
		||||
                var orderMap = {};
 | 
			
		||||
                order.forEach(function(id,i) {
 | 
			
		||||
                    orderMap[id] = i;
 | 
			
		||||
                })
 | 
			
		||||
                tabMap[z].sort(function(A,B) {
 | 
			
		||||
                    A._reordered = true;
 | 
			
		||||
                    B._reordered = true;
 | 
			
		||||
                    return orderMap[A.id] - orderMap[B.id];
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return api;
 | 
			
		||||
    })()
 | 
			
		||||
 | 
			
		||||
    function getID() {
 | 
			
		||||
        // return Math.floor(Math.random()*15728640 + 1048576).toString(16)
 | 
			
		||||
        return (1+Math.random()*4294967295).toString(16);
 | 
			
		||||
        var bytes = [];
 | 
			
		||||
        for (var i=0;i<8;i++) {
 | 
			
		||||
            bytes.push(Math.round(0xff*Math.random()).toString(16).padStart(2,'0'));
 | 
			
		||||
        }
 | 
			
		||||
        return bytes.join("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function parseNodePropertyTypeString(typeString) {
 | 
			
		||||
@@ -287,15 +589,10 @@ RED.nodes = (function() {
 | 
			
		||||
                });
 | 
			
		||||
                n.i = nextId+1;
 | 
			
		||||
            }
 | 
			
		||||
            nodes[n.id] = n;
 | 
			
		||||
            allNodes.addNode(n);
 | 
			
		||||
            if (!nodeLinks[n.id]) {
 | 
			
		||||
                nodeLinks[n.id] = {in:[],out:[]};
 | 
			
		||||
            }
 | 
			
		||||
            if (nodeTabMap[n.z]) {
 | 
			
		||||
                nodeTabMap[n.z][n.id] = n;
 | 
			
		||||
            } else {
 | 
			
		||||
                console.warn("Node added to unknown tab/subflow:",n);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        RED.events.emit('nodes:add',n);
 | 
			
		||||
    }
 | 
			
		||||
@@ -323,10 +620,8 @@ RED.nodes = (function() {
 | 
			
		||||
    function getNode(id) {
 | 
			
		||||
        if (id in configNodes) {
 | 
			
		||||
            return configNodes[id];
 | 
			
		||||
        } else if (id in nodes) {
 | 
			
		||||
            return nodes[id];
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
        return allNodes.getNode(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeNode(id) {
 | 
			
		||||
@@ -338,13 +633,10 @@ RED.nodes = (function() {
 | 
			
		||||
            delete configNodes[id];
 | 
			
		||||
            RED.events.emit('nodes:remove',node);
 | 
			
		||||
            RED.workspaces.refresh();
 | 
			
		||||
        } else if (id in nodes) {
 | 
			
		||||
            node = nodes[id];
 | 
			
		||||
            delete nodes[id]
 | 
			
		||||
        } else if (allNodes.hasNode(id)) {
 | 
			
		||||
            node = allNodes.getNode(id);
 | 
			
		||||
            allNodes.removeNode(node);
 | 
			
		||||
            delete nodeLinks[id];
 | 
			
		||||
            if (nodeTabMap[node.z]) {
 | 
			
		||||
                delete nodeTabMap[node.z][node.id];
 | 
			
		||||
            }
 | 
			
		||||
            removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
 | 
			
		||||
            removedLinks.forEach(removeLink);
 | 
			
		||||
            var updatedConfigNode = false;
 | 
			
		||||
@@ -363,6 +655,7 @@ RED.nodes = (function() {
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    var users = configNode.users;
 | 
			
		||||
                                    users.splice(users.indexOf(node),1);
 | 
			
		||||
                                    RED.events.emit('nodes:change',configNode)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@@ -401,40 +694,54 @@ RED.nodes = (function() {
 | 
			
		||||
        return {links:removedLinks,nodes:removedNodes};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function moveNodesForwards(nodes) {
 | 
			
		||||
        return allNodes.moveNodesForwards(nodes);
 | 
			
		||||
    }
 | 
			
		||||
    function moveNodesBackwards(nodes) {
 | 
			
		||||
        return allNodes.moveNodesBackwards(nodes);
 | 
			
		||||
    }
 | 
			
		||||
    function moveNodesToFront(nodes) {
 | 
			
		||||
        return allNodes.moveNodesToFront(nodes);
 | 
			
		||||
    }
 | 
			
		||||
    function moveNodesToBack(nodes) {
 | 
			
		||||
        return allNodes.moveNodesToBack(nodes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeOrder(z) {
 | 
			
		||||
        return allNodes.getNodeOrder(z);
 | 
			
		||||
    }
 | 
			
		||||
    function setNodeOrder(z, order) {
 | 
			
		||||
        allNodes.setNodeOrder(z,order);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function moveNodeToTab(node, z) {
 | 
			
		||||
        if (node.type === "group") {
 | 
			
		||||
            moveGroupToTab(node,z);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (nodeTabMap[node.z]) {
 | 
			
		||||
            delete nodeTabMap[node.z][node.id];
 | 
			
		||||
        }
 | 
			
		||||
        if (!nodeTabMap[z]) {
 | 
			
		||||
            nodeTabMap[z] = {};
 | 
			
		||||
        }
 | 
			
		||||
        nodeTabMap[z][node.id] = node;
 | 
			
		||||
        var oldZ = node.z;
 | 
			
		||||
        allNodes.moveNode(node,z);
 | 
			
		||||
        var nl = nodeLinks[node.id];
 | 
			
		||||
        if (nl) {
 | 
			
		||||
            nl.in.forEach(function(l) {
 | 
			
		||||
                var idx = linkTabMap[node.z].indexOf(l);
 | 
			
		||||
                var idx = linkTabMap[oldZ].indexOf(l);
 | 
			
		||||
                if (idx != -1) {
 | 
			
		||||
                    linkTabMap[node.z].splice(idx, 1);
 | 
			
		||||
                    linkTabMap[oldZ].splice(idx, 1);
 | 
			
		||||
                }
 | 
			
		||||
                if ((l.source.z === z) && linkTabMap[z]) {
 | 
			
		||||
                    linkTabMap[z].push(l);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            nl.out.forEach(function(l) {
 | 
			
		||||
                var idx = linkTabMap[node.z].indexOf(l);
 | 
			
		||||
                var idx = linkTabMap[oldZ].indexOf(l);
 | 
			
		||||
                if (idx != -1) {
 | 
			
		||||
                    linkTabMap[node.z].splice(idx, 1);
 | 
			
		||||
                    linkTabMap[oldZ].splice(idx, 1);
 | 
			
		||||
                }
 | 
			
		||||
                if ((l.target.z === z) && linkTabMap[z]) {
 | 
			
		||||
                    linkTabMap[z].push(l);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        node.z = z;
 | 
			
		||||
        RED.events.emit("nodes:change",node);
 | 
			
		||||
    }
 | 
			
		||||
    function moveGroupToTab(group, z) {
 | 
			
		||||
@@ -474,7 +781,7 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    function addWorkspace(ws,targetIndex) {
 | 
			
		||||
        workspaces[ws.id] = ws;
 | 
			
		||||
        nodeTabMap[ws.id] = {};
 | 
			
		||||
        allNodes.addTab(ws.id);
 | 
			
		||||
        linkTabMap[ws.id] = [];
 | 
			
		||||
 | 
			
		||||
        ws._def = RED.nodes.getType('tab');
 | 
			
		||||
@@ -498,21 +805,15 @@ RED.nodes = (function() {
 | 
			
		||||
        var removedGroups = [];
 | 
			
		||||
        if (ws) {
 | 
			
		||||
            delete workspaces[id];
 | 
			
		||||
            delete nodeTabMap[id];
 | 
			
		||||
            delete linkTabMap[id];
 | 
			
		||||
            workspacesOrder.splice(workspacesOrder.indexOf(id),1);
 | 
			
		||||
            var i;
 | 
			
		||||
            var node;
 | 
			
		||||
            // TODO: this should use nodeTabMap
 | 
			
		||||
            for (i in nodes) {
 | 
			
		||||
                if (nodes.hasOwnProperty(i)) {
 | 
			
		||||
                    node = nodes[i];
 | 
			
		||||
                    if (node.z == id) {
 | 
			
		||||
                        removedNodes.push(node);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            if (allNodes.hasTab(id)) {
 | 
			
		||||
                removedNodes = allNodes.getNodes(id).slice()
 | 
			
		||||
            }
 | 
			
		||||
            for(i in configNodes) {
 | 
			
		||||
            for (i in configNodes) {
 | 
			
		||||
                if (configNodes.hasOwnProperty(i)) {
 | 
			
		||||
                    node = configNodes[i];
 | 
			
		||||
                    if (node.z == id) {
 | 
			
		||||
@@ -541,6 +842,7 @@ RED.nodes = (function() {
 | 
			
		||||
            for (i=removedGroups.length-1; i>=0; i--) {
 | 
			
		||||
                removeGroup(removedGroups[i]);
 | 
			
		||||
            }
 | 
			
		||||
            allNodes.removeTab(id);
 | 
			
		||||
            RED.events.emit('flows:remove',ws);
 | 
			
		||||
        }
 | 
			
		||||
        return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
 | 
			
		||||
@@ -564,7 +866,7 @@ RED.nodes = (function() {
 | 
			
		||||
            sf.name = subflowName;
 | 
			
		||||
        }
 | 
			
		||||
        subflows[sf.id] = sf;
 | 
			
		||||
        nodeTabMap[sf.id] = {};
 | 
			
		||||
        allNodes.addTab(sf.id);
 | 
			
		||||
        linkTabMap[sf.id] = [];
 | 
			
		||||
 | 
			
		||||
        RED.nodes.registerType("subflow:"+sf.id, {
 | 
			
		||||
@@ -583,18 +885,18 @@ RED.nodes = (function() {
 | 
			
		||||
            inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
 | 
			
		||||
            outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
 | 
			
		||||
            oneditprepare: function() {
 | 
			
		||||
                RED.subflow.buildEditForm("subflow",this);
 | 
			
		||||
                RED.subflow.buildPropertiesForm(this);
 | 
			
		||||
                if (this.type !== 'subflow') {
 | 
			
		||||
                    // A subflow instance node
 | 
			
		||||
                    RED.subflow.buildEditForm("subflow",this);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // A subflow template node
 | 
			
		||||
                    RED.subflow.buildEditForm("subflow-template", this);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            oneditresize: function(size) {
 | 
			
		||||
                // var rows = $(".dialog-form>div:not(.node-input-env-container-row)");
 | 
			
		||||
                var height = size.height;
 | 
			
		||||
                // for (var i=0; i<rows.size(); i++) {
 | 
			
		||||
                //     height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
                // }
 | 
			
		||||
                // var editorRow = $("#dialog-form>div.node-input-env-container-row");
 | 
			
		||||
                // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
 | 
			
		||||
                $("ol.red-ui-editor-subflow-env-list").editableList('height',height);
 | 
			
		||||
                if (this.type === 'subflow') {
 | 
			
		||||
                    $("#node-input-env-container").editableList('height',size.height - 80);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            set:{
 | 
			
		||||
                module: "node-red"
 | 
			
		||||
@@ -610,27 +912,24 @@ RED.nodes = (function() {
 | 
			
		||||
    function removeSubflow(sf) {
 | 
			
		||||
        if (subflows[sf.id]) {
 | 
			
		||||
            delete subflows[sf.id];
 | 
			
		||||
            delete nodeTabMap[sf.id];
 | 
			
		||||
            allNodes.removeTab(sf.id);
 | 
			
		||||
            registry.removeNodeType("subflow:"+sf.id);
 | 
			
		||||
            RED.events.emit("subflows:remove",sf);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function subflowContains(sfid,nodeid) {
 | 
			
		||||
        for (var i in nodes) {
 | 
			
		||||
            if (nodes.hasOwnProperty(i)) {
 | 
			
		||||
                var node = nodes[i];
 | 
			
		||||
                if (node.z === sfid) {
 | 
			
		||||
                    var m = /^subflow:(.+)$/.exec(node.type);
 | 
			
		||||
                    if (m) {
 | 
			
		||||
                        if (m[1] === nodeid) {
 | 
			
		||||
                            return true;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            var result = subflowContains(m[1],nodeid);
 | 
			
		||||
                            if (result) {
 | 
			
		||||
                                return true;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
        var sfNodes = allNodes.getNodes(sfid);
 | 
			
		||||
        for (var i = 0; i<sfNodes.length; i++) {
 | 
			
		||||
            var node = sfNodes[i];
 | 
			
		||||
            var m = /^subflow:(.+)$/.exec(node.type);
 | 
			
		||||
            if (m) {
 | 
			
		||||
                if (m[1] === nodeid) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                } else {
 | 
			
		||||
                    var result = subflowContains(m[1],nodeid);
 | 
			
		||||
                    if (result) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -673,7 +972,13 @@ RED.nodes = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function convertWorkspace(n) {
 | 
			
		||||
    function convertWorkspace(n,opts) {
 | 
			
		||||
        var exportCreds = true;
 | 
			
		||||
        if (opts) {
 | 
			
		||||
            if (opts.hasOwnProperty("credentials")) {
 | 
			
		||||
                exportCreds = opts.credentials;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var node = {};
 | 
			
		||||
        node.id = n.id;
 | 
			
		||||
        node.type = n.type;
 | 
			
		||||
@@ -682,16 +987,45 @@ RED.nodes = (function() {
 | 
			
		||||
                node[d] = n[d];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (exportCreds) {
 | 
			
		||||
            var credentialSet = {};
 | 
			
		||||
            if (n.credentials) {
 | 
			
		||||
                for (var tabCred in n.credentials) {
 | 
			
		||||
                    if (n.credentials.hasOwnProperty(tabCred)) {
 | 
			
		||||
                        if (!n.credentials._ ||
 | 
			
		||||
                            n.credentials["has_"+tabCred] != n.credentials._["has_"+tabCred] ||
 | 
			
		||||
                            (n.credentials["has_"+tabCred] && n.credentials[tabCred])) {
 | 
			
		||||
                            credentialSet[tabCred] = n.credentials[tabCred];
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (Object.keys(credentialSet).length > 0) {
 | 
			
		||||
                    node.credentials = credentialSet;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a node to an exportable JSON Object
 | 
			
		||||
     **/
 | 
			
		||||
    function convertNode(n, exportCreds) {
 | 
			
		||||
        if (n.type === 'tab') {
 | 
			
		||||
            return convertWorkspace(n);
 | 
			
		||||
    function convertNode(n, opts) {
 | 
			
		||||
        var exportCreds = true;
 | 
			
		||||
        var exportDimensions = false;
 | 
			
		||||
        if (opts === false) {
 | 
			
		||||
            exportCreds = false;
 | 
			
		||||
        } else if (typeof opts === "object") {
 | 
			
		||||
            if (opts.hasOwnProperty("credentials")) {
 | 
			
		||||
                exportCreds = opts.credentials;
 | 
			
		||||
            }
 | 
			
		||||
            if (opts.hasOwnProperty("dimensions")) {
 | 
			
		||||
                exportDimensions = opts.dimensions;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (n.type === 'tab') {
 | 
			
		||||
            return convertWorkspace(n, { credentials: exportCreds });
 | 
			
		||||
        }
 | 
			
		||||
        exportCreds = exportCreds || false;
 | 
			
		||||
        var node = {};
 | 
			
		||||
        node.id = n.id;
 | 
			
		||||
        node.type = n.type;
 | 
			
		||||
@@ -719,8 +1053,10 @@ RED.nodes = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            if (exportCreds) {
 | 
			
		||||
                var credentialSet = {};
 | 
			
		||||
                if (/^subflow:/.test(node.type) && n.credentials) {
 | 
			
		||||
                    // A subflow instance node can have arbitrary creds
 | 
			
		||||
                if ((/^subflow:/.test(node.type) ||
 | 
			
		||||
                     (node.type === "group")) &&
 | 
			
		||||
                    n.credentials) {
 | 
			
		||||
                    // A subflow instance/group node can have arbitrary creds
 | 
			
		||||
                    for (var sfCred in n.credentials) {
 | 
			
		||||
                        if (n.credentials.hasOwnProperty(sfCred)) {
 | 
			
		||||
                            if (!n.credentials._ ||
 | 
			
		||||
@@ -761,9 +1097,27 @@ RED.nodes = (function() {
 | 
			
		||||
            // Until we know how that can happen, add a filter here to remove them
 | 
			
		||||
            node.nodes = node.nodes.filter(function(n) { return !!n }).map(function(n) { return n.id });
 | 
			
		||||
        }
 | 
			
		||||
        if (n.type === "tab" || n.type === "group") {
 | 
			
		||||
            if (node.env && node.env.length === 0) {
 | 
			
		||||
                delete node.env;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (n._def.category != "config") {
 | 
			
		||||
            node.x = n.x;
 | 
			
		||||
            node.y = n.y;
 | 
			
		||||
            if (exportDimensions) {
 | 
			
		||||
                if (!n.hasOwnProperty('w')) {
 | 
			
		||||
                    // This node has not yet been drawn in the view. So we need
 | 
			
		||||
                    // to explicitly calculate its dimensions. Store the result
 | 
			
		||||
                    // on the node as if it had been drawn will save us doing
 | 
			
		||||
                    // it again
 | 
			
		||||
                    var dimensions = RED.view.calculateNodeDimensions(n);
 | 
			
		||||
                    n.w = dimensions[0];
 | 
			
		||||
                    n.h = dimensions[1];
 | 
			
		||||
                }
 | 
			
		||||
                node.w = n.w;
 | 
			
		||||
                node.h = n.h;
 | 
			
		||||
            }
 | 
			
		||||
            node.wires = [];
 | 
			
		||||
            for(var i=0;i<n.outputs;i++) {
 | 
			
		||||
                node.wires.push([]);
 | 
			
		||||
@@ -791,8 +1145,8 @@ RED.nodes = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("l")) && n.hasOwnProperty('l')) {
 | 
			
		||||
                var isLink = /^link (in|out)$/.test(node.type);
 | 
			
		||||
                if (isLink == n.l) {
 | 
			
		||||
                var showLabel = n._def.hasOwnProperty("showLabel")?n._def.showLabel:true;
 | 
			
		||||
                if (showLabel != n.l) {
 | 
			
		||||
                    node.l = n.l;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -803,7 +1157,21 @@ RED.nodes = (function() {
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function convertSubflow(n, exportCreds) {
 | 
			
		||||
    function convertSubflow(n, opts) {
 | 
			
		||||
        var exportCreds = true;
 | 
			
		||||
        var exportDimensions = false;
 | 
			
		||||
        if (opts === false) {
 | 
			
		||||
            exportCreds = false;
 | 
			
		||||
        } else if (typeof opts === "object") {
 | 
			
		||||
            if (opts.hasOwnProperty("credentials")) {
 | 
			
		||||
                exportCreds = opts.credentials;
 | 
			
		||||
            }
 | 
			
		||||
            if (opts.hasOwnProperty("dimensions")) {
 | 
			
		||||
                exportDimensions = opts.dimensions;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var node = {};
 | 
			
		||||
        node.id = n.id;
 | 
			
		||||
        node.type = n.type;
 | 
			
		||||
@@ -887,11 +1255,15 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    function createExportableSubflow(id) {
 | 
			
		||||
        var sf = getSubflow(id);
 | 
			
		||||
        var nodeSet = [sf];
 | 
			
		||||
        var sfNodeIds = Object.keys(nodeTabMap[sf.id]||{});
 | 
			
		||||
        for (var i=0, l=sfNodeIds.length; i<l; i++) {
 | 
			
		||||
            nodeSet.push(nodeTabMap[sf.id][sfNodeIds[i]]);
 | 
			
		||||
        var nodeSet;
 | 
			
		||||
        var sfNodes = allNodes.getNodes(sf.id);
 | 
			
		||||
        if (sfNodes) {
 | 
			
		||||
            nodeSet = sfNodes.slice();
 | 
			
		||||
            nodeSet.unshift(sf);
 | 
			
		||||
        } else {
 | 
			
		||||
            nodeSet = [sf];
 | 
			
		||||
        }
 | 
			
		||||
        console.log(nodeSet);
 | 
			
		||||
        return createExportableNodeSet(nodeSet);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
@@ -918,12 +1290,9 @@ RED.nodes = (function() {
 | 
			
		||||
                if (!exportedSubflows[subflowId]) {
 | 
			
		||||
                    exportedSubflows[subflowId] = true;
 | 
			
		||||
                    var subflow = getSubflow(subflowId);
 | 
			
		||||
                    var subflowSet = [subflow];
 | 
			
		||||
                    RED.nodes.eachNode(function(n) {
 | 
			
		||||
                        if (n.z == subflowId) {
 | 
			
		||||
                            subflowSet.push(n);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    var subflowSet = allNodes.getNodes(subflowId).slice();
 | 
			
		||||
                    subflowSet.unshift(subflow);
 | 
			
		||||
 | 
			
		||||
                    RED.nodes.eachConfig(function(n) {
 | 
			
		||||
                        if (n.z == subflowId) {
 | 
			
		||||
                            subflowSet.push(n);
 | 
			
		||||
@@ -975,38 +1344,35 @@ RED.nodes = (function() {
 | 
			
		||||
        return nns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //TODO: rename this (createCompleteNodeSet)
 | 
			
		||||
    function createCompleteNodeSet(exportCredentials) {
 | 
			
		||||
        if (exportCredentials === undefined) {
 | 
			
		||||
            exportCredentials = true;
 | 
			
		||||
        }
 | 
			
		||||
    // Create the Flow JSON for the current configuration
 | 
			
		||||
    // opts.credentials (whether to include (known) credentials) - default: true
 | 
			
		||||
    // opts.dimensions (whether to include node dimensions) - default: false
 | 
			
		||||
    function createCompleteNodeSet(opts) {
 | 
			
		||||
        var nns = [];
 | 
			
		||||
        var i;
 | 
			
		||||
        for (i=0;i<workspacesOrder.length;i++) {
 | 
			
		||||
            if (workspaces[workspacesOrder[i]].type == "tab") {
 | 
			
		||||
                nns.push(convertWorkspace(workspaces[workspacesOrder[i]]));
 | 
			
		||||
                nns.push(convertWorkspace(workspaces[workspacesOrder[i]], opts));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (i in subflows) {
 | 
			
		||||
            if (subflows.hasOwnProperty(i)) {
 | 
			
		||||
                nns.push(convertSubflow(subflows[i], exportCredentials));
 | 
			
		||||
                nns.push(convertSubflow(subflows[i], opts));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (i in groups) {
 | 
			
		||||
            if (groups.hasOwnProperty(i)) {
 | 
			
		||||
                nns.push(convertNode(groups[i]));
 | 
			
		||||
                nns.push(convertNode(groups[i], opts));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (i in configNodes) {
 | 
			
		||||
            if (configNodes.hasOwnProperty(i)) {
 | 
			
		||||
                nns.push(convertNode(configNodes[i], exportCredentials));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (i in nodes) {
 | 
			
		||||
            if (nodes.hasOwnProperty(i)) {
 | 
			
		||||
                nns.push(convertNode(nodes[i], exportCredentials));
 | 
			
		||||
                nns.push(convertNode(configNodes[i], opts));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        RED.nodes.eachNode(function(n) {
 | 
			
		||||
            nns.push(convertNode(n, opts));
 | 
			
		||||
        })
 | 
			
		||||
        return nns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1103,7 +1469,7 @@ RED.nodes = (function() {
 | 
			
		||||
            var nodeZ = n.z || "__global__";
 | 
			
		||||
            imported.zMap[nodeZ] = imported.zMap[nodeZ] || [];
 | 
			
		||||
            imported.zMap[nodeZ].push(n)
 | 
			
		||||
            if (nodes[n.id] || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id]) {
 | 
			
		||||
            if (allNodes.hasNode(n.id) || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id]) {
 | 
			
		||||
                imported.conflicted[n.id] = n;
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
@@ -1111,7 +1477,6 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Replace the provided nodes.
 | 
			
		||||
     * This must contain complete Subflow defs or complete Flow Tabs.
 | 
			
		||||
@@ -1270,7 +1635,7 @@ RED.nodes = (function() {
 | 
			
		||||
            if (!options.generateIds) {
 | 
			
		||||
                if (!options.importMap[id]) {
 | 
			
		||||
                    // No conflict resolution for this node
 | 
			
		||||
                    var existing = nodes[id] || configNodes[id] || workspaces[id] || subflows[id] || groups[id];
 | 
			
		||||
                    var existing = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id];
 | 
			
		||||
                    if (existing) {
 | 
			
		||||
                        existingNodes.push({existing:existing, imported:n});
 | 
			
		||||
                    }
 | 
			
		||||
@@ -1341,7 +1706,8 @@ RED.nodes = (function() {
 | 
			
		||||
                        type: "tab",
 | 
			
		||||
                        disabled: false,
 | 
			
		||||
                        label: RED._("clipboard.recoveredNodes"),
 | 
			
		||||
                        info: RED._("clipboard.recoveredNodesInfo")
 | 
			
		||||
                        info: RED._("clipboard.recoveredNodesInfo"),
 | 
			
		||||
                        env: []
 | 
			
		||||
                    }
 | 
			
		||||
                    addWorkspace(recoveryWorkspace);
 | 
			
		||||
                    RED.workspaces.add(recoveryWorkspace);
 | 
			
		||||
@@ -1424,6 +1790,8 @@ RED.nodes = (function() {
 | 
			
		||||
                    nid = getID();
 | 
			
		||||
                    workspace_map[n.id] = nid;
 | 
			
		||||
                    n.id = nid;
 | 
			
		||||
                } else {
 | 
			
		||||
                    workspace_map[n.id] = n.id;
 | 
			
		||||
                }
 | 
			
		||||
                addWorkspace(n);
 | 
			
		||||
                RED.workspaces.add(n);
 | 
			
		||||
@@ -1470,7 +1838,7 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
        // Add a tab if there isn't one there already
 | 
			
		||||
        if (defaultWorkspace == null) {
 | 
			
		||||
            defaultWorkspace = { type:"tab", id:getID(), disabled: false, info:"",  label:RED._('workspace.defaultName',{number:1})};
 | 
			
		||||
            defaultWorkspace = { type:"tab", id:getID(), disabled: false, info:"",  label:RED._('workspace.defaultName',{number:1}), env:[]};
 | 
			
		||||
            addWorkspace(defaultWorkspace);
 | 
			
		||||
            RED.workspaces.add(defaultWorkspace);
 | 
			
		||||
            new_workspaces.push(defaultWorkspace);
 | 
			
		||||
@@ -1523,7 +1891,7 @@ RED.nodes = (function() {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (n.z && !workspaces[n.z] && !subflow_map[n.z]) {
 | 
			
		||||
                    if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
 | 
			
		||||
                        n.z = activeWorkspace;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -1621,7 +1989,7 @@ RED.nodes = (function() {
 | 
			
		||||
                        node.id = getID();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.id = n.id;
 | 
			
		||||
                        if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
 | 
			
		||||
                        if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) {
 | 
			
		||||
                            if (createMissingWorkspace) {
 | 
			
		||||
                                if (missingWorkspace === null) {
 | 
			
		||||
                                    missingWorkspace = RED.workspaces.add(null,true);
 | 
			
		||||
@@ -1931,32 +2299,9 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    // TODO: supports filter.z|type
 | 
			
		||||
    function filterNodes(filter) {
 | 
			
		||||
        var result = [];
 | 
			
		||||
        var searchSet = null;
 | 
			
		||||
        var doZFilter = false;
 | 
			
		||||
        if (filter.hasOwnProperty("z")) {
 | 
			
		||||
            if (nodeTabMap.hasOwnProperty(filter.z)) {
 | 
			
		||||
                searchSet = Object.keys(nodeTabMap[filter.z]);
 | 
			
		||||
            } else {
 | 
			
		||||
                doZFilter = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (searchSet === null) {
 | 
			
		||||
            searchSet = Object.keys(nodes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (var n=0;n<searchSet.length;n++) {
 | 
			
		||||
            var node = nodes[searchSet[n]];
 | 
			
		||||
            if (filter.hasOwnProperty("type") && node.type !== filter.type) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (doZFilter && node.z !== filter.z) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            result.push(node);
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
        return allNodes.filterNodes(filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function filterLinks(filter) {
 | 
			
		||||
        var result = [];
 | 
			
		||||
        var candidateLinks = [];
 | 
			
		||||
@@ -2026,6 +2371,7 @@ RED.nodes = (function() {
 | 
			
		||||
                        if (configNode) {
 | 
			
		||||
                            if (configNode.users.indexOf(n) === -1) {
 | 
			
		||||
                                configNode.users.push(n);
 | 
			
		||||
                                RED.events.emit('nodes:change',configNode)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@@ -2043,9 +2389,7 @@ RED.nodes = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clear() {
 | 
			
		||||
        nodes = {};
 | 
			
		||||
        links = [];
 | 
			
		||||
        nodeTabMap = {};
 | 
			
		||||
        linkTabMap = {};
 | 
			
		||||
        nodeLinks = {};
 | 
			
		||||
        configNodes = {};
 | 
			
		||||
@@ -2065,6 +2409,8 @@ RED.nodes = (function() {
 | 
			
		||||
        initialLoad = null;
 | 
			
		||||
        workspaces = {};
 | 
			
		||||
 | 
			
		||||
        allNodes.clear();
 | 
			
		||||
 | 
			
		||||
        RED.nodes.dirty(false);
 | 
			
		||||
        RED.view.redraw(true, true);
 | 
			
		||||
        RED.palette.refresh();
 | 
			
		||||
@@ -2137,10 +2483,7 @@ RED.nodes = (function() {
 | 
			
		||||
                        if (configNodes.hasOwnProperty(n.id)) {
 | 
			
		||||
                            delete configNodes[n.id];
 | 
			
		||||
                        } else {
 | 
			
		||||
                            delete nodes[n.id];
 | 
			
		||||
                            if (nodeTabMap[n.z]) {
 | 
			
		||||
                                delete nodeTabMap[n.z][n.id];
 | 
			
		||||
                            }
 | 
			
		||||
                            allNodes.removeNode(n);
 | 
			
		||||
                        }
 | 
			
		||||
                        reimportList.push(convertNode(n));
 | 
			
		||||
                        RED.events.emit('nodes:remove',n);
 | 
			
		||||
@@ -2197,6 +2540,13 @@ RED.nodes = (function() {
 | 
			
		||||
        remove: removeNode,
 | 
			
		||||
        clear: clear,
 | 
			
		||||
 | 
			
		||||
        moveNodesForwards: moveNodesForwards,
 | 
			
		||||
        moveNodesBackwards: moveNodesBackwards,
 | 
			
		||||
        moveNodesToFront: moveNodesToFront,
 | 
			
		||||
        moveNodesToBack: moveNodesToBack,
 | 
			
		||||
        getNodeOrder: getNodeOrder,
 | 
			
		||||
        setNodeOrder: setNodeOrder,
 | 
			
		||||
 | 
			
		||||
        moveNodeToTab: moveNodeToTab,
 | 
			
		||||
 | 
			
		||||
        addLink: addLink,
 | 
			
		||||
@@ -2216,16 +2566,10 @@ RED.nodes = (function() {
 | 
			
		||||
        addGroup: addGroup,
 | 
			
		||||
        removeGroup: removeGroup,
 | 
			
		||||
        group: function(id) { return groups[id] },
 | 
			
		||||
        groups: function(z) { return groupsByZ[z]||[] },
 | 
			
		||||
        groups: function(z) { return groupsByZ[z]?groupsByZ[z].slice():[] },
 | 
			
		||||
 | 
			
		||||
        eachNode: function(cb) {
 | 
			
		||||
            for (var id in nodes) {
 | 
			
		||||
                if (nodes.hasOwnProperty(id)) {
 | 
			
		||||
                    if (cb(nodes[id]) === false) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            allNodes.eachNode(cb);
 | 
			
		||||
        },
 | 
			
		||||
        eachLink: function(cb) {
 | 
			
		||||
            for (var l=0;l<links.length;l++) {
 | 
			
		||||
 
 | 
			
		||||
@@ -201,6 +201,7 @@ var RED = (function() {
 | 
			
		||||
                            RED.projects.refresh(function(activeProject) {
 | 
			
		||||
                                loadFlows(function() {
 | 
			
		||||
                                    RED.sidebar.info.refresh()
 | 
			
		||||
                                    var showProjectWelcome = false;
 | 
			
		||||
                                    if (!activeProject) {
 | 
			
		||||
                                        // Projects enabled but no active project
 | 
			
		||||
                                        RED.menu.setDisabled('menu-item-projects-open',true);
 | 
			
		||||
@@ -208,10 +209,10 @@ var RED = (function() {
 | 
			
		||||
                                        if (activeProject === false) {
 | 
			
		||||
                                            // User previously decline the migration to projects.
 | 
			
		||||
                                        } else { // null/undefined
 | 
			
		||||
                                            RED.projects.showStartup();
 | 
			
		||||
                                            showProjectWelcome = true;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    completeLoad();
 | 
			
		||||
                                    completeLoad(showProjectWelcome);
 | 
			
		||||
                                });
 | 
			
		||||
                            });
 | 
			
		||||
                        } else {
 | 
			
		||||
@@ -251,6 +252,9 @@ var RED = (function() {
 | 
			
		||||
                        if (/^#flow\/.+$/.test(currentHash)) {
 | 
			
		||||
                            RED.workspaces.show(currentHash.substring(6),true);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (RED.workspaces.active() === 0 && RED.workspaces.count() > 0) {
 | 
			
		||||
                            RED.workspaces.show(RED.nodes.getWorkspaceOrder()[0])
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.warn(err);
 | 
			
		||||
                        RED.notify(
 | 
			
		||||
@@ -267,7 +271,7 @@ var RED = (function() {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function completeLoad() {
 | 
			
		||||
    function completeLoad(showProjectWelcome) {
 | 
			
		||||
        var persistentNotifications = {};
 | 
			
		||||
        RED.comms.subscribe("notification/#",function(topic,msg) {
 | 
			
		||||
            var parts = topic.split("/");
 | 
			
		||||
@@ -471,22 +475,33 @@ var RED = (function() {
 | 
			
		||||
            var typeList;
 | 
			
		||||
            var info;
 | 
			
		||||
            if (topic == "notification/node/added") {
 | 
			
		||||
                var addedTypes = [];
 | 
			
		||||
                msg.forEach(function(m) {
 | 
			
		||||
                    var id = m.id;
 | 
			
		||||
                    RED.nodes.addNodeSet(m);
 | 
			
		||||
                    addedTypes = addedTypes.concat(m.types);
 | 
			
		||||
                    RED.i18n.loadNodeCatalog(id, function() {
 | 
			
		||||
                        $.get('nodes/'+id, function(data) {
 | 
			
		||||
                            appendNodeConfig(data);
 | 
			
		||||
                RED.settings.refreshSettings(function(err, data) {
 | 
			
		||||
                    var addedTypes = [];
 | 
			
		||||
                    msg.forEach(function(m) {
 | 
			
		||||
                        var id = m.id;
 | 
			
		||||
                        RED.nodes.addNodeSet(m);
 | 
			
		||||
                        addedTypes = addedTypes.concat(m.types);
 | 
			
		||||
                        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: 'nodes/'+id,
 | 
			
		||||
                                success: function(data) {
 | 
			
		||||
                                    appendNodeConfig(data);
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                if (addedTypes.length) {
 | 
			
		||||
                    typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
 | 
			
		||||
                    RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
 | 
			
		||||
                }
 | 
			
		||||
                loadIconList();
 | 
			
		||||
                    if (addedTypes.length) {
 | 
			
		||||
                        typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
 | 
			
		||||
                        RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
 | 
			
		||||
                    }
 | 
			
		||||
                    loadIconList();
 | 
			
		||||
                })
 | 
			
		||||
            } else if (topic == "notification/node/removed") {
 | 
			
		||||
                for (i=0;i<msg.length;i++) {
 | 
			
		||||
                    m = msg[i];
 | 
			
		||||
@@ -499,18 +514,29 @@ var RED = (function() {
 | 
			
		||||
                loadIconList();
 | 
			
		||||
            } else if (topic == "notification/node/enabled") {
 | 
			
		||||
                if (msg.types) {
 | 
			
		||||
                    info = RED.nodes.getNodeSet(msg.id);
 | 
			
		||||
                    if (info.added) {
 | 
			
		||||
                        RED.nodes.enableNodeSet(msg.id);
 | 
			
		||||
                        typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
 | 
			
		||||
                        RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $.get('nodes/'+msg.id, function(data) {
 | 
			
		||||
                            appendNodeConfig(data);
 | 
			
		||||
                    RED.settings.refreshSettings(function(err, data) {
 | 
			
		||||
                        info = RED.nodes.getNodeSet(msg.id);
 | 
			
		||||
                        if (info.added) {
 | 
			
		||||
                            RED.nodes.enableNodeSet(msg.id);
 | 
			
		||||
                            typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
 | 
			
		||||
                            RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                            RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
 | 
			
		||||
                        } else {
 | 
			
		||||
                            var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
 | 
			
		||||
                            $.ajax({
 | 
			
		||||
                                headers: {
 | 
			
		||||
                                    "Accept":"text/html",
 | 
			
		||||
                                    "Accept-Language": lang
 | 
			
		||||
                                },
 | 
			
		||||
                                cache: false,
 | 
			
		||||
                                url: 'nodes/'+msg.id,
 | 
			
		||||
                                success: function(data) {
 | 
			
		||||
                                    appendNodeConfig(data);
 | 
			
		||||
                                    typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
 | 
			
		||||
                                    RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            } else if (topic == "notification/node/disabled") {
 | 
			
		||||
                if (msg.types) {
 | 
			
		||||
@@ -535,17 +561,24 @@ var RED = (function() {
 | 
			
		||||
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
            loader.end();
 | 
			
		||||
            checkFirstRun(function() {
 | 
			
		||||
                if (showProjectWelcome) {
 | 
			
		||||
                    RED.projects.showStartup();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showAbout() {
 | 
			
		||||
        $.get('red/about', function(data) {
 | 
			
		||||
            var aboutHeader = '<div style="text-align:center;">'+
 | 
			
		||||
            '<img width="50px" src="red/images/node-red-icon.svg" />'+
 | 
			
		||||
            '</div>';
 | 
			
		||||
 | 
			
		||||
            RED.sidebar.help.set(aboutHeader+RED.utils.renderMarkdown(data));
 | 
			
		||||
        });
 | 
			
		||||
    function checkFirstRun(done) {
 | 
			
		||||
        if (RED.settings.theme("tours") === false) {
 | 
			
		||||
            done();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!RED.settings.get("editor.view.view-show-welcome-tours", true)) {
 | 
			
		||||
            done();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        RED.actions.invoke("core:show-welcome-tour", RED.settings.get("editor.tours.welcome"), done);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildMainMenu() {
 | 
			
		||||
@@ -557,6 +590,22 @@ var RED = (function() {
 | 
			
		||||
                {id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
 | 
			
		||||
            ]});
 | 
			
		||||
        }
 | 
			
		||||
        menuOptions.push({id:"menu-item-edit-menu", label:RED._("menu.label.edit"), options: [
 | 
			
		||||
            {id: "menu-item-edit-undo", label:RED._("keyboard.undoChange"), disabled: true, onselect: "core:undo"},
 | 
			
		||||
            {id: "menu-item-edit-redo", label:RED._("keyboard.redoChange"), disabled: true, onselect: "core:redo"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-edit-cut", label:RED._("keyboard.cutNode"), onselect: "core:cut-selection-to-internal-clipboard"},
 | 
			
		||||
            {id: "menu-item-edit-copy", label:RED._("keyboard.copyNode"), onselect: "core:copy-selection-to-internal-clipboard"},
 | 
			
		||||
            {id: "menu-item-edit-paste", label:RED._("keyboard.pasteNode"), disabled: true, onselect: "core:paste-from-internal-clipboard"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-edit-copy-group-style", label:RED._("keyboard.copyGroupStyle"), onselect: "core:copy-group-style"},
 | 
			
		||||
            {id: "menu-item-edit-paste-group-style", label:RED._("keyboard.pasteGroupStyle"), disabled: true, onselect: "core:paste-group-style"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-edit-select-all", label:RED._("keyboard.selectAll"), onselect: "core:select-all-nodes"},
 | 
			
		||||
            {id: "menu-item-edit-select-connected", label:RED._("keyboard.selectAllConnected"), onselect: "core:select-connected-nodes"},
 | 
			
		||||
            {id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"}
 | 
			
		||||
        ]});
 | 
			
		||||
 | 
			
		||||
        menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
 | 
			
		||||
            {id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true},
 | 
			
		||||
            {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
 | 
			
		||||
@@ -564,6 +613,25 @@ var RED = (function() {
 | 
			
		||||
            {id:"menu-item-action-list",label:RED._("keyboard.actionList"),onselect:"core:show-action-list"},
 | 
			
		||||
            null
 | 
			
		||||
        ]});
 | 
			
		||||
 | 
			
		||||
        menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-align-left", label:RED._("menu.label.alignLeft"), disabled: true, onselect: "core:align-selection-to-left"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-center", label:RED._("menu.label.alignCenter"), disabled: true, onselect: "core:align-selection-to-center"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-right", label:RED._("menu.label.alignRight"), disabled: true, onselect: "core:align-selection-to-right"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-align-top", label:RED._("menu.label.alignTop"), disabled: true, onselect: "core:align-selection-to-top"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-middle", label:RED._("menu.label.alignMiddle"), disabled: true, onselect: "core:align-selection-to-middle"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-distribute-horizontally", label:RED._("menu.label.distributeHorizontally"), disabled: true, onselect: "core:distribute-selection-horizontally"},
 | 
			
		||||
            {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"}
 | 
			
		||||
        ]});
 | 
			
		||||
 | 
			
		||||
        menuOptions.push(null);
 | 
			
		||||
        if (RED.settings.theme("menu.menu-item-import-library", true)) {
 | 
			
		||||
            menuOptions.push({id: "menu-item-import", label: RED._("menu.label.import"), onselect: "core:show-import-dialog"});
 | 
			
		||||
@@ -624,7 +692,6 @@ var RED = (function() {
 | 
			
		||||
        RED.user.init();
 | 
			
		||||
        RED.notifications.init();
 | 
			
		||||
        RED.library.init();
 | 
			
		||||
        RED.keyboard.init();
 | 
			
		||||
        RED.palette.init();
 | 
			
		||||
        RED.eventLog.init();
 | 
			
		||||
 | 
			
		||||
@@ -653,16 +720,13 @@ var RED = (function() {
 | 
			
		||||
 | 
			
		||||
        RED.deploy.init(RED.settings.theme("deployButton",null));
 | 
			
		||||
 | 
			
		||||
        buildMainMenu();
 | 
			
		||||
        RED.keyboard.init(buildMainMenu);
 | 
			
		||||
 | 
			
		||||
        RED.nodes.init();
 | 
			
		||||
        RED.comms.connect();
 | 
			
		||||
 | 
			
		||||
        $("#red-ui-main-container").show();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:show-about", showAbout);
 | 
			
		||||
 | 
			
		||||
        loadPluginList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -711,7 +775,7 @@ var RED = (function() {
 | 
			
		||||
            throw new Error("RED already initialised");
 | 
			
		||||
        }
 | 
			
		||||
        initialised = true;
 | 
			
		||||
        ace.require("ace/ext/language_tools");
 | 
			
		||||
        if(window.ace) { window.ace.require("ace/ext/language_tools"); }
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        options.apiRootUrl = options.apiRootUrl || "";
 | 
			
		||||
        if (options.apiRootUrl && !/\/$/.test(options.apiRootUrl)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@ RED.settings = (function () {
 | 
			
		||||
 | 
			
		||||
    var loadedSettings = {};
 | 
			
		||||
    var userSettings = {};
 | 
			
		||||
    var settingsDirty = false;
 | 
			
		||||
    var pendingSave;
 | 
			
		||||
 | 
			
		||||
    var hasLocalStorage = function () {
 | 
			
		||||
@@ -126,7 +125,7 @@ RED.settings = (function () {
 | 
			
		||||
        load(done);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var load = function(done) {
 | 
			
		||||
    var refreshSettings = function(done) {
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
@@ -136,6 +135,23 @@ RED.settings = (function () {
 | 
			
		||||
            url: 'settings',
 | 
			
		||||
            success: function (data) {
 | 
			
		||||
                setProperties(data);
 | 
			
		||||
                done(null, data);
 | 
			
		||||
            },
 | 
			
		||||
            error: function(jqXHR,textStatus,errorThrown) {
 | 
			
		||||
                if (jqXHR.status === 401) {
 | 
			
		||||
                    if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
 | 
			
		||||
                        window.location.search = "";
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.user.login(function() { refreshSettings(done); });
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    var load = function(done) {
 | 
			
		||||
        refreshSettings(function(err, data) {
 | 
			
		||||
            if (!err) {
 | 
			
		||||
                if (!RED.settings.user || RED.settings.user.anonymous) {
 | 
			
		||||
                    RED.settings.remove("auth-tokens");
 | 
			
		||||
                }
 | 
			
		||||
@@ -143,22 +159,13 @@ RED.settings = (function () {
 | 
			
		||||
                console.groupCollapsed("Versions");
 | 
			
		||||
                console.log("jQuery",$().jquery)
 | 
			
		||||
                console.log("jQuery UI",$.ui.version);
 | 
			
		||||
                console.log("ACE",ace.version);
 | 
			
		||||
                if(window.ace) { console.log("ACE",ace.version); }
 | 
			
		||||
                if(window.monaco) { console.log("MONACO",monaco.version || "unknown"); }
 | 
			
		||||
                console.log("D3",d3.version);
 | 
			
		||||
                console.groupEnd();
 | 
			
		||||
                loadUserSettings(done);
 | 
			
		||||
            },
 | 
			
		||||
            error: function(jqXHR,textStatus,errorThrown) {
 | 
			
		||||
                if (jqXHR.status === 401) {
 | 
			
		||||
                    if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
 | 
			
		||||
                        window.location.search = "";
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.user.login(function() { load(done); });
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function loadUserSettings(done) {
 | 
			
		||||
@@ -219,14 +226,28 @@ RED.settings = (function () {
 | 
			
		||||
            return defaultValue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function getLocal(key) {
 | 
			
		||||
        return localStorage.getItem(key)
 | 
			
		||||
    }
 | 
			
		||||
    function setLocal(key, value) {
 | 
			
		||||
        localStorage.setItem(key, value);
 | 
			
		||||
    }
 | 
			
		||||
    function removeLocal(key) {
 | 
			
		||||
        localStorage.removeItem(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        load: load,
 | 
			
		||||
        loadUserSettings: loadUserSettings,
 | 
			
		||||
        refreshSettings: refreshSettings,
 | 
			
		||||
        set: set,
 | 
			
		||||
        get: get,
 | 
			
		||||
        remove: remove,
 | 
			
		||||
        theme: theme
 | 
			
		||||
        theme: theme,
 | 
			
		||||
        setLocal: setLocal,
 | 
			
		||||
        getLocal: getLocal,
 | 
			
		||||
        removeLocal: removeLocal
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,12 @@ RED.actions = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addAction(name,handler) {
 | 
			
		||||
        if (typeof handler !== 'function') {
 | 
			
		||||
            throw new Error("Action handler not a function");
 | 
			
		||||
        }
 | 
			
		||||
        if (actions[name]) {
 | 
			
		||||
            throw new Error("Cannot override existing action");
 | 
			
		||||
        }
 | 
			
		||||
        actions[name] = handler;
 | 
			
		||||
    }
 | 
			
		||||
    function removeAction(name) {
 | 
			
		||||
@@ -12,9 +18,11 @@ RED.actions = (function() {
 | 
			
		||||
    function getAction(name) {
 | 
			
		||||
        return actions[name];
 | 
			
		||||
    }
 | 
			
		||||
    function invokeAction(name,args) {
 | 
			
		||||
    function invokeAction() {
 | 
			
		||||
        var args = Array.prototype.slice.call(arguments);
 | 
			
		||||
        var name = args.shift();
 | 
			
		||||
        if (actions.hasOwnProperty(name)) {
 | 
			
		||||
            actions[name](args);
 | 
			
		||||
            actions[name].apply(null, args);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function listActions() {
 | 
			
		||||
 
 | 
			
		||||
@@ -452,7 +452,7 @@ RED.clipboard = (function() {
 | 
			
		||||
 | 
			
		||||
        var libraries = RED.settings.libraries || [];
 | 
			
		||||
        libraries.forEach(function(lib) {
 | 
			
		||||
            var tabId = "red-ui-clipboard-dialog-import-tab-library-"+lib.id
 | 
			
		||||
            var tabId = "red-ui-clipboard-dialog-import-tab-"+lib.id
 | 
			
		||||
            tabs.addTab({
 | 
			
		||||
                id: tabId,
 | 
			
		||||
                label: RED._(lib.label||lib.id)
 | 
			
		||||
@@ -498,6 +498,13 @@ RED.clipboard = (function() {
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
 | 
			
		||||
 | 
			
		||||
        if (RED.workspaces.active() === 0) {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
 | 
			
		||||
        } else {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-current").removeClass('disabled').addClass("selected");
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-new").removeClass("selected");
 | 
			
		||||
        }
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-opt > a").on("click", function(evt) {
 | 
			
		||||
            evt.preventDefault();
 | 
			
		||||
            if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
 | 
			
		||||
@@ -611,9 +618,6 @@ RED.clipboard = (function() {
 | 
			
		||||
            activeLibraries[tabId] = browser;
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $("#red-ui-clipboard-dialog-tab-library-name").on("keyup", validateExportFilename);
 | 
			
		||||
        $("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
 | 
			
		||||
        $("#red-ui-clipboard-dialog-export").button("enable");
 | 
			
		||||
@@ -636,7 +640,6 @@ RED.clipboard = (function() {
 | 
			
		||||
            label: RED._("editor.types.json")
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
 | 
			
		||||
            data: []
 | 
			
		||||
        })
 | 
			
		||||
@@ -701,6 +704,13 @@ RED.clipboard = (function() {
 | 
			
		||||
                var activeWorkspace = RED.workspaces.active();
 | 
			
		||||
                nodes = RED.nodes.groups(activeWorkspace);
 | 
			
		||||
                nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace}));
 | 
			
		||||
                RED.nodes.eachConfig(function(n) {
 | 
			
		||||
                    if (n.z === RED.workspaces.active() && n._def.hasUsers === false) {
 | 
			
		||||
                        // Grab any config nodes scoped to this flow that don't
 | 
			
		||||
                        // require any flow-nodes to use them
 | 
			
		||||
                        nodes.push(n);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
 | 
			
		||||
                nodes.unshift(parentNode);
 | 
			
		||||
                nodes = RED.nodes.createExportableNodeSet(nodes);
 | 
			
		||||
@@ -731,16 +741,22 @@ RED.clipboard = (function() {
 | 
			
		||||
        $("#red-ui-clipboard-dialog-export").hide();
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-conflict").hide();
 | 
			
		||||
 | 
			
		||||
        var selection = RED.workspaces.selection();
 | 
			
		||||
        if (selection.length > 0) {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
 | 
			
		||||
        if (RED.workspaces.active() === 0) {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-rng-selected").addClass('disabled').removeClass('selected');
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-rng-flow").addClass('disabled').removeClass('selected');
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-rng-full").trigger("click");
 | 
			
		||||
        } else {
 | 
			
		||||
            selection = RED.view.selection();
 | 
			
		||||
            if (selection.nodes) {
 | 
			
		||||
            var selection = RED.workspaces.selection();
 | 
			
		||||
            if (selection.length > 0) {
 | 
			
		||||
                $("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
 | 
			
		||||
            } else {
 | 
			
		||||
                $("#red-ui-clipboard-dialog-export-rng-selected").addClass('disabled').removeClass('selected');
 | 
			
		||||
                $("#red-ui-clipboard-dialog-export-rng-flow").trigger("click");
 | 
			
		||||
                selection = RED.view.selection();
 | 
			
		||||
                if (selection.nodes) {
 | 
			
		||||
                    $("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#red-ui-clipboard-dialog-export-rng-selected").addClass('disabled').removeClass('selected');
 | 
			
		||||
                    $("#red-ui-clipboard-dialog-export-rng-flow").trigger("click");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (format === "red-ui-clipboard-dialog-export-fmt-full") {
 | 
			
		||||
@@ -1186,22 +1202,6 @@ RED.clipboard = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeLabelText(n) {
 | 
			
		||||
        var label = n.name || n.type+": "+n.id;
 | 
			
		||||
        if (n._def.label) {
 | 
			
		||||
            try {
 | 
			
		||||
                label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                console.log("Definition error: "+n.type+".label",err);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var newlineIndex = label.indexOf("\\n");
 | 
			
		||||
        if (newlineIndex > -1) {
 | 
			
		||||
            label = label.substring(0,newlineIndex)+"...";
 | 
			
		||||
        }
 | 
			
		||||
        return label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getFlowLabel(n) {
 | 
			
		||||
        n = JSON.parse(JSON.stringify(n));
 | 
			
		||||
        n._def = RED.nodes.getType(n.type) || {};
 | 
			
		||||
@@ -1227,16 +1227,8 @@ RED.clipboard = (function() {
 | 
			
		||||
        if (n._def) {
 | 
			
		||||
            n._ = n._def._;
 | 
			
		||||
        }
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-info-outline-item"});
 | 
			
		||||
        RED.utils.createNodeIcon(n).appendTo(div);
 | 
			
		||||
        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
 | 
			
		||||
        var labelText = getNodeLabelText(n);
 | 
			
		||||
        var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
 | 
			
		||||
        if (labelText) {
 | 
			
		||||
            label.text(labelText)
 | 
			
		||||
        } else {
 | 
			
		||||
            label.html(n.type)
 | 
			
		||||
        }
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-node-list-item"});
 | 
			
		||||
        RED.utils.createNodeIcon(n,true).appendTo(div);
 | 
			
		||||
        return div;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1284,22 +1276,27 @@ RED.clipboard = (function() {
 | 
			
		||||
                hideDropTarget();
 | 
			
		||||
            })
 | 
			
		||||
            .on("drop",function(event) {
 | 
			
		||||
                if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                    var data = event.originalEvent.dataTransfer.getData("text/plain");
 | 
			
		||||
                    data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
 | 
			
		||||
                    importNodes(data);
 | 
			
		||||
                } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                    var files = event.originalEvent.dataTransfer.files;
 | 
			
		||||
                    if (files.length === 1) {
 | 
			
		||||
                        var file = files[0];
 | 
			
		||||
                        var reader = new FileReader();
 | 
			
		||||
                        reader.onload = (function(theFile) {
 | 
			
		||||
                            return function(e) {
 | 
			
		||||
                                importNodes(e.target.result);
 | 
			
		||||
                            };
 | 
			
		||||
                        })(file);
 | 
			
		||||
                        reader.readAsText(file);
 | 
			
		||||
                try {
 | 
			
		||||
                    if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                        var data = event.originalEvent.dataTransfer.getData("text/plain");
 | 
			
		||||
                        data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
 | 
			
		||||
                        importNodes(data);
 | 
			
		||||
                    } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                        var files = event.originalEvent.dataTransfer.files;
 | 
			
		||||
                        if (files.length === 1) {
 | 
			
		||||
                            var file = files[0];
 | 
			
		||||
                            var reader = new FileReader();
 | 
			
		||||
                            reader.onload = (function(theFile) {
 | 
			
		||||
                                return function(e) {
 | 
			
		||||
                                    importNodes(e.target.result);
 | 
			
		||||
                                };
 | 
			
		||||
                            })(file);
 | 
			
		||||
                            reader.readAsText(file);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    // Ensure any errors throw above doesn't stop the drop target from
 | 
			
		||||
                    // being hidden.
 | 
			
		||||
                }
 | 
			
		||||
                hideDropTarget();
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
(function($) {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Attach to an <input type="text"> to provide auto-complete
 | 
			
		||||
 *
 | 
			
		||||
 * $("#node-red-text").autoComplete({
 | 
			
		||||
 *     search: function(value) { return ['a','b','c'] }
 | 
			
		||||
 * })
 | 
			
		||||
 *
 | 
			
		||||
 * options:
 | 
			
		||||
 *
 | 
			
		||||
 *  search : function(value, [done])
 | 
			
		||||
 *           A function that is passed the current contents of the input whenever
 | 
			
		||||
 *           it changes.
 | 
			
		||||
 *           The function must either return auto-complete options, or pass them
 | 
			
		||||
 *           to the optional 'done' parameter.
 | 
			
		||||
 *           If the function signature includes 'done', it must be used
 | 
			
		||||
 *
 | 
			
		||||
 * The auto-complete options should be an array of objects in the form:
 | 
			
		||||
 *  {
 | 
			
		||||
 *      value: String : the value to insert if selected
 | 
			
		||||
 *      label: String|DOM Element : the label to display in the dropdown.
 | 
			
		||||
 *  }
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
    $.widget( "nodered.autoComplete", {
 | 
			
		||||
        _create: function() {
 | 
			
		||||
            var that = this;
 | 
			
		||||
            this.completionMenuShown = false;
 | 
			
		||||
            this.options.search = this.options.search || function() { return [] }
 | 
			
		||||
            this.element.addClass("red-ui-autoComplete")
 | 
			
		||||
            this.element.on("keydown.red-ui-autoComplete", function(evt) {
 | 
			
		||||
                if ((evt.keyCode === 13 || evt.keyCode === 9) && that.completionMenuShown) {
 | 
			
		||||
                    var opts = that.menu.options();
 | 
			
		||||
                    that.element.val(opts[0].value);
 | 
			
		||||
                    that.menu.hide();
 | 
			
		||||
                    evt.preventDefault();
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            this.element.on("keyup.red-ui-autoComplete", function(evt) {
 | 
			
		||||
                if (evt.keyCode === 13 || evt.keyCode === 9 || evt.keyCode === 27) {
 | 
			
		||||
                    // ENTER / TAB / ESCAPE
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (evt.keyCode === 8 || evt.keyCode === 46) {
 | 
			
		||||
                    // Delete/Backspace
 | 
			
		||||
                    if (!that.completionMenuShown) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                that._updateCompletions(this.value);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        _showCompletionMenu: function(completions) {
 | 
			
		||||
            if (this.completionMenuShown) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.menu = RED.popover.menu({
 | 
			
		||||
                tabSelect: true,
 | 
			
		||||
                width: 300,
 | 
			
		||||
                maxHeight: 200,
 | 
			
		||||
                class: "red-ui-autoComplete-container",
 | 
			
		||||
                options: completions,
 | 
			
		||||
                onselect: (opt) => { this.element.val(opt.value); this.element.focus(); this.element.trigger("change") },
 | 
			
		||||
                onclose: () => { this.completionMenuShown = false; delete this.menu; this.element.focus()}
 | 
			
		||||
            });
 | 
			
		||||
            this.menu.show({
 | 
			
		||||
                target: this.element
 | 
			
		||||
            })
 | 
			
		||||
            this.completionMenuShown = true;
 | 
			
		||||
        },
 | 
			
		||||
        _updateCompletions: function(val) {
 | 
			
		||||
            var that = this;
 | 
			
		||||
            if (val.trim() === "") {
 | 
			
		||||
                if (this.completionMenuShown) {
 | 
			
		||||
                    this.menu.hide();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            function displayResults(completions,requestId) {
 | 
			
		||||
                if (requestId && requestId !== that.pendingRequest) {
 | 
			
		||||
                    // This request has been superseded
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (!completions || completions.length === 0) {
 | 
			
		||||
                    if (that.completionMenuShown) {
 | 
			
		||||
                        that.menu.hide();
 | 
			
		||||
                    }
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (that.completionMenuShown) {
 | 
			
		||||
                    that.menu.options(completions);
 | 
			
		||||
                } else {
 | 
			
		||||
                    that._showCompletionMenu(completions);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (this.options.search.length === 2) {
 | 
			
		||||
                var requestId = 1+Math.floor(Math.random()*10000);
 | 
			
		||||
                this.pendingRequest = requestId;
 | 
			
		||||
                this.options.search(val,function(completions) { displayResults(completions,requestId);})
 | 
			
		||||
            } else {
 | 
			
		||||
                displayResults(this.options.search(val))
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        _destroy: function() {
 | 
			
		||||
            this.element.removeClass("red-ui-autoComplete")
 | 
			
		||||
            this.element.off("keydown.red-ui-autoComplete")
 | 
			
		||||
            this.element.off("keyup.red-ui-autoComplete")
 | 
			
		||||
            if (this.completionMenuShown) {
 | 
			
		||||
                this.menu.hide();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})(jQuery);
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
/**
 | 
			
		||||
 * options:
 | 
			
		||||
 *   - addButton : boolean|string - text for add label, default 'add'
 | 
			
		||||
 *   - buttons : array - list of custom buttons (objects with fields 'label', 'icon', 'title', 'click')
 | 
			
		||||
 *   - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click')
 | 
			
		||||
 *   - height : number|'auto'
 | 
			
		||||
 *   - resize : function - called when list as a whole is resized
 | 
			
		||||
 *   - resizeItem : function(item) - called to resize individual item
 | 
			
		||||
@@ -71,7 +71,7 @@
 | 
			
		||||
            var buttons = this.options.buttons || [];
 | 
			
		||||
 | 
			
		||||
            if (this.options.addButton !== false) {
 | 
			
		||||
                var addLabel, addTittle;
 | 
			
		||||
                var addLabel, addTitle;
 | 
			
		||||
                if (typeof this.options.addButton === 'string') {
 | 
			
		||||
                    addLabel = this.options.addButton
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -94,7 +94,7 @@
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            buttons.forEach(function(button) {
 | 
			
		||||
                var element = $('<a href="#" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></a>')
 | 
			
		||||
                var element = $('<button type="button" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></button>')
 | 
			
		||||
                    .appendTo(that.topContainer)
 | 
			
		||||
                    .on("click", function(evt) {
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
@@ -102,7 +102,10 @@
 | 
			
		||||
                            button.click(evt);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                if (button.id) {
 | 
			
		||||
                    element.attr("id", button.id);
 | 
			
		||||
                }
 | 
			
		||||
                if (button.title) {
 | 
			
		||||
                    element.attr("title", button.title);
 | 
			
		||||
                }
 | 
			
		||||
@@ -113,7 +116,7 @@
 | 
			
		||||
                    element.append($("<span></span>").text(" " + button.label));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
            if (this.element.css("position") === "absolute") {
 | 
			
		||||
                ["top","left","bottom","right"].forEach(function(s) {
 | 
			
		||||
                    var v = that.element.css(s);
 | 
			
		||||
 
 | 
			
		||||
@@ -82,12 +82,19 @@ RED.menu = (function() {
 | 
			
		||||
                linkContent += '<span class="red-ui-menu-label-container"><span class="red-ui-menu-label">'+opt.label+'</span>'+
 | 
			
		||||
                               '<span class="red-ui-menu-sublabel">'+opt.sublabel+'</span></span>'
 | 
			
		||||
            } else {
 | 
			
		||||
                linkContent += '<span class="red-ui-menu-label">'+opt.label+'</span>'
 | 
			
		||||
                linkContent += '<span class="red-ui-menu-label"><span>'+opt.label+'</span></span>'
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            linkContent += '</a>';
 | 
			
		||||
 | 
			
		||||
            var link = $(linkContent).appendTo(item);
 | 
			
		||||
            opt.link = link;
 | 
			
		||||
            if (typeof opt.onselect === 'string') {
 | 
			
		||||
                var shortcut = RED.keyboard.getShortcut(opt.onselect);
 | 
			
		||||
                if (shortcut && shortcut.key) {
 | 
			
		||||
                    opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            menuItems[opt.id] = opt;
 | 
			
		||||
 | 
			
		||||
@@ -276,6 +283,22 @@ RED.menu = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function refreshShortcuts() {
 | 
			
		||||
        for (var id in menuItems) {
 | 
			
		||||
            if (menuItems.hasOwnProperty(id)) {
 | 
			
		||||
                var opt = menuItems[id];
 | 
			
		||||
                if (typeof opt.onselect === "string" && opt.shortcutSpan) {
 | 
			
		||||
                    opt.shortcutSpan.remove();
 | 
			
		||||
                    delete opt.shortcutSpan;
 | 
			
		||||
                    var shortcut = RED.keyboard.getShortcut(opt.onselect);
 | 
			
		||||
                    if (shortcut && shortcut.key) {
 | 
			
		||||
                        opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(opt.link.find(".red-ui-menu-label"));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: createMenu,
 | 
			
		||||
        setSelected: setSelected,
 | 
			
		||||
@@ -284,6 +307,7 @@ RED.menu = (function() {
 | 
			
		||||
        setDisabled: setDisabled,
 | 
			
		||||
        addItem: addItem,
 | 
			
		||||
        removeItem: removeItem,
 | 
			
		||||
        setAction: setAction
 | 
			
		||||
        setAction: setAction,
 | 
			
		||||
        refreshShortcuts: refreshShortcuts
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -13,24 +13,138 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 **/
 | 
			
		||||
/*
 | 
			
		||||
 * RED.popover.create(options) - create a popover callout box
 | 
			
		||||
 * RED.popover.tooltip(target,content, action) - add a tooltip to an element
 | 
			
		||||
 * RED.popover.menu(options) - create a dropdown menu
 | 
			
		||||
 * RED.popover.panel(content) - create a dropdown container element
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * RED.popover.create(options)
 | 
			
		||||
 *
 | 
			
		||||
 *  options
 | 
			
		||||
 *    - target : DOM element - the element to target with the popover
 | 
			
		||||
 *    - direction : string - position of the popover relative to target
 | 
			
		||||
 *                  'top', 'right'(default), 'bottom', 'left', 'inset-[top,right,bottom,left]'
 | 
			
		||||
 *    - trigger : string - what triggers the popover to be displayed
 | 
			
		||||
 *                  'hover' - display when hovering the target
 | 
			
		||||
 *                  'click' - display when target is clicked
 | 
			
		||||
 *                  'modal' - hmm not sure, need to find where we use that mode
 | 
			
		||||
 *    - content : string|function - contents of the popover. If a string, handled
 | 
			
		||||
 *                                  as raw HTML, so take care.
 | 
			
		||||
 *                                  If a function, can return a String to be added
 | 
			
		||||
 *                                  as text (not HTML), or a DOM element to append
 | 
			
		||||
 *    - delay : object - sets show/hide delays after mouseover/out events
 | 
			
		||||
 *                  { show: 750, hide: 50 }
 | 
			
		||||
 *    - autoClose : number - delay before closing the popover in some cases
 | 
			
		||||
 *                     if trigger is click - delay after mouseout
 | 
			
		||||
 *                     else if trigger not hover/modal - delay after showing
 | 
			
		||||
 *    - width : number - width of popover, default 'auto'
 | 
			
		||||
 *    - maxWidth : number - max width of popover, default 'auto'
 | 
			
		||||
 *    - size : string - scale of popover. 'default', 'small'
 | 
			
		||||
 *    - offset : number - px offset from target
 | 
			
		||||
 *    - tooltip : boolean - if true, clicking on popover closes it
 | 
			
		||||
 *    - class : string - optional css class to apply to popover
 | 
			
		||||
 *    - interactive : if trigger is 'hover' and this is set to true, allow the mouse
 | 
			
		||||
 *                    to move over the popover without hiding it.
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the popover object with the following properties/functions:
 | 
			
		||||
 *   properties:
 | 
			
		||||
 *    - element : DOM element - the popover dom element
 | 
			
		||||
 *   functions:
 | 
			
		||||
 *    - setContent(content) - change the popover content. This only works if the
 | 
			
		||||
 *                            popover is not currently displayed. It does not
 | 
			
		||||
 *                            change the content of a visible popover.
 | 
			
		||||
 *    - open(instant) - show the popover. If 'instant' is true, don't fade in
 | 
			
		||||
 *    - close(instant) - hide the popover. If 'instant' is true, don't fade out
 | 
			
		||||
 *    - move(options) - move the popover. The options parameter can take many
 | 
			
		||||
 *                      of the options detailed above including:
 | 
			
		||||
 *                       target,direction,content,width,offset
 | 
			
		||||
 *                      Other settings probably won't work because we haven't needed to change them
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * RED.popover.tooltip(target,content, action)
 | 
			
		||||
 *
 | 
			
		||||
 *  - target : DOM element - the element to apply the tooltip to
 | 
			
		||||
 *  - content : string - the text of the tooltip
 | 
			
		||||
 *  - action : string - *optional* the name of an Action this tooltip is tied to
 | 
			
		||||
 *                      For example, it 'target' is a button that triggers a particular action.
 | 
			
		||||
 *                      The tooltip will include the keyboard shortcut for the action
 | 
			
		||||
 *                      if one is defined
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * RED.popover.menu(options)
 | 
			
		||||
 *
 | 
			
		||||
 *  options
 | 
			
		||||
 *    - options : array - list of menu options - see below for format
 | 
			
		||||
 *    - width : number - width of the menu. Default: 'auto'
 | 
			
		||||
 *    - class : string - class to apply to the menu container
 | 
			
		||||
 *    - maxHeight : number - maximum height of menu before scrolling items. Default: none
 | 
			
		||||
 *    - onselect : function(item) - called when a menu item is selected, if that item doesn't
 | 
			
		||||
 *                                  have its own onselect function
 | 
			
		||||
 *    - onclose : function(cancelled) - called when the menu is closed
 | 
			
		||||
 *    - disposeOnClose : boolean - by default, the menu is discarded when it closes
 | 
			
		||||
 *                                 and mustbe rebuilt to redisplay. Setting this to 'false'
 | 
			
		||||
 *                                 keeps the menu on the DOM so it can be shown again.
 | 
			
		||||
 *
 | 
			
		||||
 *  Menu Options array:
 | 
			
		||||
 *  [
 | 
			
		||||
 *      label : string|DOM element - the label of the item. Can be custom DOM element
 | 
			
		||||
 *      onselect : function - called when the item is selected
 | 
			
		||||
 *  ]
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the menu object with the following functions:
 | 
			
		||||
 *
 | 
			
		||||
 *  - options([menuItems]) - if menuItems is undefined, returns the current items.
 | 
			
		||||
 *                           otherwise, sets the current menu items
 | 
			
		||||
 *  - show(opts) - shows the menu. `opts` is an object of options. See  RED.popover.panel.show(opts)
 | 
			
		||||
 *                 for the full list of options. In most scenarios, this just needs:
 | 
			
		||||
 *                  - target : DOM element - the element to display the menu below
 | 
			
		||||
 *  - hide(cancelled) - hide the menu
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * RED.popover.panel(content)
 | 
			
		||||
 *  Create a UI panel that can be displayed relative to any target element.
 | 
			
		||||
 *  Handles auto-closing when mouse clicks outside the panel
 | 
			
		||||
 *
 | 
			
		||||
 *  - 'content' - DOM element to display in the panel
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the panel object with the following functions:
 | 
			
		||||
 *
 | 
			
		||||
 *  properties:
 | 
			
		||||
 *    - container : DOM element - the panel element
 | 
			
		||||
 *
 | 
			
		||||
 *  functions:
 | 
			
		||||
 *    - show(opts) - show the panel.
 | 
			
		||||
 *       opts:
 | 
			
		||||
 *          - onclose : function - called when the panel closes
 | 
			
		||||
 *          - closeButton : DOM element - if the panel is closeable by a click of a button,
 | 
			
		||||
 *                                        by providing a reference to it here, we can
 | 
			
		||||
 *                                        handle the events properly to hide the panel
 | 
			
		||||
 *          - target : DOM element - the element to display the panel relative to
 | 
			
		||||
 *          - align : string - should the panel align to the left or right edge of target
 | 
			
		||||
 *                             default: 'right'
 | 
			
		||||
 *          - offset : Array - px offset to apply from the target. [width, height]
 | 
			
		||||
 *          - dispose : boolean - whether the panel should be removed from DOM when hidden
 | 
			
		||||
 *                                default: true
 | 
			
		||||
 *    - hide(dispose) - hide the panel.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
RED.popover = (function() {
 | 
			
		||||
    var deltaSizes = {
 | 
			
		||||
        "default": {
 | 
			
		||||
            top: 10,
 | 
			
		||||
            topTop: 30,
 | 
			
		||||
            leftRight: 17,
 | 
			
		||||
            leftLeft: 25,
 | 
			
		||||
            leftBottom: 8,
 | 
			
		||||
            leftTop: 11
 | 
			
		||||
            x: 12,
 | 
			
		||||
            y: 12
 | 
			
		||||
        },
 | 
			
		||||
        "small": {
 | 
			
		||||
            top: 6,
 | 
			
		||||
            topTop: 20,
 | 
			
		||||
            leftRight: 8,
 | 
			
		||||
            leftLeft: 26,
 | 
			
		||||
            leftBottom: 8,
 | 
			
		||||
            leftTop: 9
 | 
			
		||||
            x:8,
 | 
			
		||||
            y:8
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function createPopover(options) {
 | 
			
		||||
@@ -41,7 +155,9 @@ RED.popover = (function() {
 | 
			
		||||
        var delay = options.delay ||  { show: 750, hide: 50 };
 | 
			
		||||
        var autoClose = options.autoClose;
 | 
			
		||||
        var width = options.width||"auto";
 | 
			
		||||
        var maxWidth = options.maxWidth;
 | 
			
		||||
        var size = options.size||"default";
 | 
			
		||||
        var popupOffset = options.offset || 0;
 | 
			
		||||
        if (!deltaSizes[size]) {
 | 
			
		||||
            throw new Error("Invalid RED.popover size value:",size);
 | 
			
		||||
        }
 | 
			
		||||
@@ -49,6 +165,8 @@ RED.popover = (function() {
 | 
			
		||||
        var timer = null;
 | 
			
		||||
        var active;
 | 
			
		||||
        var div;
 | 
			
		||||
        var contentDiv;
 | 
			
		||||
        var currentStyle;
 | 
			
		||||
 | 
			
		||||
        var openPopup = function(instant) {
 | 
			
		||||
            if (active) {
 | 
			
		||||
@@ -58,6 +176,10 @@ RED.popover = (function() {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                div = $('<div class="red-ui-popover"></div>');
 | 
			
		||||
                if (options.class) {
 | 
			
		||||
                    div.addClass(options.class);
 | 
			
		||||
                }
 | 
			
		||||
                contentDiv = $('<div class="red-ui-popover-content">').appendTo(div);
 | 
			
		||||
                if (size !== "default") {
 | 
			
		||||
                    div.addClass("red-ui-popover-size-"+size);
 | 
			
		||||
                }
 | 
			
		||||
@@ -67,71 +189,23 @@ RED.popover = (function() {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (typeof result === 'string') {
 | 
			
		||||
                        div.text(result);
 | 
			
		||||
                        contentDiv.text(result);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        div.append(result);
 | 
			
		||||
                        contentDiv.append(result);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    div.html(content);
 | 
			
		||||
                }
 | 
			
		||||
                if (width !== "auto") {
 | 
			
		||||
                    div.width(width);
 | 
			
		||||
                    contentDiv.html(content);
 | 
			
		||||
                }
 | 
			
		||||
                div.appendTo("body");
 | 
			
		||||
 | 
			
		||||
                var targetPos = target.offset();
 | 
			
		||||
                var targetWidth = target.outerWidth();
 | 
			
		||||
                var targetHeight = target.outerHeight();
 | 
			
		||||
                var divHeight = div.height();
 | 
			
		||||
                var divWidth = div.width();
 | 
			
		||||
                var paddingRight = 10;
 | 
			
		||||
                movePopup({target,direction,width,maxWidth});
 | 
			
		||||
 | 
			
		||||
                var viewportTop = $(window).scrollTop();
 | 
			
		||||
                var viewportLeft = $(window).scrollLeft();
 | 
			
		||||
                var viewportBottom = viewportTop + $(window).height();
 | 
			
		||||
                var viewportRight = viewportLeft + $(window).width();
 | 
			
		||||
                var top = 0;
 | 
			
		||||
                var left = 0;
 | 
			
		||||
                var d = direction;
 | 
			
		||||
                if (d === 'right') {
 | 
			
		||||
                    top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
 | 
			
		||||
                    left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
 | 
			
		||||
                } else if (d === 'left') {
 | 
			
		||||
                    top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
 | 
			
		||||
                    left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
 | 
			
		||||
                } else if (d === 'bottom') {
 | 
			
		||||
                    top = targetPos.top+targetHeight+deltaSizes[size].top;
 | 
			
		||||
                    left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
 | 
			
		||||
                    if (left < 0) {
 | 
			
		||||
                        d = "right";
 | 
			
		||||
                        top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
 | 
			
		||||
                        left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
 | 
			
		||||
                    } else if (left+divWidth+paddingRight > viewportRight) {
 | 
			
		||||
                        d = "left";
 | 
			
		||||
                        top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
 | 
			
		||||
                        left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
 | 
			
		||||
                        if (top+divHeight+targetHeight/2 + 5 > viewportBottom) {
 | 
			
		||||
                            top -= (top+divHeight+targetHeight/2 - viewportBottom + 5)
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (top+divHeight > viewportBottom) {
 | 
			
		||||
                        d = 'top';
 | 
			
		||||
                        top = targetPos.top-deltaSizes[size].topTop-divHeight;
 | 
			
		||||
                        left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (d === 'top') {
 | 
			
		||||
                    top = targetPos.top-deltaSizes[size].topTop-divHeight;
 | 
			
		||||
                    left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
 | 
			
		||||
                    if (top < 0) {
 | 
			
		||||
                        d = 'bottom';
 | 
			
		||||
                        top = targetPos.top+targetHeight+deltaSizes[size].top;
 | 
			
		||||
                        left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                div.addClass('red-ui-popover-'+d).css({top: top, left: left});
 | 
			
		||||
                if (existingPopover) {
 | 
			
		||||
                    existingPopover.close(true);
 | 
			
		||||
                }
 | 
			
		||||
                target.data("red-ui-popover",res)
 | 
			
		||||
                if (options.trigger !== 'manual') {
 | 
			
		||||
                    target.data("red-ui-popover",res)
 | 
			
		||||
                }
 | 
			
		||||
                if (options.tooltip) {
 | 
			
		||||
                    div.on("mousedown", function(evt) {
 | 
			
		||||
                        closePopup(true);
 | 
			
		||||
@@ -161,6 +235,104 @@ RED.popover = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var movePopup = function(options) {
 | 
			
		||||
            target = options.target || target;
 | 
			
		||||
            direction = options.direction || direction || "right";
 | 
			
		||||
            popupOffset = options.offset || popupOffset;
 | 
			
		||||
            var transition = options.transition;
 | 
			
		||||
 | 
			
		||||
            var width = options.width||"auto";
 | 
			
		||||
            div.width(width);
 | 
			
		||||
            if (options.maxWidth) {
 | 
			
		||||
                div.css("max-width",options.maxWidth)
 | 
			
		||||
            } else {
 | 
			
		||||
                div.css("max-width", 'auto');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var targetPos = target[0].getBoundingClientRect();
 | 
			
		||||
            var targetHeight = targetPos.height;
 | 
			
		||||
            var targetWidth = targetPos.width;
 | 
			
		||||
 | 
			
		||||
            var divHeight = div.outerHeight();
 | 
			
		||||
            var divWidth = div.outerWidth();
 | 
			
		||||
            var paddingRight = 10;
 | 
			
		||||
 | 
			
		||||
            var viewportTop = $(window).scrollTop();
 | 
			
		||||
            var viewportLeft = $(window).scrollLeft();
 | 
			
		||||
            var viewportBottom = viewportTop + $(window).height();
 | 
			
		||||
            var viewportRight = viewportLeft + $(window).width();
 | 
			
		||||
            var top = 0;
 | 
			
		||||
            var left = 0;
 | 
			
		||||
            if (direction === 'right') {
 | 
			
		||||
                top = targetPos.top+targetHeight/2-divHeight/2;
 | 
			
		||||
                left = targetPos.left+targetWidth+deltaSizes[size].x+popupOffset;
 | 
			
		||||
            } else if (direction === 'left') {
 | 
			
		||||
                top = targetPos.top+targetHeight/2-divHeight/2;
 | 
			
		||||
                left = targetPos.left-deltaSizes[size].x-divWidth-popupOffset;
 | 
			
		||||
            } else if (direction === 'bottom') {
 | 
			
		||||
                top = targetPos.top+targetHeight+deltaSizes[size].y+popupOffset;
 | 
			
		||||
                left = targetPos.left+targetWidth/2-divWidth/2;
 | 
			
		||||
                if (left < 0) {
 | 
			
		||||
                    direction = "right";
 | 
			
		||||
                    top = targetPos.top+targetHeight/2-divHeight/2;
 | 
			
		||||
                    left = targetPos.left+targetWidth+deltaSizes[size].x+popupOffset;
 | 
			
		||||
                } else if (left+divWidth+paddingRight > viewportRight) {
 | 
			
		||||
                    direction = "left";
 | 
			
		||||
                    top = targetPos.top+targetHeight/2-divHeight/2;
 | 
			
		||||
                    left = targetPos.left-deltaSizes[size].x-divWidth-popupOffset;
 | 
			
		||||
                    if (top+divHeight+targetHeight/2 + 5 > viewportBottom) {
 | 
			
		||||
                        top -= (top+divHeight+targetHeight/2 - viewportBottom + 5)
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (top+divHeight > viewportBottom) {
 | 
			
		||||
                    direction = 'top';
 | 
			
		||||
                    top = targetPos.top-deltaSizes[size].y-divHeight-popupOffset;
 | 
			
		||||
                    left = targetPos.left+targetWidth/2-divWidth/2;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (direction === 'top') {
 | 
			
		||||
                top = targetPos.top-deltaSizes[size].y-divHeight-popupOffset;
 | 
			
		||||
                left = targetPos.left+targetWidth/2-divWidth/2;
 | 
			
		||||
                if (top < 0) {
 | 
			
		||||
                    direction = 'bottom';
 | 
			
		||||
                    top = targetPos.top+targetHeight+deltaSizes[size].y+popupOffset;
 | 
			
		||||
                    left = targetPos.left+targetWidth/2-divWidth/2;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (/inset/.test(direction)) {
 | 
			
		||||
                top = targetPos.top + targetHeight/2 - divHeight/2;
 | 
			
		||||
                left = targetPos.left + targetWidth/2 - divWidth/2;
 | 
			
		||||
 | 
			
		||||
                if (/bottom/.test(direction)) {
 | 
			
		||||
                    top = targetPos.top + targetHeight - divHeight-popupOffset;
 | 
			
		||||
                }
 | 
			
		||||
                if (/top/.test(direction)) {
 | 
			
		||||
                    top = targetPos.top+popupOffset;
 | 
			
		||||
                }
 | 
			
		||||
                if (/left/.test(direction)) {
 | 
			
		||||
                    left = targetPos.left+popupOffset;
 | 
			
		||||
                }
 | 
			
		||||
                if (/right/.test(direction)) {
 | 
			
		||||
                    left = targetPos.left + targetWidth - divWidth-popupOffset;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (currentStyle) {
 | 
			
		||||
                div.removeClass(currentStyle);
 | 
			
		||||
            }
 | 
			
		||||
            if (transition) {
 | 
			
		||||
                div.css({
 | 
			
		||||
                    "transition": "0.6s ease",
 | 
			
		||||
                    "transition-property": "top,left,right,bottom"
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            currentStyle = 'red-ui-popover-'+direction;
 | 
			
		||||
            div.addClass(currentStyle).css({top: top, left: left});
 | 
			
		||||
            if (transition) {
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    div.css({
 | 
			
		||||
                        "transition": "none"
 | 
			
		||||
                    });
 | 
			
		||||
                },600);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        var closePopup = function(instant) {
 | 
			
		||||
            $(document).off('mousedown.red-ui-popover');
 | 
			
		||||
            if (!active) {
 | 
			
		||||
@@ -236,8 +408,10 @@ RED.popover = (function() {
 | 
			
		||||
            },autoClose);
 | 
			
		||||
        }
 | 
			
		||||
        var res = {
 | 
			
		||||
            get element() { return div },
 | 
			
		||||
            setContent: function(_content) {
 | 
			
		||||
                content = _content;
 | 
			
		||||
 | 
			
		||||
                return res;
 | 
			
		||||
            },
 | 
			
		||||
            open: function (instant) {
 | 
			
		||||
@@ -249,6 +423,10 @@ RED.popover = (function() {
 | 
			
		||||
                active = false;
 | 
			
		||||
                closePopup(instant);
 | 
			
		||||
                return res;
 | 
			
		||||
            },
 | 
			
		||||
            move: function(options) {
 | 
			
		||||
                movePopup(options);
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return res;
 | 
			
		||||
@@ -258,18 +436,17 @@ RED.popover = (function() {
 | 
			
		||||
    return {
 | 
			
		||||
        create: createPopover,
 | 
			
		||||
        tooltip: function(target,content, action) {
 | 
			
		||||
            var label = content;
 | 
			
		||||
            if (action) {
 | 
			
		||||
                label = function() {
 | 
			
		||||
                    var label = content;
 | 
			
		||||
            var label = function() {
 | 
			
		||||
                var label = content;
 | 
			
		||||
                if (action) {
 | 
			
		||||
                    var shortcut = RED.keyboard.getShortcut(action);
 | 
			
		||||
                    if (shortcut && shortcut.key) {
 | 
			
		||||
                        label = $('<span>'+content+' <span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span></span>');
 | 
			
		||||
                    }
 | 
			
		||||
                    return label;
 | 
			
		||||
                }
 | 
			
		||||
                return label;
 | 
			
		||||
            }
 | 
			
		||||
            return RED.popover.create({
 | 
			
		||||
            var popover = RED.popover.create({
 | 
			
		||||
                tooltip: true,
 | 
			
		||||
                target:target,
 | 
			
		||||
                trigger: "hover",
 | 
			
		||||
@@ -278,6 +455,14 @@ RED.popover = (function() {
 | 
			
		||||
                content: label,
 | 
			
		||||
                delay: { show: 750, hide: 50 }
 | 
			
		||||
            });
 | 
			
		||||
            popover.setContent = function(newContent) {
 | 
			
		||||
                content = newContent;
 | 
			
		||||
            }
 | 
			
		||||
            popover.setAction = function(newAction) {
 | 
			
		||||
                action = newAction;
 | 
			
		||||
            }
 | 
			
		||||
            return popover;
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        menu: function(options) {
 | 
			
		||||
            var list = $('<ul class="red-ui-menu"></ul>');
 | 
			
		||||
@@ -286,20 +471,47 @@ RED.popover = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            var menuOptions = options.options || [];
 | 
			
		||||
            var first;
 | 
			
		||||
            menuOptions.forEach(function(opt) {
 | 
			
		||||
                var item = $('<li>').appendTo(list);
 | 
			
		||||
                var link = $('<a href="#"></a>').text(opt.label).appendTo(item);
 | 
			
		||||
                link.on("click", function(evt) {
 | 
			
		||||
                    evt.preventDefault();
 | 
			
		||||
                    if (opt.onselect) {
 | 
			
		||||
                        opt.onselect();
 | 
			
		||||
                    }
 | 
			
		||||
                    menu.hide();
 | 
			
		||||
                })
 | 
			
		||||
                if (!first) { first = link}
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            var container = RED.popover.panel(list);
 | 
			
		||||
            if (options.width) {
 | 
			
		||||
                container.container.width(options.width);
 | 
			
		||||
            }
 | 
			
		||||
            if (options.class) {
 | 
			
		||||
                container.container.addClass(options.class);
 | 
			
		||||
            }
 | 
			
		||||
            if (options.maxHeight) {
 | 
			
		||||
                container.container.css({
 | 
			
		||||
                    "max-height": options.maxHeight,
 | 
			
		||||
                    "overflow-y": 'auto'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            var menu = {
 | 
			
		||||
                options: function(opts) {
 | 
			
		||||
                    if (opts === undefined) {
 | 
			
		||||
                        return menuOptions
 | 
			
		||||
                    }
 | 
			
		||||
                    menuOptions = opts || [];
 | 
			
		||||
                    list.empty();
 | 
			
		||||
                    menuOptions.forEach(function(opt) {
 | 
			
		||||
                        var item = $('<li>').appendTo(list);
 | 
			
		||||
                        var link = $('<a href="#"></a>').appendTo(item);
 | 
			
		||||
                        if (typeof opt.label == "string") {
 | 
			
		||||
                            link.text(opt.label)
 | 
			
		||||
                        } else if (opt.label){
 | 
			
		||||
                            opt.label.appendTo(link);
 | 
			
		||||
                        }
 | 
			
		||||
                        link.on("click", function(evt) {
 | 
			
		||||
                            evt.preventDefault();
 | 
			
		||||
                            if (opt.onselect) {
 | 
			
		||||
                                opt.onselect();
 | 
			
		||||
                            } else if (options.onselect) {
 | 
			
		||||
                                options.onselect(opt);
 | 
			
		||||
                            }
 | 
			
		||||
                            menu.hide();
 | 
			
		||||
                        })
 | 
			
		||||
                        if (!first) { first = link}
 | 
			
		||||
                    })
 | 
			
		||||
                },
 | 
			
		||||
                show: function(opts) {
 | 
			
		||||
                    $(document).on("keydown.red-ui-menu", function(evt) {
 | 
			
		||||
                        var currentItem = list.find(":focus").parent();
 | 
			
		||||
@@ -308,11 +520,9 @@ RED.popover = (function() {
 | 
			
		||||
                            // DOWN
 | 
			
		||||
                            if (currentItem.length > 0) {
 | 
			
		||||
                                if (currentItem.index() === menuOptions.length-1) {
 | 
			
		||||
                                    console.log("WARP TO TOP")
 | 
			
		||||
                                    // Wrap to top of list
 | 
			
		||||
                                    list.children().first().children().first().focus();
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    console.log("GO DOWN ONE")
 | 
			
		||||
                                    currentItem.next().children().first().focus();
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
@@ -323,11 +533,9 @@ RED.popover = (function() {
 | 
			
		||||
                            // UP
 | 
			
		||||
                            if (currentItem.length > 0) {
 | 
			
		||||
                                if (currentItem.index() === 0) {
 | 
			
		||||
                                    console.log("WARP TO BOTTOM")
 | 
			
		||||
                                    // Wrap to bottom of list
 | 
			
		||||
                                    list.children().last().children().first().focus();
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    console.log("GO UP ONE")
 | 
			
		||||
                                    currentItem.prev().children().first().focus();
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
@@ -337,6 +545,11 @@ RED.popover = (function() {
 | 
			
		||||
                            // ESCAPE
 | 
			
		||||
                            evt.preventDefault();
 | 
			
		||||
                            menu.hide(true);
 | 
			
		||||
                        } else if (evt.keyCode === 9 && options.tabSelect) {
 | 
			
		||||
                            // TAB - with tabSelect enabled
 | 
			
		||||
                            evt.preventDefault();
 | 
			
		||||
                            currentItem.find("a").trigger("click");
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                    })
 | 
			
		||||
@@ -356,6 +569,7 @@ RED.popover = (function() {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            menu.options(menuOptions);
 | 
			
		||||
            return menu;
 | 
			
		||||
        },
 | 
			
		||||
        panel: function(content) {
 | 
			
		||||
@@ -363,7 +577,6 @@ RED.popover = (function() {
 | 
			
		||||
            panel.css({ display: "none" });
 | 
			
		||||
            panel.appendTo(document.body);
 | 
			
		||||
            content.appendTo(panel);
 | 
			
		||||
            var closeCallback;
 | 
			
		||||
 | 
			
		||||
            function hide(dispose) {
 | 
			
		||||
                $(document).off("mousedown.red-ui-popover-panel-close");
 | 
			
		||||
@@ -378,22 +591,23 @@ RED.popover = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            function show(options) {
 | 
			
		||||
                var closeCallback = options.onclose;
 | 
			
		||||
                var closeButton = options.closeButton;
 | 
			
		||||
                var target = options.target;
 | 
			
		||||
                var align = options.align || "right";
 | 
			
		||||
                var offset = options.offset || [0,0];
 | 
			
		||||
 | 
			
		||||
                var pos = target.offset();
 | 
			
		||||
                var targetWidth = target.width();
 | 
			
		||||
                var targetHeight = target.height();
 | 
			
		||||
                var targetHeight = target.outerHeight();
 | 
			
		||||
                var panelHeight = panel.height();
 | 
			
		||||
                var panelWidth = panel.width();
 | 
			
		||||
 | 
			
		||||
                var top = (targetHeight+pos.top) + offset[1];
 | 
			
		||||
                if (top+panelHeight > $(window).height()) {
 | 
			
		||||
                if (top+panelHeight-$(document).scrollTop() > $(window).height()) {
 | 
			
		||||
                    top -= (top+panelHeight)-$(window).height() + 5;
 | 
			
		||||
                }
 | 
			
		||||
                if (top < 0) {
 | 
			
		||||
                    panelHeight.height(panelHeight+top)
 | 
			
		||||
                    panel.height(panelHeight+top)
 | 
			
		||||
                    top = 0;
 | 
			
		||||
                }
 | 
			
		||||
                if (align === "right") {
 | 
			
		||||
@@ -420,7 +634,8 @@ RED.popover = (function() {
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $(document).on("mousedown.red-ui-popover-panel-close", function(event) {
 | 
			
		||||
                    if(!$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) {
 | 
			
		||||
                    var hitCloseButton = closeButton && $(event.target).closest(closeButton).length;
 | 
			
		||||
                    if(!hitCloseButton && !$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) {
 | 
			
		||||
                        if (closeCallback) {
 | 
			
		||||
                            closeCallback();
 | 
			
		||||
                        }
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ RED.tabs = (function() {
 | 
			
		||||
        if (options.vertical) {
 | 
			
		||||
            wrapper.addClass("red-ui-tabs-vertical");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (options.addButton) {
 | 
			
		||||
            wrapper.addClass("red-ui-tabs-add");
 | 
			
		||||
            var addButton = $('<div class="red-ui-tab-button red-ui-tabs-add"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
 | 
			
		||||
@@ -75,6 +76,8 @@ RED.tabs = (function() {
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (options.searchButton) {
 | 
			
		||||
            // This is soft-deprecated as we don't use this in the core anymore
 | 
			
		||||
            // We no use the `menu` option to provide a drop-down list of actions
 | 
			
		||||
            wrapper.addClass("red-ui-tabs-search");
 | 
			
		||||
            var searchButton = $('<div class="red-ui-tab-button red-ui-tabs-search"><a href="#"><i class="fa fa-list-ul"></i></a></div>').appendTo(wrapper);
 | 
			
		||||
            searchButton.find('a').on("click", function(evt) {
 | 
			
		||||
@@ -94,17 +97,78 @@ RED.tabs = (function() {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        if (options.menu) {
 | 
			
		||||
            wrapper.addClass("red-ui-tabs-menu");
 | 
			
		||||
            var menuButton = $('<div class="red-ui-tab-button red-ui-tabs-menu"><a href="#"><i class="fa fa-caret-down"></i></a></div>').appendTo(wrapper);
 | 
			
		||||
            var menuButtonLink = menuButton.find('a')
 | 
			
		||||
            var menuOpen = false;
 | 
			
		||||
            var menu;
 | 
			
		||||
            menuButtonLink.on("click", function(evt) {
 | 
			
		||||
                evt.stopPropagation();
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                if (menuOpen) {
 | 
			
		||||
                    menu.remove();
 | 
			
		||||
                    menuOpen = false;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                menuOpen = true;
 | 
			
		||||
                var menuOptions = [];
 | 
			
		||||
                if (typeof options.searchButton === 'function') {
 | 
			
		||||
                    menuOptions = options.menu()
 | 
			
		||||
                } else if (Array.isArray(options.menu)) {
 | 
			
		||||
                    menuOptions = options.menu;
 | 
			
		||||
                } else if (typeof options.menu === 'function') {
 | 
			
		||||
                    menuOptions = options.menu();
 | 
			
		||||
                }
 | 
			
		||||
                menu = RED.menu.init({options: menuOptions});
 | 
			
		||||
                menu.attr("id",options.id+"-menu");
 | 
			
		||||
                menu.css({
 | 
			
		||||
                    position: "absolute"
 | 
			
		||||
                })
 | 
			
		||||
                menu.appendTo("body");
 | 
			
		||||
                var elementPos = menuButton.offset();
 | 
			
		||||
                menu.css({
 | 
			
		||||
                    top: (elementPos.top+menuButton.height()-2)+"px",
 | 
			
		||||
                    left: (elementPos.left - menu.width() + menuButton.width())+"px"
 | 
			
		||||
                })
 | 
			
		||||
                $(".red-ui-menu.red-ui-menu-dropdown").hide();
 | 
			
		||||
                $(document).on("click.red-ui-tabmenu", function(evt) {
 | 
			
		||||
                    $(document).off("click.red-ui-tabmenu");
 | 
			
		||||
                    menuOpen = false;
 | 
			
		||||
                    menu.remove();
 | 
			
		||||
                });
 | 
			
		||||
                menu.show();
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var scrollLeft;
 | 
			
		||||
        var scrollRight;
 | 
			
		||||
 | 
			
		||||
        if (options.scrollable) {
 | 
			
		||||
            wrapper.addClass("red-ui-tabs-scrollable");
 | 
			
		||||
            scrollContainer.addClass("red-ui-tabs-scroll-container");
 | 
			
		||||
            scrollContainer.on("scroll",updateScroll);
 | 
			
		||||
            scrollContainer.on("scroll",function(evt) {
 | 
			
		||||
                // Generated by trackpads - not mousewheel
 | 
			
		||||
                updateScroll(evt);
 | 
			
		||||
            });
 | 
			
		||||
            scrollContainer.on("wheel", function(evt) {
 | 
			
		||||
                if (evt.originalEvent.deltaX === 0) {
 | 
			
		||||
                    // Prevent the scroll event from firing
 | 
			
		||||
                    evt.preventDefault();
 | 
			
		||||
 | 
			
		||||
                    // Assume this is wheel event which might not trigger
 | 
			
		||||
                    // the scroll event, so do things manually
 | 
			
		||||
                    var sl = scrollContainer.scrollLeft();
 | 
			
		||||
                    sl -= evt.originalEvent.deltaY;
 | 
			
		||||
                    scrollContainer.scrollLeft(sl);
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
 | 
			
		||||
            scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
 | 
			
		||||
            scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();});
 | 
			
		||||
            scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
 | 
			
		||||
            scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
 | 
			
		||||
            scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,evt.shiftKey?('+='+(scrollContainer[0].scrollWidth - scrollContainer.width()-scrollContainer.scrollLeft())):'+=150') }).on('click',function(evt){ evt.preventDefault();});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (options.collapsible) {
 | 
			
		||||
@@ -322,6 +386,12 @@ RED.tabs = (function() {
 | 
			
		||||
            if (link.length === 0) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (link.parent().hasClass("hide-tab")) {
 | 
			
		||||
                link.parent().removeClass("hide-tab").removeClass("hide");
 | 
			
		||||
                if (options.onshow) {
 | 
			
		||||
                    options.onshow(tabs[link.attr('href').slice(1)])
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!link.parent().hasClass("active")) {
 | 
			
		||||
                ul.children().removeClass("active");
 | 
			
		||||
                ul.children().css({"transition": "width 100ms"});
 | 
			
		||||
@@ -347,13 +417,13 @@ RED.tabs = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        function activatePreviousTab() {
 | 
			
		||||
            var previous = ul.find("li.active").prev();
 | 
			
		||||
            var previous = findPreviousVisibleTab();
 | 
			
		||||
            if (previous.length > 0) {
 | 
			
		||||
                activateTab(previous.find("a"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        function activateNextTab() {
 | 
			
		||||
            var next = ul.find("li.active").next();
 | 
			
		||||
            var next = findNextVisibleTab();
 | 
			
		||||
            if (next.length > 0) {
 | 
			
		||||
                activateTab(next.find("a"));
 | 
			
		||||
            }
 | 
			
		||||
@@ -363,7 +433,9 @@ RED.tabs = (function() {
 | 
			
		||||
            if (options.vertical) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var tabs = ul.find("li.red-ui-tab");
 | 
			
		||||
            var allTabs = ul.find("li.red-ui-tab");
 | 
			
		||||
            var tabs = allTabs.filter(":not(.hide-tab)");
 | 
			
		||||
            var hiddenTabs = allTabs.filter(".hide-tab");
 | 
			
		||||
            var width = wrapper.width();
 | 
			
		||||
            var tabCount = tabs.length;
 | 
			
		||||
            var tabWidth;
 | 
			
		||||
@@ -373,7 +445,7 @@ RED.tabs = (function() {
 | 
			
		||||
                var visibleCount = collapsedButtonsRow.children(":visible").length;
 | 
			
		||||
                tabWidth = width - collapsedButtonsRow.width()-10;
 | 
			
		||||
                var maxTabWidth = 198;
 | 
			
		||||
                var minTabWidth = 80;
 | 
			
		||||
                var minTabWidth = 120;
 | 
			
		||||
                if (tabWidth <= minTabWidth || (tabWidth < maxTabWidth && visibleCount > 5)) {
 | 
			
		||||
                    // The tab is too small. Hide the next button to make room
 | 
			
		||||
                    // Start at the end of the button row, -1 for the menu button
 | 
			
		||||
@@ -431,6 +503,7 @@ RED.tabs = (function() {
 | 
			
		||||
                // }
 | 
			
		||||
 | 
			
		||||
                tabs.css({width:currentTabWidth});
 | 
			
		||||
                hiddenTabs.css({width:"0px"});
 | 
			
		||||
                if (tabWidth < 50) {
 | 
			
		||||
                    // ul.find(".red-ui-tab-close").hide();
 | 
			
		||||
                    ul.find(".red-ui-tab-icon").hide();
 | 
			
		||||
@@ -471,24 +544,104 @@ RED.tabs = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            var li = ul.find("a[href='#"+id+"']").parent();
 | 
			
		||||
            if (li.hasClass("active")) {
 | 
			
		||||
                var tab = li.prev();
 | 
			
		||||
                var tab = findPreviousVisibleTab(li);
 | 
			
		||||
                if (tab.length === 0) {
 | 
			
		||||
                    tab = li.next();
 | 
			
		||||
                    tab = findNextVisibleTab(li);
 | 
			
		||||
                }
 | 
			
		||||
                if (tab.length > 0) {
 | 
			
		||||
                    activateTab(tab.find("a"));
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (options.onchange) {
 | 
			
		||||
                        options.onchange(null);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                activateTab(tab.find("a"));
 | 
			
		||||
            }
 | 
			
		||||
            li.remove();
 | 
			
		||||
            if (tabs[id].pinned) {
 | 
			
		||||
                pinnedTabsCount--;
 | 
			
		||||
 | 
			
		||||
            li.one("transitionend", function(evt) {
 | 
			
		||||
                li.remove();
 | 
			
		||||
                if (tabs[id].pinned) {
 | 
			
		||||
                    pinnedTabsCount--;
 | 
			
		||||
                }
 | 
			
		||||
                if (options.onremove) {
 | 
			
		||||
                    options.onremove(tabs[id]);
 | 
			
		||||
                }
 | 
			
		||||
                delete tabs[id];
 | 
			
		||||
                updateTabWidths();
 | 
			
		||||
                if (collapsibleMenu) {
 | 
			
		||||
                    collapsibleMenu.remove();
 | 
			
		||||
                    collapsibleMenu = null;
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            li.addClass("hide-tab");
 | 
			
		||||
            li.width(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function findPreviousVisibleTab(li) {
 | 
			
		||||
            if (!li) {
 | 
			
		||||
                li = ul.find("li.active").parent();
 | 
			
		||||
            }
 | 
			
		||||
            if (options.onremove) {
 | 
			
		||||
                options.onremove(tabs[id]);
 | 
			
		||||
            var previous = li.prev();
 | 
			
		||||
            while(previous.length > 0 && previous.hasClass("hide-tab")) {
 | 
			
		||||
                previous = previous.prev();
 | 
			
		||||
            }
 | 
			
		||||
            delete tabs[id];
 | 
			
		||||
            updateTabWidths();
 | 
			
		||||
            if (collapsibleMenu) {
 | 
			
		||||
                collapsibleMenu.remove();
 | 
			
		||||
                collapsibleMenu = null;
 | 
			
		||||
            return previous;
 | 
			
		||||
        }
 | 
			
		||||
        function findNextVisibleTab(li) {
 | 
			
		||||
            if (!li) {
 | 
			
		||||
                li = ul.find("li.active").parent();
 | 
			
		||||
            }
 | 
			
		||||
            var next = ul.find("li.active").next();
 | 
			
		||||
            while(next.length > 0 && next.hasClass("hide-tab")) {
 | 
			
		||||
                next = next.next();
 | 
			
		||||
            }
 | 
			
		||||
            return next;
 | 
			
		||||
        }
 | 
			
		||||
        function showTab(id) {
 | 
			
		||||
            if (tabs[id]) {
 | 
			
		||||
                var li = ul.find("a[href='#"+id+"']").parent();
 | 
			
		||||
                if (li.hasClass("hide-tab")) {
 | 
			
		||||
                    li.removeClass("hide-tab").removeClass("hide");
 | 
			
		||||
                    if (ul.find("li.red-ui-tab:not(.hide-tab)").length === 1) {
 | 
			
		||||
                        activateTab(li.find("a"))
 | 
			
		||||
                    }
 | 
			
		||||
                    updateTabWidths();
 | 
			
		||||
                    if (options.onshow) {
 | 
			
		||||
                        options.onshow(tabs[id])
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        function hideTab(id) {
 | 
			
		||||
            if (tabs[id]) {
 | 
			
		||||
                var li = ul.find("a[href='#"+id+"']").parent();
 | 
			
		||||
                if (!li.hasClass("hide-tab")) {
 | 
			
		||||
                    if (li.hasClass("active")) {
 | 
			
		||||
                        var tab = findPreviousVisibleTab(li);
 | 
			
		||||
                        if (tab.length === 0) {
 | 
			
		||||
                            tab = findNextVisibleTab(li);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (tab.length > 0) {
 | 
			
		||||
                            activateTab(tab.find("a"));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (options.onchange) {
 | 
			
		||||
                                options.onchange(null);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    li.removeClass("active");
 | 
			
		||||
                    li.one("transitionend", function(evt) {
 | 
			
		||||
                        li.addClass("hide");
 | 
			
		||||
                        updateTabWidths();
 | 
			
		||||
                        if (options.onhide) {
 | 
			
		||||
                            options.onhide(tabs[id])
 | 
			
		||||
                        }
 | 
			
		||||
                        setTimeout(function() {
 | 
			
		||||
                            updateScroll()
 | 
			
		||||
                        },200)
 | 
			
		||||
                    })
 | 
			
		||||
                    li.addClass("hide-tab");
 | 
			
		||||
                    li.css({width:0})
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -648,6 +801,7 @@ RED.tabs = (function() {
 | 
			
		||||
                link.on("click", function(evt) { evt.preventDefault(); })
 | 
			
		||||
                link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
 | 
			
		||||
 | 
			
		||||
                $('<span class="red-ui-tabs-fade"></span>').appendTo(li);
 | 
			
		||||
 | 
			
		||||
                if (tab.closeable) {
 | 
			
		||||
                    li.addClass("red-ui-tabs-closeable")
 | 
			
		||||
@@ -657,6 +811,18 @@ RED.tabs = (function() {
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                        removeTab(tab.id);
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
 | 
			
		||||
                }
 | 
			
		||||
                if (tab.hideable) {
 | 
			
		||||
                    li.addClass("red-ui-tabs-closeable")
 | 
			
		||||
                    var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
 | 
			
		||||
                    closeLink.append('<i class="fa fa-eye" />');
 | 
			
		||||
                    closeLink.append('<i class="fa fa-eye-slash" />');
 | 
			
		||||
                    closeLink.on("click",function(event) {
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                        hideTab(tab.id);
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
 | 
			
		||||
@@ -664,10 +830,13 @@ RED.tabs = (function() {
 | 
			
		||||
                    $('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
 | 
			
		||||
                    $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // link.attr("title",tab.label);
 | 
			
		||||
                RED.popover.tooltip(link,function() { return tab.label})
 | 
			
		||||
 | 
			
		||||
                if (options.onadd) {
 | 
			
		||||
                    options.onadd(tab);
 | 
			
		||||
                }
 | 
			
		||||
                link.attr("title",tab.label);
 | 
			
		||||
                if (ul.find("li.red-ui-tab").length == 1) {
 | 
			
		||||
                    activateTab(link);
 | 
			
		||||
                }
 | 
			
		||||
@@ -768,19 +937,37 @@ RED.tabs = (function() {
 | 
			
		||||
            previousTab: activatePreviousTab,
 | 
			
		||||
            resize: updateTabWidths,
 | 
			
		||||
            count: function() {
 | 
			
		||||
                return ul.find("li.red-ui-tab").length;
 | 
			
		||||
                return ul.find("li.red-ui-tab:not(.hide)").length;
 | 
			
		||||
            },
 | 
			
		||||
            activeIndex: function() {
 | 
			
		||||
                return ul.find("li.active").index()
 | 
			
		||||
            },
 | 
			
		||||
            contains: function(id) {
 | 
			
		||||
                return ul.find("a[href='#"+id+"']").length > 0;
 | 
			
		||||
            },
 | 
			
		||||
            showTab: showTab,
 | 
			
		||||
            hideTab: hideTab,
 | 
			
		||||
 | 
			
		||||
            renameTab: function(id,label) {
 | 
			
		||||
                tabs[id].label = label;
 | 
			
		||||
                var tab = ul.find("a[href='#"+id+"']");
 | 
			
		||||
                tab.attr("title",label);
 | 
			
		||||
                tab.find("span.red-ui-text-bidi-aware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
 | 
			
		||||
                updateTabWidths();
 | 
			
		||||
            },
 | 
			
		||||
            listTabs: function() {
 | 
			
		||||
                return $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
 | 
			
		||||
            },
 | 
			
		||||
            selection: getSelection,
 | 
			
		||||
            clearSelection: function() {
 | 
			
		||||
                if (options.onselect) {
 | 
			
		||||
                    var selection = ul.find("li.red-ui-tab.selected");
 | 
			
		||||
                    if (selection.length > 0) {
 | 
			
		||||
                        selection.removeClass("selected");
 | 
			
		||||
                        selectionChanged();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            order: function(order) {
 | 
			
		||||
                preferredOrder = order;
 | 
			
		||||
                var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
            });
 | 
			
		||||
            this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"></button>');
 | 
			
		||||
            if (enabledLabel || disabledLabel) {
 | 
			
		||||
                this.buttonLabel = $("<span>").appendTo(this.button);
 | 
			
		||||
                this.buttonLabel = $("<span>").appendTo(this.button).css("margin-left", "5px");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.options.class) {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,9 @@
 | 
			
		||||
 *   - rootSortable: boolean - if 'sortable' is set, then setting this to
 | 
			
		||||
 *                             false, prevents items being sorted to the
 | 
			
		||||
 *                             top level of the tree
 | 
			
		||||
 *   - autoSelect: boolean - default true - triggers item selection when navigating
 | 
			
		||||
 *                           list by keyboard. If the list has checkboxed items
 | 
			
		||||
 *                           you probably want to set this to false
 | 
			
		||||
 *
 | 
			
		||||
 * methods:
 | 
			
		||||
 *   - data(items) - clears existing items and replaces with new data
 | 
			
		||||
@@ -41,6 +44,7 @@
 | 
			
		||||
 *         sublabel: 'Local', // a sub-label for the item
 | 
			
		||||
 *         icon: 'fa fa-rocket', // (optional) icon for the item
 | 
			
		||||
 *         checkbox: true/false, // (optional) if present, display checkbox accordingly
 | 
			
		||||
 *         radio: 'group-name',  // (optional) if present, display radio box - using group-name to set radio group
 | 
			
		||||
 *         selected: true/false, // (optional) whether the item is selected or not
 | 
			
		||||
 *         children: [] | function(done,item) // (optional) an array of child items, or a function
 | 
			
		||||
 *                                       // that will call the `done` callback with an array
 | 
			
		||||
@@ -49,6 +53,7 @@
 | 
			
		||||
 *         deferBuild: true/false, // don't build any ui elements for the item's children
 | 
			
		||||
 *                                    until it is expanded by the user.
 | 
			
		||||
 *         element: // custom dom element to use for the item - ignored if `label` is set
 | 
			
		||||
 *         collapsible: true/false, // prevent a parent item from being collapsed. default true.
 | 
			
		||||
 *     }
 | 
			
		||||
 * ]
 | 
			
		||||
 *
 | 
			
		||||
@@ -89,77 +94,99 @@
 | 
			
		||||
    $.widget( "nodered.treeList", {
 | 
			
		||||
        _create: function() {
 | 
			
		||||
            var that = this;
 | 
			
		||||
 | 
			
		||||
            var autoSelect = true;
 | 
			
		||||
            if (that.options.autoSelect === false) {
 | 
			
		||||
                autoSelect = false;
 | 
			
		||||
            }
 | 
			
		||||
            this.element.addClass('red-ui-treeList');
 | 
			
		||||
            this.element.attr("tabIndex",0);
 | 
			
		||||
            var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
 | 
			
		||||
            this.element.on('keydown', function(evt) {
 | 
			
		||||
                var selected = that._topList.find(".selected").parent().data('data');
 | 
			
		||||
                if (!selected && (evt.keyCode === 40 || evt.keyCode === 38)) {
 | 
			
		||||
                    that.select(that._data[0]);
 | 
			
		||||
                var focussed = that._topList.find(".focus").parent().data('data');
 | 
			
		||||
                if (!focussed && (evt.keyCode === 40 || evt.keyCode === 38)) {
 | 
			
		||||
                    if (that._data[0]) {
 | 
			
		||||
                        if (autoSelect) {
 | 
			
		||||
                            that.select(that._data[0]);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                        }
 | 
			
		||||
                        that._data[0].treeList.label.addClass('focus')
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var target;
 | 
			
		||||
                switch(evt.keyCode) {
 | 
			
		||||
                    case 32: // SPACE
 | 
			
		||||
                    case 13: // ENTER
 | 
			
		||||
                        if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                        if (selected.children) {
 | 
			
		||||
                            if (selected.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                                selected.treeList.collapse()
 | 
			
		||||
                        if (focussed.checkbox) {
 | 
			
		||||
                            focussed.treeList.checkbox.trigger("click");
 | 
			
		||||
                        } else if (focussed.radio) {
 | 
			
		||||
                            focussed.treeList.radio.trigger("click");
 | 
			
		||||
                        } else if (focussed.children) {
 | 
			
		||||
                            if (focussed.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                                focussed.treeList.collapse()
 | 
			
		||||
                            } else {
 | 
			
		||||
                                selected.treeList.expand()
 | 
			
		||||
                                focussed.treeList.expand()
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            that._trigger("confirm",null,selected)
 | 
			
		||||
                            that._trigger("confirm",null,focussed)
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                    case 37: // LEFT
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                        if (selected.children&& selected.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                            selected.treeList.collapse()
 | 
			
		||||
                        } else if (selected.parent) {
 | 
			
		||||
                            target = selected.parent;
 | 
			
		||||
                        if (focussed.children&& focussed.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                            focussed.treeList.collapse()
 | 
			
		||||
                        } else if (focussed.parent) {
 | 
			
		||||
                            target = focussed.parent;
 | 
			
		||||
                        }
 | 
			
		||||
                    break;
 | 
			
		||||
                    case 38: // UP
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                        target = that._getPreviousSibling(selected);
 | 
			
		||||
                        target = that._getPreviousSibling(focussed);
 | 
			
		||||
                        if (target) {
 | 
			
		||||
                            target = that._getLastDescendant(target);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!target && selected.parent) {
 | 
			
		||||
                            target = selected.parent;
 | 
			
		||||
                        if (!target && focussed.parent) {
 | 
			
		||||
                            target = focussed.parent;
 | 
			
		||||
                        }
 | 
			
		||||
                    break;
 | 
			
		||||
                    case 39: // RIGHT
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                        if (selected.children) {
 | 
			
		||||
                            if (!selected.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                                selected.treeList.expand()
 | 
			
		||||
                        if (focussed.children) {
 | 
			
		||||
                            if (!focussed.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                                focussed.treeList.expand()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    break
 | 
			
		||||
                    case 40: //DOWN
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        evt.stopPropagation();
 | 
			
		||||
                        if (selected.children && Array.isArray(selected.children) && selected.children.length > 0 && selected.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                            target = selected.children[0];
 | 
			
		||||
                        if (focussed.children && Array.isArray(focussed.children) && focussed.children.length > 0 && focussed.treeList.container.hasClass("expanded")) {
 | 
			
		||||
                            target = focussed.children[0];
 | 
			
		||||
                        } else {
 | 
			
		||||
                            target = that._getNextSibling(selected);
 | 
			
		||||
                            while (!target && selected.parent) {
 | 
			
		||||
                                selected = selected.parent;
 | 
			
		||||
                                target = that._getNextSibling(selected);
 | 
			
		||||
                            target = that._getNextSibling(focussed);
 | 
			
		||||
                            while (!target && focussed.parent) {
 | 
			
		||||
                                focussed = focussed.parent;
 | 
			
		||||
                                target = that._getNextSibling(focussed);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
                if (target) {
 | 
			
		||||
                    that.select(target);
 | 
			
		||||
                    if (autoSelect) {
 | 
			
		||||
                        that.select(target);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                    }
 | 
			
		||||
                    target.treeList.label.addClass('focus')
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            this._data = [];
 | 
			
		||||
@@ -312,7 +339,8 @@
 | 
			
		||||
            }
 | 
			
		||||
            if (child.depth !== parent.depth+1) {
 | 
			
		||||
                child.depth = parent.depth+1;
 | 
			
		||||
                var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
 | 
			
		||||
                // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
 | 
			
		||||
                var labelPaddingWidth = (((child.gutter&&!child.gutter.hasClass("red-ui-treeList-gutter-float"))?child.gutter.width()+2:0)+(child.depth*20));
 | 
			
		||||
                child.treeList.labelPadding.width(labelPaddingWidth+'px');
 | 
			
		||||
                if (child.element) {
 | 
			
		||||
                    $(child.element).css({
 | 
			
		||||
@@ -348,6 +376,18 @@
 | 
			
		||||
                that._selected.delete(item);
 | 
			
		||||
                delete item.treeList;
 | 
			
		||||
                delete that._items[item.id];
 | 
			
		||||
                if(item.depth === 0) {
 | 
			
		||||
                    for(var key in that._items) {
 | 
			
		||||
                        if (that._items.hasOwnProperty(key)) {
 | 
			
		||||
                            var child = that._items[key];
 | 
			
		||||
                            if(child.parent && child.parent.id === item.id) {
 | 
			
		||||
                                delete that._items[key].treeList;
 | 
			
		||||
                                delete that._items[key];
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    that._data = that._data.filter(function(data) { return data.id !== item.id})
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            item.treeList.insertChildAt = function(newItem,position,select) {
 | 
			
		||||
                newItem.parent = item;
 | 
			
		||||
@@ -449,6 +489,9 @@
 | 
			
		||||
                container.addClass("expanded");
 | 
			
		||||
            }
 | 
			
		||||
            item.treeList.collapse = function() {
 | 
			
		||||
                if (item.collapsible === false) {
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (!item.children) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
@@ -480,7 +523,10 @@
 | 
			
		||||
                    if (item.treeList.container) {
 | 
			
		||||
                        $(item.element).remove();
 | 
			
		||||
                        $(element).appendTo(item.treeList.label);
 | 
			
		||||
                        var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(item.depth*20);
 | 
			
		||||
                        // using the JQuery Object, the gutter width will
 | 
			
		||||
                        // be wrong when the element is reattached the second time
 | 
			
		||||
                        var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20);
 | 
			
		||||
 | 
			
		||||
                        $(element).css({
 | 
			
		||||
                            width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
 | 
			
		||||
                        })
 | 
			
		||||
@@ -516,9 +562,12 @@
 | 
			
		||||
                }).appendTo(label)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
 | 
			
		||||
 | 
			
		||||
            var labelPaddingWidth = ((item.gutter&&!item.gutter.hasClass("red-ui-treeList-gutter-float"))?item.gutter.width()+2:0)+(depth*20);
 | 
			
		||||
 | 
			
		||||
            item.treeList.labelPadding = $('<span>').css({
 | 
			
		||||
                display: "inline-block",
 | 
			
		||||
                "flex-shrink": 0,
 | 
			
		||||
                width:  labelPaddingWidth+'px'
 | 
			
		||||
            }).appendTo(label);
 | 
			
		||||
 | 
			
		||||
@@ -564,7 +613,7 @@
 | 
			
		||||
                    // Already a parent because we've got the angle-right icon
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                $('<i class="fa fa-angle-right" />').appendTo(treeListIcon);
 | 
			
		||||
                $('<i class="fa fa-angle-right" />').toggleClass("hide",item.collapsible === false).appendTo(treeListIcon);
 | 
			
		||||
                treeListIcon.on("click.red-ui-treeList-expand", function(e) {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
@@ -615,6 +664,46 @@
 | 
			
		||||
                    label.on("click", function(e) {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        cb.trigger("click");
 | 
			
		||||
                        that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                        label.addClass('focus')
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                item.treeList.select = function(v) {
 | 
			
		||||
                    if (v !== item.selected) {
 | 
			
		||||
                        cb.trigger("click");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                item.treeList.checkbox = cb;
 | 
			
		||||
                selectWrapper.appendTo(label)
 | 
			
		||||
            } else if (item.radio) {
 | 
			
		||||
                var selectWrapper = $('<span class="red-ui-treeList-icon"></span>');
 | 
			
		||||
                var cb = $('<input class="red-ui-treeList-radio" type="radio">').prop('name', item.radio).prop('checked',item.selected).appendTo(selectWrapper);
 | 
			
		||||
                cb.on('click', function(e) {
 | 
			
		||||
                    e.stopPropagation();
 | 
			
		||||
                });
 | 
			
		||||
                cb.on('change', function(e) {
 | 
			
		||||
                    item.selected = this.checked;
 | 
			
		||||
                    that._selected.forEach(function(selectedItem) {
 | 
			
		||||
                        if (selectedItem.radio === item.radio) {
 | 
			
		||||
                            selectedItem.treeList.label.removeClass("selected");
 | 
			
		||||
                            selectedItem.selected = false;
 | 
			
		||||
                            that._selected.delete(selectedItem);
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    if (item.selected) {
 | 
			
		||||
                        that._selected.add(item);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        that._selected.delete(item);
 | 
			
		||||
                    }
 | 
			
		||||
                    label.toggleClass("selected",this.checked);
 | 
			
		||||
                    that._trigger("select",e,item);
 | 
			
		||||
                })
 | 
			
		||||
                if (!item.children) {
 | 
			
		||||
                    label.on("click", function(e) {
 | 
			
		||||
                        e.stopPropagation();
 | 
			
		||||
                        cb.trigger("click");
 | 
			
		||||
                        that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                        label.addClass('focus')
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                item.treeList.select = function(v) {
 | 
			
		||||
@@ -623,6 +712,7 @@
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                selectWrapper.appendTo(label)
 | 
			
		||||
                item.treeList.radio = cb;
 | 
			
		||||
            } else {
 | 
			
		||||
                label.on("click", function(e) {
 | 
			
		||||
                    if (!that.options.multi) {
 | 
			
		||||
@@ -630,10 +720,14 @@
 | 
			
		||||
                    }
 | 
			
		||||
                    label.addClass("selected");
 | 
			
		||||
                    that._selected.add(item);
 | 
			
		||||
                    that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                    label.addClass('focus')
 | 
			
		||||
 | 
			
		||||
                    that._trigger("select",e,item)
 | 
			
		||||
                })
 | 
			
		||||
                label.on("dblclick", function(e) {
 | 
			
		||||
                    that._topList.find(".focus").removeClass("focus")
 | 
			
		||||
                    label.addClass('focus')
 | 
			
		||||
                    if (!item.children) {
 | 
			
		||||
                        that._trigger("confirm",e,item);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -781,6 +875,9 @@
 | 
			
		||||
            if (item.treeList.label) {
 | 
			
		||||
                item.treeList.label.addClass("selected");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            that._topList.find(".focus").removeClass("focus");
 | 
			
		||||
 | 
			
		||||
            if (triggerEvent !== false) {
 | 
			
		||||
                this._trigger("select",null,item)
 | 
			
		||||
            }
 | 
			
		||||
@@ -788,6 +885,9 @@
 | 
			
		||||
        clearSelection: function() {
 | 
			
		||||
            this._selected.forEach(function(item) {
 | 
			
		||||
                item.selected = false;
 | 
			
		||||
                if (item.treeList.checkbox) {
 | 
			
		||||
                    item.treeList.checkbox.prop('checked',false)
 | 
			
		||||
                }
 | 
			
		||||
                if (item.treeList.label) {
 | 
			
		||||
                    item.treeList.label.removeClass("selected")
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 **/
 | 
			
		||||
(function($) {
 | 
			
		||||
    var contextParse = function(v,defaultStore) {
 | 
			
		||||
        var parts = RED.utils.parseContextKey(v, defaultStore);
 | 
			
		||||
        var parts = RED.utils.parseContextKey(v, defaultStore&&defaultStore.value);
 | 
			
		||||
        return {
 | 
			
		||||
            option: parts.store,
 | 
			
		||||
            value: parts.key
 | 
			
		||||
@@ -53,8 +53,88 @@
 | 
			
		||||
        }
 | 
			
		||||
        return icon;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var autoComplete = function(options) {
 | 
			
		||||
        return function(val) {
 | 
			
		||||
            var matches = [];
 | 
			
		||||
            options.forEach(opt => {
 | 
			
		||||
                let v = opt.value;
 | 
			
		||||
                var i = v.toLowerCase().indexOf(val.toLowerCase());
 | 
			
		||||
                if (i > -1) {
 | 
			
		||||
                    var pre = v.substring(0,i);
 | 
			
		||||
                    var matchedVal = v.substring(i,i+val.length);
 | 
			
		||||
                    var post = v.substring(i+val.length)
 | 
			
		||||
 | 
			
		||||
                    var el = $('<div/>',{style:"white-space:nowrap; overflow: hidden; flex-grow:1"});
 | 
			
		||||
                    $('<span/>').text(pre).appendTo(el);
 | 
			
		||||
                    $('<span/>',{style:"font-weight: bold"}).text(matchedVal).appendTo(el);
 | 
			
		||||
                    $('<span/>').text(post).appendTo(el);
 | 
			
		||||
 | 
			
		||||
                    var element = $('<div>',{style: "display: flex"});
 | 
			
		||||
                    el.appendTo(element);
 | 
			
		||||
                    if (opt.source) {
 | 
			
		||||
                        $('<div>').css({
 | 
			
		||||
                            "font-size": "0.8em"
 | 
			
		||||
                        }).text(opt.source.join(",")).appendTo(element);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    matches.push({
 | 
			
		||||
                        value: v,
 | 
			
		||||
                        label: element,
 | 
			
		||||
                        i:i
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            matches.sort(function(A,B){return A.i-B.i})
 | 
			
		||||
            return matches;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This is a hand-generated list of completions for the core nodes (based on the node help html).
 | 
			
		||||
    var msgCompletions = [
 | 
			
		||||
        { value: "payload" },
 | 
			
		||||
        { value: "req", source: ["http in"]},
 | 
			
		||||
        { value: "req.body", source: ["http in"]},
 | 
			
		||||
        { value: "req.headers", source: ["http in"]},
 | 
			
		||||
        { value: "req.query", source: ["http in"]},
 | 
			
		||||
        { value: "req.params", source: ["http in"]},
 | 
			
		||||
        { value: "req.cookies", source: ["http in"]},
 | 
			
		||||
        { value: "req.files", source: ["http in"]},
 | 
			
		||||
        { value: "complete", source: ["join"] },
 | 
			
		||||
        { value: "contentType", source: ["mqtt"] },
 | 
			
		||||
        { value: "cookies", source: ["http in","http request"] },
 | 
			
		||||
        { value: "correlationData", source: ["mqtt"] },
 | 
			
		||||
        { value: "delay", source: ["delay","trigger"] },
 | 
			
		||||
        { value: "encoding", source: ["file"] },
 | 
			
		||||
        { value: "error", source: ["catch"] },
 | 
			
		||||
        { value: "filename", source: ["file","file in"] },
 | 
			
		||||
        { value: "flush", source: ["delay"] },
 | 
			
		||||
        { value: "followRedirects", source: ["http request"] },
 | 
			
		||||
        { value: "headers", source: ["http in"," http request"] },
 | 
			
		||||
        { value: "kill", source: ["exec"] },
 | 
			
		||||
        { value: "messageExpiryInterval", source: ["mqtt"] },
 | 
			
		||||
        { value: "method", source: ["http-request"] },
 | 
			
		||||
        { value: "options", source: ["xml"] },
 | 
			
		||||
        { value: "parts", source: ["split","join"] },
 | 
			
		||||
        { value: "pid", source: ["exec"] },
 | 
			
		||||
        { value: "qos", source: ["mqtt"] },
 | 
			
		||||
        { value: "rate", source: ["delay"] },
 | 
			
		||||
        { value: "rejectUnauthorized", source: ["http request"] },
 | 
			
		||||
        { value: "requestTimeout", source: ["http request"] },
 | 
			
		||||
        { value: "reset", source: ["delay","trigger","join","rbe"] },
 | 
			
		||||
        { value: "responseTopic", source: ["mqtt"] },
 | 
			
		||||
        { value: "restartTimeout", source: ["join"] },
 | 
			
		||||
        { value: "retain", source: ["mqtt"] },
 | 
			
		||||
        { value: "select", source: ["html"] },
 | 
			
		||||
        { value: "statusCode", source: ["http in"] },
 | 
			
		||||
        { value: "template", source: ["template"] },
 | 
			
		||||
        { value: "toFront", source: ["delay"] },
 | 
			
		||||
        { value: "topic", source: ["inject","mqtt","rbe"] },
 | 
			
		||||
        { value: "url", source: ["http request"] },
 | 
			
		||||
        { value: "userProperties", source: ["mqtt"] }
 | 
			
		||||
    ]
 | 
			
		||||
    var allOptions = {
 | 
			
		||||
        msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
 | 
			
		||||
        msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
 | 
			
		||||
        flow: {value:"flow",label:"flow.",hasValue:true,
 | 
			
		||||
            options:[],
 | 
			
		||||
            validate:RED.utils.validatePropertyExpression,
 | 
			
		||||
@@ -265,6 +345,47 @@
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // For a type with options, check value is a valid selection
 | 
			
		||||
    // If !opt.multiple, returns the valid option object
 | 
			
		||||
    // if opt.multiple, returns an array of valid option objects
 | 
			
		||||
    // If not valid, returns null;
 | 
			
		||||
 | 
			
		||||
    function isOptionValueValid(opt, currentVal) {
 | 
			
		||||
        if (!opt.multiple) {
 | 
			
		||||
            for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                op = opt.options[i];
 | 
			
		||||
                if (typeof op === "string" && op === currentVal) {
 | 
			
		||||
                    return {value:currentVal}
 | 
			
		||||
                } else if (op.value === currentVal) {
 | 
			
		||||
                    return op;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Check to see if value is a valid csv of
 | 
			
		||||
            // options.
 | 
			
		||||
            var currentValues = {};
 | 
			
		||||
            var selected = [];
 | 
			
		||||
            currentVal.split(",").forEach(function(v) {
 | 
			
		||||
                if (v) {
 | 
			
		||||
                    currentValues[v] = true;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                op = opt.options[i];
 | 
			
		||||
                var val = typeof op === "string" ? op : op.value;
 | 
			
		||||
                if (currentValues.hasOwnProperty(val)) {
 | 
			
		||||
                    delete currentValues[val];
 | 
			
		||||
                    selected.push(typeof op === "string" ? {value:op} : op.value)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!$.isEmptyObject(currentValues)) {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            return selected
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var nlsd = false;
 | 
			
		||||
 | 
			
		||||
    $.widget( "nodered.typedInput", {
 | 
			
		||||
@@ -279,6 +400,14 @@
 | 
			
		||||
                var contextStores = RED.settings.context.stores;
 | 
			
		||||
                var contextOptions = contextStores.map(function(store) {
 | 
			
		||||
                    return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
 | 
			
		||||
                }).sort(function(A,B) {
 | 
			
		||||
                    if (A.value === RED.settings.context.default) {
 | 
			
		||||
                        return -1;
 | 
			
		||||
                    } else if (B.value === RED.settings.context.default) {
 | 
			
		||||
                        return 1;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return A.value.localeCompare(B.value);
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                if (contextOptions.length < 2) {
 | 
			
		||||
                    allOptions.flow.options = [];
 | 
			
		||||
@@ -290,7 +419,8 @@
 | 
			
		||||
            }
 | 
			
		||||
            nlsd = true;
 | 
			
		||||
            var that = this;
 | 
			
		||||
 | 
			
		||||
            this.identifier = this.element.attr('id') || "TypedInput-"+Math.floor(Math.random()*100);
 | 
			
		||||
            if (this.options.debug) { console.log(this.identifier,"Create",{defaultType:this.options.default, value:this.element.val()}) }
 | 
			
		||||
            this.disarmClick = false;
 | 
			
		||||
            this.input = $('<input class="red-ui-typedInput-input" type="text"></input>');
 | 
			
		||||
            this.input.insertAfter(this.element);
 | 
			
		||||
@@ -320,6 +450,8 @@
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.defaultInputType = this.input.attr('type');
 | 
			
		||||
            // Used to remember selections per-type to restore them when switching between types
 | 
			
		||||
            this.oldValues = {};
 | 
			
		||||
 | 
			
		||||
            this.uiSelect.addClass("red-ui-typedInput-container");
 | 
			
		||||
 | 
			
		||||
@@ -372,6 +504,9 @@
 | 
			
		||||
                that.element.trigger('paste',evt);
 | 
			
		||||
            });
 | 
			
		||||
            this.input.on('keydown', function(evt) {
 | 
			
		||||
                if (that.typeMap[that.propertyType].autoComplete) {
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                if (evt.keyCode >= 37 && evt.keyCode <= 40) {
 | 
			
		||||
                    evt.stopPropagation();
 | 
			
		||||
                }
 | 
			
		||||
@@ -389,14 +524,19 @@
 | 
			
		||||
                evt.stopPropagation();
 | 
			
		||||
            }).on('focus', function() {
 | 
			
		||||
                that.uiSelect.addClass('red-ui-typedInput-focus');
 | 
			
		||||
            }).on('blur', function() {
 | 
			
		||||
                var opt = that.typeMap[that.propertyType];
 | 
			
		||||
                if (opt.hasValue === false) {
 | 
			
		||||
                    that.uiSelect.removeClass('red-ui-typedInput-focus');
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
 | 
			
		||||
            this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
 | 
			
		||||
            this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
 | 
			
		||||
            RED.popover.tooltip(this.optionSelectLabel,function() {
 | 
			
		||||
                return that.optionValue;
 | 
			
		||||
            });
 | 
			
		||||
            // RED.popover.tooltip(this.optionSelectLabel,function() {
 | 
			
		||||
            //     return that.optionValue;
 | 
			
		||||
            // });
 | 
			
		||||
            this.optionSelectTrigger.on("click", function(event) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
@@ -415,7 +555,9 @@
 | 
			
		||||
 | 
			
		||||
            this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
 | 
			
		||||
            this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
 | 
			
		||||
            this.type(this.options.default||this.typeList[0].value);
 | 
			
		||||
 | 
			
		||||
            this.type(this.typeField.val() || this.options.default||this.typeList[0].value);
 | 
			
		||||
            this.typeChanged = !!this.options.default;
 | 
			
		||||
        }catch(err) {
 | 
			
		||||
            console.log(err.stack);
 | 
			
		||||
        }
 | 
			
		||||
@@ -438,7 +580,11 @@
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                this._showMenu(this.optionMenu,this.optionSelectTrigger);
 | 
			
		||||
                var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
 | 
			
		||||
                var targetValue = this.optionValue;
 | 
			
		||||
                if (this.optionValue === null || this.optionValue === undefined) {
 | 
			
		||||
                    targetValue = this.value();
 | 
			
		||||
                }
 | 
			
		||||
                var selectedOption = this.optionMenu.find("[value='"+targetValue+"']");
 | 
			
		||||
                if (selectedOption.length === 0) {
 | 
			
		||||
                    selectedOption = this.optionMenu.children(":first");
 | 
			
		||||
                }
 | 
			
		||||
@@ -562,7 +708,7 @@
 | 
			
		||||
            var height = relativeTo.height();
 | 
			
		||||
            var menuHeight = menu.height();
 | 
			
		||||
            var top = (height+pos.top);
 | 
			
		||||
            if (top+menuHeight > $(window).height()) {
 | 
			
		||||
            if (top+menuHeight-$(document).scrollTop() > $(window).height()) {
 | 
			
		||||
                top -= (top+menuHeight)-$(window).height()+5;
 | 
			
		||||
            }
 | 
			
		||||
            if (top < 0) {
 | 
			
		||||
@@ -659,6 +805,7 @@
 | 
			
		||||
            var that = this;
 | 
			
		||||
            var currentType = this.type();
 | 
			
		||||
            this.typeMap = {};
 | 
			
		||||
            var firstCall = (this.typeList === undefined);
 | 
			
		||||
            this.typeList = types.map(function(opt) {
 | 
			
		||||
                var result;
 | 
			
		||||
                if (typeof opt === 'string') {
 | 
			
		||||
@@ -669,6 +816,13 @@
 | 
			
		||||
                that.typeMap[result.value] = result;
 | 
			
		||||
                return result;
 | 
			
		||||
            });
 | 
			
		||||
            if (this.typeList.length < 2) {
 | 
			
		||||
                this.selectTrigger.attr("tabindex", -1)
 | 
			
		||||
                this.selectTrigger.on("mousedown.red-ui-typedInput-focus-block", function(evt) { evt.preventDefault(); })
 | 
			
		||||
            } else {
 | 
			
		||||
                this.selectTrigger.attr("tabindex", 0)
 | 
			
		||||
                this.selectTrigger.off("mousedown.red-ui-typedInput-focus-block")
 | 
			
		||||
            }
 | 
			
		||||
            this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
 | 
			
		||||
            this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1)
 | 
			
		||||
            if (this.menu) {
 | 
			
		||||
@@ -676,10 +830,19 @@
 | 
			
		||||
            }
 | 
			
		||||
            this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
 | 
			
		||||
            if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
 | 
			
		||||
                this.type(this.typeList[0].value);
 | 
			
		||||
                if (!firstCall) {
 | 
			
		||||
                    this.type(this.typeList[0].value);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.propertyType = null;
 | 
			
		||||
                this.type(currentType);
 | 
			
		||||
                if (!firstCall) {
 | 
			
		||||
                    this.type(currentType);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (this.typeList.length === 1 && !this.typeList[0].icon && (!this.typeList[0].label || this.typeList[0].showLabel === false)) {
 | 
			
		||||
                this.selectTrigger.hide()
 | 
			
		||||
            } else {
 | 
			
		||||
                this.selectTrigger.show()
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        width: function(desiredWidth) {
 | 
			
		||||
@@ -690,7 +853,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        value: function(value) {
 | 
			
		||||
            var that = this;
 | 
			
		||||
            var opt = this.typeMap[this.propertyType];
 | 
			
		||||
            // If the default type has been set to an invalid type, then on first
 | 
			
		||||
            // creation, the current propertyType will not exist. Default to an
 | 
			
		||||
            // empty object on the assumption the corrent type will be set shortly
 | 
			
		||||
            var opt = this.typeMap[this.propertyType] || {};
 | 
			
		||||
            if (!arguments.length) {
 | 
			
		||||
                var v = this.input.val();
 | 
			
		||||
                if (opt.export) {
 | 
			
		||||
@@ -698,27 +864,38 @@
 | 
			
		||||
                }
 | 
			
		||||
                return v;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (this.options.debug) { console.log(this.identifier,"----- SET VALUE ------",value) }
 | 
			
		||||
                var selectedOption = [];
 | 
			
		||||
                var valueToCheck = value;
 | 
			
		||||
                if (opt.options) {
 | 
			
		||||
                    var checkValues = [value];
 | 
			
		||||
                    if (opt.hasValue && opt.parse) {
 | 
			
		||||
                        var parts = opt.parse(value);
 | 
			
		||||
                        if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
 | 
			
		||||
                        value = parts.value;
 | 
			
		||||
                        valueToCheck = parts.option || parts.value;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var checkValues = [valueToCheck];
 | 
			
		||||
                    if (opt.multiple) {
 | 
			
		||||
                        selectedOption = [];
 | 
			
		||||
                        checkValues = value.split(",");
 | 
			
		||||
                        checkValues = valueToCheck.split(",");
 | 
			
		||||
                    }
 | 
			
		||||
                    checkValues.forEach(function(value) {
 | 
			
		||||
                    checkValues.forEach(function(valueToCheck) {
 | 
			
		||||
                        for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                            var op = opt.options[i];
 | 
			
		||||
                            if (typeof op === "string") {
 | 
			
		||||
                                if (op === value || op === ""+value) {
 | 
			
		||||
                                if (op === valueToCheck || op === ""+valueToCheck) {
 | 
			
		||||
                                    selectedOption.push(that.activeOptions[op]);
 | 
			
		||||
                                    break;
 | 
			
		||||
                                }
 | 
			
		||||
                            } else if (op.value === value) {
 | 
			
		||||
                            } else if (op.value === valueToCheck) {
 | 
			
		||||
                                selectedOption.push(op);
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    if (this.options.debug) { console.log(this.identifier,"set value to",value) }
 | 
			
		||||
 | 
			
		||||
                    this.input.val(value);
 | 
			
		||||
                    if (!opt.multiple) {
 | 
			
		||||
                        if (selectedOption.length === 0) {
 | 
			
		||||
@@ -743,9 +920,64 @@
 | 
			
		||||
                return this.propertyType;
 | 
			
		||||
            } else {
 | 
			
		||||
                var that = this;
 | 
			
		||||
                if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
 | 
			
		||||
                var previousValue = null;
 | 
			
		||||
                var opt = this.typeMap[type];
 | 
			
		||||
                if (opt && this.propertyType !== type) {
 | 
			
		||||
                    // If previousType is !null, then this is a change of the type, rather than the initialisation
 | 
			
		||||
                    var previousType = this.typeMap[this.propertyType];
 | 
			
		||||
                    previousValue = this.input.val();
 | 
			
		||||
 | 
			
		||||
                    if (previousType && this.typeChanged) {
 | 
			
		||||
                        if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
 | 
			
		||||
                        if (previousType.options && opt.hasValue !== true) {
 | 
			
		||||
                            this.oldValues[previousType.value] = previousValue;
 | 
			
		||||
                        } else if (previousType.hasValue === false) {
 | 
			
		||||
                            this.oldValues[previousType.value] = previousValue;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.oldValues["_"] = previousValue;
 | 
			
		||||
                        }
 | 
			
		||||
                        if ((opt.options && opt.hasValue !== true) || opt.hasValue === false) {
 | 
			
		||||
                            if (this.oldValues.hasOwnProperty(opt.value)) {
 | 
			
		||||
                                if (this.options.debug) { console.log(this.identifier,"restored previous (1)",this.oldValues[opt.value]) }
 | 
			
		||||
                                this.input.val(this.oldValues[opt.value]);
 | 
			
		||||
                            } else if (opt.options) {
 | 
			
		||||
                                // No old value for the option type.
 | 
			
		||||
                                // It is possible code has called 'value' then 'type'
 | 
			
		||||
                                // to set the selected option. This is what the Inject/Switch/Change
 | 
			
		||||
                                // nodes did before 2.1.
 | 
			
		||||
                                // So we need to be careful to not reset the value if it is a valid option.
 | 
			
		||||
                                var validOptions = isOptionValueValid(opt,previousValue);
 | 
			
		||||
                                if (this.options.debug) { console.log(this.identifier,{previousValue,opt,validOptions}) }
 | 
			
		||||
                                if ((previousValue || previousValue === '') && validOptions) {
 | 
			
		||||
                                    if (this.options.debug) { console.log(this.identifier,"restored previous (2)") }
 | 
			
		||||
                                    this.input.val(previousValue);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    if (typeof opt.default === "string") {
 | 
			
		||||
                                        if (this.options.debug) { console.log(this.identifier,"restored previous (3)",opt.default) }
 | 
			
		||||
                                        this.input.val(opt.default);
 | 
			
		||||
                                    } else if (Array.isArray(opt.default)) {
 | 
			
		||||
                                        if (this.options.debug) { console.log(this.identifier,"restored previous (4)",opt.default.join(",")) }
 | 
			
		||||
                                        this.input.val(opt.default.join(","))
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        if (this.options.debug) { console.log(this.identifier,"restored previous (5)") }
 | 
			
		||||
                                        this.input.val("");
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if (this.options.debug) { console.log(this.identifier,"restored default/blank",opt.default||"") }
 | 
			
		||||
                                this.input.val(opt.default||"")
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (this.options.debug) { console.log(this.identifier,"restored old/default/blank") }
 | 
			
		||||
                            this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
 | 
			
		||||
                        }
 | 
			
		||||
                        if (previousType.autoComplete) {
 | 
			
		||||
                            this.input.autoComplete("destroy");
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    this.propertyType = type;
 | 
			
		||||
                    this.typeChanged = true;
 | 
			
		||||
                    if (this.typeField) {
 | 
			
		||||
                        this.typeField.val(type);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -768,6 +1000,11 @@
 | 
			
		||||
                    if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
 | 
			
		||||
                        this.selectLabel.text(opt.label);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (opt.label) {
 | 
			
		||||
                        this.selectTrigger.attr("title",opt.label);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.selectTrigger.attr("title","");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (opt.hasValue === false) {
 | 
			
		||||
                        this.selectTrigger.addClass("red-ui-typedInput-full-width");
 | 
			
		||||
                    } else {
 | 
			
		||||
@@ -809,22 +1046,12 @@
 | 
			
		||||
 | 
			
		||||
                            var op;
 | 
			
		||||
                            if (!opt.hasValue) {
 | 
			
		||||
                                var validValue = false;
 | 
			
		||||
                                var currentVal = this.input.val();
 | 
			
		||||
                                // Check the value is valid for the available options
 | 
			
		||||
                                var validValues = isOptionValueValid(opt,this.input.val());
 | 
			
		||||
                                if (!opt.multiple) {
 | 
			
		||||
                                    for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                                        op = opt.options[i];
 | 
			
		||||
                                        if (typeof op === "string" && op === currentVal) {
 | 
			
		||||
                                            that._updateOptionSelectLabel({value:currentVal});
 | 
			
		||||
                                            validValue = true;
 | 
			
		||||
                                            break;
 | 
			
		||||
                                        } else if (op.value === currentVal) {
 | 
			
		||||
                                            that._updateOptionSelectLabel(op);
 | 
			
		||||
                                            validValue = true;
 | 
			
		||||
                                            break;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (!validValue) {
 | 
			
		||||
                                    if (validValues) {
 | 
			
		||||
                                        that._updateOptionSelectLabel(validValues)
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        op = opt.options[0];
 | 
			
		||||
                                        if (typeof op === "string") {
 | 
			
		||||
                                            this.value(op);
 | 
			
		||||
@@ -835,27 +1062,19 @@
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    // Check to see if value is a valid csv of
 | 
			
		||||
                                    // options.
 | 
			
		||||
                                    var currentValues = {};
 | 
			
		||||
                                    currentVal.split(",").forEach(function(v) {
 | 
			
		||||
                                        if (v) {
 | 
			
		||||
                                            currentValues[v] = true;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    for (var i=0;i<opt.options.length;i++) {
 | 
			
		||||
                                        op = opt.options[i];
 | 
			
		||||
                                        delete currentValues[op.value||op];
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (!$.isEmptyObject(currentValues)) {
 | 
			
		||||
                                        // Invalid, set to default/empty
 | 
			
		||||
                                        this.value((opt.default||[]).join(","));
 | 
			
		||||
                                    if (!validValues) {
 | 
			
		||||
                                        validValues = (opt.default || []).map(function(v) {
 | 
			
		||||
                                            return typeof v === "string"?v:v.value
 | 
			
		||||
                                        });
 | 
			
		||||
                                        this.value(validValues.join(","));
 | 
			
		||||
                                    }
 | 
			
		||||
                                    that._updateOptionSelectLabel(validValues);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                var selectedOption = this.optionValue||opt.options[0];
 | 
			
		||||
                                if (opt.parse) {
 | 
			
		||||
                                    var parts = opt.parse(this.input.val(),selectedOption);
 | 
			
		||||
                                    var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
 | 
			
		||||
                                    var parts = opt.parse(this.input.val(),selectedOptionObj);
 | 
			
		||||
                                    if (parts.option) {
 | 
			
		||||
                                        selectedOption = parts.option;
 | 
			
		||||
                                        if (!this.activeOptions.hasOwnProperty(selectedOption)) {
 | 
			
		||||
@@ -879,6 +1098,7 @@
 | 
			
		||||
                                        this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else if (selectedOption) {
 | 
			
		||||
                                    if (this.options.debug) { console.log(this.identifier,"HERE",{optionValue:selectedOption.value}) }
 | 
			
		||||
                                    this.optionValue = selectedOption.value;
 | 
			
		||||
                                    this._updateOptionSelectLabel(selectedOption);
 | 
			
		||||
                                } else {
 | 
			
		||||
@@ -911,8 +1131,6 @@
 | 
			
		||||
                            this.input.attr('type',this.defaultInputType)
 | 
			
		||||
                        }
 | 
			
		||||
                        if (opt.hasValue === false) {
 | 
			
		||||
                            this.oldValue = this.input.val();
 | 
			
		||||
                            this.input.val("");
 | 
			
		||||
                            this.elementDiv.hide();
 | 
			
		||||
                            this.valueLabelContainer.hide();
 | 
			
		||||
                        } else if (opt.valueLabel) {
 | 
			
		||||
@@ -925,12 +1143,13 @@
 | 
			
		||||
                            this.elementDiv.hide();
 | 
			
		||||
                            opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (this.oldValue !== undefined) {
 | 
			
		||||
                                this.input.val(this.oldValue);
 | 
			
		||||
                                delete this.oldValue;
 | 
			
		||||
                            }
 | 
			
		||||
                            this.valueLabelContainer.hide();
 | 
			
		||||
                            this.elementDiv.show();
 | 
			
		||||
                            if (opt.autoComplete) {
 | 
			
		||||
                                this.input.autoComplete({
 | 
			
		||||
                                    search: opt.autoComplete
 | 
			
		||||
                                })
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (this.optionExpandButton) {
 | 
			
		||||
                            if (opt.expand) {
 | 
			
		||||
@@ -1004,16 +1223,20 @@
 | 
			
		||||
            this.uiSelect.hide();
 | 
			
		||||
        },
 | 
			
		||||
        disable: function(val) {
 | 
			
		||||
            if(val === true) {
 | 
			
		||||
            if(val === undefined || !!val ) {
 | 
			
		||||
                this.uiSelect.attr("disabled", "disabled");
 | 
			
		||||
            } else if (val === false) {
 | 
			
		||||
                this.uiSelect.attr("disabled", null); //remove attr
 | 
			
		||||
            } else {
 | 
			
		||||
                this.uiSelect.attr("disabled", val); //user value
 | 
			
		||||
                this.uiSelect.attr("disabled", null); //remove attr
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        enable: function() {
 | 
			
		||||
            this.uiSelect.attr("disabled", null); //remove attr
 | 
			
		||||
        },
 | 
			
		||||
        disabled: function() {
 | 
			
		||||
            return this.uiSelect.attr("disabled");
 | 
			
		||||
            return this.uiSelect.attr("disabled") === "disabled";
 | 
			
		||||
        },
 | 
			
		||||
        focus: function() {
 | 
			
		||||
            this.input.focus();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})(jQuery);
 | 
			
		||||
 
 | 
			
		||||
@@ -554,6 +554,8 @@ RED.diff = (function() {
 | 
			
		||||
                    color: "#DDAA99",
 | 
			
		||||
                    defaults:{name:{value:""}}
 | 
			
		||||
                }
 | 
			
		||||
            } else if (node.type === "group") {
 | 
			
		||||
                def = RED.group.def;
 | 
			
		||||
            } else {
 | 
			
		||||
                def = {};
 | 
			
		||||
            }
 | 
			
		||||
@@ -763,16 +765,15 @@ RED.diff = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (node.hasOwnProperty('x')) {
 | 
			
		||||
            if (localNode) {
 | 
			
		||||
                if (localNode.x !== node.x || localNode.y !== node.y) {
 | 
			
		||||
                if (localNode.x !== node.x || localNode.y !== node.y || localNode.w !== node.w || localNode.h !== node.h ) {
 | 
			
		||||
                    localChanged = true;
 | 
			
		||||
                    localChanges++;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (remoteNode) {
 | 
			
		||||
                if (remoteNode.x !== node.x || remoteNode.y !== node.y) {
 | 
			
		||||
                if (remoteNode.x !== node.x || remoteNode.y !== node.y|| remoteNode.w !== node.w || remoteNode.h !== node.h) {
 | 
			
		||||
                    remoteChanged = true;
 | 
			
		||||
                    remoteChanges++;
 | 
			
		||||
                }
 | 
			
		||||
@@ -790,7 +791,12 @@ RED.diff = (function() {
 | 
			
		||||
                localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
 | 
			
		||||
                $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
 | 
			
		||||
                element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
 | 
			
		||||
                propertyElements['local.position'] = RED.utils.createObjectElement({x:localNode.x,y:localNode.y},
 | 
			
		||||
                var localPosition = {x:localNode.x,y:localNode.y};
 | 
			
		||||
                if (localNode.hasOwnProperty('w')) {
 | 
			
		||||
                    localPosition.w = localNode.w;
 | 
			
		||||
                    localPosition.h = localNode.h;
 | 
			
		||||
                }
 | 
			
		||||
                propertyElements['local.position'] = RED.utils.createObjectElement(localPosition,
 | 
			
		||||
                    {
 | 
			
		||||
                        path: "position",
 | 
			
		||||
                        exposeApi: true,
 | 
			
		||||
@@ -811,7 +817,12 @@ RED.diff = (function() {
 | 
			
		||||
                if (remoteNode) {
 | 
			
		||||
                    $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
 | 
			
		||||
                    element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
 | 
			
		||||
                    propertyElements['remote.position'] = RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y},
 | 
			
		||||
                    var remotePosition = {x:remoteNode.x,y:remoteNode.y};
 | 
			
		||||
                    if (remoteNode.hasOwnProperty('w')) {
 | 
			
		||||
                        remotePosition.w = remoteNode.w;
 | 
			
		||||
                        remotePosition.h = remoteNode.h;
 | 
			
		||||
                    }
 | 
			
		||||
                    propertyElements['remote.position'] = RED.utils.createObjectElement(remotePosition,
 | 
			
		||||
                        {
 | 
			
		||||
                            path: "position",
 | 
			
		||||
                            exposeApi: true,
 | 
			
		||||
@@ -883,11 +894,11 @@ RED.diff = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
 | 
			
		||||
        var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='w'&&p!=='h'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
 | 
			
		||||
        if (def.defaults) {
 | 
			
		||||
            properties = properties.concat(Object.keys(def.defaults));
 | 
			
		||||
        }
 | 
			
		||||
        if (node.type !== 'tab') {
 | 
			
		||||
        if (node.type !== 'tab' && node.type !== "group") {
 | 
			
		||||
            properties = properties.concat(['inputLabels','outputLabels']);
 | 
			
		||||
        }
 | 
			
		||||
        if ( ((localNode && localNode.hasOwnProperty('icon')) || (remoteNode && remoteNode.hasOwnProperty('icon'))) &&
 | 
			
		||||
@@ -1376,6 +1387,7 @@ RED.diff = (function() {
 | 
			
		||||
 | 
			
		||||
    function mergeDiff(diff) {
 | 
			
		||||
        //console.log(diff);
 | 
			
		||||
        var selectedTab = RED.workspaces.active();
 | 
			
		||||
        var appliedDiff = applyDiff(diff);
 | 
			
		||||
 | 
			
		||||
        var newConfig = appliedDiff.config;
 | 
			
		||||
@@ -1426,6 +1438,7 @@ RED.diff = (function() {
 | 
			
		||||
        RED.view.redraw(true);
 | 
			
		||||
        RED.palette.refresh();
 | 
			
		||||
        RED.workspaces.refresh();
 | 
			
		||||
        RED.workspaces.show(selectedTab, true);
 | 
			
		||||
        RED.sidebar.config.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										107
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @namespace RED.editor.codeEditor
 | 
			
		||||
 */
 | 
			
		||||
 RED.editor.codeEditor = (function() {
 | 
			
		||||
 | 
			
		||||
    const MONACO = "monaco";
 | 
			
		||||
    const ACE = "ace";
 | 
			
		||||
    const defaultEditor = ACE;
 | 
			
		||||
    const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} };
 | 
			
		||||
    var selectedCodeEditor = null;
 | 
			
		||||
    var initialised = false;
 | 
			
		||||
 | 
			
		||||
    function init() {
 | 
			
		||||
        var codeEditorSettings = RED.editor.codeEditor.settings;
 | 
			
		||||
        var editorChoice = codeEditorSettings.lib === MONACO ? MONACO : ACE;
 | 
			
		||||
        try {
 | 
			
		||||
            var browser = RED.utils.getBrowserInfo();
 | 
			
		||||
            selectedCodeEditor = RED.editor.codeEditor[editorChoice];
 | 
			
		||||
            //fall back to default code editor if there are any issues
 | 
			
		||||
            if (!selectedCodeEditor || (editorChoice === MONACO && (browser.ie || !window.monaco))) {
 | 
			
		||||
                selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
 | 
			
		||||
            }
 | 
			
		||||
            initialised = selectedCodeEditor.init();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            selectedCodeEditor = null;
 | 
			
		||||
            console.warn("Problem initialising '" + editorChoice + "' code editor", error);
 | 
			
		||||
        }
 | 
			
		||||
        if(!initialised) {
 | 
			
		||||
            selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
 | 
			
		||||
            initialised = selectedCodeEditor.init();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function create(options) {
 | 
			
		||||
        //TODO: (quandry - for consideration) 
 | 
			
		||||
        // Below, I had to create a hidden element if options.id || options.element is not in the DOM
 | 
			
		||||
        // I have seen 1 node calling  `this.editor = RED.editor.createEditor()` with an 
 | 
			
		||||
        // invalid (non existing html element selector) (e.g. node-red-contrib-components does this)
 | 
			
		||||
        // This causes monaco to throw an error when attempting to hook up its events to the dom  & the rest of the 'oneditperapre' 
 | 
			
		||||
        // code is thus skipped. 
 | 
			
		||||
        // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI)
 | 
			
		||||
        // Because one (or more) contrib nodes have left this bad code in place, how would we handle this?
 | 
			
		||||
        // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur.
 | 
			
		||||
        // IMO, we should warn and exit as it is a coding error by the contrib author.
 | 
			
		||||
 | 
			
		||||
        if (!options) {
 | 
			
		||||
            console.warn("createEditor() options are missing");
 | 
			
		||||
            options = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.editor.type === MONACO) {
 | 
			
		||||
            // compatibility (see above note)
 | 
			
		||||
            if (!options.element && !options.id) {
 | 
			
		||||
                options.id = 'node-backwards-compatability-dummy-editor';
 | 
			
		||||
            }
 | 
			
		||||
            options.element = options.element || $("#" + options.id)[0];
 | 
			
		||||
            if (!options.element) {
 | 
			
		||||
                console.warn("createEditor() options.element or options.id is not valid", options);
 | 
			
		||||
                $("#dialog-form").append('<div id="' + options.id + '" style="display: none;" />');
 | 
			
		||||
            }
 | 
			
		||||
            return this.editor.create(options);
 | 
			
		||||
        } else {
 | 
			
		||||
            return this.editor.create(options);//fallback to ACE
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        /**
 | 
			
		||||
         * Get editor settings object
 | 
			
		||||
         * @memberof RED.editor.codeEditor
 | 
			
		||||
         */
 | 
			
		||||
        get settings() {
 | 
			
		||||
          return RED.settings.get('codeEditor') || DEFAULT_SETTINGS;
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Get user selected code editor
 | 
			
		||||
         * @return {string} Returns 
 | 
			
		||||
         * @memberof RED.editor.codeEditor
 | 
			
		||||
         */
 | 
			
		||||
        get editor() {
 | 
			
		||||
            return selectedCodeEditor;
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Create a editor ui component
 | 
			
		||||
         * @param {object} options - the editor options
 | 
			
		||||
         * @memberof RED.editor.codeEditor
 | 
			
		||||
         */
 | 
			
		||||
        create: create
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										153
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
/*
 | 
			
		||||
* 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.
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* @namespace RED.editor.codeEditor.ace
 | 
			
		||||
*/
 | 
			
		||||
RED.editor.codeEditor.ace = (function() {
 | 
			
		||||
 | 
			
		||||
    const type = "ace";
 | 
			
		||||
    var initialised = false;
 | 
			
		||||
    var initOptions = {};
 | 
			
		||||
 | 
			
		||||
    function init(options) {
 | 
			
		||||
        initOptions = options || {}; 
 | 
			
		||||
        initialised = true;
 | 
			
		||||
        return initialised;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function create(options) {
 | 
			
		||||
        var editorSettings = RED.editor.codeEditor.settings || {};
 | 
			
		||||
        var el = options.element || $("#"+options.id)[0];
 | 
			
		||||
        var toolbarRow = $("<div>").appendTo(el);
 | 
			
		||||
        el = $("<div>").appendTo(el).addClass("red-ui-editor-text-container")[0];
 | 
			
		||||
        var editor = window.ace.edit(el);
 | 
			
		||||
        editor.setTheme(editorSettings.theme || initOptions.theme || "ace/theme/tomorrow");
 | 
			
		||||
        var session = editor.getSession();
 | 
			
		||||
        session.on("changeAnnotation", function () {
 | 
			
		||||
            var annotations = session.getAnnotations() || [];
 | 
			
		||||
            var i = annotations.length;
 | 
			
		||||
            var len = annotations.length;
 | 
			
		||||
            while (i--) {
 | 
			
		||||
                if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
 | 
			
		||||
                else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
 | 
			
		||||
            }
 | 
			
		||||
            if (len > annotations.length) { session.setAnnotations(annotations); }
 | 
			
		||||
        });
 | 
			
		||||
        if (options.mode) {
 | 
			
		||||
            session.setMode(options.mode);
 | 
			
		||||
        }
 | 
			
		||||
        if (options.foldStyle) {
 | 
			
		||||
            session.setFoldStyle(options.foldStyle);
 | 
			
		||||
        } else {
 | 
			
		||||
            session.setFoldStyle('markbeginend');
 | 
			
		||||
        }
 | 
			
		||||
        if (options.options) {
 | 
			
		||||
            editor.setOptions(options.options);
 | 
			
		||||
        } else {
 | 
			
		||||
            editor.setOptions({
 | 
			
		||||
                enableBasicAutocompletion:true,
 | 
			
		||||
                enableSnippets:true,
 | 
			
		||||
                tooltipFollowsMouse: false
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (options.readOnly) {
 | 
			
		||||
            editor.setOption('readOnly',options.readOnly);
 | 
			
		||||
            editor.container.classList.add("ace_read-only");
 | 
			
		||||
        }
 | 
			
		||||
        if (options.hasOwnProperty('lineNumbers')) {
 | 
			
		||||
            editor.renderer.setOption('showGutter',options.lineNumbers);
 | 
			
		||||
        }
 | 
			
		||||
        editor.$blockScrolling = Infinity;
 | 
			
		||||
        if (options.value) {
 | 
			
		||||
            session.setValue(options.value,-1);
 | 
			
		||||
        }
 | 
			
		||||
        if (options.globals) {
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                if (!!session.$worker) {
 | 
			
		||||
                    session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]);
 | 
			
		||||
                }
 | 
			
		||||
            },100);
 | 
			
		||||
        }
 | 
			
		||||
        if (options.mode === 'ace/mode/markdown') {
 | 
			
		||||
            $(el).addClass("red-ui-editor-text-container-toolbar");
 | 
			
		||||
            editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
 | 
			
		||||
            if (options.expandable !== false) {
 | 
			
		||||
                var expandButton = $('<button type="button" class="red-ui-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
 | 
			
		||||
                RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
 | 
			
		||||
                expandButton.on("click", function(e) {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    var value = editor.getValue();
 | 
			
		||||
                    RED.editor.editMarkdown({
 | 
			
		||||
                        value: value,
 | 
			
		||||
                        width: "Infinity",
 | 
			
		||||
                        cursor: editor.getCursorPosition(),
 | 
			
		||||
                        complete: function(v,cursor) {
 | 
			
		||||
                            editor.setValue(v, -1);
 | 
			
		||||
                            editor.gotoLine(cursor.row+1,cursor.column,false);
 | 
			
		||||
                            setTimeout(function() {
 | 
			
		||||
                                editor.focus();
 | 
			
		||||
                            },300);
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            var helpButton = $('<button type="button" class="red-ui-editor-text-help red-ui-button red-ui-button-small"><i class="fa fa-question"></i></button>').appendTo($(el).parent());
 | 
			
		||||
            RED.popover.create({
 | 
			
		||||
                target: helpButton,
 | 
			
		||||
                trigger: 'click',
 | 
			
		||||
                size: "small",
 | 
			
		||||
                direction: "left",
 | 
			
		||||
                content: RED._("markdownEditor.format"),
 | 
			
		||||
                autoClose: 50
 | 
			
		||||
            });
 | 
			
		||||
            session.setUseWrapMode(true);
 | 
			
		||||
        }
 | 
			
		||||
        editor._destroy = editor.destroy;
 | 
			
		||||
        editor.destroy = function() {
 | 
			
		||||
            try {
 | 
			
		||||
                this._destroy();
 | 
			
		||||
            } catch (e) { }
 | 
			
		||||
            $(el).remove();
 | 
			
		||||
            $(toolbarRow).remove();
 | 
			
		||||
        }
 | 
			
		||||
        editor.type = type;
 | 
			
		||||
        return editor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        /**
 | 
			
		||||
         * Editor type
 | 
			
		||||
         * @memberof RED.editor.codeEditor.ace
 | 
			
		||||
         */
 | 
			
		||||
         get type() { return type; },
 | 
			
		||||
         /**
 | 
			
		||||
          * Editor initialised
 | 
			
		||||
         * @memberof RED.editor.codeEditor.ace
 | 
			
		||||
         */
 | 
			
		||||
        get initialised() { return initialised; },
 | 
			
		||||
        /**
 | 
			
		||||
         * Initialise code editor
 | 
			
		||||
         * @param {object} options - initialisation options
 | 
			
		||||
         * @memberof RED.editor.codeEditor.ace
 | 
			
		||||
         */
 | 
			
		||||
         init: init,
 | 
			
		||||
         /**
 | 
			
		||||
          * Create a code editor
 | 
			
		||||
          * @param {object} options - the editor options
 | 
			
		||||
          * @memberof RED.editor.codeEditor.ace
 | 
			
		||||
          */
 | 
			
		||||
         create: create
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										1391
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1391
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
RED.colorPicker = (function() {
 | 
			
		||||
RED.editor.colorPicker = RED.colorPicker = (function() {
 | 
			
		||||
 | 
			
		||||
    function create(options) {
 | 
			
		||||
        var color = options.value;
 | 
			
		||||
							
								
								
									
										616
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										616
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,616 @@
 | 
			
		||||
RED.editor.envVarList = (function() {
 | 
			
		||||
 | 
			
		||||
    var currentLocale = 'en-US';
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create env var edit interface
 | 
			
		||||
     * @param container - container
 | 
			
		||||
     * @param node - subflow node
 | 
			
		||||
     */
 | 
			
		||||
    function buildPropertiesList(envContainer, node) {
 | 
			
		||||
 | 
			
		||||
        var isTemplateNode = (node.type === "subflow");
 | 
			
		||||
 | 
			
		||||
        envContainer
 | 
			
		||||
            .css({
 | 
			
		||||
                'min-height':'150px',
 | 
			
		||||
                'min-width':'450px'
 | 
			
		||||
            })
 | 
			
		||||
            .editableList({
 | 
			
		||||
                header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined,
 | 
			
		||||
                addItem: function(container, i, opt) {
 | 
			
		||||
                    // If this is an instance node, these are properties unique to
 | 
			
		||||
                    // this instance - ie opt.parent will not be defined.
 | 
			
		||||
 | 
			
		||||
                    if (isTemplateNode) {
 | 
			
		||||
                        container.addClass("red-ui-editor-subflow-env-editable")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var envRow = $('<div/>').appendTo(container);
 | 
			
		||||
                    var nameField = null;
 | 
			
		||||
                    var valueField = null;
 | 
			
		||||
 | 
			
		||||
                    nameField = $('<input/>', {
 | 
			
		||||
                        class: "node-input-env-name",
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                        placeholder: RED._("common.label.name")
 | 
			
		||||
                    }).attr("autocomplete","disable").appendTo(envRow).val(opt.name);
 | 
			
		||||
                    valueField = $('<input/>',{
 | 
			
		||||
                        style: "width:100%",
 | 
			
		||||
                        class: "node-input-env-value",
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                    }).attr("autocomplete","disable").appendTo(envRow)
 | 
			
		||||
                    valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED});
 | 
			
		||||
                    valueField.typedInput('type', opt.type);
 | 
			
		||||
                    if (opt.type === "cred") {
 | 
			
		||||
                        if (opt.value) {
 | 
			
		||||
                            valueField.typedInput('value', opt.value);
 | 
			
		||||
                        } else if (node.credentials && node.credentials[opt.name]) {
 | 
			
		||||
                            valueField.typedInput('value', node.credentials[opt.name]);
 | 
			
		||||
                        } else if (node.credentials && node.credentials['has_'+opt.name]) {
 | 
			
		||||
                            valueField.typedInput('value', "__PWRD__");
 | 
			
		||||
                        } else {
 | 
			
		||||
                            valueField.typedInput('value', "");
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        valueField.typedInput('value', opt.value);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    opt.nameField = nameField;
 | 
			
		||||
                    opt.valueField = valueField;
 | 
			
		||||
 | 
			
		||||
                    var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow);
 | 
			
		||||
                    $('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
 | 
			
		||||
                    var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
 | 
			
		||||
                    actionButton.on("click", function(evt) {
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        removeTip.close();
 | 
			
		||||
                        container.parent().addClass("red-ui-editableList-item-deleting")
 | 
			
		||||
                        container.fadeOut(300, function() {
 | 
			
		||||
                            envContainer.editableList('removeItem',opt);
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if (isTemplateNode) {
 | 
			
		||||
                        // Add the UI customisation row
 | 
			
		||||
                        // if `opt.ui` does not exist, then apply defaults. If these
 | 
			
		||||
                        // defaults do not change then they will get stripped off
 | 
			
		||||
                        // before saving.
 | 
			
		||||
                        if (opt.type === 'cred') {
 | 
			
		||||
                            opt.ui = opt.ui || {
 | 
			
		||||
                                icon: "",
 | 
			
		||||
                                type: "cred"
 | 
			
		||||
                            }
 | 
			
		||||
                            opt.ui.type = "cred";
 | 
			
		||||
                        } else {
 | 
			
		||||
                            opt.ui = opt.ui || {
 | 
			
		||||
                                icon: "",
 | 
			
		||||
                                type: "input",
 | 
			
		||||
                                opts: {types:DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        opt.ui.label = opt.ui.label || {};
 | 
			
		||||
                        opt.ui.type = opt.ui.type || "input";
 | 
			
		||||
 | 
			
		||||
                        var uiRow = $('<div/>').appendTo(container).hide();
 | 
			
		||||
                        // save current info for reverting on cancel
 | 
			
		||||
                        // var copy = $.extend(true, {}, ui);
 | 
			
		||||
 | 
			
		||||
                         $('<a href="#"><i class="fa fa-angle-right"></a>').prependTo(envRow).on("click", function (evt) {
 | 
			
		||||
                            evt.preventDefault();
 | 
			
		||||
                            if ($(this).hasClass('expanded')) {
 | 
			
		||||
                                uiRow.slideUp();
 | 
			
		||||
                                $(this).removeClass('expanded');
 | 
			
		||||
                            } else {
 | 
			
		||||
                                uiRow.slideDown();
 | 
			
		||||
                                $(this).addClass('expanded');
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
 | 
			
		||||
                        nameField.trigger('change');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                sortable: ".red-ui-editableList-item-handle",
 | 
			
		||||
                removable: false
 | 
			
		||||
            });
 | 
			
		||||
        var parentEnv = {};
 | 
			
		||||
        var envList = [];
 | 
			
		||||
        if (/^subflow:/.test(node.type)) {
 | 
			
		||||
            var subflowDef = RED.nodes.subflow(node.type.substring(8));
 | 
			
		||||
            if (subflowDef.env) {
 | 
			
		||||
                subflowDef.env.forEach(function(env) {
 | 
			
		||||
                    var item = {
 | 
			
		||||
                        name:env.name,
 | 
			
		||||
                        parent: {
 | 
			
		||||
                            type: env.type,
 | 
			
		||||
                            value: env.value,
 | 
			
		||||
                            ui: env.ui
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    envList.push(item);
 | 
			
		||||
                    parentEnv[env.name] = item;
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (node.env) {
 | 
			
		||||
            for (var i = 0; i < node.env.length; i++) {
 | 
			
		||||
                var env = node.env[i];
 | 
			
		||||
                if (parentEnv.hasOwnProperty(env.name)) {
 | 
			
		||||
                    parentEnv[env.name].type = env.type;
 | 
			
		||||
                    parentEnv[env.name].value = env.value;
 | 
			
		||||
                } else {
 | 
			
		||||
                    envList.push({
 | 
			
		||||
                        name: env.name,
 | 
			
		||||
                        type: env.type,
 | 
			
		||||
                        value: env.value,
 | 
			
		||||
                        ui: env.ui
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        envList.forEach(function(env) {
 | 
			
		||||
            if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!isTemplateNode && env.parent) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            envContainer.editableList('addItem', JSON.parse(JSON.stringify(env)));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create UI edit interface for environment variable
 | 
			
		||||
     * @param container - container
 | 
			
		||||
     * @param env - env var definition
 | 
			
		||||
     * @param nameField - name field of env var
 | 
			
		||||
     * @param valueField - value field of env var
 | 
			
		||||
     */
 | 
			
		||||
     function buildEnvEditRow(container, ui, nameField, valueField) {
 | 
			
		||||
         container.addClass("red-ui-editor-subflow-env-ui-row")
 | 
			
		||||
         var topRow = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div></div>').appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.icon")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.label")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.inputType")).appendTo(topRow);
 | 
			
		||||
 | 
			
		||||
         var row = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
 | 
			
		||||
         var typeOptions = {
 | 
			
		||||
             'input': {types:DEFAULT_ENV_TYPE_LIST},
 | 
			
		||||
             'select': {opts:[]},
 | 
			
		||||
             'spinner': {},
 | 
			
		||||
             'cred': {}
 | 
			
		||||
         };
 | 
			
		||||
         if (ui.opts) {
 | 
			
		||||
             typeOptions[ui.type] = ui.opts;
 | 
			
		||||
         } else {
 | 
			
		||||
             // Pick up the default values if not otherwise provided
 | 
			
		||||
             ui.opts = typeOptions[ui.type];
 | 
			
		||||
         }
 | 
			
		||||
         var iconCell = $('<div></div>').appendTo(row);
 | 
			
		||||
 | 
			
		||||
         var iconButton = $('<a href="#"></a>').appendTo(iconCell);
 | 
			
		||||
         iconButton.on("click", function(evt) {
 | 
			
		||||
             evt.preventDefault();
 | 
			
		||||
             var icon = ui.icon || "";
 | 
			
		||||
             var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
 | 
			
		||||
             RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) {
 | 
			
		||||
                 iconButton.empty();
 | 
			
		||||
                 var path = newIcon || "";
 | 
			
		||||
                 var newPath = RED.utils.separateIconPath(path);
 | 
			
		||||
                 if (newPath) {
 | 
			
		||||
                     $('<i class="fa"></i>').addClass(newPath.file).appendTo(iconButton);
 | 
			
		||||
                 }
 | 
			
		||||
                 ui.icon = path;
 | 
			
		||||
             });
 | 
			
		||||
         })
 | 
			
		||||
 | 
			
		||||
         if (ui.icon) {
 | 
			
		||||
             var newPath = RED.utils.separateIconPath(ui.icon);
 | 
			
		||||
             $('<i class="fa '+newPath.file+'"></i>').appendTo(iconButton);
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         var labelCell = $('<div></div>').appendTo(row);
 | 
			
		||||
 | 
			
		||||
         var label = ui.label && ui.label[currentLocale] || "";
 | 
			
		||||
         var labelInput = $('<input type="text">').val(label).appendTo(labelCell);
 | 
			
		||||
         ui.labelField = labelInput;
 | 
			
		||||
         labelInput.on('change', function(evt) {
 | 
			
		||||
             ui.label = ui.label || {};
 | 
			
		||||
             var val = $(this).val().trim();
 | 
			
		||||
             if (val === "") {
 | 
			
		||||
                 delete ui.label[currentLocale];
 | 
			
		||||
             } else {
 | 
			
		||||
                 ui.label[currentLocale] = val;
 | 
			
		||||
             }
 | 
			
		||||
         })
 | 
			
		||||
         var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelCell);
 | 
			
		||||
         RED.popover.tooltip(labelIcon,function() {
 | 
			
		||||
             var langs = Object.keys(ui.label);
 | 
			
		||||
             var content = $("<div>");
 | 
			
		||||
             if (langs.indexOf(currentLocale) === -1) {
 | 
			
		||||
                 langs.push(currentLocale);
 | 
			
		||||
                 langs.sort();
 | 
			
		||||
             }
 | 
			
		||||
             langs.forEach(function(l) {
 | 
			
		||||
                 var row = $('<div>').appendTo(content);
 | 
			
		||||
                 $('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row);
 | 
			
		||||
                 $('<span>').text(ui.label[l]||"").appendTo(row);
 | 
			
		||||
             });
 | 
			
		||||
             return content;
 | 
			
		||||
         })
 | 
			
		||||
 | 
			
		||||
         nameField.on('change',function(evt) {
 | 
			
		||||
            labelInput.attr("placeholder",$(this).val())
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var inputCell = $('<div></div>').appendTo(row);
 | 
			
		||||
        var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
 | 
			
		||||
        if (ui.type === "input") {
 | 
			
		||||
            inputCellInput.val(ui.opts.types.join(","));
 | 
			
		||||
        }
 | 
			
		||||
        var checkbox;
 | 
			
		||||
        var selectBox;
 | 
			
		||||
 | 
			
		||||
        inputCellInput.typedInput({
 | 
			
		||||
            types: [
 | 
			
		||||
                {
 | 
			
		||||
                    value:"input",
 | 
			
		||||
                    label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[
 | 
			
		||||
                        {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"},
 | 
			
		||||
                        {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"},
 | 
			
		||||
                        {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"},
 | 
			
		||||
                        {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"},
 | 
			
		||||
                        {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"},
 | 
			
		||||
                        {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"},
 | 
			
		||||
                        {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"}
 | 
			
		||||
                    ],
 | 
			
		||||
                    default: DEFAULT_ENV_TYPE_LIST,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
 | 
			
		||||
 | 
			
		||||
                        var input = $('<div class="placeholder-input">').appendTo(innerContainer);
 | 
			
		||||
                        $('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input);
 | 
			
		||||
                        if (value.length) {
 | 
			
		||||
                            value.forEach(function(v) {
 | 
			
		||||
                                if (!/^fa /.test(v.icon)) {
 | 
			
		||||
                                    $('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
 | 
			
		||||
                                    $("<i>",{class: v.icon}).appendTo(s);
 | 
			
		||||
                                }
 | 
			
		||||
                            })
 | 
			
		||||
                        } else {
 | 
			
		||||
                            $('<span class="red-ui-editor-subflow-env-input-type-placeholder"></span>').text(RED._("editor.selectType")).appendTo(input);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value: "cred",
 | 
			
		||||
                    label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type">').css({
 | 
			
		||||
                            "border-top-right-radius": "4px",
 | 
			
		||||
                            "border-bottom-right-radius": "4px"
 | 
			
		||||
                        }).appendTo(container);
 | 
			
		||||
                        $('<div class="placeholder-input">').html("••••••••").appendTo(innerContainer);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"select",
 | 
			
		||||
                    label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding","0");
 | 
			
		||||
 | 
			
		||||
                        selectBox = $('<select></select>').appendTo(container);
 | 
			
		||||
                        if (ui.opts && Array.isArray(ui.opts.opts)) {
 | 
			
		||||
                            ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                                var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale);
 | 
			
		||||
                                // $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox);
 | 
			
		||||
                                $('<option>').val(o.v).text(label).appendTo(selectBox);
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        selectBox.on('change', function(evt) {
 | 
			
		||||
                            var v = selectBox.val();
 | 
			
		||||
                            // var parts = v.split(":");
 | 
			
		||||
                            // var t = parts.shift();
 | 
			
		||||
                            // v = parts.join(":");
 | 
			
		||||
                            //
 | 
			
		||||
                            // valueField.typedInput("type",'str')
 | 
			
		||||
                            valueField.typedInput("value",v)
 | 
			
		||||
                        });
 | 
			
		||||
                        selectBox.val(valueField.typedInput("value"));
 | 
			
		||||
                        // selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value"));
 | 
			
		||||
                    },
 | 
			
		||||
                    expand: {
 | 
			
		||||
                        icon: "fa-caret-down",
 | 
			
		||||
                        minWidth: 400,
 | 
			
		||||
                        content: function(container) {
 | 
			
		||||
                            var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
 | 
			
		||||
                            var optList = $('<ol>').appendTo(content).editableList({
 | 
			
		||||
                                header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"),
 | 
			
		||||
                                addItem: function(row,index,itemData) {
 | 
			
		||||
                                    var labelDiv = $('<div>').appendTo(row);
 | 
			
		||||
                                    var label = lookupLabel(itemData.l, "", currentLocale);
 | 
			
		||||
                                    itemData.label = $('<input type="text">').val(label).appendTo(labelDiv);
 | 
			
		||||
                                    itemData.label.on('keydown', function(evt) {
 | 
			
		||||
                                        if (evt.keyCode === 13) {
 | 
			
		||||
                                            itemData.input.focus();
 | 
			
		||||
                                            evt.preventDefault();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv);
 | 
			
		||||
                                    RED.popover.tooltip(labelIcon,function() {
 | 
			
		||||
                                        return currentLocale;
 | 
			
		||||
                                    })
 | 
			
		||||
                                    itemData.input = $('<input type="text">').val(itemData.v).appendTo(row);
 | 
			
		||||
 | 
			
		||||
                                    // Problem using a TI here:
 | 
			
		||||
                                    //  - this is in a popout panel
 | 
			
		||||
                                    //  - clicking the expand button in the TI will close the parent edit tray
 | 
			
		||||
                                    //    and open the type editor.
 | 
			
		||||
                                    //  - but it leaves the popout panel over the top.
 | 
			
		||||
                                    //  - there is no way to get back to the popout panel after closing the type editor
 | 
			
		||||
                                    //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST});
 | 
			
		||||
                                    itemData.input.on('keydown', function(evt) {
 | 
			
		||||
                                        if (evt.keyCode === 13) {
 | 
			
		||||
                                            // Enter or Tab
 | 
			
		||||
                                            var index = optList.editableList('indexOf',itemData);
 | 
			
		||||
                                            var length = optList.editableList('length');
 | 
			
		||||
                                            if (index + 1 === length) {
 | 
			
		||||
                                                var newItem = {};
 | 
			
		||||
                                                optList.editableList('addItem',newItem);
 | 
			
		||||
                                                setTimeout(function() {
 | 
			
		||||
                                                    if (newItem.label) {
 | 
			
		||||
                                                        newItem.label.focus();
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                },100)
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                var nextItem = optList.editableList('getItemAt',index+1);
 | 
			
		||||
                                                if (nextItem.label) {
 | 
			
		||||
                                                    nextItem.label.focus()
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            evt.preventDefault();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                },
 | 
			
		||||
                                sortable: true,
 | 
			
		||||
                                removable: true,
 | 
			
		||||
                                height: 160
 | 
			
		||||
                            })
 | 
			
		||||
                            if (ui.opts.opts.length > 0) {
 | 
			
		||||
                                ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                                    optList.editableList('addItem',$.extend(true,{},o))
 | 
			
		||||
                                })
 | 
			
		||||
                            } else {
 | 
			
		||||
                                optList.editableList('addItem',{})
 | 
			
		||||
                            }
 | 
			
		||||
                            return {
 | 
			
		||||
                                onclose: function() {
 | 
			
		||||
                                    var items = optList.editableList('items');
 | 
			
		||||
                                    var vals = [];
 | 
			
		||||
                                    items.each(function (i,el) {
 | 
			
		||||
                                        var data = el.data('data');
 | 
			
		||||
                                        var l = data.label.val().trim();
 | 
			
		||||
                                        var v = data.input.val();
 | 
			
		||||
                                        // var t = data.input.typedInput('type');
 | 
			
		||||
                                        // var v = data.input.typedInput('value');
 | 
			
		||||
                                        if (l.length > 0) {
 | 
			
		||||
                                            data.l = data.l || {};
 | 
			
		||||
                                            data.l[currentLocale] = l;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        data.v = v;
 | 
			
		||||
 | 
			
		||||
                                        if (l.length > 0 || v.length > 0) {
 | 
			
		||||
                                            var val = {l:data.l,v:data.v};
 | 
			
		||||
                                            // if (t !== 'str') {
 | 
			
		||||
                                            //     val.t = t;
 | 
			
		||||
                                            // }
 | 
			
		||||
                                            vals.push(val);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    ui.opts.opts = vals;
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"checkbox",
 | 
			
		||||
                    label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        checkbox = $('<input type="checkbox">').appendTo(container);
 | 
			
		||||
                        checkbox.on('change', function(evt) {
 | 
			
		||||
                            valueField.typedInput('value',$(this).prop('checked')?"true":"false");
 | 
			
		||||
                        })
 | 
			
		||||
                        checkbox.prop('checked',valueField.typedInput('value')==="true");
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"spinner",
 | 
			
		||||
                    label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
 | 
			
		||||
 | 
			
		||||
                        var input = $('<div class="placeholder-input">').appendTo(innerContainer);
 | 
			
		||||
                        $('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input);
 | 
			
		||||
 | 
			
		||||
                        var min = ui.opts && ui.opts.min;
 | 
			
		||||
                        var max = ui.opts && ui.opts.max;
 | 
			
		||||
                        var label = "";
 | 
			
		||||
                        if (min !== undefined && max !== undefined) {
 | 
			
		||||
                            label = Math.min(min,max)+" - "+Math.max(min,max);
 | 
			
		||||
                        } else if (min !== undefined) {
 | 
			
		||||
                            label = "> "+min;
 | 
			
		||||
                        } else if (max !== undefined) {
 | 
			
		||||
                            label = "< "+max;
 | 
			
		||||
                        }
 | 
			
		||||
                        $('<span>').css("margin-left","15px").text(label).appendTo(input);
 | 
			
		||||
                    },
 | 
			
		||||
                    expand: {
 | 
			
		||||
                        icon: "fa-caret-down",
 | 
			
		||||
                        content: function(container) {
 | 
			
		||||
                            var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
 | 
			
		||||
                            content.css("padding","8px 5px")
 | 
			
		||||
                            var min = ui.opts.min;
 | 
			
		||||
                            var max = ui.opts.max;
 | 
			
		||||
                            var minInput = $('<input type="number" style="margin-bottom:0; width:60px">');
 | 
			
		||||
                            minInput.val(min);
 | 
			
		||||
                            var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">');
 | 
			
		||||
                            maxInput.val(max);
 | 
			
		||||
                            $('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content);
 | 
			
		||||
                            $('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').append(maxInput).appendTo(content);
 | 
			
		||||
                            return {
 | 
			
		||||
                                onclose: function() {
 | 
			
		||||
                                    var min = minInput.val().trim();
 | 
			
		||||
                                    var max = maxInput.val().trim();
 | 
			
		||||
                                    if (min !== "") {
 | 
			
		||||
                                        ui.opts.min = parseInt(min);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        delete ui.opts.min;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (max !== "") {
 | 
			
		||||
                                        ui.opts.max = parseInt(max);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        delete ui.opts.max;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"none",
 | 
			
		||||
                    label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"hide",
 | 
			
		||||
                    label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            default: 'none'
 | 
			
		||||
        }).on("typedinputtypechange", function(evt,type) {
 | 
			
		||||
            ui.type = $(this).typedInput("type");
 | 
			
		||||
            ui.opts = typeOptions[ui.type];
 | 
			
		||||
            if (ui.type === 'input') {
 | 
			
		||||
                // In the case of 'input' type, the typedInput uses the multiple-option
 | 
			
		||||
                // mode. Its value needs to be set to a comma-separately list of the
 | 
			
		||||
                // selected options.
 | 
			
		||||
                inputCellInput.typedInput('value',ui.opts.types.join(","))
 | 
			
		||||
            } else {
 | 
			
		||||
                // No other type cares about `value`, but doing this will
 | 
			
		||||
                // force a refresh of the label now that `ui.opts` has
 | 
			
		||||
                // been updated.
 | 
			
		||||
                inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch (ui.type) {
 | 
			
		||||
                case 'input':
 | 
			
		||||
                    valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'select':
 | 
			
		||||
                    valueField.typedInput('types',['str']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'checkbox':
 | 
			
		||||
                    valueField.typedInput('types',['bool']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'spinner':
 | 
			
		||||
                    valueField.typedInput('types',['num']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'cred':
 | 
			
		||||
                    valueField.typedInput('types',['cred']);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
 | 
			
		||||
            }
 | 
			
		||||
            if (ui.type === 'checkbox') {
 | 
			
		||||
                valueField.typedInput('type','bool');
 | 
			
		||||
            } else if (ui.type === 'spinner') {
 | 
			
		||||
                valueField.typedInput('type','num');
 | 
			
		||||
            }
 | 
			
		||||
            if (ui.type !== 'checkbox') {
 | 
			
		||||
                checkbox = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }).on("change", function(evt,type) {
 | 
			
		||||
            if (ui.type === 'input') {
 | 
			
		||||
                var types = inputCellInput.typedInput('value');
 | 
			
		||||
                ui.opts.types = (types === "") ? ["str"] : types.split(",");
 | 
			
		||||
                valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        valueField.on("change", function(evt) {
 | 
			
		||||
            if (checkbox) {
 | 
			
		||||
                checkbox.prop('checked',$(this).typedInput('value')==="true")
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        // Set the input to the right type. This will trigger the 'typedinputtypechange'
 | 
			
		||||
        // event handler (just above ^^) to update the value if needed
 | 
			
		||||
        inputCellInput.typedInput('type',ui.type)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setLocale(l, list) {
 | 
			
		||||
        currentLocale = l;
 | 
			
		||||
        if (list) {
 | 
			
		||||
            var items = list.editableList("items");
 | 
			
		||||
            items.each(function (i, item) {
 | 
			
		||||
                var entry = $(this).data('data');
 | 
			
		||||
                var labelField = entry.ui.labelField;
 | 
			
		||||
                labelField.val(lookupLabel(entry.ui.label, "", currentLocale));
 | 
			
		||||
                if (labelField.timeout) {
 | 
			
		||||
                    clearTimeout(labelField.timeout);
 | 
			
		||||
                    delete labelField.timeout;
 | 
			
		||||
                }
 | 
			
		||||
                labelField.addClass("input-updated");
 | 
			
		||||
                labelField.timeout = setTimeout(function() {
 | 
			
		||||
                    delete labelField.timeout
 | 
			
		||||
                    labelField.removeClass("input-updated");
 | 
			
		||||
                },3000);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lookup text for specific locale
 | 
			
		||||
     * @param labels - dict of labels
 | 
			
		||||
     * @param defaultLabel - fallback label if not found
 | 
			
		||||
     * @param locale - target locale
 | 
			
		||||
     * @returns {string} text for specified locale
 | 
			
		||||
     */
 | 
			
		||||
    function lookupLabel(labels, defaultLabel, locale) {
 | 
			
		||||
        if (labels) {
 | 
			
		||||
            if (labels[locale]) {
 | 
			
		||||
                return labels[locale];
 | 
			
		||||
            }
 | 
			
		||||
            if (locale) {
 | 
			
		||||
                var lang = locale.substring(0, 2);
 | 
			
		||||
                if (labels[lang]) {
 | 
			
		||||
                    return labels[lang];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return defaultLabel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        create: buildPropertiesList,
 | 
			
		||||
        setLocale: setLocale,
 | 
			
		||||
        lookupLabel: lookupLabel,
 | 
			
		||||
        DEFAULT_ENV_TYPE_LIST: DEFAULT_ENV_TYPE_LIST,
 | 
			
		||||
        DEFAULT_ENV_TYPE_LIST_INC_CRED: DEFAULT_ENV_TYPE_LIST_INC_CRED
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
@@ -121,73 +121,75 @@
 | 
			
		||||
                    var currentFunctionMarker = null;
 | 
			
		||||
 | 
			
		||||
                    expressionEditor.getSession().setValue(value||"",-1);
 | 
			
		||||
                    expressionEditor.on("changeSelection", function() {
 | 
			
		||||
                        var c = expressionEditor.getCursorPosition();
 | 
			
		||||
                        var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
 | 
			
		||||
                        if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
 | 
			
		||||
                            currentToken = token;
 | 
			
		||||
                            var r,p;
 | 
			
		||||
                            var scopedFunction = null;
 | 
			
		||||
                            if (token && token.type === 'keyword') {
 | 
			
		||||
                                r = c.row;
 | 
			
		||||
                                scopedFunction = token;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                var depth = 0;
 | 
			
		||||
                                var next = false;
 | 
			
		||||
                                if (token) {
 | 
			
		||||
                                    if (token.type === 'paren.rparen') {
 | 
			
		||||
                                        // If this is a block of parens ')))', set
 | 
			
		||||
                                        // depth to offset against the cursor position
 | 
			
		||||
                                        // within the block
 | 
			
		||||
                                        currentTokenPos = c.column;
 | 
			
		||||
                                        depth = c.column - (token.start + token.value.length);
 | 
			
		||||
                                    }
 | 
			
		||||
                    //ace only (monaco has jsonata tokeniser)
 | 
			
		||||
                    if(expressionEditor.type == "ace") {
 | 
			
		||||
                        expressionEditor.on("changeSelection", function() {
 | 
			
		||||
                            var c = expressionEditor.getCursorPosition();
 | 
			
		||||
                            var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
 | 
			
		||||
                            if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
 | 
			
		||||
                                currentToken = token;
 | 
			
		||||
                                var r,p;
 | 
			
		||||
                                var scopedFunction = null;
 | 
			
		||||
                                if (token && token.type === 'keyword') {
 | 
			
		||||
                                    r = c.row;
 | 
			
		||||
                                    p = token.index;
 | 
			
		||||
                                    scopedFunction = token;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    r = c.row-1;
 | 
			
		||||
                                    p = -1;
 | 
			
		||||
                                }
 | 
			
		||||
                                while ( scopedFunction === null && r > -1) {
 | 
			
		||||
                                    var rowTokens = expressionEditor.getSession().getTokens(r);
 | 
			
		||||
                                    if (p === -1) {
 | 
			
		||||
                                        p = rowTokens.length-1;
 | 
			
		||||
                                    var depth = 0;
 | 
			
		||||
                                    var next = false;
 | 
			
		||||
                                    if (token) {
 | 
			
		||||
                                        if (token.type === 'paren.rparen') {
 | 
			
		||||
                                            // If this is a block of parens ')))', set
 | 
			
		||||
                                            // depth to offset against the cursor position
 | 
			
		||||
                                            // within the block
 | 
			
		||||
                                            currentTokenPos = c.column;
 | 
			
		||||
                                            depth = c.column - (token.start + token.value.length);
 | 
			
		||||
                                        }
 | 
			
		||||
                                        r = c.row;
 | 
			
		||||
                                        p = token.index;
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        r = c.row-1;
 | 
			
		||||
                                        p = -1;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    while (p > -1) {
 | 
			
		||||
                                        var type = rowTokens[p].type;
 | 
			
		||||
                                        if (next) {
 | 
			
		||||
                                            if (type === 'keyword') {
 | 
			
		||||
                                                scopedFunction = rowTokens[p];
 | 
			
		||||
                                                // console.log("HIT",scopedFunction);
 | 
			
		||||
                                                break;
 | 
			
		||||
                                    while ( scopedFunction === null && r > -1) {
 | 
			
		||||
                                        var rowTokens = expressionEditor.getSession().getTokens(r);
 | 
			
		||||
                                        if (p === -1) {
 | 
			
		||||
                                            p = rowTokens.length-1;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        while (p > -1) {
 | 
			
		||||
                                            var type = rowTokens[p].type;
 | 
			
		||||
                                            if (next) {
 | 
			
		||||
                                                if (type === 'keyword') {
 | 
			
		||||
                                                    scopedFunction = rowTokens[p];
 | 
			
		||||
                                                    // console.log("HIT",scopedFunction);
 | 
			
		||||
                                                    break;
 | 
			
		||||
                                                }
 | 
			
		||||
                                                next = false;
 | 
			
		||||
                                            }
 | 
			
		||||
                                            next = false;
 | 
			
		||||
                                            if (type === 'paren.lparen') {
 | 
			
		||||
                                                depth-=rowTokens[p].value.length;
 | 
			
		||||
                                            } else if (type === 'paren.rparen') {
 | 
			
		||||
                                                depth+=rowTokens[p].value.length;
 | 
			
		||||
                                            }
 | 
			
		||||
                                            if (depth < 0) {
 | 
			
		||||
                                                next = true;
 | 
			
		||||
                                                depth = 0;
 | 
			
		||||
                                            }
 | 
			
		||||
                                            // console.log(r,p,depth,next,rowTokens[p]);
 | 
			
		||||
                                            p--;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        if (type === 'paren.lparen') {
 | 
			
		||||
                                            depth-=rowTokens[p].value.length;
 | 
			
		||||
                                        } else if (type === 'paren.rparen') {
 | 
			
		||||
                                            depth+=rowTokens[p].value.length;
 | 
			
		||||
                                        if (!scopedFunction) {
 | 
			
		||||
                                            r--;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        if (depth < 0) {
 | 
			
		||||
                                            next = true;
 | 
			
		||||
                                            depth = 0;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        // console.log(r,p,depth,next,rowTokens[p]);
 | 
			
		||||
                                        p--;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (!scopedFunction) {
 | 
			
		||||
                                        r--;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                expressionEditor.session.removeMarker(currentFunctionMarker);
 | 
			
		||||
                                if (scopedFunction) {
 | 
			
		||||
                                //console.log(token,.map(function(t) { return t.type}));
 | 
			
		||||
                                    funcSelect.val(scopedFunction.value).trigger("change");
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            expressionEditor.session.removeMarker(currentFunctionMarker);
 | 
			
		||||
                            if (scopedFunction) {
 | 
			
		||||
                            //console.log(token,.map(function(t) { return t.type}));
 | 
			
		||||
                                funcSelect.val(scopedFunction.value).trigger("change");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    dialogForm.i18n();
 | 
			
		||||
                    $("#red-ui-editor-type-expression-func-insert").on("click", function(e) {
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
@@ -343,6 +345,7 @@
 | 
			
		||||
                    }
 | 
			
		||||
                    expressionEditor.destroy();
 | 
			
		||||
                    testDataEditor.destroy();
 | 
			
		||||
                    testResultEditor.destroy();
 | 
			
		||||
                },
 | 
			
		||||
                show: function() {}
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										99
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
RED.editor.iconPicker = (function() {
 | 
			
		||||
    function showIconPicker(container, backgroundColor, iconPath, faOnly, done) {
 | 
			
		||||
        var picker = $('<div class="red-ui-icon-picker">');
 | 
			
		||||
        var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(picker);
 | 
			
		||||
        searchInput = $('<input type="text">').attr("placeholder",RED._("editor.searchIcons")).appendTo(searchDiv).searchBox({
 | 
			
		||||
            delay: 50,
 | 
			
		||||
            change: function() {
 | 
			
		||||
                var searchTerm = $(this).val().trim();
 | 
			
		||||
                if (searchTerm === "") {
 | 
			
		||||
                    iconList.find(".red-ui-icon-list-module").show();
 | 
			
		||||
                    iconList.find(".red-ui-icon-list-icon").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    iconList.find(".red-ui-icon-list-module").hide();
 | 
			
		||||
                    iconList.find(".red-ui-icon-list-icon").each(function(i,n) {
 | 
			
		||||
                        if ($(n).data('icon').indexOf(searchTerm) === -1) {
 | 
			
		||||
                            $(n).hide();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            $(n).show();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var row = $('<div>').appendTo(picker);
 | 
			
		||||
        var iconList = $('<div class="red-ui-icon-list">').appendTo(picker);
 | 
			
		||||
        var metaRow = $('<div class="red-ui-icon-meta"></div>').appendTo(picker);
 | 
			
		||||
        var summary = $('<span>').appendTo(metaRow);
 | 
			
		||||
        var resetButton = $('<button type="button" class="red-ui-button red-ui-button-small">'+RED._("editor.useDefault")+'</button>').appendTo(metaRow).on("click", function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            iconPanel.hide();
 | 
			
		||||
            done(null);
 | 
			
		||||
        });
 | 
			
		||||
        if (!backgroundColor && faOnly) {
 | 
			
		||||
            iconList.addClass("red-ui-icon-list-dark");
 | 
			
		||||
        }
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
            var iconSets = RED.nodes.getIconSets();
 | 
			
		||||
            Object.keys(iconSets).forEach(function(moduleName) {
 | 
			
		||||
                if (faOnly && (moduleName !== "font-awesome")) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var icons = iconSets[moduleName];
 | 
			
		||||
                if (icons.length > 0) {
 | 
			
		||||
                    // selectIconModule.append($("<option></option>").val(moduleName).text(moduleName));
 | 
			
		||||
                    var header = $('<div class="red-ui-icon-list-module"></div>').text(moduleName).appendTo(iconList);
 | 
			
		||||
                    $('<i class="fa fa-cube"></i>').prependTo(header);
 | 
			
		||||
                    icons.forEach(function(icon) {
 | 
			
		||||
                        var iconDiv = $('<div>',{class:"red-ui-icon-list-icon"}).appendTo(iconList);
 | 
			
		||||
                        var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconDiv);
 | 
			
		||||
                        var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon;
 | 
			
		||||
                        iconDiv.data('icon',icon_url);
 | 
			
		||||
                        if (backgroundColor) {
 | 
			
		||||
                            nodeDiv.css({
 | 
			
		||||
                                'backgroundColor': backgroundColor
 | 
			
		||||
                            });
 | 
			
		||||
                            var borderColor = RED.utils.getDarkerColor(backgroundColor);
 | 
			
		||||
                            if (borderColor !== backgroundColor) {
 | 
			
		||||
                                nodeDiv.css('border-color',borderColor)
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
 | 
			
		||||
                        RED.utils.createIconElement(icon_url, iconContainer, true);
 | 
			
		||||
 | 
			
		||||
                        if (iconPath.module === moduleName && iconPath.file === icon) {
 | 
			
		||||
                            iconDiv.addClass("selected");
 | 
			
		||||
                        }
 | 
			
		||||
                        iconDiv.on("mouseover", function() {
 | 
			
		||||
                            summary.text(icon);
 | 
			
		||||
                        })
 | 
			
		||||
                        iconDiv.on("mouseout", function() {
 | 
			
		||||
                            summary.html(" ");
 | 
			
		||||
                        })
 | 
			
		||||
                        iconDiv.on("click", function() {
 | 
			
		||||
                            iconPanel.hide();
 | 
			
		||||
                            done(moduleName+"/"+icon);
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                spinner.remove();
 | 
			
		||||
            },50);
 | 
			
		||||
        },300);
 | 
			
		||||
        var spinner = RED.utils.addSpinnerOverlay(iconList,true);
 | 
			
		||||
        var iconPanel = RED.popover.panel(picker);
 | 
			
		||||
        iconPanel.show({
 | 
			
		||||
            target: container
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        picker.slideDown(100);
 | 
			
		||||
        searchInput.trigger("focus");
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
        show: showIconPicker
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
@@ -602,10 +602,10 @@
 | 
			
		||||
 | 
			
		||||
                },
 | 
			
		||||
                close: function() {
 | 
			
		||||
                    // expressionEditor.destroy();
 | 
			
		||||
                    if (options.onclose) {
 | 
			
		||||
                        options.onclose();
 | 
			
		||||
                    }
 | 
			
		||||
                    expressionEditor.destroy();
 | 
			
		||||
                },
 | 
			
		||||
                show: function() {}
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										517
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										517
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,517 @@
 | 
			
		||||
;(function() {
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-appearance", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.appearance"),
 | 
			
		||||
            name: RED._("editor-tab.appearance"),
 | 
			
		||||
            iconClass: "fa fa-object-group",
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
                this.content = container;
 | 
			
		||||
                buildAppearanceForm(this.content,node);
 | 
			
		||||
 | 
			
		||||
                if (node.type === 'subflow') {
 | 
			
		||||
                    this.defaultIcon = "node-red/subflow.svg";
 | 
			
		||||
                } else {
 | 
			
		||||
                    var iconPath = RED.utils.getDefaultNodeIcon(node._def,node);
 | 
			
		||||
                    this.defaultIcon = iconPath.module+"/"+iconPath.file;
 | 
			
		||||
                    if (node.icon && node.icon !== this.defaultIcon) {
 | 
			
		||||
                        this.isDefaultIcon = false;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.isDefaultIcon = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            show: function() {
 | 
			
		||||
                refreshLabelForm(this.content, node);
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                if (updateLabels(node, editState.changes, editState.outputMap)) {
 | 
			
		||||
                    editState.changed = true;
 | 
			
		||||
                }
 | 
			
		||||
                if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
 | 
			
		||||
                    var icon = $("#red-ui-editor-node-icon").val()||""
 | 
			
		||||
                    if (!this.isDefaultIcon) {
 | 
			
		||||
                        if (icon !== node.icon) {
 | 
			
		||||
                            editState.changes.icon = node.icon;
 | 
			
		||||
                            node.icon = icon;
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (icon !== "" && icon !== this.defaultIcon) {
 | 
			
		||||
                            editState.changes.icon = node.icon;
 | 
			
		||||
                            node.icon = icon;
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            var iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
 | 
			
		||||
                            var currentDefaultIcon = iconPath.module+"/"+iconPath.file;
 | 
			
		||||
                            if (this.defaultIcon !== currentDefaultIcon) {
 | 
			
		||||
                                editState.changes.icon = node.icon;
 | 
			
		||||
                                node.icon = currentDefaultIcon;
 | 
			
		||||
                                editState.changed = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (node.type === "subflow") {
 | 
			
		||||
                    var newCategory = $("#subflow-appearance-input-category").val().trim();
 | 
			
		||||
                    if (newCategory === "_custom_") {
 | 
			
		||||
                        newCategory = $("#subflow-appearance-input-custom-category").val().trim();
 | 
			
		||||
                        if (newCategory === "") {
 | 
			
		||||
                            newCategory = node.category;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (newCategory === 'subflows') {
 | 
			
		||||
                        newCategory = '';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (newCategory != node.category) {
 | 
			
		||||
                        editState.changes['category'] = node.category;
 | 
			
		||||
                        node.category = newCategory;
 | 
			
		||||
                        editState.changed = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var oldColor = node.color;
 | 
			
		||||
                    var newColor =  $("#red-ui-editor-node-color").val();
 | 
			
		||||
                    if (oldColor !== newColor) {
 | 
			
		||||
                        editState.changes.color = node.color;
 | 
			
		||||
                        node.color = newColor;
 | 
			
		||||
                        editState.changed = true;
 | 
			
		||||
                        RED.utils.clearNodeColorCache();
 | 
			
		||||
                        if (node.type === "subflow") {
 | 
			
		||||
                            var nodeDefinition = RED.nodes.getType(
 | 
			
		||||
                                "subflow:" + node.id
 | 
			
		||||
                            );
 | 
			
		||||
                            nodeDefinition["color"] = newColor;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                var showLabel = node._def.hasOwnProperty("showLabel")?node._def.showLabel:true;
 | 
			
		||||
 | 
			
		||||
                if (!$("#node-input-show-label").prop('checked')) {
 | 
			
		||||
                    // Not checked - hide label
 | 
			
		||||
 | 
			
		||||
                    if (showLabel) {
 | 
			
		||||
                        // Default to show label
 | 
			
		||||
                        if (node.l !== false) {
 | 
			
		||||
                            editState.changes.l = node.l
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        node.l = false;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Node has showLabel:false (eg link nodes)
 | 
			
		||||
                        if (node.hasOwnProperty('l') && node.l) {
 | 
			
		||||
                            editState.changes.l = node.l
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        delete node.l;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Checked - show label
 | 
			
		||||
                    if (showLabel) {
 | 
			
		||||
                        // Default to show label
 | 
			
		||||
                        if (node.hasOwnProperty('l') && !node.l) {
 | 
			
		||||
                            editState.changes.l = node.l
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        delete node.l;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (!node.l) {
 | 
			
		||||
                            editState.changes.l = node.l
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        node.l = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function buildAppearanceForm(container,node) {
 | 
			
		||||
        var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
 | 
			
		||||
 | 
			
		||||
        var i,row;
 | 
			
		||||
 | 
			
		||||
        if (node.type === "subflow") {
 | 
			
		||||
            var categoryRow = $("<div/>", {
 | 
			
		||||
                class: "form-row"
 | 
			
		||||
            }).appendTo(dialogForm);
 | 
			
		||||
            $("<label/>", {
 | 
			
		||||
                for: "subflow-appearance-input-category",
 | 
			
		||||
                "data-i18n": "editor:subflow.category"
 | 
			
		||||
            }).appendTo(categoryRow);
 | 
			
		||||
            var categorySelector = $("<select/>", {
 | 
			
		||||
                id: "subflow-appearance-input-category"
 | 
			
		||||
            }).css({
 | 
			
		||||
                width: "250px"
 | 
			
		||||
            }).appendTo(categoryRow);
 | 
			
		||||
            $("<input/>", {
 | 
			
		||||
                type: "text",
 | 
			
		||||
                id: "subflow-appearance-input-custom-category"
 | 
			
		||||
            }).css({
 | 
			
		||||
                display: "none",
 | 
			
		||||
                "margin-left": "10px",
 | 
			
		||||
                width: "calc(100% - 250px)"
 | 
			
		||||
            }).appendTo(categoryRow);
 | 
			
		||||
 | 
			
		||||
            var categories = RED.palette.getCategories();
 | 
			
		||||
            categories.sort(function(A,B) {
 | 
			
		||||
                return A.label.localeCompare(B.label);
 | 
			
		||||
            })
 | 
			
		||||
            categories.forEach(function(cat) {
 | 
			
		||||
                categorySelector.append($("<option/>").val(cat.id).text(cat.label));
 | 
			
		||||
            })
 | 
			
		||||
            categorySelector.append($("<option/>").attr('disabled',true).text("---"));
 | 
			
		||||
            categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
 | 
			
		||||
 | 
			
		||||
            $("#subflow-appearance-input-category").on("change", function() {
 | 
			
		||||
                var val = $(this).val();
 | 
			
		||||
                if (val === "_custom_") {
 | 
			
		||||
                    $("#subflow-appearance-input-category").width(120);
 | 
			
		||||
                    $("#subflow-appearance-input-custom-category").show();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#subflow-appearance-input-category").width(250);
 | 
			
		||||
                    $("#subflow-appearance-input-custom-category").hide();
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            $("#subflow-appearance-input-category").val(node.category||"subflows");
 | 
			
		||||
            var userCount = 0;
 | 
			
		||||
            var subflowType = "subflow:"+node.id;
 | 
			
		||||
 | 
			
		||||
            // RED.nodes.eachNode(function(n) {
 | 
			
		||||
            //     if (n.type === subflowType) {
 | 
			
		||||
            //         userCount++;
 | 
			
		||||
            //     }
 | 
			
		||||
            // });
 | 
			
		||||
            $("#red-ui-editor-subflow-user-count")
 | 
			
		||||
                .text(RED._("subflow.subflowInstances", {count:node.instances.length})).show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('<div class="form-row">'+
 | 
			
		||||
            '<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
 | 
			
		||||
            '<span style="margin-right: 2px;"/>'+
 | 
			
		||||
            '<input type="checkbox" id="node-input-show-label"/>'+
 | 
			
		||||
        '</div>').appendTo(dialogForm);
 | 
			
		||||
 | 
			
		||||
        $("#node-input-show-label").toggleButton({
 | 
			
		||||
            enabledLabel: RED._("editor.show"),
 | 
			
		||||
            disabledLabel: RED._("editor.hide")
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (!node.hasOwnProperty("l")) {
 | 
			
		||||
            // Show label unless def.showLabel set to false
 | 
			
		||||
            node.l =  node._def.hasOwnProperty("showLabel")?node._def.showLabel:true;
 | 
			
		||||
        }
 | 
			
		||||
        $("#node-input-show-label").prop("checked",node.l).trigger("change");
 | 
			
		||||
 | 
			
		||||
        if (node.type === "subflow") {
 | 
			
		||||
            // subflow template can select its color
 | 
			
		||||
            var color = node.color ? node.color : "#DDAA99";
 | 
			
		||||
            var colorRow = $("<div/>", {
 | 
			
		||||
                class: "form-row"
 | 
			
		||||
            }).appendTo(dialogForm);
 | 
			
		||||
            $("<label/>").text(RED._("editor.color")).appendTo(colorRow);
 | 
			
		||||
 | 
			
		||||
            var recommendedColors = [
 | 
			
		||||
                "#DDAA99",
 | 
			
		||||
                "#3FADB5", "#87A980", "#A6BBCF",
 | 
			
		||||
                "#AAAA66", "#C0C0C0", "#C0DEED",
 | 
			
		||||
                "#C7E9C0", "#D7D7A0", "#D8BFD8",
 | 
			
		||||
                "#DAC4B4", "#DEB887", "#DEBD5C",
 | 
			
		||||
                "#E2D96E", "#E6E0F8", "#E7E7AE",
 | 
			
		||||
                "#E9967A", "#F3B567", "#FDD0A2",
 | 
			
		||||
                "#FDF0C2", "#FFAAAA", "#FFCC66",
 | 
			
		||||
                "#FFF0F0", "#FFFFFF"
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            RED.editor.colorPicker.create({
 | 
			
		||||
                id: "red-ui-editor-node-color",
 | 
			
		||||
                value: color,
 | 
			
		||||
                palette: recommendedColors,
 | 
			
		||||
                sortPalette: function (a, b) {return a.l - b.l;}
 | 
			
		||||
            }).appendTo(colorRow);
 | 
			
		||||
 | 
			
		||||
            $("#red-ui-editor-node-color").on('change', function(ev) {
 | 
			
		||||
                // Horribly out of scope...
 | 
			
		||||
                var colour = $(this).val();
 | 
			
		||||
                nodeDiv.css('backgroundColor',colour);
 | 
			
		||||
                var borderColor = RED.utils.getDarkerColor(colour);
 | 
			
		||||
                if (borderColor !== colour) {
 | 
			
		||||
                    nodeDiv.css('border-color',borderColor)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // If a node has icon property in defaults, the icon of the node cannot be modified. (e.g, ui_button node in dashboard)
 | 
			
		||||
        if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) {
 | 
			
		||||
            var iconRow = $('<div class="form-row"></div>').appendTo(dialogForm);
 | 
			
		||||
            $('<label data-i18n="editor.settingIcon">').appendTo(iconRow);
 | 
			
		||||
 | 
			
		||||
            var iconButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(iconRow);
 | 
			
		||||
            $('<i class="fa fa-caret-down"></i>').appendTo(iconButton);
 | 
			
		||||
            var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconButton);
 | 
			
		||||
            var colour = RED.utils.getNodeColor(node.type, node._def);
 | 
			
		||||
            var icon_url = RED.utils.getNodeIcon(node._def,node);
 | 
			
		||||
            nodeDiv.css('backgroundColor',colour);
 | 
			
		||||
            var borderColor = RED.utils.getDarkerColor(colour);
 | 
			
		||||
            if (borderColor !== colour) {
 | 
			
		||||
                nodeDiv.css('border-color',borderColor)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
 | 
			
		||||
            RED.utils.createIconElement(icon_url, iconContainer, true);
 | 
			
		||||
 | 
			
		||||
            iconButton.on("click", function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                var iconPath;
 | 
			
		||||
                var icon = $("#red-ui-editor-node-icon").val()||"";
 | 
			
		||||
                if (icon) {
 | 
			
		||||
                    iconPath = RED.utils.separateIconPath(icon);
 | 
			
		||||
                } else {
 | 
			
		||||
                    iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
 | 
			
		||||
                }
 | 
			
		||||
                var backgroundColor = RED.utils.getNodeColor(node.type, node._def);
 | 
			
		||||
                if (node.type === "subflow") {
 | 
			
		||||
                    backgroundColor = $("#red-ui-editor-node-color").val();
 | 
			
		||||
                }
 | 
			
		||||
                RED.editor.iconPicker.show(iconButton,backgroundColor,iconPath,false,function(newIcon) {
 | 
			
		||||
                    $("#red-ui-editor-node-icon").val(newIcon||"");
 | 
			
		||||
                    var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
 | 
			
		||||
                    RED.utils.createIconElement(icon_url, iconContainer, true);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            RED.popover.tooltip(iconButton, function() {
 | 
			
		||||
                return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
 | 
			
		||||
            })
 | 
			
		||||
            $('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $('<div class="form-row"><span data-i18n="editor.portLabels"></span></div>').appendTo(dialogForm);
 | 
			
		||||
 | 
			
		||||
        var inputCount = node.inputs || node._def.inputs || 0;
 | 
			
		||||
        var outputCount = node.outputs || node._def.outputs || 0;
 | 
			
		||||
        if (node.type === 'subflow') {
 | 
			
		||||
            inputCount = node.in.length;
 | 
			
		||||
            outputCount = node.out.length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var inputLabels = node.inputLabels || [];
 | 
			
		||||
        var outputLabels = node.outputLabels || [];
 | 
			
		||||
 | 
			
		||||
        var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
 | 
			
		||||
        var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
 | 
			
		||||
 | 
			
		||||
        $('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelInputs"></span><div id="red-ui-editor-node-label-form-inputs"></div></div>').appendTo(dialogForm);
 | 
			
		||||
        var inputsDiv = $("#red-ui-editor-node-label-form-inputs");
 | 
			
		||||
        if (inputCount > 0) {
 | 
			
		||||
            for (i=0;i<inputCount;i++) {
 | 
			
		||||
                buildLabelRow("input",i,inputLabels[i],inputPlaceholder).appendTo(inputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            buildLabelRow().appendTo(inputsDiv);
 | 
			
		||||
        }
 | 
			
		||||
        $('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelOutputs"></span><div id="red-ui-editor-node-label-form-outputs"></div></div>').appendTo(dialogForm);
 | 
			
		||||
        var outputsDiv = $("#red-ui-editor-node-label-form-outputs");
 | 
			
		||||
        if (outputCount > 0) {
 | 
			
		||||
            for (i=0;i<outputCount;i++) {
 | 
			
		||||
                buildLabelRow("output",i,outputLabels[i],outputPlaceholder).appendTo(outputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            buildLabelRow().appendTo(outputsDiv);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function refreshLabelForm(container,node) {
 | 
			
		||||
 | 
			
		||||
        var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
 | 
			
		||||
        var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
 | 
			
		||||
 | 
			
		||||
        var inputsDiv = $("#red-ui-editor-node-label-form-inputs");
 | 
			
		||||
        var outputsDiv = $("#red-ui-editor-node-label-form-outputs");
 | 
			
		||||
 | 
			
		||||
        var inputCount;
 | 
			
		||||
        var formInputs = $("#node-input-inputs").val();
 | 
			
		||||
        if (formInputs === undefined) {
 | 
			
		||||
            if (node.type === 'subflow') {
 | 
			
		||||
                inputCount = node.in.length;
 | 
			
		||||
            } else {
 | 
			
		||||
                inputCount = node.inputs || node._def.inputs || 0;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            inputCount = Math.min(1,Math.max(0,parseInt(formInputs)));
 | 
			
		||||
            if (isNaN(inputCount)) {
 | 
			
		||||
                inputCount = 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var children = inputsDiv.children();
 | 
			
		||||
        var childCount = children.length;
 | 
			
		||||
        if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
 | 
			
		||||
            childCount--;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (childCount < inputCount) {
 | 
			
		||||
            if (childCount === 0) {
 | 
			
		||||
                // remove the 'none' placeholder
 | 
			
		||||
                $(children[0]).remove();
 | 
			
		||||
            }
 | 
			
		||||
            for (i = childCount;i<inputCount;i++) {
 | 
			
		||||
                buildLabelRow("input",i,"",inputPlaceholder).appendTo(inputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (childCount > inputCount) {
 | 
			
		||||
            for (i=inputCount;i<childCount;i++) {
 | 
			
		||||
                $(children[i]).remove();
 | 
			
		||||
            }
 | 
			
		||||
            if (inputCount === 0) {
 | 
			
		||||
                buildLabelRow().appendTo(inputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var outputCount;
 | 
			
		||||
        var i;
 | 
			
		||||
        var formOutputs = $("#node-input-outputs").val();
 | 
			
		||||
 | 
			
		||||
        if (formOutputs === undefined) {
 | 
			
		||||
            if (node.type === 'subflow') {
 | 
			
		||||
                outputCount = node.out.length;
 | 
			
		||||
            } else {
 | 
			
		||||
                inputCount = node.outputs || node._def.outputs || 0;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (isNaN(formOutputs)) {
 | 
			
		||||
            var outputMap = JSON.parse(formOutputs);
 | 
			
		||||
            var keys = Object.keys(outputMap);
 | 
			
		||||
            children = outputsDiv.children();
 | 
			
		||||
            childCount = children.length;
 | 
			
		||||
            if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
 | 
			
		||||
                childCount--;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            outputCount = 0;
 | 
			
		||||
            var rows = [];
 | 
			
		||||
            keys.forEach(function(p) {
 | 
			
		||||
                var row = $("#red-ui-editor-node-label-form-output-"+p).parent();
 | 
			
		||||
                if (row.length === 0 && outputMap[p] !== -1) {
 | 
			
		||||
                    if (childCount === 0) {
 | 
			
		||||
                        $(children[0]).remove();
 | 
			
		||||
                        childCount = -1;
 | 
			
		||||
                    }
 | 
			
		||||
                    row = buildLabelRow("output",p,"",outputPlaceholder);
 | 
			
		||||
                } else {
 | 
			
		||||
                    row.detach();
 | 
			
		||||
                }
 | 
			
		||||
                if (outputMap[p] !== -1) {
 | 
			
		||||
                    outputCount++;
 | 
			
		||||
                    rows.push({i:parseInt(outputMap[p]),r:row});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            rows.sort(function(A,B) {
 | 
			
		||||
                return A.i-B.i;
 | 
			
		||||
            })
 | 
			
		||||
            rows.forEach(function(r,i) {
 | 
			
		||||
                r.r.find("label").text((i+1)+".");
 | 
			
		||||
                r.r.appendTo(outputsDiv);
 | 
			
		||||
            })
 | 
			
		||||
            if (rows.length === 0) {
 | 
			
		||||
                buildLabelRow("output",i,"").appendTo(outputsDiv);
 | 
			
		||||
            } else {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            outputCount = Math.max(0,parseInt(formOutputs));
 | 
			
		||||
        }
 | 
			
		||||
        children = outputsDiv.children();
 | 
			
		||||
        childCount = children.length;
 | 
			
		||||
        if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
 | 
			
		||||
            childCount--;
 | 
			
		||||
        }
 | 
			
		||||
        if (childCount < outputCount) {
 | 
			
		||||
            if (childCount === 0) {
 | 
			
		||||
                // remove the 'none' placeholder
 | 
			
		||||
                $(children[0]).remove();
 | 
			
		||||
            }
 | 
			
		||||
            for (i = childCount;i<outputCount;i++) {
 | 
			
		||||
                buildLabelRow("output",i,"").appendTo(outputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (childCount > outputCount) {
 | 
			
		||||
            for (i=outputCount;i<childCount;i++) {
 | 
			
		||||
                $(children[i]).remove();
 | 
			
		||||
            }
 | 
			
		||||
            if (outputCount === 0) {
 | 
			
		||||
                buildLabelRow().appendTo(outputsDiv);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildLabelRow(type, index, value, placeHolder) {
 | 
			
		||||
        var result = $('<div>',{class:"red-ui-editor-node-label-form-row"});
 | 
			
		||||
        if (type === undefined) {
 | 
			
		||||
            $('<span>').text(RED._("editor.noDefaultLabel")).appendTo(result);
 | 
			
		||||
            result.addClass("red-ui-editor-node-label-form-none");
 | 
			
		||||
        } else {
 | 
			
		||||
            result.addClass("");
 | 
			
		||||
            var id = "red-ui-editor-node-label-form-"+type+"-"+index;
 | 
			
		||||
            $('<label>',{for:id}).text((index+1)+".").appendTo(result);
 | 
			
		||||
            var input = $('<input>',{type:"text",id:id, placeholder: placeHolder}).val(value).appendTo(result);
 | 
			
		||||
            var clear = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').appendTo(result);
 | 
			
		||||
            clear.on("click", function(evt) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                input.val("");
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function updateLabels(node, changes, outputMap) {
 | 
			
		||||
        var inputLabels = $("#red-ui-editor-node-label-form-inputs").children().find("input");
 | 
			
		||||
        var outputLabels = $("#red-ui-editor-node-label-form-outputs").children().find("input");
 | 
			
		||||
 | 
			
		||||
        var hasNonBlankLabel = false;
 | 
			
		||||
        var changed = false;
 | 
			
		||||
        var newValue = inputLabels.map(function() {
 | 
			
		||||
            var v = $(this).val();
 | 
			
		||||
            hasNonBlankLabel = hasNonBlankLabel || v!== "";
 | 
			
		||||
            return v;
 | 
			
		||||
        }).toArray().slice(0,node.inputs);
 | 
			
		||||
        if ((node.inputLabels === undefined && hasNonBlankLabel) ||
 | 
			
		||||
            (node.inputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(node.inputLabels))) {
 | 
			
		||||
            changes.inputLabels = node.inputLabels;
 | 
			
		||||
            node.inputLabels = newValue;
 | 
			
		||||
            changed = true;
 | 
			
		||||
        }
 | 
			
		||||
        hasNonBlankLabel = false;
 | 
			
		||||
        newValue = new Array(node.outputs);
 | 
			
		||||
        outputLabels.each(function() {
 | 
			
		||||
            var index = $(this).attr('id').substring("red-ui-editor-node-label-form-output-".length);
 | 
			
		||||
            if (outputMap && outputMap.hasOwnProperty(index)) {
 | 
			
		||||
                index = parseInt(outputMap[index]);
 | 
			
		||||
                if (index === -1) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            var v = $(this).val();
 | 
			
		||||
            hasNonBlankLabel = hasNonBlankLabel || v!== "";
 | 
			
		||||
            newValue[index] = v;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if ((node.outputLabels === undefined && hasNonBlankLabel) ||
 | 
			
		||||
            (node.outputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(node.outputLabels))) {
 | 
			
		||||
            changes.outputLabels = node.outputLabels;
 | 
			
		||||
            node.outputLabels = newValue;
 | 
			
		||||
            changed = true;
 | 
			
		||||
        }
 | 
			
		||||
        return changed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										70
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
;(function() {
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-description", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.description"),
 | 
			
		||||
            name: RED._("editor-tab.description"),
 | 
			
		||||
            iconClass: "fa fa-file-text-o",
 | 
			
		||||
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
                this.editor = buildDescriptionForm(container,node);
 | 
			
		||||
                RED.e = this.editor;
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
                this.editor.resize();
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
                this.editor.destroy();
 | 
			
		||||
                this.editor = null;
 | 
			
		||||
            },
 | 
			
		||||
            show: function() {
 | 
			
		||||
                this.editor.focus();
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                var oldInfo = node.info;
 | 
			
		||||
                var newInfo = this.editor.getValue();
 | 
			
		||||
                if (!!oldInfo) {
 | 
			
		||||
                    // Has existing info property
 | 
			
		||||
                    if (newInfo.trim() === "") {
 | 
			
		||||
                        // New value is blank - remove the property
 | 
			
		||||
                        editState.changed = true;
 | 
			
		||||
                        editState.changes.info = oldInfo;
 | 
			
		||||
                        delete node.info;
 | 
			
		||||
                    } else if (newInfo !== oldInfo) {
 | 
			
		||||
                        // New value is different
 | 
			
		||||
                        editState.changed = true;
 | 
			
		||||
                        editState.changes.info = oldInfo;
 | 
			
		||||
                        node.info = newInfo;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // No existing info
 | 
			
		||||
                    if (newInfo.trim() !== "") {
 | 
			
		||||
                        // New value is not blank
 | 
			
		||||
                        editState.changed = true;
 | 
			
		||||
                        editState.changes.info = undefined;
 | 
			
		||||
                        node.info = newInfo;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function buildDescriptionForm(container,node) {
 | 
			
		||||
        var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
 | 
			
		||||
        var toolbarRow = $('<div></div>').appendTo(dialogForm);
 | 
			
		||||
        var row = $('<div class="form-row node-text-editor-row" style="position:relative; padding-top: 4px; height: 100%"></div>').appendTo(dialogForm);
 | 
			
		||||
        var editorId = "node-info-input-info-editor-"+Math.floor(1000*Math.random());
 | 
			
		||||
        $('<div style="height: 100%" class="node-text-editor" id="'+editorId+'" ></div>').appendTo(row);
 | 
			
		||||
        var nodeInfoEditor = RED.editor.createEditor({
 | 
			
		||||
            id: editorId,
 | 
			
		||||
            mode: 'ace/mode/markdown',
 | 
			
		||||
            value: ""
 | 
			
		||||
        });
 | 
			
		||||
        if (node.info) {
 | 
			
		||||
            nodeInfoEditor.getSession().setValue(node.info, -1);
 | 
			
		||||
        }
 | 
			
		||||
        node.infoEditor = nodeInfoEditor;
 | 
			
		||||
        return nodeInfoEditor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										73
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
;(function() {
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-envProperties", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.envProperties"),
 | 
			
		||||
            name: RED._("editor-tab.envProperties"),
 | 
			
		||||
            iconClass: "fa fa-list",
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
                var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container);
 | 
			
		||||
                var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form);
 | 
			
		||||
                this.list = $('<ol></ol>').appendTo(listContainer);
 | 
			
		||||
                RED.editor.envVarList.create(this.list, node);
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
                this.list.editableList('height',size.height);
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                var old_env = node.env;
 | 
			
		||||
                var new_env = [];
 | 
			
		||||
                if (/^subflow:/.test(node.type)) {
 | 
			
		||||
                    new_env = RED.subflow.exportSubflowInstanceEnv(node);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Get the values from the Properties table tab
 | 
			
		||||
                var items = this.list.editableList('items');
 | 
			
		||||
                items.each(function (i,el) {
 | 
			
		||||
                    var data = el.data('data');
 | 
			
		||||
                    var item;
 | 
			
		||||
                    if (data.nameField && data.valueField) {
 | 
			
		||||
                        item = {
 | 
			
		||||
                            name: data.nameField.val(),
 | 
			
		||||
                            value: data.valueField.typedInput("value"),
 | 
			
		||||
                            type: data.valueField.typedInput("type")
 | 
			
		||||
                        }
 | 
			
		||||
                        if (item.name.trim() !== "") {
 | 
			
		||||
                            new_env.push(item);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if (new_env && new_env.length > 0) {
 | 
			
		||||
                    new_env.forEach(function(prop) {
 | 
			
		||||
                        if (prop.type === "cred") {
 | 
			
		||||
                            node.credentials = node.credentials || {_:{}};
 | 
			
		||||
                            node.credentials[prop.name] = prop.value;
 | 
			
		||||
                            node.credentials['has_'+prop.name] = (prop.value !== "");
 | 
			
		||||
                            if (prop.value !== '__PWRD__') {
 | 
			
		||||
                                editState.changed = true;
 | 
			
		||||
                            }
 | 
			
		||||
                            delete prop.value;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                if (!isSameObj(old_env, new_env)) {
 | 
			
		||||
                    editState.changes.env = node.env;
 | 
			
		||||
                    if (new_env.length === 0) {
 | 
			
		||||
                        delete node.env;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.env = new_env;
 | 
			
		||||
                    }
 | 
			
		||||
                    editState.changed = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    function isSameObj(env0, env1) {
 | 
			
		||||
        return (JSON.stringify(env0) === JSON.stringify(env1));
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										60
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
;(function() {
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-flow-properties", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.properties"),
 | 
			
		||||
            name: RED._("editor-tab.properties"),
 | 
			
		||||
            iconClass: "fa fa-cog",
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
                var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(container);
 | 
			
		||||
                $('<div class="form-row">'+
 | 
			
		||||
                  '<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
 | 
			
		||||
                  '<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
 | 
			
		||||
                  '</div>').appendTo(dialogForm);
 | 
			
		||||
 | 
			
		||||
                var row = $('<div class="form-row node-text-editor-row">'+
 | 
			
		||||
                            '<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
 | 
			
		||||
                            '<div style="min-height:150px;" class="node-text-editor" id="node-input-info"></div>'+
 | 
			
		||||
                            '</div>').appendTo(dialogForm);
 | 
			
		||||
                this.tabflowEditor = RED.editor.createEditor({
 | 
			
		||||
                    id: 'node-input-info',
 | 
			
		||||
                    mode: 'ace/mode/markdown',
 | 
			
		||||
                    value: ""
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $('<input type="text" style="display: none;" />').prependTo(dialogForm);
 | 
			
		||||
                dialogForm.on("submit", function(e) { e.preventDefault();});
 | 
			
		||||
 | 
			
		||||
                $("#node-input-name").val(node.label);
 | 
			
		||||
                RED.text.bidi.prepareInput($("#node-input-name"));
 | 
			
		||||
                this.tabflowEditor.getSession().setValue(node.info || "", -1);
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
                $("#node-input-info").css("height", (size.height-70)+"px");
 | 
			
		||||
                this.tabflowEditor.resize();
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
                this.tabflowEditor.destroy();
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                var label = $( "#node-input-name" ).val();
 | 
			
		||||
 | 
			
		||||
                if (node.label != label) {
 | 
			
		||||
                    editState.changes.label = node.label;
 | 
			
		||||
                    editState.changed = true;
 | 
			
		||||
                    node.label = label;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var info = this.tabflowEditor.getValue();
 | 
			
		||||
                if (node.info !== info) {
 | 
			
		||||
                    editState.changes.info = node.info;
 | 
			
		||||
                    editState.changed = true;
 | 
			
		||||
                    node.info = info;
 | 
			
		||||
                }
 | 
			
		||||
                $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										181
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,181 @@
 | 
			
		||||
;(function() {
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-properties", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.properties"),
 | 
			
		||||
            name: RED._("editor-tab.properties"),
 | 
			
		||||
            iconClass: "fa fa-cog",
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
 | 
			
		||||
                var nodeType = node.type;
 | 
			
		||||
                if (node.type === "subflow") {
 | 
			
		||||
                    nodeType = "subflow-template";
 | 
			
		||||
                } else if (node.type.substring(0,8) == "subflow:") {
 | 
			
		||||
                    nodeType = "subflow";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var i18nNamespace;
 | 
			
		||||
                if (node._def.set.module === "node-red") {
 | 
			
		||||
                    i18nNamespace = "node-red";
 | 
			
		||||
                } else {
 | 
			
		||||
                    i18nNamespace = node._def.set.id;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var formStyle = "dialog-form";
 | 
			
		||||
                this.inputClass = "node-input";
 | 
			
		||||
                if (node._def.category === "config" && nodeType !== "group") {
 | 
			
		||||
                    this.inputClass = "node-config-input";
 | 
			
		||||
                    formStyle = "node-config-dialog-edit-form";
 | 
			
		||||
                }
 | 
			
		||||
                RED.editor.buildEditForm(container,formStyle,nodeType,i18nNamespace,node);
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
                if (node && node._def.oneditresize) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        node._def.oneditresize.call(node,size);
 | 
			
		||||
                    } catch(err) {
 | 
			
		||||
                        console.log("oneditresize",node.id,node.type,err.toString());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                var newValue;
 | 
			
		||||
                var d;
 | 
			
		||||
                if (node._def.defaults) {
 | 
			
		||||
                    for (d in node._def.defaults) {
 | 
			
		||||
                        if (node._def.defaults.hasOwnProperty(d)) {
 | 
			
		||||
                            var input = $("#"+this.inputClass+"-"+d);
 | 
			
		||||
                            if (input.attr('type') === "checkbox") {
 | 
			
		||||
                                newValue = input.prop('checked');
 | 
			
		||||
                            } else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
 | 
			
		||||
                                // An empty select-multiple box returns null.
 | 
			
		||||
                                // Need to treat that as an empty array.
 | 
			
		||||
                                newValue = input.val();
 | 
			
		||||
                                if (newValue == null) {
 | 
			
		||||
                                    newValue = [];
 | 
			
		||||
                                }
 | 
			
		||||
                            } else if ("format" in node._def.defaults[d] && node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
 | 
			
		||||
                                newValue = input.text();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                newValue = input.val();
 | 
			
		||||
                            }
 | 
			
		||||
                            if (newValue != null) {
 | 
			
		||||
                                if (d === "outputs") {
 | 
			
		||||
                                    if  (newValue.trim() === "") {
 | 
			
		||||
                                        continue;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (isNaN(newValue)) {
 | 
			
		||||
                                        editState.outputMap = JSON.parse(newValue);
 | 
			
		||||
                                        var outputCount = 0;
 | 
			
		||||
                                        var outputsChanged = false;
 | 
			
		||||
                                        var keys = Object.keys(editState.outputMap);
 | 
			
		||||
                                        keys.forEach(function(p) {
 | 
			
		||||
                                            if (isNaN(p)) {
 | 
			
		||||
                                                // New output;
 | 
			
		||||
                                                outputCount ++;
 | 
			
		||||
                                                delete editState.outputMap[p];
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                editState.outputMap[p] = editState.outputMap[p]+"";
 | 
			
		||||
                                                if (editState.outputMap[p] !== "-1") {
 | 
			
		||||
                                                    outputCount++;
 | 
			
		||||
                                                    if (editState.outputMap[p] !== p) {
 | 
			
		||||
                                                        // Output moved
 | 
			
		||||
                                                        outputsChanged = true;
 | 
			
		||||
                                                    } else {
 | 
			
		||||
                                                        delete editState.outputMap[p];
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                } else {
 | 
			
		||||
                                                    // Output removed
 | 
			
		||||
                                                    outputsChanged = true;
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        });
 | 
			
		||||
 | 
			
		||||
                                        newValue = outputCount;
 | 
			
		||||
                                        if (outputsChanged) {
 | 
			
		||||
                                            editState.changed = true;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        newValue = parseInt(newValue);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                if (node._def.defaults[d].type) {
 | 
			
		||||
                                    if (newValue == "_ADD_") {
 | 
			
		||||
                                        newValue = "";
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                if (node[d] != newValue) {
 | 
			
		||||
                                    if (node._def.defaults[d].type) {
 | 
			
		||||
                                        // Change to a related config node
 | 
			
		||||
                                        var configNode = RED.nodes.node(node[d]);
 | 
			
		||||
                                        if (configNode) {
 | 
			
		||||
                                            var users = configNode.users;
 | 
			
		||||
                                            users.splice(users.indexOf(node),1);
 | 
			
		||||
                                            RED.events.emit("nodes:change",configNode);
 | 
			
		||||
                                        }
 | 
			
		||||
                                        configNode = RED.nodes.node(newValue);
 | 
			
		||||
                                        if (configNode) {
 | 
			
		||||
                                            configNode.users.push(node);
 | 
			
		||||
                                            RED.events.emit("nodes:change",configNode);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    editState.changes[d] = node[d];
 | 
			
		||||
                                    node[d] = newValue;
 | 
			
		||||
                                    editState.changed = true;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (node._def.credentials) {
 | 
			
		||||
                    var credDefinition = node._def.credentials;
 | 
			
		||||
                    var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
 | 
			
		||||
                    editState.changed = editState.changed || credsChanged;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the node credentials from the edit form
 | 
			
		||||
     * @param node - the node containing the credentials
 | 
			
		||||
     * @param credDefinition - definition of the credentials
 | 
			
		||||
     * @param prefix - prefix of the input fields
 | 
			
		||||
     * @return {boolean} whether anything has changed
 | 
			
		||||
     */
 | 
			
		||||
    function updateNodeCredentials(node, credDefinition, prefix) {
 | 
			
		||||
        var changed = false;
 | 
			
		||||
        if (!node.credentials) {
 | 
			
		||||
            node.credentials = {_:{}};
 | 
			
		||||
        } else if (!node.credentials._) {
 | 
			
		||||
            node.credentials._ = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (var cred in credDefinition) {
 | 
			
		||||
            if (credDefinition.hasOwnProperty(cred)) {
 | 
			
		||||
                var input = $("#" + prefix + '-' + cred);
 | 
			
		||||
                if (input.length > 0) {
 | 
			
		||||
                    var value = input.val();
 | 
			
		||||
                    if (credDefinition[cred].type == 'password') {
 | 
			
		||||
                        node.credentials['has_' + cred] = (value !== "");
 | 
			
		||||
                        if (value == '__PWRD__') {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        changed = true;
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    node.credentials[cred] = value;
 | 
			
		||||
                    if (value != node.credentials._[cred]) {
 | 
			
		||||
                        changed = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return changed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										179
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
(function() {
 | 
			
		||||
    var _subflowModulePaneTemplate = '<form class="dialog-form form-horizontal" autocomplete="off">'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-module" data-i18n="[append]editor:subflow.module"><i class="fa fa-cube"></i> </label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-module" data-i18n="[placeholder]common.label.name">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-type" data-i18n="[append]editor:subflow.type"> </label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-type">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-version" data-i18n="[append]editor:subflow.version"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-version" data-i18n="[placeholder]editor:subflow.versionPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-desc" data-i18n="[append]editor:subflow.desc"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-desc">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-license" data-i18n="[append]editor:subflow.license"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-license">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-author" data-i18n="[append]editor:subflow.author"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-author" data-i18n="[placeholder]editor:subflow.authorPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-keywords" data-i18n="[append]editor:subflow.keys"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-keywords" data-i18n="[placeholder]editor:subflow.keysPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
    '</form>';
 | 
			
		||||
 | 
			
		||||
    RED.editor.registerEditPane("editor-tab-subflow-module", function(node) {
 | 
			
		||||
        return {
 | 
			
		||||
            label: RED._("editor-tab.module"),
 | 
			
		||||
            name: RED._("editor-tab.module"),
 | 
			
		||||
            iconClass: "fa fa-cube",
 | 
			
		||||
            create: function(container) {
 | 
			
		||||
                buildModuleForm(container, node);
 | 
			
		||||
            },
 | 
			
		||||
            resize: function(size) {
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            apply: function(editState) {
 | 
			
		||||
                var newMeta = exportSubflowModuleProperties(node);
 | 
			
		||||
                if (!isSameObj(node.meta,newMeta)) {
 | 
			
		||||
                    editState.changes.meta = node.meta;
 | 
			
		||||
                    node.meta = newMeta;
 | 
			
		||||
                    editState.changed = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function isSameObj(env0, env1) {
 | 
			
		||||
        return (JSON.stringify(env0) === JSON.stringify(env1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setupInputValidation(input,validator) {
 | 
			
		||||
        var errorTip;
 | 
			
		||||
        var validateTimeout;
 | 
			
		||||
 | 
			
		||||
        var validateFunction = function() {
 | 
			
		||||
            if (validateTimeout) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            validateTimeout = setTimeout(function() {
 | 
			
		||||
                var error = validator(input.val());
 | 
			
		||||
                // if (!error && errorTip) {
 | 
			
		||||
                //     errorTip.close();
 | 
			
		||||
                //     errorTip = null;
 | 
			
		||||
                // } else if (error && !errorTip) {
 | 
			
		||||
                //     errorTip = RED.popover.create({
 | 
			
		||||
                //         tooltip: true,
 | 
			
		||||
                //         target:input,
 | 
			
		||||
                //         size: "small",
 | 
			
		||||
                //         direction: "bottom",
 | 
			
		||||
                //         content: error,
 | 
			
		||||
                //     }).open();
 | 
			
		||||
                // }
 | 
			
		||||
                input.toggleClass("input-error",!!error);
 | 
			
		||||
                validateTimeout = null;
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        input.on("change keyup paste", validateFunction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildModuleForm(container, node) {
 | 
			
		||||
        $(_subflowModulePaneTemplate).appendTo(container);
 | 
			
		||||
        var moduleProps = node.meta || {};
 | 
			
		||||
        [
 | 
			
		||||
            'module',
 | 
			
		||||
            'type',
 | 
			
		||||
            'version',
 | 
			
		||||
            'author',
 | 
			
		||||
            'desc',
 | 
			
		||||
            'keywords',
 | 
			
		||||
            'license'
 | 
			
		||||
        ].forEach(function(property) {
 | 
			
		||||
            $("#subflow-input-module-"+property).val(moduleProps[property]||"")
 | 
			
		||||
        })
 | 
			
		||||
        $("#subflow-input-module-type").attr("placeholder",node.id);
 | 
			
		||||
 | 
			
		||||
        setupInputValidation($("#subflow-input-module-module"), function(newValue) {
 | 
			
		||||
            newValue = newValue.trim();
 | 
			
		||||
            var isValid = newValue.length < 215;
 | 
			
		||||
            isValid = isValid && !/^[._]/.test(newValue);
 | 
			
		||||
            isValid = isValid && !/[A-Z]/.test(newValue);
 | 
			
		||||
            if (newValue !== encodeURIComponent(newValue)) {
 | 
			
		||||
                var m = /^@([^\/]+)\/([^\/]+)$/.exec(newValue);
 | 
			
		||||
                if (m) {
 | 
			
		||||
                    isValid = isValid && (m[1] === encodeURIComponent(m[1]) && m[2] === encodeURIComponent(m[2]))
 | 
			
		||||
                } else {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return isValid?"":"Invalid module name"
 | 
			
		||||
        })
 | 
			
		||||
        setupInputValidation($("#subflow-input-module-version"), function(newValue) {
 | 
			
		||||
            newValue = newValue.trim();
 | 
			
		||||
            var isValid = newValue === "" ||
 | 
			
		||||
                          /^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/.test(newValue);
 | 
			
		||||
            return isValid?"":"Invalid version number"
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        var licenses = ["none", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "GPL-2.0", "GPL-3.0", "MIT", "MPL-2.0", "CDDL-1.0", "EPL-2.0"];
 | 
			
		||||
        var typedLicenses = {
 | 
			
		||||
            types: licenses.map(function(l) {
 | 
			
		||||
                return {
 | 
			
		||||
                    value: l,
 | 
			
		||||
                    label: l === "none" ? RED._("editor:subflow.licenseNone") : l,
 | 
			
		||||
                    hasValue: false
 | 
			
		||||
                };
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        typedLicenses.types.push({
 | 
			
		||||
            value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
 | 
			
		||||
        })
 | 
			
		||||
        if (!moduleProps.license) {
 | 
			
		||||
            typedLicenses.default = "none";
 | 
			
		||||
        } else if (licenses.indexOf(moduleProps.license) > -1) {
 | 
			
		||||
            typedLicenses.default = moduleProps.license;
 | 
			
		||||
        } else {
 | 
			
		||||
            typedLicenses.default = "_custom_";
 | 
			
		||||
        }
 | 
			
		||||
        $("#subflow-input-module-license").typedInput(typedLicenses)
 | 
			
		||||
    }
 | 
			
		||||
    function exportSubflowModuleProperties(node) {
 | 
			
		||||
        var value;
 | 
			
		||||
        var moduleProps = {};
 | 
			
		||||
        [
 | 
			
		||||
            'module',
 | 
			
		||||
            'type',
 | 
			
		||||
            'version',
 | 
			
		||||
            'author',
 | 
			
		||||
            'desc',
 | 
			
		||||
            'keywords'
 | 
			
		||||
        ].forEach(function(property) {
 | 
			
		||||
            value = $("#subflow-input-module-"+property).val().trim();
 | 
			
		||||
            if (value) {
 | 
			
		||||
                moduleProps[property] = value;
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        var selectedLicenseType = $("#subflow-input-module-license").typedInput("type");
 | 
			
		||||
 | 
			
		||||
        if (selectedLicenseType === '_custom_') {
 | 
			
		||||
            value = $("#subflow-input-module-license").val();
 | 
			
		||||
            if (value) {
 | 
			
		||||
                moduleProps.license = value;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (selectedLicenseType !== "none") {
 | 
			
		||||
            moduleProps.license = selectedLicenseType;
 | 
			
		||||
        }
 | 
			
		||||
        return moduleProps;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
@@ -73,6 +73,7 @@ RED.eventLog = (function() {
 | 
			
		||||
                    var trayBody = tray.find('.red-ui-tray-body');
 | 
			
		||||
                    var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
 | 
			
		||||
                    eventLogEditor = RED.editor.createEditor({
 | 
			
		||||
                        mode:"ace/mode/shell",
 | 
			
		||||
                        id: 'red-ui-event-log-editor',
 | 
			
		||||
                        value: backlog.join("\n"),
 | 
			
		||||
                        lineNumbers: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -87,16 +87,18 @@ RED.group = (function() {
 | 
			
		||||
        "label-position": "nw"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    var groupDef = {
 | 
			
		||||
        defaults:{
 | 
			
		||||
            name:{value:""},
 | 
			
		||||
            style:{value:{label:true}},
 | 
			
		||||
            nodes:{value:[]}
 | 
			
		||||
            nodes:{value:[]},
 | 
			
		||||
            env: {value:[]},
 | 
			
		||||
        },
 | 
			
		||||
        category: "config",
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            var style = this.style || {};
 | 
			
		||||
            RED.colorPicker.create({
 | 
			
		||||
            RED.editor.colorPicker.create({
 | 
			
		||||
                id:"node-input-style-stroke",
 | 
			
		||||
                value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4",
 | 
			
		||||
                palette: colorPalette,
 | 
			
		||||
@@ -107,7 +109,7 @@ RED.group = (function() {
 | 
			
		||||
                none: true,
 | 
			
		||||
                opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0)
 | 
			
		||||
            }).appendTo("#node-input-row-style-stroke");
 | 
			
		||||
            RED.colorPicker.create({
 | 
			
		||||
            RED.editor.colorPicker.create({
 | 
			
		||||
                id:"node-input-style-fill",
 | 
			
		||||
                value: style.fill || defaultGroupStyle.fill ||"none",
 | 
			
		||||
                palette: colorPalette,
 | 
			
		||||
@@ -124,7 +126,7 @@ RED.group = (function() {
 | 
			
		||||
                value:style["label-position"] || "nw"
 | 
			
		||||
            }).appendTo("#node-input-row-style-label-position");
 | 
			
		||||
 | 
			
		||||
            RED.colorPicker.create({
 | 
			
		||||
            RED.editor.colorPicker.create({
 | 
			
		||||
                id:"node-input-style-color",
 | 
			
		||||
                value: style.color || defaultGroupStyle.color ||"#a4a4a4",
 | 
			
		||||
                palette: colorPalette,
 | 
			
		||||
@@ -144,7 +146,6 @@ RED.group = (function() {
 | 
			
		||||
            })
 | 
			
		||||
            $("#node-input-style-label").prop("checked", this.style.label)
 | 
			
		||||
            $("#node-input-style-label").trigger("change");
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        oneditresize: function(size) {
 | 
			
		||||
        },
 | 
			
		||||
@@ -183,7 +184,9 @@ RED.group = (function() {
 | 
			
		||||
            var activateUngroup = false;
 | 
			
		||||
            var activateMerge = false;
 | 
			
		||||
            var activateRemove = false;
 | 
			
		||||
            var singleGroupSelected = false;
 | 
			
		||||
            if (activateGroup) {
 | 
			
		||||
                singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
 | 
			
		||||
                selection.nodes.forEach(function (n) {
 | 
			
		||||
                    if (n.type === "group") {
 | 
			
		||||
                        activateUngroup = true;
 | 
			
		||||
@@ -200,6 +203,8 @@ RED.group = (function() {
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-merge", !activateMerge);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-remove", !activateRemove);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-copy-group-style", !singleGroupSelected);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-paste-group-style", !activateUngroup);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:group-selection", function() { groupSelection() })
 | 
			
		||||
@@ -252,6 +257,7 @@ RED.group = (function() {
 | 
			
		||||
        if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
 | 
			
		||||
            groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
 | 
			
		||||
            RED.notify(RED._("clipboard.groupStyleCopied"),{id:"clipboard"})
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-paste-group-style", false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function pasteGroupStyle() {
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,7 @@ RED.keyboard = (function() {
 | 
			
		||||
                } else {
 | 
			
		||||
                    mergedKeymap[action] = [{
 | 
			
		||||
                        scope: themeKeymap[action].scope || "*",
 | 
			
		||||
                        key: [themeKeymap[action].key],
 | 
			
		||||
                        key: themeKeymap[action].key,
 | 
			
		||||
                        user: false
 | 
			
		||||
                    }]
 | 
			
		||||
                    if (mergedKeymap[action][0].scope === "workspace") {
 | 
			
		||||
@@ -131,7 +131,7 @@ RED.keyboard = (function() {
 | 
			
		||||
        return mergedKeymap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function init() {
 | 
			
		||||
    function init(done) {
 | 
			
		||||
        // Migrate from pre-0.18
 | 
			
		||||
        migrateOldKeymap();
 | 
			
		||||
 | 
			
		||||
@@ -164,6 +164,7 @@ RED.keyboard = (function() {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.userSettings.add({
 | 
			
		||||
@@ -174,6 +175,9 @@ RED.keyboard = (function() {
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    $("#red-ui-settings-tab-keyboard-filter").trigger("focus");
 | 
			
		||||
                },200);
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
                RED.menu.refreshShortcuts();
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
@@ -239,7 +243,13 @@ RED.keyboard = (function() {
 | 
			
		||||
 | 
			
		||||
    function resolveKeyEvent(evt) {
 | 
			
		||||
        var slot = partialState||handlers;
 | 
			
		||||
        if (evt.ctrlKey || evt.metaKey) {
 | 
			
		||||
        // We cheat with MacOS CMD key and consider it the same as Ctrl.
 | 
			
		||||
        // That means we don't have to have separate keymaps for different OS.
 | 
			
		||||
        // It mostly works.
 | 
			
		||||
        // One exception is shortcuts that include both Cmd and Ctrl. We don't
 | 
			
		||||
        // support them - but we need to make sure we don't block browser-specific
 | 
			
		||||
        // shortcuts (such as Cmd-Ctrl-F for fullscreen).
 | 
			
		||||
        if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) {
 | 
			
		||||
            slot = slot.ctrl;
 | 
			
		||||
        }
 | 
			
		||||
        if (slot && evt.shiftKey) {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ RED.library = (function() {
 | 
			
		||||
                    '<div class="red-ui-panel" id="red-ui-library-dialog-load-browser"></div>'+
 | 
			
		||||
                    '<div class="red-ui-panel">'+
 | 
			
		||||
                        '<div id="red-ui-library-dialog-load-preview">'+
 | 
			
		||||
                            '<div class="red-ui-panel" id="red-ui-library-dialog-load-preview-text"></div>'+
 | 
			
		||||
                            '<div class="red-ui-panel" id="red-ui-library-dialog-load-preview-text" style="position:relative; height: 50%; overflow-y: hidden;"></div>'+
 | 
			
		||||
                            '<div class="red-ui-panel" id="red-ui-library-dialog-load-preview-details">'+
 | 
			
		||||
                                '<table id="red-ui-library-dialog-load-preview-details-table" class="red-ui-info-table"></table>'+
 | 
			
		||||
                            '</div>'+
 | 
			
		||||
@@ -216,21 +216,27 @@ RED.library = (function() {
 | 
			
		||||
            { id:'node-input-'+options.type+'-menu-open-library',
 | 
			
		||||
                label: RED._("library.openLibrary"),
 | 
			
		||||
                onselect: function() {
 | 
			
		||||
 | 
			
		||||
                    libraryEditor = ace.edit('red-ui-library-dialog-load-preview-text',{
 | 
			
		||||
                        useWorker: false
 | 
			
		||||
                    });
 | 
			
		||||
                    libraryEditor.setTheme("ace/theme/tomorrow");
 | 
			
		||||
                    if (options.mode) {
 | 
			
		||||
                        libraryEditor.getSession().setMode(options.mode);
 | 
			
		||||
                    }
 | 
			
		||||
                    libraryEditor.setOptions({
 | 
			
		||||
                    var editorOpts = {
 | 
			
		||||
                        id: 'red-ui-library-dialog-load-preview-text',
 | 
			
		||||
                        mode: options.mode,
 | 
			
		||||
                        readOnly: true,
 | 
			
		||||
                        highlightActiveLine: false,
 | 
			
		||||
                        highlightGutterLine: false
 | 
			
		||||
                    });
 | 
			
		||||
                    libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
 | 
			
		||||
                    libraryEditor.$blockScrolling = Infinity;
 | 
			
		||||
                        highlightGutterLine: false,
 | 
			
		||||
                        contextmenu: false
 | 
			
		||||
                    }
 | 
			
		||||
                    libraryEditor = RED.editor.createEditor(editorOpts); //use red.editor
 | 
			
		||||
                    if(libraryEditor.isACE) {
 | 
			
		||||
                        if (options.mode) {
 | 
			
		||||
                            libraryEditor.getSession().setMode(options.mode);
 | 
			
		||||
                        }
 | 
			
		||||
                        libraryEditor.setOptions({
 | 
			
		||||
                            readOnly: true,
 | 
			
		||||
                            highlightActiveLine: false,
 | 
			
		||||
                            highlightGutterLine: false
 | 
			
		||||
                        });
 | 
			
		||||
                        libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
 | 
			
		||||
                        libraryEditor.$blockScrolling = Infinity;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    activeLibrary = options;
 | 
			
		||||
                    var listing = [];
 | 
			
		||||
@@ -530,7 +536,7 @@ RED.library = (function() {
 | 
			
		||||
    //             evt.preventDefault();
 | 
			
		||||
    //             var icon = libraryFields['icon'].input.val() || "";
 | 
			
		||||
    //             var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
 | 
			
		||||
    //             RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) {
 | 
			
		||||
    //             RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) {
 | 
			
		||||
    //                 iconButton.empty();
 | 
			
		||||
    //                 var path = newIcon || "";
 | 
			
		||||
    //                 var newPath = RED.utils.separateIconPath(path);
 | 
			
		||||
@@ -809,6 +815,7 @@ RED.library = (function() {
 | 
			
		||||
                open: function(e) {
 | 
			
		||||
                    RED.keyboard.disable();
 | 
			
		||||
                    $(this).parent().find(".ui-dialog-titlebar-close").hide();
 | 
			
		||||
                    libraryEditor.resize();
 | 
			
		||||
                },
 | 
			
		||||
                close: function(e) {
 | 
			
		||||
                    RED.keyboard.enable();
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,22 @@ RED.notifications = (function() {
 | 
			
		||||
 | 
			
		||||
    var persistentNotifications = {};
 | 
			
		||||
 | 
			
		||||
    var shade = (function() {
 | 
			
		||||
        var shadeUsers = 0;
 | 
			
		||||
        return {
 | 
			
		||||
            show: function() {
 | 
			
		||||
                shadeUsers++;
 | 
			
		||||
                $("#red-ui-full-shade").show();
 | 
			
		||||
            },
 | 
			
		||||
            hide: function() {
 | 
			
		||||
                shadeUsers--;
 | 
			
		||||
                if (shadeUsers === 0) {
 | 
			
		||||
                    $("#red-ui-full-shade").hide();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })();
 | 
			
		||||
 | 
			
		||||
    var currentNotifications = [];
 | 
			
		||||
    var c = 0;
 | 
			
		||||
    function notify(msg,type,fixed,timeout) {
 | 
			
		||||
@@ -54,6 +70,10 @@ RED.notifications = (function() {
 | 
			
		||||
            fixed = options.fixed;
 | 
			
		||||
            timeout = options.timeout;
 | 
			
		||||
            type = options.type;
 | 
			
		||||
        } else {
 | 
			
		||||
            options.type = type;
 | 
			
		||||
            options.fixed = fixed;
 | 
			
		||||
            options.timeout = options.timeout;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (options.id && persistentNotifications.hasOwnProperty(options.id)) {
 | 
			
		||||
@@ -62,7 +82,7 @@ RED.notifications = (function() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (options.modal) {
 | 
			
		||||
            $("#red-ui-full-shade").show();
 | 
			
		||||
            shade.show();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (currentNotifications.length > 4) {
 | 
			
		||||
@@ -79,6 +99,8 @@ RED.notifications = (function() {
 | 
			
		||||
        var n = document.createElement("div");
 | 
			
		||||
        n.id="red-ui-notification-"+c;
 | 
			
		||||
        n.className = "red-ui-notification";
 | 
			
		||||
        n.options = options;
 | 
			
		||||
 | 
			
		||||
        n.fixed = fixed;
 | 
			
		||||
        if (type) {
 | 
			
		||||
            n.className = "red-ui-notification red-ui-notification-"+type;
 | 
			
		||||
@@ -115,7 +137,6 @@ RED.notifications = (function() {
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $("#red-ui-notifications").append(n);
 | 
			
		||||
        if (!RED.notifications.hide) {
 | 
			
		||||
            $(n).slideDown(300);
 | 
			
		||||
@@ -141,8 +162,8 @@ RED.notifications = (function() {
 | 
			
		||||
                } else {
 | 
			
		||||
                    nn.parentNode.removeChild(nn);
 | 
			
		||||
                }
 | 
			
		||||
                if (options.modal) {
 | 
			
		||||
                    $("#red-ui-full-shade").hide();
 | 
			
		||||
                if (nn.options.modal) {
 | 
			
		||||
                    shade.hide();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        })();
 | 
			
		||||
@@ -173,7 +194,7 @@ RED.notifications = (function() {
 | 
			
		||||
 | 
			
		||||
        n.update = (function() {
 | 
			
		||||
            var nn = n;
 | 
			
		||||
            return function(msg,options) {
 | 
			
		||||
            return function(msg,newOptions) {
 | 
			
		||||
                if (typeof msg === "string") {
 | 
			
		||||
                    if (!/<p>/i.test(msg)) {
 | 
			
		||||
                        msg = "<p>"+msg+"</p>";
 | 
			
		||||
@@ -182,16 +203,31 @@ RED.notifications = (function() {
 | 
			
		||||
                } else {
 | 
			
		||||
                    $(nn).empty().append(msg);
 | 
			
		||||
                }
 | 
			
		||||
                var timeout;
 | 
			
		||||
                if (typeof options === 'number') {
 | 
			
		||||
                    timeout = options;
 | 
			
		||||
                } else if (options !== undefined) {
 | 
			
		||||
                    if (!options.fixed) {
 | 
			
		||||
                        timeout = options.timeout || 5000;
 | 
			
		||||
                var newTimeout;
 | 
			
		||||
                if (typeof newOptions === 'number') {
 | 
			
		||||
                    newTimeout = newOptions;
 | 
			
		||||
                    nn.options.timeout = newTimeout;
 | 
			
		||||
                } else if (newOptions !== undefined) {
 | 
			
		||||
 | 
			
		||||
                    if (!options.modal && newOptions.modal) {
 | 
			
		||||
                        nn.options.modal = true;
 | 
			
		||||
                        shade.show();
 | 
			
		||||
                    } else if (options.modal && newOptions.modal === false) {
 | 
			
		||||
                        nn.options.modal = false;
 | 
			
		||||
                        shade.hide();
 | 
			
		||||
                    }
 | 
			
		||||
                    if (options.buttons) {
 | 
			
		||||
 | 
			
		||||
                    var newType = newOptions.hasOwnProperty('type')?newOptions.type:type;
 | 
			
		||||
                    if (newType) {
 | 
			
		||||
                        n.className = "red-ui-notification red-ui-notification-"+newType;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!fixed || newOptions.fixed === false) {
 | 
			
		||||
                        newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (newOptions.buttons) {
 | 
			
		||||
                        var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
 | 
			
		||||
                        options.buttons.forEach(function(buttonDef) {
 | 
			
		||||
                        newOptions.buttons.forEach(function(buttonDef) {
 | 
			
		||||
                            var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
 | 
			
		||||
                            if (buttonDef.id) {
 | 
			
		||||
                                b.attr('id',buttonDef.id);
 | 
			
		||||
@@ -202,15 +238,22 @@ RED.notifications = (function() {
 | 
			
		||||
                        })
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (timeout !== undefined && timeout > 0) {
 | 
			
		||||
                $(nn).off("click.red-ui-notification-close");
 | 
			
		||||
                if (newTimeout !== undefined && newTimeout > 0) {
 | 
			
		||||
                    window.clearTimeout(nn.timeoutid);
 | 
			
		||||
                    nn.timeoutid = window.setTimeout(nn.close,timeout);
 | 
			
		||||
                    nn.timeoutid = window.setTimeout(nn.close,newTimeout);
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        $(nn).on("click.red-ui-notification-close", function() {
 | 
			
		||||
                            nn.close();
 | 
			
		||||
                            window.clearTimeout(nn.timeoutid);
 | 
			
		||||
                        });
 | 
			
		||||
                    },50);
 | 
			
		||||
                } else {
 | 
			
		||||
                    window.clearTimeout(nn.timeoutid);
 | 
			
		||||
                }
 | 
			
		||||
                if (nn.hidden) {
 | 
			
		||||
                    nn.showNotification();
 | 
			
		||||
                } else if (!options || !options.silent){
 | 
			
		||||
                } else if (!newOptions || !newOptions.silent){
 | 
			
		||||
                    $(nn).addClass("red-ui-notification-shake-horizontal");
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        $(nn).removeClass("red-ui-notification-shake-horizontal");
 | 
			
		||||
@@ -221,7 +264,7 @@ RED.notifications = (function() {
 | 
			
		||||
        })();
 | 
			
		||||
 | 
			
		||||
        if (!fixed) {
 | 
			
		||||
            $(n).on("click", (function() {
 | 
			
		||||
            $(n).on("click.red-ui-notification-close", (function() {
 | 
			
		||||
                var nn = n;
 | 
			
		||||
                return function() {
 | 
			
		||||
                    nn.close();
 | 
			
		||||
 
 | 
			
		||||
@@ -264,6 +264,8 @@ RED.palette.editor = (function() {
 | 
			
		||||
                            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);
 | 
			
		||||
                        }
 | 
			
		||||
@@ -331,7 +333,10 @@ RED.palette.editor = (function() {
 | 
			
		||||
                nodeEntry.versionSpan.html(moduleInfo.version+' <i class="fa fa-long-arrow-right"></i> '+moduleInfo.pending_version).appendTo(nodeEntry.metaRow)
 | 
			
		||||
                nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').css('display', 'inline-block');
 | 
			
		||||
            } else if (loadedIndex.hasOwnProperty(module)) {
 | 
			
		||||
                if (semVerCompare(loadedIndex[module].version,moduleInfo.version) === 1) {
 | 
			
		||||
                if (updateAllowed &&
 | 
			
		||||
                    semVerCompare(loadedIndex[module].version,moduleInfo.version) > 0 &&
 | 
			
		||||
                    RED.utils.checkModuleAllowed(module,null,updateAllowList,updateDenyList)
 | 
			
		||||
                ) {
 | 
			
		||||
                    nodeEntry.updateButton.show();
 | 
			
		||||
                    nodeEntry.updateButton.text(RED._('palette.editor.update',{version:loadedIndex[module].version}));
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -482,6 +487,9 @@ RED.palette.editor = (function() {
 | 
			
		||||
 | 
			
		||||
    var installAllowList = ['*'];
 | 
			
		||||
    var installDenyList = [];
 | 
			
		||||
    var updateAllowed = true;
 | 
			
		||||
    var updateAllowList = ['*'];
 | 
			
		||||
    var updateDenyList = [];
 | 
			
		||||
 | 
			
		||||
    function init() {
 | 
			
		||||
        if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
 | 
			
		||||
@@ -496,6 +504,17 @@ RED.palette.editor = (function() {
 | 
			
		||||
        installAllowList = RED.utils.parseModuleList(installAllowList);
 | 
			
		||||
        installDenyList = RED.utils.parseModuleList(installDenyList);
 | 
			
		||||
 | 
			
		||||
        var settingsUpdateAllowList = RED.settings.get("externalModules.palette.allowUpdateList")
 | 
			
		||||
        var settingsUpdateDenyList = RED.settings.get("externalModules.palette.denyUpdateList")
 | 
			
		||||
        if (settingsUpdateAllowList || settingsUpdateDenyList) {
 | 
			
		||||
            updateAllowList = settingsUpdateAllowList;
 | 
			
		||||
            updateDenyList = settingsUpdateDenyList;
 | 
			
		||||
        }
 | 
			
		||||
        updateAllowList = RED.utils.parseModuleList(updateAllowList);
 | 
			
		||||
        updateDenyList = RED.utils.parseModuleList(updateDenyList);
 | 
			
		||||
        updateAllowed = RED.settings.get("externalModules.palette.allowUpdate",true);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        createSettingsPane();
 | 
			
		||||
 | 
			
		||||
        RED.userSettings.add({
 | 
			
		||||
 
 | 
			
		||||
@@ -320,12 +320,12 @@ RED.palette = (function() {
 | 
			
		||||
                    var paletteNode = getPaletteNode(nt);
 | 
			
		||||
                    ui.originalPosition.left = paletteNode.offset().left;
 | 
			
		||||
                    mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
 | 
			
		||||
                    mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
 | 
			
		||||
                    mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
 | 
			
		||||
                    if (!groupTimer) {
 | 
			
		||||
                        groupTimer = setTimeout(function() {
 | 
			
		||||
                            mouseX /= RED.view.scale();
 | 
			
		||||
                            mouseY /= RED.view.scale();
 | 
			
		||||
                            var group = RED.view.getGroupAtPoint(mouseX,mouseY);
 | 
			
		||||
                            var mx = mouseX / RED.view.scale();
 | 
			
		||||
                            var my = mouseY / RED.view.scale();
 | 
			
		||||
                            var group = RED.view.getGroupAtPoint(mx,my);
 | 
			
		||||
                            if (group !== hoverGroup) {
 | 
			
		||||
                                if (hoverGroup) {
 | 
			
		||||
                                    document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
 | 
			
		||||
@@ -357,23 +357,20 @@ RED.palette = (function() {
 | 
			
		||||
                                    svgRect.width = 1;
 | 
			
		||||
                                    svgRect.height = 1;
 | 
			
		||||
                                    nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
 | 
			
		||||
                                    mouseX /= RED.view.scale();
 | 
			
		||||
                                    mouseY /= RED.view.scale();
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    // Firefox doesn't do getIntersectionList and that
 | 
			
		||||
                                    // makes us sad
 | 
			
		||||
                                    mouseX /= RED.view.scale();
 | 
			
		||||
                                    mouseY /= RED.view.scale();
 | 
			
		||||
                                    nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                var mx = mouseX / RED.view.scale();
 | 
			
		||||
                                var my = mouseY / RED.view.scale();
 | 
			
		||||
                                for (var i=0;i<nodes.length;i++) {
 | 
			
		||||
                                    var node = d3.select(nodes[i]);
 | 
			
		||||
                                    if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
 | 
			
		||||
                                        var length = nodes[i].getTotalLength();
 | 
			
		||||
                                        for (var j=0;j<length;j+=10) {
 | 
			
		||||
                                            var p = nodes[i].getPointAtLength(j);
 | 
			
		||||
                                            var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
 | 
			
		||||
                                            var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
 | 
			
		||||
                                            if (d2 < 200 && d2 < bestDistance) {
 | 
			
		||||
                                                bestDistance = d2;
 | 
			
		||||
                                                bestLink = nodes[i];
 | 
			
		||||
 
 | 
			
		||||
@@ -928,11 +928,11 @@ RED.projects.settings = (function() {
 | 
			
		||||
 | 
			
		||||
            saveDisabled = isFlowInvalid || credFileLabelText.text()==="";
 | 
			
		||||
 | 
			
		||||
            if (credentialSecretExistingInput.is(":visible")) {
 | 
			
		||||
            if (credentialSecretExistingRow.is(":visible")) {
 | 
			
		||||
                credentialSecretExistingInput.toggleClass("input-error", credentialSecretExistingInput.val() === "");
 | 
			
		||||
                saveDisabled = saveDisabled || credentialSecretExistingInput.val() === "";
 | 
			
		||||
            }
 | 
			
		||||
            if (credentialSecretNewInput.is(":visible")) {
 | 
			
		||||
            if (credentialSecretNewRow.is(":visible")) {
 | 
			
		||||
                credentialSecretNewInput.toggleClass("input-error", credentialSecretNewInput.val() === "");
 | 
			
		||||
                saveDisabled = saveDisabled || credentialSecretNewInput.val() === "";
 | 
			
		||||
            }
 | 
			
		||||
@@ -1130,7 +1130,7 @@ RED.projects.settings = (function() {
 | 
			
		||||
                }
 | 
			
		||||
                if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) {
 | 
			
		||||
                    payload.credentialSecret = credentialSecretNewInput.val();
 | 
			
		||||
                    if (credentialSecretExistingInput.is(":visible")) {
 | 
			
		||||
                    if (credentialSecretExistingRow.is(":visible")) {
 | 
			
		||||
                        payload.currentCredentialSecret = credentialSecretExistingInput.val();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -2387,6 +2387,7 @@ RED.projects = (function() {
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        showStartup: function() {
 | 
			
		||||
            console.warn("showStartup")
 | 
			
		||||
            if (!RED.user.hasPermission("projects.write")) {
 | 
			
		||||
                RED.notify(RED._("user.errors.notAuthorized"),"error");
 | 
			
		||||
                return;
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,7 @@ RED.search = (function() {
 | 
			
		||||
        val = extractFlag(val,"unused",flags);
 | 
			
		||||
        val = extractFlag(val,"config",flags);
 | 
			
		||||
        val = extractFlag(val,"subflow",flags);
 | 
			
		||||
        val = extractFlag(val,"hidden",flags);
 | 
			
		||||
        // uses:<node-id>
 | 
			
		||||
        val = extractValue(val,"uses",flags);
 | 
			
		||||
 | 
			
		||||
@@ -150,7 +151,15 @@ RED.search = (function() {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (flags.hasOwnProperty("hidden")) {
 | 
			
		||||
                            // Only tabs can be hidden
 | 
			
		||||
                            if (node.node.type !== 'tab') {
 | 
			
		||||
                                continue
 | 
			
		||||
                            }
 | 
			
		||||
                            if (!RED.workspaces.isHidden(node.node.id)) {
 | 
			
		||||
                                continue
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (flags.hasOwnProperty("unused")) {
 | 
			
		||||
                            var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
 | 
			
		||||
                                           (isConfigNode && node.node.users.length === 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -199,7 +199,7 @@ RED.sidebar = (function() {
 | 
			
		||||
            id = RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
 | 
			
		||||
        }
 | 
			
		||||
        if (id) {
 | 
			
		||||
            if (!containsTab(id)) {
 | 
			
		||||
            if (!containsTab(id) && knownTabs[id]) {
 | 
			
		||||
                sidebar_tabs.addTab(knownTabs[id]);
 | 
			
		||||
            }
 | 
			
		||||
            sidebar_tabs.activateTab(id);
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@
 | 
			
		||||
 | 
			
		||||
RED.subflow = (function() {
 | 
			
		||||
 | 
			
		||||
    var currentLocale = "en-US";
 | 
			
		||||
 | 
			
		||||
    var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
 | 
			
		||||
@@ -37,7 +35,7 @@ RED.subflow = (function() {
 | 
			
		||||
        '<div id="subflow-env-tabs-content">'+
 | 
			
		||||
            '<div id="subflow-env-tab-edit">'+
 | 
			
		||||
                '<div class="form-row node-input-env-container-row" id="subflow-input-edit-ui">'+
 | 
			
		||||
                    '<ol class="red-ui-editor-subflow-env-list" id="node-input-env-container"></ol>'+
 | 
			
		||||
                    '<ol id="node-input-env-container"></ol>'+
 | 
			
		||||
                    '<div class="node-input-env-locales-row"><i class="fa fa-language"></i> <select id="subflow-input-env-locale"></select></div>'+
 | 
			
		||||
                '</div>'+
 | 
			
		||||
            '</div>'+
 | 
			
		||||
@@ -47,37 +45,6 @@ RED.subflow = (function() {
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '</script>';
 | 
			
		||||
 | 
			
		||||
    var _subflowModulePaneTemplate = '<form class="dialog-form form-horizontal" autocomplete="off">'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-module" data-i18n="[append]editor:subflow.module"><i class="fa fa-cube"></i> </label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-module" data-i18n="[placeholder]common.label.name">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-type" data-i18n="[append]editor:subflow.type"> </label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-type">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-version" data-i18n="[append]editor:subflow.version"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-version" data-i18n="[placeholder]editor:subflow.versionPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-desc" data-i18n="[append]editor:subflow.desc"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-desc">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-license" data-i18n="[append]editor:subflow.license"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-license">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-author" data-i18n="[append]editor:subflow.author"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-author" data-i18n="[placeholder]editor:subflow.authorPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
        '<div class="form-row">'+
 | 
			
		||||
            '<label for="subflow-input-module-keywords" data-i18n="[append]editor:subflow.keys"></label>'+
 | 
			
		||||
            '<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-keywords" data-i18n="[placeholder]editor:subflow.keysPlaceholder">'+
 | 
			
		||||
        '</div>'+
 | 
			
		||||
    '</form>';
 | 
			
		||||
 | 
			
		||||
    function findAvailableSubflowIOPosition(subflow,isInput) {
 | 
			
		||||
        var pos = {x:50,y:30};
 | 
			
		||||
        if (!isInput) {
 | 
			
		||||
@@ -909,7 +876,6 @@ RED.subflow = (function() {
 | 
			
		||||
     * Create interface for controlling env var UI definition
 | 
			
		||||
     */
 | 
			
		||||
    function buildEnvControl(envList,node) {
 | 
			
		||||
 | 
			
		||||
        var tabs = RED.tabs.create({
 | 
			
		||||
            id: "subflow-env-tabs",
 | 
			
		||||
            onchange: function(tab) {
 | 
			
		||||
@@ -950,588 +916,11 @@ RED.subflow = (function() {
 | 
			
		||||
        locales.val(locale);
 | 
			
		||||
 | 
			
		||||
        locales.on("change", function() {
 | 
			
		||||
            currentLocale = $(this).val();
 | 
			
		||||
            var items = $("#node-input-env-container").editableList("items");
 | 
			
		||||
            items.each(function (i, item) {
 | 
			
		||||
                var entry = $(this).data('data');
 | 
			
		||||
                var labelField = entry.ui.labelField;
 | 
			
		||||
                labelField.val(lookupLabel(entry.ui.label, "", currentLocale));
 | 
			
		||||
                if (labelField.timeout) {
 | 
			
		||||
                    clearTimeout(labelField.timeout);
 | 
			
		||||
                    delete labelField.timeout;
 | 
			
		||||
                }
 | 
			
		||||
                labelField.addClass("input-updated");
 | 
			
		||||
                labelField.timeout = setTimeout(function() {
 | 
			
		||||
                    delete labelField.timeout
 | 
			
		||||
                    labelField.removeClass("input-updated");
 | 
			
		||||
                },3000);
 | 
			
		||||
            });
 | 
			
		||||
            RED.editor.envVarList.setLocale($(this).val(), $("#node-input-env-container"));
 | 
			
		||||
        });
 | 
			
		||||
        RED.editor.envVarList.setLocale(locale);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create env var edit interface
 | 
			
		||||
     * @param container - container
 | 
			
		||||
     * @param node - subflow node
 | 
			
		||||
     */
 | 
			
		||||
    function buildPropertiesList(envContainer, node) {
 | 
			
		||||
 | 
			
		||||
        var isTemplateNode = (node.type === "subflow");
 | 
			
		||||
 | 
			
		||||
        if (isTemplateNode) {
 | 
			
		||||
            buildEnvControl(envContainer, node);
 | 
			
		||||
        }
 | 
			
		||||
        envContainer
 | 
			
		||||
            .css({
 | 
			
		||||
                'min-height':'150px',
 | 
			
		||||
                'min-width':'450px'
 | 
			
		||||
            })
 | 
			
		||||
            .editableList({
 | 
			
		||||
                header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined,
 | 
			
		||||
                addItem: function(container, i, opt) {
 | 
			
		||||
                    // If this is an instance node, these are properties unique to
 | 
			
		||||
                    // this instance - ie opt.parent will not be defined.
 | 
			
		||||
 | 
			
		||||
                    if (isTemplateNode) {
 | 
			
		||||
                        container.addClass("red-ui-editor-subflow-env-editable")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var envRow = $('<div/>').appendTo(container);
 | 
			
		||||
                    var nameField = null;
 | 
			
		||||
                    var valueField = null;
 | 
			
		||||
 | 
			
		||||
                    nameField = $('<input/>', {
 | 
			
		||||
                        class: "node-input-env-name",
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                        placeholder: RED._("common.label.name")
 | 
			
		||||
                    }).attr("autocomplete","disable").appendTo(envRow).val(opt.name);
 | 
			
		||||
                    valueField = $('<input/>',{
 | 
			
		||||
                        style: "width:100%",
 | 
			
		||||
                        class: "node-input-env-value",
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                    }).attr("autocomplete","disable").appendTo(envRow)
 | 
			
		||||
                    valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED});
 | 
			
		||||
                    valueField.typedInput('type', opt.type);
 | 
			
		||||
                    if (opt.type === "cred") {
 | 
			
		||||
                        if (opt.value) {
 | 
			
		||||
                            valueField.typedInput('value', opt.value);
 | 
			
		||||
                        } else if (node.credentials && node.credentials[opt.name]) {
 | 
			
		||||
                            valueField.typedInput('value', node.credentials[opt.name]);
 | 
			
		||||
                        } else if (node.credentials && node.credentials['has_'+opt.name]) {
 | 
			
		||||
                            valueField.typedInput('value', "__PWRD__");
 | 
			
		||||
                        } else {
 | 
			
		||||
                            valueField.typedInput('value', "");
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        valueField.typedInput('value', opt.value);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    opt.nameField = nameField;
 | 
			
		||||
                    opt.valueField = valueField;
 | 
			
		||||
 | 
			
		||||
                    var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow);
 | 
			
		||||
                    $('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
 | 
			
		||||
                    var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
 | 
			
		||||
                    actionButton.on("click", function(evt) {
 | 
			
		||||
                        evt.preventDefault();
 | 
			
		||||
                        removeTip.close();
 | 
			
		||||
                        container.parent().addClass("red-ui-editableList-item-deleting")
 | 
			
		||||
                        container.fadeOut(300, function() {
 | 
			
		||||
                            envContainer.editableList('removeItem',opt);
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if (isTemplateNode) {
 | 
			
		||||
                        // Add the UI customisation row
 | 
			
		||||
                        // if `opt.ui` does not exist, then apply defaults. If these
 | 
			
		||||
                        // defaults do not change then they will get stripped off
 | 
			
		||||
                        // before saving.
 | 
			
		||||
                        if (opt.type === 'cred') {
 | 
			
		||||
                            opt.ui = opt.ui || {
 | 
			
		||||
                                icon: "",
 | 
			
		||||
                                type: "cred"
 | 
			
		||||
                            }
 | 
			
		||||
                            opt.ui.type = "cred";
 | 
			
		||||
                        } else {
 | 
			
		||||
                            opt.ui = opt.ui || {
 | 
			
		||||
                                icon: "",
 | 
			
		||||
                                type: "input",
 | 
			
		||||
                                opts: {types:DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        opt.ui.label = opt.ui.label || {};
 | 
			
		||||
                        opt.ui.type = opt.ui.type || "input";
 | 
			
		||||
 | 
			
		||||
                        var uiRow = $('<div/>').appendTo(container).hide();
 | 
			
		||||
                        // save current info for reverting on cancel
 | 
			
		||||
                        // var copy = $.extend(true, {}, ui);
 | 
			
		||||
 | 
			
		||||
                         $('<a href="#"><i class="fa fa-angle-right"></a>').prependTo(envRow).on("click", function (evt) {
 | 
			
		||||
                            evt.preventDefault();
 | 
			
		||||
                            if ($(this).hasClass('expanded')) {
 | 
			
		||||
                                uiRow.slideUp();
 | 
			
		||||
                                $(this).removeClass('expanded');
 | 
			
		||||
                            } else {
 | 
			
		||||
                                uiRow.slideDown();
 | 
			
		||||
                                $(this).addClass('expanded');
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
 | 
			
		||||
                        nameField.trigger('change');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                sortable: ".red-ui-editableList-item-handle",
 | 
			
		||||
                removable: false
 | 
			
		||||
            });
 | 
			
		||||
        var parentEnv = {};
 | 
			
		||||
        var envList = [];
 | 
			
		||||
        if (/^subflow:/.test(node.type)) {
 | 
			
		||||
            var subflowDef = RED.nodes.subflow(node.type.substring(8));
 | 
			
		||||
            if (subflowDef.env) {
 | 
			
		||||
                subflowDef.env.forEach(function(env) {
 | 
			
		||||
                    var item = {
 | 
			
		||||
                        name:env.name,
 | 
			
		||||
                        parent: {
 | 
			
		||||
                            type: env.type,
 | 
			
		||||
                            value: env.value,
 | 
			
		||||
                            ui: env.ui
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    envList.push(item);
 | 
			
		||||
                    parentEnv[env.name] = item;
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (node.env) {
 | 
			
		||||
            for (var i = 0; i < node.env.length; i++) {
 | 
			
		||||
                var env = node.env[i];
 | 
			
		||||
                if (parentEnv.hasOwnProperty(env.name)) {
 | 
			
		||||
                    parentEnv[env.name].type = env.type;
 | 
			
		||||
                    parentEnv[env.name].value = env.value;
 | 
			
		||||
                } else {
 | 
			
		||||
                    envList.push({
 | 
			
		||||
                        name: env.name,
 | 
			
		||||
                        type: env.type,
 | 
			
		||||
                        value: env.value,
 | 
			
		||||
                        ui: env.ui
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        envList.forEach(function(env) {
 | 
			
		||||
            if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!isTemplateNode && env.parent) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            envContainer.editableList('addItem', JSON.parse(JSON.stringify(env)));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create UI edit interface for environment variable
 | 
			
		||||
     * @param container - container
 | 
			
		||||
     * @param env - env var definition
 | 
			
		||||
     * @param nameField - name field of env var
 | 
			
		||||
     * @param valueField - value field of env var
 | 
			
		||||
     */
 | 
			
		||||
     function buildEnvEditRow(container, ui, nameField, valueField) {
 | 
			
		||||
         container.addClass("red-ui-editor-subflow-env-ui-row")
 | 
			
		||||
         var topRow = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div></div>').appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.icon")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.label")).appendTo(topRow);
 | 
			
		||||
         $('<div>').text(RED._("editor.inputType")).appendTo(topRow);
 | 
			
		||||
 | 
			
		||||
         var row = $('<div></div>').appendTo(container);
 | 
			
		||||
         $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
 | 
			
		||||
         var typeOptions = {
 | 
			
		||||
             'input': {types:DEFAULT_ENV_TYPE_LIST},
 | 
			
		||||
             'select': {opts:[]},
 | 
			
		||||
             'spinner': {},
 | 
			
		||||
             'cred': {}
 | 
			
		||||
         };
 | 
			
		||||
         if (ui.opts) {
 | 
			
		||||
             typeOptions[ui.type] = ui.opts;
 | 
			
		||||
         } else {
 | 
			
		||||
             // Pick up the default values if not otherwise provided
 | 
			
		||||
             ui.opts = typeOptions[ui.type];
 | 
			
		||||
         }
 | 
			
		||||
         var iconCell = $('<div></div>').appendTo(row);
 | 
			
		||||
 | 
			
		||||
         var iconButton = $('<a href="#"></a>').appendTo(iconCell);
 | 
			
		||||
         iconButton.on("click", function(evt) {
 | 
			
		||||
             evt.preventDefault();
 | 
			
		||||
             var icon = ui.icon || "";
 | 
			
		||||
             var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
 | 
			
		||||
             RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) {
 | 
			
		||||
                 iconButton.empty();
 | 
			
		||||
                 var path = newIcon || "";
 | 
			
		||||
                 var newPath = RED.utils.separateIconPath(path);
 | 
			
		||||
                 if (newPath) {
 | 
			
		||||
                     $('<i class="fa"></i>').addClass(newPath.file).appendTo(iconButton);
 | 
			
		||||
                 }
 | 
			
		||||
                 ui.icon = path;
 | 
			
		||||
             });
 | 
			
		||||
         })
 | 
			
		||||
 | 
			
		||||
         if (ui.icon) {
 | 
			
		||||
             var newPath = RED.utils.separateIconPath(ui.icon);
 | 
			
		||||
             $('<i class="fa '+newPath.file+'"></i>').appendTo(iconButton);
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         var labelCell = $('<div></div>').appendTo(row);
 | 
			
		||||
 | 
			
		||||
         var label = ui.label && ui.label[currentLocale] || "";
 | 
			
		||||
         var labelInput = $('<input type="text">').val(label).appendTo(labelCell);
 | 
			
		||||
         ui.labelField = labelInput;
 | 
			
		||||
         labelInput.on('change', function(evt) {
 | 
			
		||||
             ui.label = ui.label || {};
 | 
			
		||||
             var val = $(this).val().trim();
 | 
			
		||||
             if (val === "") {
 | 
			
		||||
                 delete ui.label[currentLocale];
 | 
			
		||||
             } else {
 | 
			
		||||
                 ui.label[currentLocale] = val;
 | 
			
		||||
             }
 | 
			
		||||
         })
 | 
			
		||||
         var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelCell);
 | 
			
		||||
         RED.popover.tooltip(labelIcon,function() {
 | 
			
		||||
             var langs = Object.keys(ui.label);
 | 
			
		||||
             var content = $("<div>");
 | 
			
		||||
             if (langs.indexOf(currentLocale) === -1) {
 | 
			
		||||
                 langs.push(currentLocale);
 | 
			
		||||
                 langs.sort();
 | 
			
		||||
             }
 | 
			
		||||
             langs.forEach(function(l) {
 | 
			
		||||
                 var row = $('<div>').appendTo(content);
 | 
			
		||||
                 $('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row);
 | 
			
		||||
                 $('<span>').text(ui.label[l]||"").appendTo(row);
 | 
			
		||||
             });
 | 
			
		||||
             return content;
 | 
			
		||||
         })
 | 
			
		||||
 | 
			
		||||
         nameField.on('change',function(evt) {
 | 
			
		||||
            labelInput.attr("placeholder",$(this).val())
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var inputCell = $('<div></div>').appendTo(row);
 | 
			
		||||
        var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
 | 
			
		||||
        if (ui.type === "input") {
 | 
			
		||||
            inputCellInput.val(ui.opts.types.join(","));
 | 
			
		||||
        }
 | 
			
		||||
        var checkbox;
 | 
			
		||||
        var selectBox;
 | 
			
		||||
 | 
			
		||||
        inputCellInput.typedInput({
 | 
			
		||||
            types: [
 | 
			
		||||
                {
 | 
			
		||||
                    value:"input",
 | 
			
		||||
                    label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[
 | 
			
		||||
                        {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"},
 | 
			
		||||
                        {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"},
 | 
			
		||||
                        {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"},
 | 
			
		||||
                        {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"},
 | 
			
		||||
                        {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"},
 | 
			
		||||
                        {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"},
 | 
			
		||||
                        {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"}
 | 
			
		||||
                    ],
 | 
			
		||||
                    default: DEFAULT_ENV_TYPE_LIST,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
 | 
			
		||||
 | 
			
		||||
                        var input = $('<div class="placeholder-input">').appendTo(innerContainer);
 | 
			
		||||
                        $('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input);
 | 
			
		||||
                        if (value.length) {
 | 
			
		||||
                            value.forEach(function(v) {
 | 
			
		||||
                                if (!/^fa /.test(v.icon)) {
 | 
			
		||||
                                    $('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
 | 
			
		||||
                                    $("<i>",{class: v.icon}).appendTo(s);
 | 
			
		||||
                                }
 | 
			
		||||
                            })
 | 
			
		||||
                        } else {
 | 
			
		||||
                            $('<span class="red-ui-editor-subflow-env-input-type-placeholder"></span>').text(RED._("editor.selectType")).appendTo(input);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value: "cred",
 | 
			
		||||
                    label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type">').css({
 | 
			
		||||
                            "border-top-right-radius": "4px",
 | 
			
		||||
                            "border-bottom-right-radius": "4px"
 | 
			
		||||
                        }).appendTo(container);
 | 
			
		||||
                        $('<div class="placeholder-input">').html("••••••••").appendTo(innerContainer);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"select",
 | 
			
		||||
                    label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding","0");
 | 
			
		||||
 | 
			
		||||
                        selectBox = $('<select></select>').appendTo(container);
 | 
			
		||||
                        if (ui.opts && Array.isArray(ui.opts.opts)) {
 | 
			
		||||
                            ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                                var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale);
 | 
			
		||||
                                // $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox);
 | 
			
		||||
                                $('<option>').val(o.v).text(label).appendTo(selectBox);
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                        selectBox.on('change', function(evt) {
 | 
			
		||||
                            var v = selectBox.val();
 | 
			
		||||
                            // var parts = v.split(":");
 | 
			
		||||
                            // var t = parts.shift();
 | 
			
		||||
                            // v = parts.join(":");
 | 
			
		||||
                            //
 | 
			
		||||
                            // valueField.typedInput("type",'str')
 | 
			
		||||
                            valueField.typedInput("value",v)
 | 
			
		||||
                        });
 | 
			
		||||
                        selectBox.val(valueField.typedInput("value"));
 | 
			
		||||
                        // selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value"));
 | 
			
		||||
                    },
 | 
			
		||||
                    expand: {
 | 
			
		||||
                        icon: "fa-caret-down",
 | 
			
		||||
                        minWidth: 400,
 | 
			
		||||
                        content: function(container) {
 | 
			
		||||
                            var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
 | 
			
		||||
                            var optList = $('<ol>').appendTo(content).editableList({
 | 
			
		||||
                                header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"),
 | 
			
		||||
                                addItem: function(row,index,itemData) {
 | 
			
		||||
                                    var labelDiv = $('<div>').appendTo(row);
 | 
			
		||||
                                    var label = lookupLabel(itemData.l, "", currentLocale);
 | 
			
		||||
                                    itemData.label = $('<input type="text">').val(label).appendTo(labelDiv);
 | 
			
		||||
                                    itemData.label.on('keydown', function(evt) {
 | 
			
		||||
                                        if (evt.keyCode === 13) {
 | 
			
		||||
                                            itemData.input.focus();
 | 
			
		||||
                                            evt.preventDefault();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv);
 | 
			
		||||
                                    RED.popover.tooltip(labelIcon,function() {
 | 
			
		||||
                                        return currentLocale;
 | 
			
		||||
                                    })
 | 
			
		||||
                                    itemData.input = $('<input type="text">').val(itemData.v).appendTo(row);
 | 
			
		||||
 | 
			
		||||
                                    // Problem using a TI here:
 | 
			
		||||
                                    //  - this is in a popout panel
 | 
			
		||||
                                    //  - clicking the expand button in the TI will close the parent edit tray
 | 
			
		||||
                                    //    and open the type editor.
 | 
			
		||||
                                    //  - but it leaves the popout panel over the top.
 | 
			
		||||
                                    //  - there is no way to get back to the popout panel after closing the type editor
 | 
			
		||||
                                    //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST});
 | 
			
		||||
                                    itemData.input.on('keydown', function(evt) {
 | 
			
		||||
                                        if (evt.keyCode === 13) {
 | 
			
		||||
                                            // Enter or Tab
 | 
			
		||||
                                            var index = optList.editableList('indexOf',itemData);
 | 
			
		||||
                                            var length = optList.editableList('length');
 | 
			
		||||
                                            if (index + 1 === length) {
 | 
			
		||||
                                                var newItem = {};
 | 
			
		||||
                                                optList.editableList('addItem',newItem);
 | 
			
		||||
                                                setTimeout(function() {
 | 
			
		||||
                                                    if (newItem.label) {
 | 
			
		||||
                                                        newItem.label.focus();
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                },100)
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                var nextItem = optList.editableList('getItemAt',index+1);
 | 
			
		||||
                                                if (nextItem.label) {
 | 
			
		||||
                                                    nextItem.label.focus()
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            evt.preventDefault();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                },
 | 
			
		||||
                                sortable: true,
 | 
			
		||||
                                removable: true,
 | 
			
		||||
                                height: 160
 | 
			
		||||
                            })
 | 
			
		||||
                            if (ui.opts.opts.length > 0) {
 | 
			
		||||
                                ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                                    optList.editableList('addItem',$.extend(true,{},o))
 | 
			
		||||
                                })
 | 
			
		||||
                            } else {
 | 
			
		||||
                                optList.editableList('addItem',{})
 | 
			
		||||
                            }
 | 
			
		||||
                            return {
 | 
			
		||||
                                onclose: function() {
 | 
			
		||||
                                    var items = optList.editableList('items');
 | 
			
		||||
                                    var vals = [];
 | 
			
		||||
                                    items.each(function (i,el) {
 | 
			
		||||
                                        var data = el.data('data');
 | 
			
		||||
                                        var l = data.label.val().trim();
 | 
			
		||||
                                        var v = data.input.val();
 | 
			
		||||
                                        // var t = data.input.typedInput('type');
 | 
			
		||||
                                        // var v = data.input.typedInput('value');
 | 
			
		||||
                                        if (l.length > 0) {
 | 
			
		||||
                                            data.l = data.l || {};
 | 
			
		||||
                                            data.l[currentLocale] = l;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        data.v = v;
 | 
			
		||||
 | 
			
		||||
                                        if (l.length > 0 || v.length > 0) {
 | 
			
		||||
                                            var val = {l:data.l,v:data.v};
 | 
			
		||||
                                            // if (t !== 'str') {
 | 
			
		||||
                                            //     val.t = t;
 | 
			
		||||
                                            // }
 | 
			
		||||
                                            vals.push(val);
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                    ui.opts.opts = vals;
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"checkbox",
 | 
			
		||||
                    label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        checkbox = $('<input type="checkbox">').appendTo(container);
 | 
			
		||||
                        checkbox.on('change', function(evt) {
 | 
			
		||||
                            valueField.typedInput('value',$(this).prop('checked')?"true":"false");
 | 
			
		||||
                        })
 | 
			
		||||
                        checkbox.prop('checked',valueField.typedInput('value')==="true");
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"spinner",
 | 
			
		||||
                    label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false,
 | 
			
		||||
                    valueLabel: function(container,value) {
 | 
			
		||||
                        container.css("padding",0);
 | 
			
		||||
                        var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
 | 
			
		||||
 | 
			
		||||
                        var input = $('<div class="placeholder-input">').appendTo(innerContainer);
 | 
			
		||||
                        $('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input);
 | 
			
		||||
 | 
			
		||||
                        var min = ui.opts && ui.opts.min;
 | 
			
		||||
                        var max = ui.opts && ui.opts.max;
 | 
			
		||||
                        var label = "";
 | 
			
		||||
                        if (min !== undefined && max !== undefined) {
 | 
			
		||||
                            label = Math.min(min,max)+" - "+Math.max(min,max);
 | 
			
		||||
                        } else if (min !== undefined) {
 | 
			
		||||
                            label = "> "+min;
 | 
			
		||||
                        } else if (max !== undefined) {
 | 
			
		||||
                            label = "< "+max;
 | 
			
		||||
                        }
 | 
			
		||||
                        $('<span>').css("margin-left","15px").text(label).appendTo(input);
 | 
			
		||||
                    },
 | 
			
		||||
                    expand: {
 | 
			
		||||
                        icon: "fa-caret-down",
 | 
			
		||||
                        content: function(container) {
 | 
			
		||||
                            var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
 | 
			
		||||
                            content.css("padding","8px 5px")
 | 
			
		||||
                            var min = ui.opts.min;
 | 
			
		||||
                            var max = ui.opts.max;
 | 
			
		||||
                            var minInput = $('<input type="number" style="margin-bottom:0; width:60px">');
 | 
			
		||||
                            minInput.val(min);
 | 
			
		||||
                            var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">');
 | 
			
		||||
                            maxInput.val(max);
 | 
			
		||||
                            $('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content);
 | 
			
		||||
                            $('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').append(maxInput).appendTo(content);
 | 
			
		||||
                            return {
 | 
			
		||||
                                onclose: function() {
 | 
			
		||||
                                    var min = minInput.val().trim();
 | 
			
		||||
                                    var max = maxInput.val().trim();
 | 
			
		||||
                                    if (min !== "") {
 | 
			
		||||
                                        ui.opts.min = parseInt(min);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        delete ui.opts.min;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (max !== "") {
 | 
			
		||||
                                        ui.opts.max = parseInt(max);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        delete ui.opts.max;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"none",
 | 
			
		||||
                    label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    value:"hide",
 | 
			
		||||
                    label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            default: 'none'
 | 
			
		||||
        }).on("typedinputtypechange", function(evt,type) {
 | 
			
		||||
            ui.type = $(this).typedInput("type");
 | 
			
		||||
            ui.opts = typeOptions[ui.type];
 | 
			
		||||
            if (ui.type === 'input') {
 | 
			
		||||
                // In the case of 'input' type, the typedInput uses the multiple-option
 | 
			
		||||
                // mode. Its value needs to be set to a comma-separately list of the
 | 
			
		||||
                // selected options.
 | 
			
		||||
                inputCellInput.typedInput('value',ui.opts.types.join(","))
 | 
			
		||||
            } else {
 | 
			
		||||
                // No other type cares about `value`, but doing this will
 | 
			
		||||
                // force a refresh of the label now that `ui.opts` has
 | 
			
		||||
                // been updated.
 | 
			
		||||
                inputCellInput.typedInput('value',Date.now())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch (ui.type) {
 | 
			
		||||
                case 'input':
 | 
			
		||||
                    valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'select':
 | 
			
		||||
                    valueField.typedInput('types',['str']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'checkbox':
 | 
			
		||||
                    valueField.typedInput('types',['bool']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'spinner':
 | 
			
		||||
                    valueField.typedInput('types',['num']);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'cred':
 | 
			
		||||
                    valueField.typedInput('types',['cred']);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
 | 
			
		||||
            }
 | 
			
		||||
            if (ui.type === 'checkbox') {
 | 
			
		||||
                valueField.typedInput('type','bool');
 | 
			
		||||
            } else if (ui.type === 'spinner') {
 | 
			
		||||
                valueField.typedInput('type','num');
 | 
			
		||||
            }
 | 
			
		||||
            if (ui.type !== 'checkbox') {
 | 
			
		||||
                checkbox = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }).on("change", function(evt,type) {
 | 
			
		||||
            if (ui.type === 'input') {
 | 
			
		||||
                var types = inputCellInput.typedInput('value');
 | 
			
		||||
                ui.opts.types = (types === "") ? ["str"] : types.split(",");
 | 
			
		||||
                valueField.typedInput('types',ui.opts.types);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        valueField.on("change", function(evt) {
 | 
			
		||||
            if (checkbox) {
 | 
			
		||||
                checkbox.prop('checked',$(this).typedInput('value')==="true")
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        // Set the input to the right type. This will trigger the 'typedinputtypechange'
 | 
			
		||||
        // event handler (just above ^^) to update the value if needed
 | 
			
		||||
        inputCellInput.typedInput('type',ui.type)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildEnvUIRow(row, tenv, ui, node) {
 | 
			
		||||
        ui.label = ui.label||{};
 | 
			
		||||
@@ -1540,7 +929,7 @@ RED.subflow = (function() {
 | 
			
		||||
            ui.opts = {};
 | 
			
		||||
        } else if (!ui.type) {
 | 
			
		||||
            ui.type = "input";
 | 
			
		||||
            ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
            ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (!ui.opts) {
 | 
			
		||||
                ui.opts = (ui.type === "select") ? {opts:[]} : {};
 | 
			
		||||
@@ -1549,7 +938,7 @@ RED.subflow = (function() {
 | 
			
		||||
 | 
			
		||||
        var labels = ui.label || {};
 | 
			
		||||
        var locale = RED.i18n.lang();
 | 
			
		||||
        var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, locale);
 | 
			
		||||
        var labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"]||tenv.name, locale);
 | 
			
		||||
        var label = $('<label>').appendTo(row);
 | 
			
		||||
        $('<span> </span>').appendTo(row);
 | 
			
		||||
        var labelContainer = $('<span></span>').appendTo(label);
 | 
			
		||||
@@ -1602,7 +991,7 @@ RED.subflow = (function() {
 | 
			
		||||
                input = $('<select>').css('width','70%').appendTo(row);
 | 
			
		||||
                if (ui.opts.opts) {
 | 
			
		||||
                    ui.opts.opts.forEach(function(o) {
 | 
			
		||||
                        $('<option>').val(o.v).text(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);
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                input.val(val.value);
 | 
			
		||||
@@ -1668,9 +1057,8 @@ RED.subflow = (function() {
 | 
			
		||||
     * @param uiContainer - container for UI
 | 
			
		||||
     * @param envList - env var definitions of template
 | 
			
		||||
     */
 | 
			
		||||
    function buildEnvUI(uiContainer, envList,node) {
 | 
			
		||||
    function buildEnvUI(uiContainer, envList, node) {
 | 
			
		||||
        uiContainer.empty();
 | 
			
		||||
        var elementID = 0;
 | 
			
		||||
        for (var i = 0; i < envList.length; i++) {
 | 
			
		||||
            var tenv = envList[i];
 | 
			
		||||
            if (tenv.ui && tenv.ui.type === 'hide') {
 | 
			
		||||
@@ -1678,8 +1066,6 @@ RED.subflow = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
 | 
			
		||||
            buildEnvUIRow(row,tenv, tenv.ui || {}, node);
 | 
			
		||||
 | 
			
		||||
            // console.log(ui);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // buildEnvUI
 | 
			
		||||
@@ -1715,7 +1101,7 @@ RED.subflow = (function() {
 | 
			
		||||
                            //     icon: "",
 | 
			
		||||
                            //     label: {},
 | 
			
		||||
                            //     type: "input",
 | 
			
		||||
                            //     opts: {types:DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                            //     opts: {types:RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                            // }
 | 
			
		||||
                            if (!ui.icon) {
 | 
			
		||||
                                delete ui.icon;
 | 
			
		||||
@@ -1725,7 +1111,7 @@ RED.subflow = (function() {
 | 
			
		||||
                            }
 | 
			
		||||
                            switch (ui.type) {
 | 
			
		||||
                                case "input":
 | 
			
		||||
                                    if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) {
 | 
			
		||||
                                    if (JSON.stringify(ui.opts) === JSON.stringify({types:RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST})) {
 | 
			
		||||
                                        // This is the default input config. Delete it as it will
 | 
			
		||||
                                        // be applied automatically
 | 
			
		||||
                                        delete ui.type;
 | 
			
		||||
@@ -1841,7 +1227,6 @@ RED.subflow = (function() {
 | 
			
		||||
 | 
			
		||||
    function exportSubflowInstanceEnv(node) {
 | 
			
		||||
        var env = [];
 | 
			
		||||
 | 
			
		||||
        // First, get the values for the SubflowTemplate defined properties
 | 
			
		||||
        //  - these are the ones with custom UI elements
 | 
			
		||||
        var parentEnv = getSubflowInstanceParentEnv(node);
 | 
			
		||||
@@ -1853,7 +1238,7 @@ RED.subflow = (function() {
 | 
			
		||||
                    ui.type = "cred";
 | 
			
		||||
                } else {
 | 
			
		||||
                    ui.type = "input";
 | 
			
		||||
                    ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                    ui.opts = {types:RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST}
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                ui.opts = ui.opts || {};
 | 
			
		||||
@@ -1893,22 +1278,6 @@ RED.subflow = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        // Second, get the values from the Properties table tab
 | 
			
		||||
        var items = $('#red-ui-editor-subflow-env-list').editableList('items');
 | 
			
		||||
        items.each(function (i,el) {
 | 
			
		||||
            var data = el.data('data');
 | 
			
		||||
            var item;
 | 
			
		||||
            if (data.nameField && data.valueField) {
 | 
			
		||||
                item = {
 | 
			
		||||
                    name: data.nameField.val(),
 | 
			
		||||
                    value: data.valueField.typedInput("value"),
 | 
			
		||||
                    type: data.valueField.typedInput("type")
 | 
			
		||||
                }
 | 
			
		||||
                if (item.name.trim() !== "") {
 | 
			
		||||
                    env.push(item);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return env;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1916,164 +1285,18 @@ RED.subflow = (function() {
 | 
			
		||||
        return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lookup text for specific locale
 | 
			
		||||
     * @param labels - dict of labels
 | 
			
		||||
     * @param defaultLabel - fallback label if not found
 | 
			
		||||
     * @param locale - target locale
 | 
			
		||||
     * @returns {string} text for specified locale
 | 
			
		||||
     */
 | 
			
		||||
    function lookupLabel(labels, defaultLabel, locale) {
 | 
			
		||||
        if (labels) {
 | 
			
		||||
            if (labels[locale]) {
 | 
			
		||||
                return labels[locale];
 | 
			
		||||
            }
 | 
			
		||||
            if (locale) {
 | 
			
		||||
                var lang = locale.substring(0, 2);
 | 
			
		||||
                if (labels[lang]) {
 | 
			
		||||
                    return labels[lang];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return defaultLabel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Called by subflow.oneditprepare for both instances and templates
 | 
			
		||||
    function buildEditForm(type,node) {
 | 
			
		||||
        if (type === "subflow-template") {
 | 
			
		||||
            buildPropertiesList($('#node-input-env-container'), node);
 | 
			
		||||
            // This is the tabbed UI that offers the env list - with UI options
 | 
			
		||||
            // plus the preview tab
 | 
			
		||||
            buildEnvControl($('#node-input-env-container'), node);
 | 
			
		||||
            RED.editor.envVarList.create($('#node-input-env-container'), node);
 | 
			
		||||
        } else  if (type === "subflow") {
 | 
			
		||||
            // This gets called by the subflow type `oneditprepare` function
 | 
			
		||||
            // registered in nodes.js#addSubflow()
 | 
			
		||||
            // This is the rendered version of the subflow env var list
 | 
			
		||||
            buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function buildPropertiesForm(node) {
 | 
			
		||||
        var container = $('#editor-subflow-envProperties-content');
 | 
			
		||||
        var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container);
 | 
			
		||||
        var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form);
 | 
			
		||||
        var list = $('<ol id="red-ui-editor-subflow-env-list" class="red-ui-editor-subflow-env-list"></ol>').appendTo(listContainer);
 | 
			
		||||
        buildPropertiesList(list, node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setupInputValidation(input,validator) {
 | 
			
		||||
        var errorTip;
 | 
			
		||||
        var validateTimeout;
 | 
			
		||||
 | 
			
		||||
        var validateFunction = function() {
 | 
			
		||||
            if (validateTimeout) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            validateTimeout = setTimeout(function() {
 | 
			
		||||
                var error = validator(input.val());
 | 
			
		||||
                // if (!error && errorTip) {
 | 
			
		||||
                //     errorTip.close();
 | 
			
		||||
                //     errorTip = null;
 | 
			
		||||
                // } else if (error && !errorTip) {
 | 
			
		||||
                //     errorTip = RED.popover.create({
 | 
			
		||||
                //         tooltip: true,
 | 
			
		||||
                //         target:input,
 | 
			
		||||
                //         size: "small",
 | 
			
		||||
                //         direction: "bottom",
 | 
			
		||||
                //         content: error,
 | 
			
		||||
                //     }).open();
 | 
			
		||||
                // }
 | 
			
		||||
                input.toggleClass("input-error",!!error);
 | 
			
		||||
                validateTimeout = null;
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        input.on("change keyup paste", validateFunction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function buildModuleForm(container, node) {
 | 
			
		||||
        $(_subflowModulePaneTemplate).appendTo(container);
 | 
			
		||||
        var moduleProps = node.meta || {};
 | 
			
		||||
        [
 | 
			
		||||
            'module',
 | 
			
		||||
            'type',
 | 
			
		||||
            'version',
 | 
			
		||||
            'author',
 | 
			
		||||
            'desc',
 | 
			
		||||
            'keywords',
 | 
			
		||||
            'license'
 | 
			
		||||
        ].forEach(function(property) {
 | 
			
		||||
            $("#subflow-input-module-"+property).val(moduleProps[property]||"")
 | 
			
		||||
        })
 | 
			
		||||
        $("#subflow-input-module-type").attr("placeholder",node.id);
 | 
			
		||||
 | 
			
		||||
        setupInputValidation($("#subflow-input-module-module"), function(newValue) {
 | 
			
		||||
            newValue = newValue.trim();
 | 
			
		||||
            var isValid = newValue.length < 215;
 | 
			
		||||
            isValid = isValid && !/^[._]/.test(newValue);
 | 
			
		||||
            isValid = isValid && !/[A-Z]/.test(newValue);
 | 
			
		||||
            if (newValue !== encodeURIComponent(newValue)) {
 | 
			
		||||
                var m = /^@([^\/]+)\/([^\/]+)$/.exec(newValue);
 | 
			
		||||
                if (m) {
 | 
			
		||||
                    isValid = isValid && (m[1] === encodeURIComponent(m[1]) && m[2] === encodeURIComponent(m[2]))
 | 
			
		||||
                } else {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return isValid?"":"Invalid module name"
 | 
			
		||||
        })
 | 
			
		||||
        setupInputValidation($("#subflow-input-module-version"), function(newValue) {
 | 
			
		||||
            newValue = newValue.trim();
 | 
			
		||||
            var isValid = newValue === "" ||
 | 
			
		||||
                          /^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/.test(newValue);
 | 
			
		||||
            return isValid?"":"Invalid version number"
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        var licenses = ["none", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "GPL-2.0", "GPL-3.0", "MIT", "MPL-2.0", "CDDL-1.0", "EPL-2.0"];
 | 
			
		||||
        var typedLicenses = {
 | 
			
		||||
            types: licenses.map(function(l) {
 | 
			
		||||
                return {
 | 
			
		||||
                    value: l,
 | 
			
		||||
                    label: l === "none" ? RED._("editor:subflow.licenseNone") : l,
 | 
			
		||||
                    hasValue: false
 | 
			
		||||
                };
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        typedLicenses.types.push({
 | 
			
		||||
            value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
 | 
			
		||||
        })
 | 
			
		||||
        if (!moduleProps.license) {
 | 
			
		||||
            typedLicenses.default = "none";
 | 
			
		||||
        } else if (licenses.indexOf(moduleProps.license) > -1) {
 | 
			
		||||
            typedLicenses.default = moduleProps.license;
 | 
			
		||||
        } else {
 | 
			
		||||
            typedLicenses.default = "_custom_";
 | 
			
		||||
        }
 | 
			
		||||
        $("#subflow-input-module-license").typedInput(typedLicenses)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function exportSubflowModuleProperties(node) {
 | 
			
		||||
        var value;
 | 
			
		||||
        var moduleProps = {};
 | 
			
		||||
        [
 | 
			
		||||
            'module',
 | 
			
		||||
            'type',
 | 
			
		||||
            'version',
 | 
			
		||||
            'author',
 | 
			
		||||
            'desc',
 | 
			
		||||
            'keywords'
 | 
			
		||||
        ].forEach(function(property) {
 | 
			
		||||
            value = $("#subflow-input-module-"+property).val().trim();
 | 
			
		||||
            if (value) {
 | 
			
		||||
                moduleProps[property] = value;
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        var selectedLicenseType = $("#subflow-input-module-license").typedInput("type");
 | 
			
		||||
 | 
			
		||||
        if (selectedLicenseType === '_custom_') {
 | 
			
		||||
            value = $("#subflow-input-module-license").val();
 | 
			
		||||
            if (value) {
 | 
			
		||||
                moduleProps.license = value;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (selectedLicenseType !== "none") {
 | 
			
		||||
            moduleProps.license = selectedLicenseType;
 | 
			
		||||
        }
 | 
			
		||||
        return moduleProps;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
@@ -2085,14 +1308,9 @@ RED.subflow = (function() {
 | 
			
		||||
        removeOutput: removeSubflowOutput,
 | 
			
		||||
        removeStatus: removeSubflowStatus,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        buildEditForm: buildEditForm,
 | 
			
		||||
        buildPropertiesForm: buildPropertiesForm,
 | 
			
		||||
        buildModuleForm: buildModuleForm,
 | 
			
		||||
 | 
			
		||||
        exportSubflowTemplateEnv: exportEnvList,
 | 
			
		||||
        exportSubflowInstanceEnv: exportSubflowInstanceEnv,
 | 
			
		||||
        exportSubflowModuleProperties: exportSubflowModuleProperties
 | 
			
		||||
 | 
			
		||||
        exportSubflowInstanceEnv: exportSubflowInstanceEnv
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -229,7 +229,7 @@ RED.sidebar.config = (function() {
 | 
			
		||||
        var globalConfigNodes = [];
 | 
			
		||||
        var configList = {};
 | 
			
		||||
        RED.nodes.eachConfig(function(cn) {
 | 
			
		||||
            if (cn.z) {//} == RED.workspaces.active()) {
 | 
			
		||||
            if (cn.z) {
 | 
			
		||||
                configList[cn.z.replace(/\./g,"-")] = configList[cn.z.replace(/\./g,"-")]||[];
 | 
			
		||||
                configList[cn.z.replace(/\./g,"-")].push(cn);
 | 
			
		||||
            } else if (!cn.z) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ RED.sidebar.help = (function() {
 | 
			
		||||
    var tocPanel;
 | 
			
		||||
    var helpIndex = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function resizeStack() {
 | 
			
		||||
        var h = $(content).parent().height() - toolbar.outerHeight();
 | 
			
		||||
        panels.resize(h)
 | 
			
		||||
@@ -93,9 +92,28 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        $('<span class="red-ui-help-info-none">'+RED._("sidebar.help.noHelp")+'</span>').appendTo(helpSection);
 | 
			
		||||
 | 
			
		||||
        treeList = $("<div>").css({width: "100%"}).appendTo(tocPanel).treeList({data: []})
 | 
			
		||||
        var pendingContentLoad;
 | 
			
		||||
        treeList.on('treelistselect', function(e,item) {
 | 
			
		||||
            pendingContentLoad = item;
 | 
			
		||||
            if (item.nodeType) {
 | 
			
		||||
                showHelp(item.nodeType);
 | 
			
		||||
                showNodeTypeHelp(item.nodeType);
 | 
			
		||||
            } else if (item.content) {
 | 
			
		||||
                helpSection.empty();
 | 
			
		||||
                if (typeof item.content === "string") {
 | 
			
		||||
                    setInfoText(item.label, item.content);
 | 
			
		||||
                } else if (typeof item.content === "function") {
 | 
			
		||||
                    if (item.content.length === 0) {
 | 
			
		||||
                        setInfoText(item.label, item.content());
 | 
			
		||||
                    } else {
 | 
			
		||||
                        setInfoText(item.label, '<div class="red-ui-component-spinner red-ui-component-spinner-contain"><img src="red/images/spin.svg" /></div>',helpSection)
 | 
			
		||||
                        item.content(function(content) {
 | 
			
		||||
                            if (pendingContentLoad === item) {
 | 
			
		||||
                                helpSection.empty();
 | 
			
		||||
                                setInfoText(item.label, content);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@@ -174,21 +192,28 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        var moduleNames = Object.keys(modules);
 | 
			
		||||
        moduleNames.sort();
 | 
			
		||||
 | 
			
		||||
        var helpData = [{
 | 
			
		||||
        var nodeHelp = {
 | 
			
		||||
            label: RED._("sidebar.help.nodeHelp"),
 | 
			
		||||
            children: [],
 | 
			
		||||
            expanded: true
 | 
			
		||||
        }]
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        var helpData = [
 | 
			
		||||
            {
 | 
			
		||||
                id: 'changelog',
 | 
			
		||||
                label: "Node-RED v"+RED.settings.version,
 | 
			
		||||
                content: getChangelog
 | 
			
		||||
            },
 | 
			
		||||
            nodeHelp
 | 
			
		||||
        ]
 | 
			
		||||
        var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)});
 | 
			
		||||
        if (subflows.length > 0) {
 | 
			
		||||
            helpData[0].children.push({
 | 
			
		||||
            nodeHelp.children.push({
 | 
			
		||||
                label: RED._("menu.label.subflows"),
 | 
			
		||||
                children: []
 | 
			
		||||
            })
 | 
			
		||||
            subflows.forEach(function(nodeType) {
 | 
			
		||||
                var sf = RED.nodes.getType(nodeType);
 | 
			
		||||
                helpData[0].children[0].children.push({
 | 
			
		||||
                nodeHelp.children[0].children.push({
 | 
			
		||||
                    id:"node-type:"+nodeType,
 | 
			
		||||
                    nodeType: nodeType,
 | 
			
		||||
                    subflowLabel: sf.label().toLowerCase(),
 | 
			
		||||
@@ -218,7 +243,7 @@ RED.sidebar.help = (function() {
 | 
			
		||||
                nodeTypes.sort(function(A,B) {
 | 
			
		||||
                    return A.nodeType.localeCompare(B.nodeType)
 | 
			
		||||
                })
 | 
			
		||||
                helpData[0].children.push({
 | 
			
		||||
                nodeHelp.children.push({
 | 
			
		||||
                    id: moduleName,
 | 
			
		||||
                    icon: "fa fa-cube",
 | 
			
		||||
                    label: moduleName,
 | 
			
		||||
@@ -230,14 +255,21 @@ RED.sidebar.help = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeLabel(n) {
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-info-outline-item"});
 | 
			
		||||
        RED.utils.createNodeIcon(n).appendTo(div);
 | 
			
		||||
        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
 | 
			
		||||
        $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(n.name||n._def.paletteLabel||n.type).appendTo(contentDiv);
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-node-list-item"});
 | 
			
		||||
        var icon = RED.utils.createNodeIcon(n).appendTo(div);
 | 
			
		||||
        var label = n.name;
 | 
			
		||||
        if (!label && n._def && n._def.paletteLabel) {
 | 
			
		||||
            try {
 | 
			
		||||
                label = (typeof n._def.paletteLabel === "function" ? n._def.paletteLabel.call(n._def) : n._def.paletteLabel)||"";
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        label = label || n.type;
 | 
			
		||||
        $('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(icon);
 | 
			
		||||
        return div;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showHelp(nodeType) {
 | 
			
		||||
    function showNodeTypeHelp(nodeType) {
 | 
			
		||||
        helpSection.empty();
 | 
			
		||||
        var helpText;
 | 
			
		||||
        var title;
 | 
			
		||||
@@ -250,8 +282,15 @@ RED.sidebar.help = (function() {
 | 
			
		||||
            helpText = RED.nodes.getNodeHelp(nodeType)||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
 | 
			
		||||
            var _def = RED.nodes.registry.getNodeType(nodeType);
 | 
			
		||||
            title = (_def && _def.paletteLabel)?_def.paletteLabel:nodeType;
 | 
			
		||||
            if (typeof title === "function") {
 | 
			
		||||
                try {
 | 
			
		||||
                    title = _def.paletteLabel.call(_def);
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    title = nodeType;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        setInfoText(title, helpText, helpSection);
 | 
			
		||||
        setInfoText(title, helpText);
 | 
			
		||||
 | 
			
		||||
        var ratio = panels.ratio();
 | 
			
		||||
        if (ratio > 0.7) {
 | 
			
		||||
@@ -268,7 +307,7 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        if (type) {
 | 
			
		||||
            // hideTOC();
 | 
			
		||||
            showHelp(type);
 | 
			
		||||
            showNodeTypeHelp(type);
 | 
			
		||||
        }
 | 
			
		||||
        resizeStack();
 | 
			
		||||
    }
 | 
			
		||||
@@ -284,11 +323,12 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        return el;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setInfoText(title, infoText,target) {
 | 
			
		||||
    function setInfoText(title, infoText) {
 | 
			
		||||
        helpSection.empty();
 | 
			
		||||
        if (title) {
 | 
			
		||||
            $("<h1>",{class:"red-ui-help-title"}).text(title).appendTo(target);
 | 
			
		||||
            $("<h1>",{class:"red-ui-help-title"}).text(title).appendTo(helpSection);
 | 
			
		||||
        }
 | 
			
		||||
        var info = addTargetToExternalLinks($('<div class="red-ui-help"><span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(target);
 | 
			
		||||
        var info = addTargetToExternalLinks($('<div class="red-ui-help"><span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(helpSection);
 | 
			
		||||
        info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
 | 
			
		||||
        var foldingHeader = "H3";
 | 
			
		||||
        info.find(foldingHeader).wrapInner('<a class="red-ui-help-info-header expanded" href="#"></a>')
 | 
			
		||||
@@ -302,12 +342,12 @@ RED.sidebar.help = (function() {
 | 
			
		||||
                }
 | 
			
		||||
                $(this).toggleClass('expanded',!isExpanded);
 | 
			
		||||
            })
 | 
			
		||||
        target.parent().scrollTop(0);
 | 
			
		||||
        helpSection.parent().scrollTop(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function set(html,title) {
 | 
			
		||||
        $(helpSection).empty();
 | 
			
		||||
        setInfoText(title,html,helpSection);
 | 
			
		||||
        setInfoText(title,html);
 | 
			
		||||
        hideTOC();
 | 
			
		||||
        show();
 | 
			
		||||
    }
 | 
			
		||||
@@ -322,13 +362,91 @@ RED.sidebar.help = (function() {
 | 
			
		||||
                if (node.type === "subflow" && node.direction) {
 | 
			
		||||
                    // ignore subflow virtual ports
 | 
			
		||||
                } else if (node.type !== 'group'){
 | 
			
		||||
                    showHelp(node.type);
 | 
			
		||||
                    showNodeTypeHelp(node.type);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.events.on("view:selection-changed",refreshSelection);
 | 
			
		||||
 | 
			
		||||
    function getChangelog(done) {
 | 
			
		||||
        $.get('red/about', function(data) {
 | 
			
		||||
            // data will be strictly markdown. Any HTML should be escaped.
 | 
			
		||||
            data = RED.utils.sanitize(data);
 | 
			
		||||
            RED.tourGuide.load("./tours/welcome.js", function(err, tour) {
 | 
			
		||||
                var tourHeader = '<div><img width="50px" src="red/images/node-red-icon.svg" /></div>';
 | 
			
		||||
                if (tour) {
 | 
			
		||||
                    var currentVersionParts = RED.settings.version.split(".");
 | 
			
		||||
                    var tourVersionParts = tour.version.split(".");
 | 
			
		||||
                    if (tourVersionParts[0] === currentVersionParts[0] && tourVersionParts[1] === currentVersionParts[1]) {
 | 
			
		||||
                        tourHeader = '<div><button type="button" onclick="RED.actions.invoke(\'core:show-welcome-tour\')" class="red-ui-button">' + RED._("tourGuide.takeATour") + '</button></div>';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                var aboutHeader = '<div style="text-align:center;">'+tourHeader+'</div>'
 | 
			
		||||
                done(aboutHeader+RED.utils.renderMarkdown(data))
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    function showAbout() {
 | 
			
		||||
        treeList.treeList("show","changelog")
 | 
			
		||||
        treeList.treeList("select","changelog");
 | 
			
		||||
        show();
 | 
			
		||||
    }
 | 
			
		||||
    function showWelcomeTour(lastSeenVersion, done) {
 | 
			
		||||
        done = done || function() {};
 | 
			
		||||
        RED.tourGuide.load("./tours/welcome.js", function(err, tour) {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                console.warn("Failed to load welcome tour",err);
 | 
			
		||||
                done()
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var currentVersionParts = RED.settings.version.split(".");
 | 
			
		||||
            var tourVersionParts = tour.version.split(".");
 | 
			
		||||
 | 
			
		||||
            // Only display the tour if its MAJ.MIN versions the current version
 | 
			
		||||
            // This means if we update MAJ/MIN without updating the tour, the old tour won't get shown
 | 
			
		||||
            if (tourVersionParts[0] !== currentVersionParts[0] || tourVersionParts[1] !== currentVersionParts[1]) {
 | 
			
		||||
                done()
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (lastSeenVersion) {
 | 
			
		||||
                // Previously displayed a welcome tour.
 | 
			
		||||
                if (lastSeenVersion === RED.settings.version) {
 | 
			
		||||
                    // Exact match - don't show the tour
 | 
			
		||||
                    done()
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var lastSeenParts = lastSeenVersion.split(".");
 | 
			
		||||
                if (currentVersionParts[0] < lastSeenParts[0] || (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] < lastSeenParts[1])) {
 | 
			
		||||
                    // Running an *older* version than last displayed tour.
 | 
			
		||||
                    done()
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] === lastSeenParts[1]) {
 | 
			
		||||
                    if (lastSeenParts.length === 3 && currentVersionParts.length === 3) {
 | 
			
		||||
                        // Matching non-beta MAJ.MIN - don't repeat tour
 | 
			
		||||
                        done()
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (currentVersionParts.length === 4 && (lastSeenParts.length === 3 || currentVersionParts[3] < lastSeenParts[3])) {
 | 
			
		||||
                        // Running an *older* beta than last displayed tour.
 | 
			
		||||
                        done()
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            RED.tourGuide.run("./tours/welcome.js", function(err) {
 | 
			
		||||
                RED.settings.set("editor.tours.welcome", RED.settings.version)
 | 
			
		||||
                done()
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    RED.actions.add("core:show-about", showAbout);
 | 
			
		||||
    RED.actions.add("core:show-welcome-tour", showWelcomeTour);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        show: show,
 | 
			
		||||
 
 | 
			
		||||
@@ -73,36 +73,11 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeLabelText(n) {
 | 
			
		||||
        var label = n.name || n.type+": "+n.id;
 | 
			
		||||
        if (n._def.label) {
 | 
			
		||||
            try {
 | 
			
		||||
                label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                console.log("Definition error: "+n.type+".label",err);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var newlineIndex = label.indexOf("\\n");
 | 
			
		||||
        if (newlineIndex > -1) {
 | 
			
		||||
            label = label.substring(0,newlineIndex)+"...";
 | 
			
		||||
        }
 | 
			
		||||
        return label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeLabel(n) {
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-info-outline-item"});
 | 
			
		||||
        RED.utils.createNodeIcon(n).appendTo(div);
 | 
			
		||||
        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
 | 
			
		||||
        var labelText = getNodeLabelText(n);
 | 
			
		||||
        var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
 | 
			
		||||
        if (labelText) {
 | 
			
		||||
            label.text(labelText)
 | 
			
		||||
        } else {
 | 
			
		||||
            label.html(" ")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var div = $('<div>',{class:"red-ui-node-list-item red-ui-info-outline-item"});
 | 
			
		||||
        RED.utils.createNodeIcon(n, true).appendTo(div);
 | 
			
		||||
        div.find(".red-ui-node-label").addClass("red-ui-info-outline-item-label")
 | 
			
		||||
        addControls(n, div);
 | 
			
		||||
 | 
			
		||||
        return div;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -147,11 +122,20 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
            })
 | 
			
		||||
            RED.popover.tooltip(triggerButton,RED._("sidebar.info.triggerAction"));
 | 
			
		||||
        }
 | 
			
		||||
        // $('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-eye"></i></button>').appendTo(controls).on("click",function(evt) {
 | 
			
		||||
        //     evt.preventDefault();
 | 
			
		||||
        //     evt.stopPropagation();
 | 
			
		||||
        //     RED.view.reveal(n.id);
 | 
			
		||||
        // })
 | 
			
		||||
 | 
			
		||||
        if (n.type === "tab") {
 | 
			
		||||
            var toggleVisibleButton = $('<button type="button" class="red-ui-info-outline-item-control-hide red-ui-button red-ui-button-small"><i class="fa fa-eye"></i><i class="fa fa-eye-slash"></i></button>').appendTo(controls).on("click",function(evt) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                evt.stopPropagation();
 | 
			
		||||
                var isHidden = !div.hasClass("red-ui-info-outline-item-hidden");
 | 
			
		||||
                div.toggleClass("red-ui-info-outline-item-hidden",isHidden);
 | 
			
		||||
                if (isHidden) {
 | 
			
		||||
                    RED.workspaces.hide(n.id);
 | 
			
		||||
                } else {
 | 
			
		||||
                    RED.workspaces.show(n.id, null, true);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (n.type !== 'subflow') {
 | 
			
		||||
            var toggleButton = $('<button type="button" class="red-ui-info-outline-item-control-disable red-ui-button red-ui-button-small"><i class="fa fa-circle-thin"></i><i class="fa fa-ban"></i></button>').appendTo(controls).on("click",function(evt) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
@@ -191,6 +175,7 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
                                    n.d = true;
 | 
			
		||||
                                }
 | 
			
		||||
                                n.dirty = true;
 | 
			
		||||
                                n.dirtyStatus = true;
 | 
			
		||||
                                n.changed = true;
 | 
			
		||||
                                RED.events.emit("nodes:change",n);
 | 
			
		||||
                                groupHistoryEvent.events.push(historyEvent);
 | 
			
		||||
@@ -219,6 +204,7 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
                        n.d = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    n.dirty = true;
 | 
			
		||||
                    n.dirtyStatus = true;
 | 
			
		||||
                    n.changed = true;
 | 
			
		||||
                    RED.events.emit("nodes:change",n);
 | 
			
		||||
                    RED.history.push(historyEvent);
 | 
			
		||||
@@ -288,6 +274,7 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
                {label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"},
 | 
			
		||||
                {label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"},
 | 
			
		||||
                {label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"},
 | 
			
		||||
                {label:RED._("sidebar.info.search.hiddenFlows"), value:"is:hidden"},
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -303,9 +290,11 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
            var node = RED.nodes.node(item.id) || RED.nodes.group(item.id);
 | 
			
		||||
            if (node) {
 | 
			
		||||
                if (node.type === 'group' || node._def.category !== "config") {
 | 
			
		||||
                    RED.view.select({nodes:[node]})
 | 
			
		||||
                    // RED.view.select({nodes:[node]})
 | 
			
		||||
                } else if (node._def.category === "config") {
 | 
			
		||||
                    RED.sidebar.info.refresh(node);
 | 
			
		||||
                } else {
 | 
			
		||||
                    RED.view.select({nodes:[]})
 | 
			
		||||
                    // RED.view.select({nodes:[]})
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
@@ -334,18 +323,33 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        RED.events.on("nodes:add",onNodeAdd);
 | 
			
		||||
        RED.events.on("nodes:remove",onObjectRemove);
 | 
			
		||||
        RED.events.on("nodes:change",onNodeChange);
 | 
			
		||||
        // RED.events.on("nodes:reorder",onNodesReorder);
 | 
			
		||||
 | 
			
		||||
        RED.events.on("groups:add",onNodeAdd);
 | 
			
		||||
        RED.events.on("groups:remove",onObjectRemove);
 | 
			
		||||
        RED.events.on("groups:change",onNodeChange);
 | 
			
		||||
 | 
			
		||||
        RED.events.on("workspace:clear", onWorkspaceClear)
 | 
			
		||||
        RED.events.on("workspace:show", onWorkspaceShow);
 | 
			
		||||
        RED.events.on("workspace:hide", onWorkspaceHide);
 | 
			
		||||
        RED.events.on("workspace:clear", onWorkspaceClear);
 | 
			
		||||
 | 
			
		||||
        return container;
 | 
			
		||||
    }
 | 
			
		||||
    function onWorkspaceClear() {
 | 
			
		||||
        treeList.treeList('data',getFlowData());
 | 
			
		||||
    }
 | 
			
		||||
    function onWorkspaceShow(event) {
 | 
			
		||||
        var existingObject = objects[event.workspace];
 | 
			
		||||
        if (existingObject) {
 | 
			
		||||
            existingObject.element.removeClass("red-ui-info-outline-item-hidden")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function onWorkspaceHide(event) {
 | 
			
		||||
        var existingObject = objects[event.workspace];
 | 
			
		||||
        if (existingObject) {
 | 
			
		||||
            existingObject.element.addClass("red-ui-info-outline-item-hidden")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function onFlowAdd(ws) {
 | 
			
		||||
        objects[ws.id] = {
 | 
			
		||||
            id: ws.id,
 | 
			
		||||
@@ -392,6 +396,21 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
            return indexMap[A.id] - indexMap[B.id]
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    // function onNodesReorder(event) {
 | 
			
		||||
    //     //
 | 
			
		||||
    //     var nodes = RED.nodes.getNodeOrder(event.z);
 | 
			
		||||
    //     var indexMap = {};
 | 
			
		||||
    //     nodes.forEach(function(id,index) {
 | 
			
		||||
    //         indexMap[id] = index;
 | 
			
		||||
    //     })
 | 
			
		||||
    //     var existingObject = objects[event.z];
 | 
			
		||||
    //     existingObject.treeList.sortChildren(function(A,B) {
 | 
			
		||||
    //         if (A.children && !B.children) { return -1 }
 | 
			
		||||
    //         if (!A.children && B.children) { return 1 }
 | 
			
		||||
    //         if (A.children && B.children) { return -1 }
 | 
			
		||||
    //         return indexMap[A.id] - indexMap[B.id]
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
    function onSubflowAdd(sf) {
 | 
			
		||||
        objects[sf.id] = {
 | 
			
		||||
            id: sf.id,
 | 
			
		||||
@@ -430,7 +449,7 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        var existingObject = objects[n.id];
 | 
			
		||||
        var parent = n.g||n.z||"__global__";
 | 
			
		||||
 | 
			
		||||
        var nodeLabelText = getNodeLabelText(n);
 | 
			
		||||
        var nodeLabelText = RED.utils.getNodeLabel(n,n.name || (n.type+": "+n.id));
 | 
			
		||||
        if (nodeLabelText) {
 | 
			
		||||
            existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText);
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -547,7 +566,7 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function getGutter(n) {
 | 
			
		||||
        var span = $("<span>",{class:"red-ui-info-outline-gutter"});
 | 
			
		||||
        var span = $("<span>",{class:"red-ui-info-outline-gutter red-ui-treeList-gutter-float"});
 | 
			
		||||
        var revealButton = $('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-search"></i></button>').appendTo(span).on("click",function(evt) {
 | 
			
		||||
            evt.preventDefault();
 | 
			
		||||
            evt.stopPropagation();
 | 
			
		||||
 
 | 
			
		||||
@@ -180,6 +180,10 @@ RED.sidebar.info = (function() {
 | 
			
		||||
 | 
			
		||||
        if (node === null) {
 | 
			
		||||
            RED.sidebar.info.outliner.select(null);
 | 
			
		||||
            propertiesPanelHeaderIcon.empty();
 | 
			
		||||
            propertiesPanelHeaderLabel.text("");
 | 
			
		||||
            propertiesPanelHeaderReveal.hide();
 | 
			
		||||
            propertiesPanelHeaderHelp.hide();
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (Array.isArray(node)) {
 | 
			
		||||
            // Multiple things selected
 | 
			
		||||
@@ -477,7 +481,7 @@ RED.sidebar.info = (function() {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            while ((m=/(\[(.*?)\])/.exec(tip))) {
 | 
			
		||||
            while ((m=/(\[([a-z]*?)\])/.exec(tip))) {
 | 
			
		||||
                tip = tip.replace(m[1],RED.keyboard.formatKey(m[2]));
 | 
			
		||||
            }
 | 
			
		||||
            tipBox.html(tip).fadeIn(200);
 | 
			
		||||
@@ -499,7 +503,7 @@ RED.sidebar.info = (function() {
 | 
			
		||||
                    if (tipCount === -1) {
 | 
			
		||||
                        do {
 | 
			
		||||
                            tipCount++;
 | 
			
		||||
                        } while(RED._("infotips:info.tip"+tipCount)!=="infotips:info.tip"+tipCount);
 | 
			
		||||
                        } while(RED._("infotips:info.tip"+tipCount)!=="info.tip"+tipCount);
 | 
			
		||||
                    }
 | 
			
		||||
                    startTimeout = setTimeout(setTip,startDelay);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										441
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,441 @@
 | 
			
		||||
RED.tourGuide = (function() {
 | 
			
		||||
    var activeListeners = [];
 | 
			
		||||
    var shade;
 | 
			
		||||
    var focus;
 | 
			
		||||
    var popover;
 | 
			
		||||
    var stepContent;
 | 
			
		||||
    var targetElement;
 | 
			
		||||
    var fullscreen;
 | 
			
		||||
 | 
			
		||||
    var tourCache = {};
 | 
			
		||||
 | 
			
		||||
    function run(tourPath, done) {
 | 
			
		||||
        done = done || function(err) {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                console.error(err);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        loadTour(tourPath, function(err, tour) {
 | 
			
		||||
            if (err) {
 | 
			
		||||
                console.warn("Error loading tour:",err);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            runTour(tour, done);
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function loadTour(tourPath, done) {
 | 
			
		||||
        if (tourCache[tourPath]) {
 | 
			
		||||
            done(null, tourCache[tourPath]);
 | 
			
		||||
        } else {
 | 
			
		||||
            /* jshint ignore:start */
 | 
			
		||||
            // jshint<2.13 doesn't support dynamic imports. Once grunt-contrib-jshint
 | 
			
		||||
            // has been updated with the new jshint, we can stop ignoring this block
 | 
			
		||||
            import(tourPath).then(function(module) {
 | 
			
		||||
                tourCache[tourPath] = module.default;
 | 
			
		||||
                done(null, tourCache[tourPath]);
 | 
			
		||||
            }).catch(function(err) {
 | 
			
		||||
                done(err);
 | 
			
		||||
            })
 | 
			
		||||
            /* jshint ignore:end */
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function repositionFocus() {
 | 
			
		||||
        if (targetElement) {
 | 
			
		||||
            if (!fullscreen) {
 | 
			
		||||
                var pos = targetElement[0].getBoundingClientRect();
 | 
			
		||||
                var dimension = Math.max(50, Math.max(pos.width,pos.height)*1.5);
 | 
			
		||||
                focus.css({
 | 
			
		||||
                    left: (pos.left+pos.width/2)+"px",
 | 
			
		||||
                    top: (pos.top+pos.height/2)+"px",
 | 
			
		||||
                    width: (2*dimension)+"px",
 | 
			
		||||
                    height: (2*dimension)+"px"
 | 
			
		||||
                })
 | 
			
		||||
                var flush = focus[0].offsetHeight; // Flush CSS changes
 | 
			
		||||
                focus.addClass("transition");
 | 
			
		||||
                focus.css({
 | 
			
		||||
                    width: dimension+"px",
 | 
			
		||||
                    height: dimension+"px"
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                focus.css({
 | 
			
		||||
                    left: ($(window).width()/2)+"px",
 | 
			
		||||
                    top: ($(window).height()/2)+"px",
 | 
			
		||||
                    width: "0px",
 | 
			
		||||
                    height: "0px"
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            if (popover) {
 | 
			
		||||
                popover.move({
 | 
			
		||||
                    target: targetElement,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function runTour(tour, done) {
 | 
			
		||||
 | 
			
		||||
        shade = $('<div class="red-ui-tourGuide-shade"></div>').appendTo(document.body);
 | 
			
		||||
        focus = $('<div class="red-ui-tourGuide-shade-focus"></div>').appendTo(shade);
 | 
			
		||||
 | 
			
		||||
        // var resizeTimer;
 | 
			
		||||
        //
 | 
			
		||||
        $(window).on("resize.red-ui-tourGuide", function() {
 | 
			
		||||
            repositionFocus();
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var i = 0;
 | 
			
		||||
        var state = {
 | 
			
		||||
            index: 0,
 | 
			
		||||
            count: tour.steps.length
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        function endTour(err) {
 | 
			
		||||
            $(window).off("resize.red-ui-tourGuide");
 | 
			
		||||
            $(document).off('keydown.red-ui-tourGuide');
 | 
			
		||||
            if (popover) {
 | 
			
		||||
                popover.close();
 | 
			
		||||
            }
 | 
			
		||||
            stepContent = null;
 | 
			
		||||
            popover = null;
 | 
			
		||||
            shade.remove();
 | 
			
		||||
            shade = null;
 | 
			
		||||
            done(err);
 | 
			
		||||
        }
 | 
			
		||||
        function runStep(carryOn) {
 | 
			
		||||
            if (carryOn === false) {
 | 
			
		||||
                endTour(false);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (i === tour.steps.length) {
 | 
			
		||||
                endTour();
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            state.index = i;
 | 
			
		||||
            // console.log("TOUR STEP",i+1,"OF",tour.steps.length)
 | 
			
		||||
            try {
 | 
			
		||||
                runTourStep(tour.steps[i++], state, runStep)
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
                endTour(err);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        runStep();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clearListeners() {
 | 
			
		||||
        activeListeners.forEach(function(listener) {
 | 
			
		||||
            if (listener.type === "dom-event") {
 | 
			
		||||
                listener.target[0].removeEventListener(listener.event,listener.listener,listener.opts);
 | 
			
		||||
            } else if (listener.type === "nr-event") {
 | 
			
		||||
                RED.events.off(listener.event, listener.listener)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        activeListeners = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function prepareStep(step, state, done) {
 | 
			
		||||
        if (step.prepare) {
 | 
			
		||||
            if (step.prepare.length === 0) {
 | 
			
		||||
                step.prepare.call(state);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (popover) {
 | 
			
		||||
                    popover.element.hide();
 | 
			
		||||
                    if (!fullscreen) {
 | 
			
		||||
                        fullscreen = true;
 | 
			
		||||
                        repositionFocus()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                step.prepare.call(state, function() {
 | 
			
		||||
                    if (popover) {
 | 
			
		||||
                        popover.element.show();
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                })
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        done();
 | 
			
		||||
    }
 | 
			
		||||
    function completeStep(step, state, done) {
 | 
			
		||||
        function finish() {
 | 
			
		||||
            clearListeners();
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                done();
 | 
			
		||||
            },0)
 | 
			
		||||
        }
 | 
			
		||||
        if (step.complete) {
 | 
			
		||||
            if (step.complete.length === 0) {
 | 
			
		||||
                step.complete.call(state);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (popover) {
 | 
			
		||||
                    popover.element.hide();
 | 
			
		||||
                    if (!fullscreen) {
 | 
			
		||||
                        fullscreen = true;
 | 
			
		||||
                        repositionFocus()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                step.complete.call(state, function() {
 | 
			
		||||
                    if (popover) {
 | 
			
		||||
                        popover.element.show();
 | 
			
		||||
                    }
 | 
			
		||||
                    finish();
 | 
			
		||||
                })
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        finish();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    function getLocaleText(property) {
 | 
			
		||||
        if (typeof property === 'string') {
 | 
			
		||||
            return property;
 | 
			
		||||
        }
 | 
			
		||||
        var currentLang = RED.i18n.lang() || 'en-US';
 | 
			
		||||
        var availableLangs = Object.keys(property);
 | 
			
		||||
        return property[currentLang]||property['en-US']||property[availableLangs[0]]
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    function runTourStep(step, state, done) {
 | 
			
		||||
        shade.fadeIn();
 | 
			
		||||
        prepareStep(step, state, function() {
 | 
			
		||||
            var zIndex;
 | 
			
		||||
            var direction = step.direction || "bottom";
 | 
			
		||||
            fullscreen = false;
 | 
			
		||||
 | 
			
		||||
            if (typeof step.element === "string") {
 | 
			
		||||
                targetElement = $(step.element)
 | 
			
		||||
            } else if (typeof step.element === "function") {
 | 
			
		||||
                targetElement = step.element.call(state);
 | 
			
		||||
            } else if (!step.element) {
 | 
			
		||||
                targetElement = $(".red-ui-editor")
 | 
			
		||||
                fullscreen = true;
 | 
			
		||||
                direction = "inset";
 | 
			
		||||
            } else {
 | 
			
		||||
                targetElement = step.element;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (targetElement.length === 0) {
 | 
			
		||||
                targetElement = null;
 | 
			
		||||
                shade.hide();
 | 
			
		||||
                throw new Error("Element not found")
 | 
			
		||||
            }
 | 
			
		||||
            if ($(window).width() < 400) {
 | 
			
		||||
                targetElement = $(".red-ui-editor");
 | 
			
		||||
                fullscreen = true;
 | 
			
		||||
                direction = "inset";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            zIndex = targetElement.css("z-index");
 | 
			
		||||
            if (!fullscreen && (step.interactive || step.wait)) {
 | 
			
		||||
                targetElement.css("z-index",2002);
 | 
			
		||||
            }
 | 
			
		||||
            repositionFocus();
 | 
			
		||||
 | 
			
		||||
            if (!stepContent) {
 | 
			
		||||
                stepContent = $('<div style="position:relative"></div>');
 | 
			
		||||
            } else {
 | 
			
		||||
                stepContent.empty();
 | 
			
		||||
            }
 | 
			
		||||
            $('<button type="button" class="red-ui-button red-ui-button-small" style="float: right; margin-top: -4px; margin-right: -4px;"><i class="fa fa-times"></i></button>').appendTo(stepContent).click(function(evt) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                completeStep(step, state, function() {
 | 
			
		||||
                    done(false);
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            var stepDescription = $('<div class="red-ui-tourGuide-popover-description"></div>').appendTo(stepContent);
 | 
			
		||||
            if (step.titleIcon) {
 | 
			
		||||
                $('<h2><i class="'+step.titleIcon+'"></i></h2>').appendTo(stepDescription);
 | 
			
		||||
            }
 | 
			
		||||
            if (step.title) {
 | 
			
		||||
                $('<h2>').text(getLocaleText(step.title)).appendTo(stepDescription);
 | 
			
		||||
            }
 | 
			
		||||
            $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
 | 
			
		||||
 | 
			
		||||
            var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
 | 
			
		||||
 | 
			
		||||
            // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);
 | 
			
		||||
            // var bcStart = Math.max(0,state.index - 3);
 | 
			
		||||
            // var bcEnd = Math.min(state.count, bcStart + 7);
 | 
			
		||||
            // if (bcEnd === state.count) {
 | 
			
		||||
            //     bcStart = Math.max(0,bcEnd - 7);
 | 
			
		||||
            // }
 | 
			
		||||
            // for (var i = bcStart; i < bcEnd; i++) {
 | 
			
		||||
            //     var bullet = $('<i class="fa"></i>').addClass(i===state.index ? "fa-circle":"fa-circle-o").appendTo(breadcrumbs);
 | 
			
		||||
            //     if (i === bcStart) {
 | 
			
		||||
            //         if (i > 1) {
 | 
			
		||||
            //             bullet.css("font-size", "3px");
 | 
			
		||||
            //         } else if (i === 1) {
 | 
			
		||||
            //             bullet.css("font-size", "4px");
 | 
			
		||||
            //         }
 | 
			
		||||
            //     } else if (i === bcStart + 1) {
 | 
			
		||||
            //         if (i > 2) {
 | 
			
		||||
            //             bullet.css("font-size", "4px");
 | 
			
		||||
            //         }
 | 
			
		||||
            //     }
 | 
			
		||||
            //     if (i === bcEnd - 1) {
 | 
			
		||||
            //         if (i < state.count - 2) {
 | 
			
		||||
            //             bullet.css("font-size", "3px");
 | 
			
		||||
            //         } else if (i === state.count - 2) {
 | 
			
		||||
            //             bullet.css("font-size", "4px");
 | 
			
		||||
            //         }
 | 
			
		||||
            //     } else if (i === bcEnd - 2) {
 | 
			
		||||
            //         if (i < state.count - 3) {
 | 
			
		||||
            //             bullet.css("font-size", "4px");
 | 
			
		||||
            //         }
 | 
			
		||||
            //     }
 | 
			
		||||
            //     // if (i === bcEnd - 1) {
 | 
			
		||||
            //     //     if (i < state.count - 2) {
 | 
			
		||||
            //     //         bullet.css("font-size", "3px");
 | 
			
		||||
            //     //     } else if (i === state.count - 2) {
 | 
			
		||||
            //     //         bullet.css("font-size", "4px");
 | 
			
		||||
            //     //     }
 | 
			
		||||
            //     // }
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            $('<small>').text((state.index+1)+"/"+state.count).appendTo(stepToolbar)
 | 
			
		||||
            var nextButton;
 | 
			
		||||
            if (fullscreen || !step.wait) {
 | 
			
		||||
                nextButton = $('<button type="button" class="red-ui-button" style="position: absolute; right:0;bottom:0;"></button>').appendTo(stepToolbar).one('click',function(evt) {
 | 
			
		||||
                    evt.preventDefault();
 | 
			
		||||
                    stepEventListener();
 | 
			
		||||
                });
 | 
			
		||||
                if (state.index === state.count - 1) {
 | 
			
		||||
                    $('<span></span>').text(RED._("common.label.close")).appendTo(nextButton);
 | 
			
		||||
                } else if (state.index === 0) {
 | 
			
		||||
                    $('<span>start</span>').text(RED._("tourGuide.start")).appendTo(nextButton);
 | 
			
		||||
                    $('<span style="margin-left: 6px"><i class="fa fa-chevron-right"></i></span>').appendTo(nextButton);
 | 
			
		||||
                } else if (state.index < state.count-1) {
 | 
			
		||||
                    $('<span></span>').text(RED._("tourGuide.next")).appendTo(nextButton);
 | 
			
		||||
                    $('<span style="margin-left: 6px"><i class="fa fa-chevron-right"></i></span>').appendTo(nextButton);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var width = step.width;
 | 
			
		||||
            if (fullscreen) {
 | 
			
		||||
                width = 500;
 | 
			
		||||
            }
 | 
			
		||||
            var maxWidth = Math.min($(window).width()-10,Math.max(width || 0, 300));
 | 
			
		||||
            if (!popover) {
 | 
			
		||||
                popover = RED.popover.create({
 | 
			
		||||
                    target: targetElement,
 | 
			
		||||
                    width: width || "auto",
 | 
			
		||||
                    maxWidth: maxWidth+"px",
 | 
			
		||||
                    direction: direction,
 | 
			
		||||
                    class: "red-ui-tourGuide-popover"+(fullscreen?" ":""),
 | 
			
		||||
                    trigger: "manual",
 | 
			
		||||
                    content: stepContent
 | 
			
		||||
                }).open();
 | 
			
		||||
            }
 | 
			
		||||
            $(document).off('keydown.red-ui-tourGuide');
 | 
			
		||||
            $(document).on('keydown.red-ui-tourGuide', function(evt) {
 | 
			
		||||
                if (evt.key === "Escape" || evt.key === "Esc") {
 | 
			
		||||
                    evt.preventDefault();
 | 
			
		||||
                    evt.stopPropagation();
 | 
			
		||||
                    completeStep(step, state, function() {
 | 
			
		||||
                        done(false);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            popover.element.toggleClass("red-ui-tourGuide-popover-full",!!fullscreen);
 | 
			
		||||
            popover.move({
 | 
			
		||||
                target: targetElement,
 | 
			
		||||
                width: width || "auto",
 | 
			
		||||
                maxWidth: maxWidth+"px",
 | 
			
		||||
                direction: direction,
 | 
			
		||||
            })
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                var pos = popover.element.position()
 | 
			
		||||
                if (pos.left < 0) {
 | 
			
		||||
                    popover.element.css({left: 0});
 | 
			
		||||
                }
 | 
			
		||||
            },100);
 | 
			
		||||
            if (nextButton) {
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    nextButton.focus();
 | 
			
		||||
                },100);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var isSVG = targetElement[0] instanceof SVGElement;
 | 
			
		||||
            if (step.fallback) {
 | 
			
		||||
                focus.one("mouseenter", function(evt) {
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        var pos = targetElement[0].getBoundingClientRect();
 | 
			
		||||
                        var dimension = Math.max(50, Math.max(pos.width,pos.height)*1.5);
 | 
			
		||||
                        focus.css({
 | 
			
		||||
                            width: (4*dimension)+"px",
 | 
			
		||||
                            height: (4*dimension)+"px"
 | 
			
		||||
                        })
 | 
			
		||||
                        shade.fadeOut();
 | 
			
		||||
                        popover.move({
 | 
			
		||||
                            target: $(".red-ui-editor"),
 | 
			
		||||
                            direction: step.fallback,
 | 
			
		||||
                            offset: 10,
 | 
			
		||||
                            transition: true
 | 
			
		||||
                        })
 | 
			
		||||
                        // popover.element.addClass('red-ui-tourGuide-popover-bounce');
 | 
			
		||||
                    },isSVG?0:500);
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var stepEventListener = function() {
 | 
			
		||||
                focus.removeClass("transition");
 | 
			
		||||
                targetElement.css("z-index",zIndex);
 | 
			
		||||
                completeStep(step, state, done);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (step.wait) {
 | 
			
		||||
                if (step.wait.type === "dom-event") {
 | 
			
		||||
                    var eventTarget = targetElement;
 | 
			
		||||
                    if (step.wait.element) {
 | 
			
		||||
                        if (typeof step.wait.element === "string") {
 | 
			
		||||
                            eventTarget = $(step.wait.element);
 | 
			
		||||
                        } else if (typeof step.wait.element === "function") {
 | 
			
		||||
                            eventTarget = step.wait.element.call(state);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    var listener = {
 | 
			
		||||
                        type: step.wait.type,
 | 
			
		||||
                        target: eventTarget,
 | 
			
		||||
                        event: step.wait.event,
 | 
			
		||||
                        listener: function() {
 | 
			
		||||
                            stepEventListener();
 | 
			
		||||
                        },
 | 
			
		||||
                        opts: { once: true }
 | 
			
		||||
                    }
 | 
			
		||||
                    activeListeners.push(listener)
 | 
			
		||||
                    eventTarget[0].addEventListener(listener.event,listener.listener,listener.opts)
 | 
			
		||||
                } else if (step.wait.type === "nr-event") {
 | 
			
		||||
                    var listener = {
 | 
			
		||||
                        type: step.wait.type,
 | 
			
		||||
                        event: step.wait.event,
 | 
			
		||||
                        listener: function() {
 | 
			
		||||
                            if (step.wait.filter) {
 | 
			
		||||
                                if (!step.wait.filter.apply(state,arguments)) {
 | 
			
		||||
                                    return;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            stepEventListener();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    activeListeners.push(listener);
 | 
			
		||||
                    RED.events.on(listener.event,listener.listener);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        load: loadTour,
 | 
			
		||||
        run: run,
 | 
			
		||||
        reset: function() {
 | 
			
		||||
            RED.settings.set("editor.tours.welcome",'');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 **/
 | 
			
		||||
RED.tray = (function() {
 | 
			
		||||
 RED.tray = (function() {
 | 
			
		||||
 | 
			
		||||
    var stack = [];
 | 
			
		||||
    var editorStack;
 | 
			
		||||
@@ -166,6 +166,8 @@ RED.tray = (function() {
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        // Delay resetting the flag, so we don't close prematurely
 | 
			
		||||
                        openingTray = false;
 | 
			
		||||
                        raiseTrayZ();
 | 
			
		||||
                        handleWindowResize();//cause call to monaco layout
 | 
			
		||||
                    },200);
 | 
			
		||||
                    body.find(":focusable:first").trigger("focus");
 | 
			
		||||
 | 
			
		||||
@@ -206,6 +208,17 @@ RED.tray = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //raise tray z-index to prevent editor context menu being clipped by sidebar
 | 
			
		||||
    function raiseTrayZ() {
 | 
			
		||||
        setTimeout(function(){
 | 
			
		||||
            $('#red-ui-editor-stack').css("zIndex","13");
 | 
			
		||||
        },300);
 | 
			
		||||
    }
 | 
			
		||||
    //lower tray z-index back to original place for correct slide animation (related to fix for editor context menu clipped by sidebar)
 | 
			
		||||
    function lowerTrayZ(){
 | 
			
		||||
        $('#red-ui-editor-stack').css("zIndex","9");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: function init() {
 | 
			
		||||
            editorStack = $("#red-ui-editor-stack");
 | 
			
		||||
@@ -221,6 +234,7 @@ RED.tray = (function() {
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        show: function show(options) {
 | 
			
		||||
            lowerTrayZ();
 | 
			
		||||
            if (!options) {
 | 
			
		||||
                if (stack.length > 0) {
 | 
			
		||||
                    var tray = stack[stack.length-1];
 | 
			
		||||
@@ -246,12 +260,16 @@ RED.tray = (function() {
 | 
			
		||||
                    showTray(options);
 | 
			
		||||
                },250)
 | 
			
		||||
            } else {
 | 
			
		||||
                if (stack.length > 0) {
 | 
			
		||||
                    stack[stack.length-1].tray.css("z-index", 0);
 | 
			
		||||
                }
 | 
			
		||||
                RED.events.emit("editor:open");
 | 
			
		||||
                showTray(options);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        hide: function hide() {
 | 
			
		||||
            lowerTrayZ();
 | 
			
		||||
            if (stack.length > 0) {
 | 
			
		||||
                var tray = stack[stack.length-1];
 | 
			
		||||
                tray.tray.css({
 | 
			
		||||
@@ -266,15 +284,16 @@ RED.tray = (function() {
 | 
			
		||||
        },
 | 
			
		||||
        resize: handleWindowResize,
 | 
			
		||||
        close: function close(done) {
 | 
			
		||||
            lowerTrayZ(); //lower tray z-index for correct animation
 | 
			
		||||
            if (stack.length > 0) {
 | 
			
		||||
                var tray = stack.pop();
 | 
			
		||||
                tray.tray.css({
 | 
			
		||||
                    right: -(tray.tray.width()+10)+"px"
 | 
			
		||||
                });
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    if (tray.options.close) {
 | 
			
		||||
                        tray.options.close();
 | 
			
		||||
                    }
 | 
			
		||||
                    try {
 | 
			
		||||
                        if (tray.options.close) { tray.options.close(); }
 | 
			
		||||
                    } catch (ex) { }
 | 
			
		||||
                    tray.tray.remove();
 | 
			
		||||
                    if (stack.length > 0) {
 | 
			
		||||
                        var oldTray = stack[stack.length-1];
 | 
			
		||||
@@ -284,6 +303,8 @@ RED.tray = (function() {
 | 
			
		||||
                                handleWindowResize();
 | 
			
		||||
                                oldTray.tray.css({right:0});
 | 
			
		||||
                                if (oldTray.options.show) {
 | 
			
		||||
                                    raiseTrayZ();
 | 
			
		||||
                                    handleWindowResize();//cause call to monaco layout
 | 
			
		||||
                                    oldTray.options.show();
 | 
			
		||||
                                }
 | 
			
		||||
                            },0);
 | 
			
		||||
@@ -304,6 +325,8 @@ RED.tray = (function() {
 | 
			
		||||
                        $(".red-ui-sidebar-shade").hide();
 | 
			
		||||
                        RED.events.emit("editor:close");
 | 
			
		||||
                        RED.view.focus();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        stack[stack.length-1].tray.css("z-index", "auto");
 | 
			
		||||
                    }
 | 
			
		||||
                },250)
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -139,7 +139,8 @@ RED.userSettings = (function() {
 | 
			
		||||
        {
 | 
			
		||||
            title: "menu.label.other",
 | 
			
		||||
            options: [
 | 
			
		||||
                {setting:"view-show-tips",oldSettings:"menu-menu-item-show-tips",label:"menu.label.showTips",toggle:true,default:true,onchange:"core:toggle-show-tips"}
 | 
			
		||||
                {setting:"view-show-tips",oldSettings:"menu-menu-item-show-tips",label:"menu.label.showTips",toggle:true,default:true,onchange:"core:toggle-show-tips"},
 | 
			
		||||
                {setting:"view-show-welcome-tours",label:"menu.label.showWelcomeTours",toggle:true,default:true}
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,82 @@ RED.utils = (function() {
 | 
			
		||||
        return renderMarkdown(txt);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _marked.setOptions({
 | 
			
		||||
        renderer: new _marked.Renderer(),
 | 
			
		||||
    const descriptionList = {
 | 
			
		||||
        name: 'descriptionList',
 | 
			
		||||
        level: 'block',                                     // Is this a block-level or inline-level tokenizer?
 | 
			
		||||
        start(src) {
 | 
			
		||||
            if (!src) { return null; }
 | 
			
		||||
            let m = src.match(/:[^:\n]/g);
 | 
			
		||||
            return m && m.index; // Hint to Marked.js to stop and check for a match
 | 
			
		||||
        },
 | 
			
		||||
        tokenizer(src, tokens) {
 | 
			
		||||
            if (!src) { return null; }
 | 
			
		||||
            const rule = /^(?::[^:\n]+:[^:\n]*(?:\n|$))+/;    // Regex for the complete token
 | 
			
		||||
            const match = rule.exec(src);
 | 
			
		||||
            if (match) {
 | 
			
		||||
                return {                                        // Token to generate
 | 
			
		||||
                    type: 'descriptionList',                      // Should match "name" above
 | 
			
		||||
                    raw: match[0],                                // Text to consume from the source
 | 
			
		||||
                    text: match[0].trim(),                        // Additional custom properties
 | 
			
		||||
                    tokens: this.lexer.inlineTokens(match[0].trim())    // inlineTokens to process **bold**, *italics*, etc.
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        renderer(token) {
 | 
			
		||||
            return `<dl class="message-properties">${this.parser.parseInline(token.tokens)}\n</dl>`; // parseInline to turn child tokens into HTML
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const description = {
 | 
			
		||||
        name: 'description',
 | 
			
		||||
        level: 'inline',           // Is this a block-level or inline-level tokenizer?
 | 
			
		||||
        start(src) {
 | 
			
		||||
            if (!src) { return null; }
 | 
			
		||||
            let m = src.match(/:/g);
 | 
			
		||||
            return m && m.index;   // Hint to Marked.js to stop and check for a match
 | 
			
		||||
        },
 | 
			
		||||
        tokenizer(src, tokens) {
 | 
			
		||||
            if (!src) { return null; }
 | 
			
		||||
            const rule = /^:([^:\n]+)\(([^:\n]+)\).*?:([^:\n]*)(?:\n|$)/;  // Regex for the complete token
 | 
			
		||||
            const match = rule.exec(src);
 | 
			
		||||
            if (match) {
 | 
			
		||||
                return {                                       // Token to generate
 | 
			
		||||
                    type: 'description',                       // Should match "name" above
 | 
			
		||||
                    raw: match[0],                             // Text to consume from the source
 | 
			
		||||
                    dt: this.lexer.inlineTokens(match[1].trim()),    // Additional custom properties
 | 
			
		||||
                    types: this.lexer.inlineTokens(match[2].trim()),
 | 
			
		||||
                    dd: this.lexer.inlineTokens(match[3].trim()),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        renderer(token) {
 | 
			
		||||
            return `\n<dt>${this.parser.parseInline(token.dt)}<span class="property-type">${this.parser.parseInline(token.types)}</span></dt><dd>${this.parser.parseInline(token.dd)}</dd>`;
 | 
			
		||||
        },
 | 
			
		||||
        childTokens: ['dt', 'dd'],                 // Any child tokens to be visited by walkTokens
 | 
			
		||||
        walkTokens(token) {                        // Post-processing on the completed token tree
 | 
			
		||||
            if (token.type === 'strong') {
 | 
			
		||||
                token.text += ' walked';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const renderer = new window._marked.Renderer();
 | 
			
		||||
 | 
			
		||||
    //override list creation - add node-ports to order lists
 | 
			
		||||
    renderer.list = function (body, ordered, start) {
 | 
			
		||||
        let addClass = /dl.*?class.*?message-properties.*/.test(body);
 | 
			
		||||
        if (addClass && ordered) {
 | 
			
		||||
            return '<ol class="node-ports">' + body + '</ol>';
 | 
			
		||||
        } else if (ordered) {
 | 
			
		||||
            return '<ol>' + body + '</ol>';
 | 
			
		||||
        } else {
 | 
			
		||||
            return '<ul>' + body + '</ul>'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window._marked.setOptions({
 | 
			
		||||
        renderer: renderer,
 | 
			
		||||
        gfm: true,
 | 
			
		||||
        tables: true,
 | 
			
		||||
        breaks: false,
 | 
			
		||||
@@ -32,6 +106,8 @@ RED.utils = (function() {
 | 
			
		||||
        smartypants: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    window._marked.use({extensions: [descriptionList, description] } );
 | 
			
		||||
 | 
			
		||||
    function renderMarkdown(txt) {
 | 
			
		||||
        var rendered = _marked(txt);
 | 
			
		||||
        var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
 | 
			
		||||
@@ -58,10 +134,16 @@ RED.utils = (function() {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('buffer['+value.length+']');
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('array['+value.length+']');
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && value.type === 'set' && value.hasOwnProperty('data')) {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('set['+value.length+']');
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && value.type === 'map' && value.hasOwnProperty('data')) {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('map');
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && value.type === 'function') {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('function');
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && (value.type === 'number' || value.type === 'bigint')) {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data);
 | 
			
		||||
            } else if (value.hasOwnProperty('type') && value.type === 'regexp') {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-string"></span>').text(value.data);
 | 
			
		||||
            } else {
 | 
			
		||||
                result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>');
 | 
			
		||||
            }
 | 
			
		||||
@@ -178,8 +260,14 @@ RED.utils = (function() {
 | 
			
		||||
            RED.popover.tooltip(pinPath,RED._("node-red:debug.sidebar.pinPath"));
 | 
			
		||||
        }
 | 
			
		||||
        if (extraTools) {
 | 
			
		||||
            extraTools.addClass("red-ui-debug-msg-tools-other");
 | 
			
		||||
            extraTools.appendTo(tools);
 | 
			
		||||
            var t = extraTools;
 | 
			
		||||
            if (typeof t === 'function') {
 | 
			
		||||
                t = t(key,msg);
 | 
			
		||||
            }
 | 
			
		||||
            if (t) {
 | 
			
		||||
                t.addClass("red-ui-debug-msg-tools-other");
 | 
			
		||||
                t.appendTo(tools);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function checkExpanded(strippedKey,expandPaths,minRange,maxRange) {
 | 
			
		||||
@@ -344,7 +432,7 @@ RED.utils = (function() {
 | 
			
		||||
 | 
			
		||||
        var isArray = Array.isArray(obj);
 | 
			
		||||
        var isArrayObject = false;
 | 
			
		||||
        if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__enc__ && obj.type === 'array') || obj.type === 'Buffer')) {
 | 
			
		||||
        if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__enc__ && obj.type === 'set') || (obj.__enc__ && obj.type === 'array') || obj.type === 'Buffer')) {
 | 
			
		||||
            isArray = true;
 | 
			
		||||
            isArrayObject = true;
 | 
			
		||||
        }
 | 
			
		||||
@@ -354,6 +442,8 @@ RED.utils = (function() {
 | 
			
		||||
            $('<span class="red-ui-debug-msg-type-null">undefined</span>').appendTo(entryObj);
 | 
			
		||||
        } else if (obj.__enc__ && (obj.type === 'number' || obj.type === 'bigint')) {
 | 
			
		||||
            e = $('<span class="red-ui-debug-msg-type-number red-ui-debug-msg-object-header"></span>').text(obj.data).appendTo(entryObj);
 | 
			
		||||
        } else if (typeHint === "regexp" || (obj.__enc__ && obj.type === 'regexp')) {
 | 
			
		||||
            e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').text((typeof obj === "string")?obj:obj.data).appendTo(entryObj);
 | 
			
		||||
        } else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
 | 
			
		||||
            e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("function").appendTo(entryObj);
 | 
			
		||||
        } else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
 | 
			
		||||
@@ -411,7 +501,7 @@ RED.utils = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            var fullLength = data.length;
 | 
			
		||||
 | 
			
		||||
                if (originalLength > 0) {
 | 
			
		||||
            if (originalLength > 0) {
 | 
			
		||||
                $('<i class="fa fa-caret-right red-ui-debug-msg-object-handle"></i> ').prependTo(header);
 | 
			
		||||
                var arrayRows = $('<div class="red-ui-debug-msg-array-rows"></div>').appendTo(element);
 | 
			
		||||
                element.addClass('red-ui-debug-msg-buffer-raw');
 | 
			
		||||
@@ -476,7 +566,9 @@ RED.utils = (function() {
 | 
			
		||||
                                    rootPath: rootPath,
 | 
			
		||||
                                    expandPaths: expandPaths,
 | 
			
		||||
                                    ontoggle: ontoggle,
 | 
			
		||||
                                    exposeApi: exposeApi
 | 
			
		||||
                                    exposeApi: exposeApi,
 | 
			
		||||
                                    // tools: tools // Do not pass tools down as we
 | 
			
		||||
                                                    // keep them attached to the top-level header
 | 
			
		||||
                                }
 | 
			
		||||
                            ).appendTo(row);
 | 
			
		||||
                        }
 | 
			
		||||
@@ -504,8 +596,9 @@ RED.utils = (function() {
 | 
			
		||||
                                                rootPath: rootPath,
 | 
			
		||||
                                                expandPaths: expandPaths,
 | 
			
		||||
                                                ontoggle: ontoggle,
 | 
			
		||||
                                                exposeApi: exposeApi
 | 
			
		||||
 | 
			
		||||
                                                exposeApi: exposeApi,
 | 
			
		||||
                                                // tools: tools // Do not pass tools down as we
 | 
			
		||||
                                                                // keep them attached to the top-level header
 | 
			
		||||
                                            }
 | 
			
		||||
                                        ).appendTo(row);
 | 
			
		||||
                                    }
 | 
			
		||||
@@ -525,12 +618,18 @@ RED.utils = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        } else if (typeof obj === 'object') {
 | 
			
		||||
            element.addClass('collapsed');
 | 
			
		||||
            var keys = Object.keys(obj);
 | 
			
		||||
            var data = obj;
 | 
			
		||||
            var type = "object";
 | 
			
		||||
            if (data.__enc__) {
 | 
			
		||||
                data = data.data;
 | 
			
		||||
                type = obj.type.toLowerCase();
 | 
			
		||||
            }
 | 
			
		||||
            var keys = Object.keys(data);
 | 
			
		||||
            if (key || keys.length > 0) {
 | 
			
		||||
                $('<i class="fa fa-caret-right red-ui-debug-msg-object-handle"></i> ').prependTo(header);
 | 
			
		||||
                makeExpandable(header, function() {
 | 
			
		||||
                    if (!key) {
 | 
			
		||||
                        $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text('object').appendTo(header);
 | 
			
		||||
                        $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-type-header"></span>').text(type).appendTo(header);
 | 
			
		||||
                    }
 | 
			
		||||
                    for (i=0;i<keys.length;i++) {
 | 
			
		||||
                        var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(element);
 | 
			
		||||
@@ -543,7 +642,7 @@ RED.utils = (function() {
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        subElements[newPath] = buildMessageElement(
 | 
			
		||||
                            obj[keys[i]],
 | 
			
		||||
                            data[keys[i]],
 | 
			
		||||
                            {
 | 
			
		||||
                                key: keys[i],
 | 
			
		||||
                                typeHint: false,
 | 
			
		||||
@@ -553,8 +652,9 @@ RED.utils = (function() {
 | 
			
		||||
                                rootPath: rootPath,
 | 
			
		||||
                                expandPaths: expandPaths,
 | 
			
		||||
                                ontoggle: ontoggle,
 | 
			
		||||
                                exposeApi: exposeApi
 | 
			
		||||
 | 
			
		||||
                                exposeApi: exposeApi,
 | 
			
		||||
                                // tools: tools // Do not pass tools down as we
 | 
			
		||||
                                                // keep them attached to the top-level header
 | 
			
		||||
                            }
 | 
			
		||||
                        ).appendTo(row);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -566,7 +666,7 @@ RED.utils = (function() {
 | 
			
		||||
                checkExpanded(strippedKey,expandPaths));
 | 
			
		||||
            }
 | 
			
		||||
            if (key) {
 | 
			
		||||
                $('<span class="red-ui-debug-msg-type-meta"></span>').text('object').appendTo(entryObj);
 | 
			
		||||
                $('<span class="red-ui-debug-msg-type-meta"></span>').text(type).appendTo(entryObj);
 | 
			
		||||
            } else {
 | 
			
		||||
                headerHead = $('<span class="red-ui-debug-msg-object-header"></span>').appendTo(entryObj);
 | 
			
		||||
                $('<span>{ </span>').appendTo(headerHead);
 | 
			
		||||
@@ -574,7 +674,7 @@ RED.utils = (function() {
 | 
			
		||||
                for (i=0;i<keysLength;i++) {
 | 
			
		||||
                    $('<span class="red-ui-debug-msg-object-key"></span>').text(keys[i]).appendTo(headerHead);
 | 
			
		||||
                    $('<span>: </span>').appendTo(headerHead);
 | 
			
		||||
                    buildMessageSummaryValue(obj[keys[i]]).appendTo(headerHead);
 | 
			
		||||
                    buildMessageSummaryValue(data[keys[i]]).appendTo(headerHead);
 | 
			
		||||
                    if (i < keysLength-1) {
 | 
			
		||||
                        $('<span>, </span>').appendTo(headerHead);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -856,6 +956,7 @@ RED.utils = (function() {
 | 
			
		||||
            obj[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function separateIconPath(icon) {
 | 
			
		||||
        var result = {module: "", file: ""};
 | 
			
		||||
        if (icon) {
 | 
			
		||||
@@ -863,10 +964,10 @@ RED.utils = (function() {
 | 
			
		||||
            if (index === 0) {
 | 
			
		||||
                icon = icon.substring((RED.settings.apiRootUrl+'icons/').length);
 | 
			
		||||
            }
 | 
			
		||||
            index = icon.indexOf('/');
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
                result.module = icon.slice(0, index);
 | 
			
		||||
                result.file = icon.slice(index + 1);
 | 
			
		||||
            var match = /^((?:@[^/]+\/)?[^/]+)\/(.*)$/.exec(icon);
 | 
			
		||||
            if (match) {
 | 
			
		||||
                result.module = match[1];
 | 
			
		||||
                result.file = match[2];
 | 
			
		||||
            } else {
 | 
			
		||||
                result.file = icon;
 | 
			
		||||
            }
 | 
			
		||||
@@ -875,6 +976,7 @@ RED.utils = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getDefaultNodeIcon(def,node) {
 | 
			
		||||
        def = def || {};
 | 
			
		||||
        var icon_url;
 | 
			
		||||
        if (node && node.type === "subflow") {
 | 
			
		||||
            icon_url = "node-red/subflow.svg";
 | 
			
		||||
@@ -912,6 +1014,7 @@ RED.utils = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeIcon(def,node) {
 | 
			
		||||
        def = def || {};
 | 
			
		||||
        if (node && node.type === '_selection_') {
 | 
			
		||||
            return "font-awesome/fa-object-ungroup";
 | 
			
		||||
        } else if (node && node.type === 'group') {
 | 
			
		||||
@@ -999,6 +1102,7 @@ RED.utils = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getNodeColor(type, def) {
 | 
			
		||||
        def = def || {};
 | 
			
		||||
        var result = def.color;
 | 
			
		||||
        var paletteTheme = RED.settings.theme('palette.theme') || [];
 | 
			
		||||
        if (paletteTheme.length > 0) {
 | 
			
		||||
@@ -1051,7 +1155,7 @@ RED.utils = (function() {
 | 
			
		||||
            payload = Infinity;
 | 
			
		||||
        } else if ((format === 'number') && (payload === "-Infinity")) {
 | 
			
		||||
            payload = -Infinity;
 | 
			
		||||
        } else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
 | 
			
		||||
        } else if (format === 'Object' || /^(array|set|map)/.test(format) || format === 'boolean' || format === 'number' ) {
 | 
			
		||||
            payload = JSON.parse(payload);
 | 
			
		||||
        } else if (/error/i.test(format)) {
 | 
			
		||||
            payload = JSON.parse(payload);
 | 
			
		||||
@@ -1125,9 +1229,11 @@ RED.utils = (function() {
 | 
			
		||||
        imageIconElement.css("backgroundImage", "url("+iconUrl+")");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createNodeIcon(node) {
 | 
			
		||||
    function createNodeIcon(node, includeLabel) {
 | 
			
		||||
        var container = $('<span class="red-ui-node-icon-container">');
 | 
			
		||||
 | 
			
		||||
        var def = node._def;
 | 
			
		||||
        var nodeDiv = $('<div>',{class:"red-ui-search-result-node"})
 | 
			
		||||
        var nodeDiv = $('<div>',{class:"red-ui-node-icon"})
 | 
			
		||||
        if (node.type === "_selection_") {
 | 
			
		||||
            nodeDiv.addClass("red-ui-palette-icon-selection");
 | 
			
		||||
        } else if (node.type === "group") {
 | 
			
		||||
@@ -1147,9 +1253,20 @@ RED.utils = (function() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var icon_url = RED.utils.getNodeIcon(def,node);
 | 
			
		||||
        var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
 | 
			
		||||
        RED.utils.createIconElement(icon_url, iconContainer, true);
 | 
			
		||||
        return nodeDiv;
 | 
			
		||||
        RED.utils.createIconElement(icon_url, nodeDiv, true);
 | 
			
		||||
 | 
			
		||||
        nodeDiv.appendTo(container);
 | 
			
		||||
 | 
			
		||||
        if (includeLabel) {
 | 
			
		||||
            var labelText = RED.utils.getNodeLabel(node,node.name || (node.type+": "+node.id));
 | 
			
		||||
            var label = $('<div>',{class:"red-ui-node-label"}).appendTo(container);
 | 
			
		||||
            if (labelText) {
 | 
			
		||||
                label.text(labelText)
 | 
			
		||||
            } else {
 | 
			
		||||
                label.html(" ")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return container;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getDarkerColor(c) {
 | 
			
		||||
@@ -1234,6 +1351,23 @@ RED.utils = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getBrowserInfo() {
 | 
			
		||||
        var r = {}
 | 
			
		||||
        try {
 | 
			
		||||
            var ua = navigator.userAgent;
 | 
			
		||||
            r.ua = ua;
 | 
			
		||||
            r.browser = /Edge\/\d+/.test(ua) ? 'ed' : /MSIE 9/.test(ua) ? 'ie9' : /MSIE 10/.test(ua) ? 'ie10' : /MSIE 11/.test(ua) ? 'ie11' : /MSIE\s\d/.test(ua) ? 'ie?' : /rv\:11/.test(ua) ? 'ie11' : /Firefox\W\d/.test(ua) ? 'ff' : /Chrom(e|ium)\W\d|CriOS\W\d/.test(ua) ? 'gc' : /\bSafari\W\d/.test(ua) ? 'sa' : /\bOpera\W\d/.test(ua) ? 'op' : /\bOPR\W\d/i.test(ua) ? 'op' : typeof MSPointerEvent !== 'undefined' ? 'ie?' : '';
 | 
			
		||||
            r.os = /Windows NT 10/.test(ua) ? "win10" : /Windows NT 6\.0/.test(ua) ? "winvista" : /Windows NT 6\.1/.test(ua) ? "win7" : /Windows NT 6\.\d/.test(ua) ? "win8" : /Windows NT 5\.1/.test(ua) ? "winxp" : /Windows NT [1-5]\./.test(ua) ? "winnt" : /Mac/.test(ua) ? "mac" : /Linux/.test(ua) ? "linux" : /X11/.test(ua) ? "nix" : "";
 | 
			
		||||
            r.touch = 'ontouchstart' in document.documentElement;
 | 
			
		||||
            r.mobile = /IEMobile|Windows Phone|Lumia/i.test(ua) ? 'w' : /iPhone|iP[oa]d/.test(ua) ? 'i' : /Android/.test(ua) ? 'a' : /BlackBerry|PlayBook|BB10/.test(ua) ? 'b' : /Mobile Safari/.test(ua) ? 's' : /webOS|Mobile|Tablet|Opera Mini|\bCrMo\/|Opera Mobi/i.test(ua) ? 1 : 0;
 | 
			
		||||
            r.tablet = /Tablet|iPad/i.test(ua);
 | 
			
		||||
            r.ie = /MSIE \d|Trident.*rv:/.test(navigator.userAgent);
 | 
			
		||||
            r.android = /android/i.test(navigator.userAgent);
 | 
			
		||||
        } catch (error) { }
 | 
			
		||||
        return r;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        createObjectElement: buildMessageElement,
 | 
			
		||||
        getMessageProperty: getMessageProperty,
 | 
			
		||||
@@ -1255,6 +1389,7 @@ RED.utils = (function() {
 | 
			
		||||
        createNodeIcon: createNodeIcon,
 | 
			
		||||
        getDarkerColor: getDarkerColor,
 | 
			
		||||
        parseModuleList: parseModuleList,
 | 
			
		||||
        checkModuleAllowed: checkModuleAllowed
 | 
			
		||||
        checkModuleAllowed: checkModuleAllowed,
 | 
			
		||||
        getBrowserInfo: getBrowserInfo
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										151
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
RED.view.annotations = (function() {
 | 
			
		||||
 | 
			
		||||
    var annotations = {};
 | 
			
		||||
 | 
			
		||||
    function init() {
 | 
			
		||||
        RED.hooks.add("viewRedrawNode.annotations", function(evt) {
 | 
			
		||||
            try {
 | 
			
		||||
                if (evt.node.__pendingAnnotation__) {
 | 
			
		||||
                    addAnnotation(evt.node.__pendingAnnotation__,evt);
 | 
			
		||||
                    delete evt.node.__pendingAnnotation__;
 | 
			
		||||
                }
 | 
			
		||||
                var badgeDX = 0;
 | 
			
		||||
                var controlDX = 0;
 | 
			
		||||
                for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
 | 
			
		||||
                    var annotation = evt.el.__annotations__[i];
 | 
			
		||||
                    if (annotations.hasOwnProperty(annotation.id)) {
 | 
			
		||||
                        var opts = annotations[annotation.id];
 | 
			
		||||
                        var showAnnotation = true;
 | 
			
		||||
                        var isBadge = opts.type === 'badge';
 | 
			
		||||
                        if (opts.show !== undefined) {
 | 
			
		||||
                            if (typeof opts.show === "string") {
 | 
			
		||||
                                showAnnotation = !!evt.node[opts.show]
 | 
			
		||||
                            } else if (typeof opts.show === "function"){
 | 
			
		||||
                                showAnnotation = opts.show(evt.node)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                showAnnotation = !!opts.show;
 | 
			
		||||
                            }
 | 
			
		||||
                            annotation.element.classList.toggle("hide", !showAnnotation);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (isBadge) {
 | 
			
		||||
                            if (showAnnotation) {
 | 
			
		||||
                                var rect = annotation.element.getBoundingClientRect();
 | 
			
		||||
                                badgeDX += rect.width;
 | 
			
		||||
                                annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
 | 
			
		||||
                                badgeDX += 4;
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (showAnnotation) {
 | 
			
		||||
                                var rect = annotation.element.getBoundingClientRect();
 | 
			
		||||
                                annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
 | 
			
		||||
                                controlDX += rect.width + 4;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        annotation.element.parentNode.removeChild(annotation.element);
 | 
			
		||||
                        evt.el.__annotations__.splice(i,1);
 | 
			
		||||
                        i--;
 | 
			
		||||
                        l--;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        }catch(err) {
 | 
			
		||||
            console.log(err)
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a new node annotation
 | 
			
		||||
     * @param {string} id - unique identifier
 | 
			
		||||
     * @param {type} opts - annotations options
 | 
			
		||||
     *
 | 
			
		||||
     * opts: {
 | 
			
		||||
     *   type: "badge"
 | 
			
		||||
     *   class: "",
 | 
			
		||||
     *   element: function(node),
 | 
			
		||||
     *   show: string|function(node),
 | 
			
		||||
     *   filter: function(node) -> boolean
 | 
			
		||||
     * }
 | 
			
		||||
     */
 | 
			
		||||
    function register(id, opts) {
 | 
			
		||||
        if (opts.type !== 'badge') {
 | 
			
		||||
            throw new Error("Unsupported annotation type: "+opts.type);
 | 
			
		||||
        }
 | 
			
		||||
        annotations[id] = opts
 | 
			
		||||
        RED.hooks.add("viewAddNode.annotation-"+id, function(evt) {
 | 
			
		||||
            if (opts.filter && !opts.filter(evt.node)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            addAnnotation(id,evt);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var nodes = RED.view.getActiveNodes();
 | 
			
		||||
        nodes.forEach(function(n) {
 | 
			
		||||
            n.__pendingAnnotation__ = id;
 | 
			
		||||
        })
 | 
			
		||||
        RED.view.redraw();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addAnnotation(id,evt) {
 | 
			
		||||
        var opts = annotations[id];
 | 
			
		||||
        evt.el.__annotations__ = evt.el.__annotations__ || [];
 | 
			
		||||
        var annotationGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
 | 
			
		||||
        annotationGroup.setAttribute("class",opts.class || "");
 | 
			
		||||
        evt.el.__annotations__.push({
 | 
			
		||||
            id:id,
 | 
			
		||||
            element: annotationGroup
 | 
			
		||||
        });
 | 
			
		||||
        var annotation = opts.element(evt.node);
 | 
			
		||||
        if (opts.tooltip) {
 | 
			
		||||
            annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
 | 
			
		||||
            annotation.addEventListener("mouseleave", annotationMouseLeave);
 | 
			
		||||
        }
 | 
			
		||||
        annotationGroup.appendChild(annotation);
 | 
			
		||||
        evt.el.appendChild(annotationGroup);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function unregister(id) {
 | 
			
		||||
        delete annotations[id]
 | 
			
		||||
        RED.hooks.remove("*.annotation-"+id);
 | 
			
		||||
        RED.view.redraw();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var badgeHoverTimeout;
 | 
			
		||||
    var badgeHover;
 | 
			
		||||
    function getAnnotationMouseEnter(annotation,node,tooltip) {
 | 
			
		||||
        return function() {
 | 
			
		||||
            var text = typeof tooltip === "function"?tooltip(node):tooltip;
 | 
			
		||||
            if (text) {
 | 
			
		||||
                clearTimeout(badgeHoverTimeout);
 | 
			
		||||
                badgeHoverTimeout = setTimeout(function() {
 | 
			
		||||
                    var pos = RED.view.getElementPosition(annotation);
 | 
			
		||||
                    var rect = annotation.getBoundingClientRect();
 | 
			
		||||
                    badgeHoverTimeout = null;
 | 
			
		||||
                    badgeHover = RED.view.showTooltip(
 | 
			
		||||
                        (pos[0]+rect.width/2),
 | 
			
		||||
                        (pos[1]),
 | 
			
		||||
                        text,
 | 
			
		||||
                        "top"
 | 
			
		||||
                    );
 | 
			
		||||
                },500);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function annotationMouseLeave() {
 | 
			
		||||
        clearTimeout(badgeHoverTimeout);
 | 
			
		||||
        if (badgeHover) {
 | 
			
		||||
            badgeHover.remove();
 | 
			
		||||
            badgeHover = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        register:register,
 | 
			
		||||
        unregister:unregister
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
@@ -159,15 +159,15 @@ RED.view.tools = (function() {
 | 
			
		||||
        nodes.forEach(function(n) {
 | 
			
		||||
            var modified = false;
 | 
			
		||||
            var oldValue = n.l === undefined?true:n.l;
 | 
			
		||||
            var isLink = /^link (in|out)$/.test(n._def.type);
 | 
			
		||||
            var showLabel = n._def.hasOwnProperty("showLabel")?n._def.showLabel:true;
 | 
			
		||||
 | 
			
		||||
            if (labelShown) {
 | 
			
		||||
                if (n.l === false || (isLink && !n.hasOwnProperty('l'))) {
 | 
			
		||||
                if (n.l === false || (!showLabel && !n.hasOwnProperty('l'))) {
 | 
			
		||||
                    n.l = true;
 | 
			
		||||
                    modified = true;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if ((!isLink && (!n.hasOwnProperty('l') || n.l === true)) || (isLink && n.l === true) ) {
 | 
			
		||||
                if ((showLabel && (!n.hasOwnProperty('l') || n.l === true)) || (!showLabel && n.l === true) ) {
 | 
			
		||||
                    n.l = false;
 | 
			
		||||
                    modified = true;
 | 
			
		||||
                }
 | 
			
		||||
@@ -427,18 +427,309 @@ RED.view.tools = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function alignSelectionToEdge(direction) {
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
 | 
			
		||||
        if (selection.nodes && selection.nodes.length > 1) {
 | 
			
		||||
            var changedNodes = [];
 | 
			
		||||
            var bounds = {
 | 
			
		||||
                minX: Number.MAX_SAFE_INTEGER,
 | 
			
		||||
                minY: Number.MAX_SAFE_INTEGER,
 | 
			
		||||
                maxX: Number.MIN_SAFE_INTEGER,
 | 
			
		||||
                maxY: Number.MIN_SAFE_INTEGER
 | 
			
		||||
            }
 | 
			
		||||
            selection.nodes.forEach(function(n) {
 | 
			
		||||
                if (n.type === "group") {
 | 
			
		||||
                    bounds.minX = Math.min(bounds.minX, n.x);
 | 
			
		||||
                    bounds.minY = Math.min(bounds.minY, n.y);
 | 
			
		||||
                    bounds.maxX = Math.max(bounds.maxX, n.x + n.w);
 | 
			
		||||
                    bounds.maxY = Math.max(bounds.maxY, n.y + n.h);
 | 
			
		||||
                } else {
 | 
			
		||||
                    bounds.minX = Math.min(bounds.minX, n.x - n.w/2);
 | 
			
		||||
                    bounds.minY = Math.min(bounds.minY, n.y - n.h/2);
 | 
			
		||||
                    bounds.maxX = Math.max(bounds.maxX, n.x + n.w/2);
 | 
			
		||||
                    bounds.maxY = Math.max(bounds.maxY, n.y + n.h/2);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            bounds.midX = bounds.minX + (bounds.maxX - bounds.minX)/2;
 | 
			
		||||
            bounds.midY = bounds.minY + (bounds.maxY - bounds.minY)/2;
 | 
			
		||||
 | 
			
		||||
            selection.nodes.forEach(function(n) {
 | 
			
		||||
                var targetX;
 | 
			
		||||
                var targetY;
 | 
			
		||||
                var isGroup = n.type==="group";
 | 
			
		||||
                switch(direction) {
 | 
			
		||||
                    case 'top':
 | 
			
		||||
                        targetX = n.x;
 | 
			
		||||
                        targetY = bounds.minY + (isGroup?0:(n.h/2));
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'bottom':
 | 
			
		||||
                        targetX = n.x;
 | 
			
		||||
                        targetY = bounds.maxY - (isGroup?n.h:(n.h/2));
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'left':
 | 
			
		||||
                        targetX = bounds.minX + (isGroup?0:(n.w/2));
 | 
			
		||||
                        targetY = n.y;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'right':
 | 
			
		||||
                        targetX = bounds.maxX - (isGroup?n.w:(n.w/2));
 | 
			
		||||
                        targetY = n.y;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'middle':
 | 
			
		||||
                        targetX = n.x;
 | 
			
		||||
                        targetY = bounds.midY - (isGroup?n.h/2:0)
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'center':
 | 
			
		||||
                        targetX = bounds.midX - (isGroup?n.w/2:0)
 | 
			
		||||
                        targetY = n.y;
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (n.x !== targetX || n.y !== targetY) {
 | 
			
		||||
                    if (!isGroup) {
 | 
			
		||||
                        changedNodes.push({
 | 
			
		||||
                            n:n,
 | 
			
		||||
                            ox: n.x,
 | 
			
		||||
                            oy: n.y,
 | 
			
		||||
                            moved: n.moved
 | 
			
		||||
                        });
 | 
			
		||||
                        n.x = targetX;
 | 
			
		||||
                        n.y = targetY;
 | 
			
		||||
                        n.dirty = true;
 | 
			
		||||
                        n.moved = true;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var groupNodes = RED.group.getNodes(n, true);
 | 
			
		||||
                        var deltaX = n.x - targetX;
 | 
			
		||||
                        var deltaY = n.y - targetY;
 | 
			
		||||
                        groupNodes.forEach(function(gn) {
 | 
			
		||||
                            if (gn.type !== "group" ) {
 | 
			
		||||
                                changedNodes.push({
 | 
			
		||||
                                    n:gn,
 | 
			
		||||
                                    ox: gn.x,
 | 
			
		||||
                                    oy: gn.y,
 | 
			
		||||
                                    moved: gn.moved
 | 
			
		||||
                                });
 | 
			
		||||
                                gn.x = gn.x - deltaX;
 | 
			
		||||
                                gn.y = gn.y - deltaY;
 | 
			
		||||
                                gn.dirty = true;
 | 
			
		||||
                                gn.moved = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (changedNodes.length > 0) {
 | 
			
		||||
                RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()});
 | 
			
		||||
                RED.nodes.dirty(true);
 | 
			
		||||
                RED.view.redraw(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function distributeSelection(direction) {
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
 | 
			
		||||
        if (selection.nodes && selection.nodes.length > 2) {
 | 
			
		||||
            var changedNodes = [];
 | 
			
		||||
            var bounds = {
 | 
			
		||||
                minX: Number.MAX_SAFE_INTEGER,
 | 
			
		||||
                minY: Number.MAX_SAFE_INTEGER,
 | 
			
		||||
                maxX: Number.MIN_SAFE_INTEGER,
 | 
			
		||||
                maxY: Number.MIN_SAFE_INTEGER
 | 
			
		||||
            }
 | 
			
		||||
            var startAnchors = [];
 | 
			
		||||
            var endAnchors = [];
 | 
			
		||||
 | 
			
		||||
            selection.nodes.forEach(function(n) {
 | 
			
		||||
                var nx,ny;
 | 
			
		||||
                if (n.type === "group") {
 | 
			
		||||
                    nx = n.x + n.w/2;
 | 
			
		||||
                    ny = n.y + n.h/2;
 | 
			
		||||
                } else {
 | 
			
		||||
                    nx = n.x;
 | 
			
		||||
                    ny = n.y;
 | 
			
		||||
                }
 | 
			
		||||
                if (direction === "h") {
 | 
			
		||||
                    if (nx < bounds.minX) {
 | 
			
		||||
                        startAnchors = [];
 | 
			
		||||
                        bounds.minX = nx;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (nx === bounds.minX) {
 | 
			
		||||
                        startAnchors.push(n);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (nx > bounds.maxX) {
 | 
			
		||||
                        endAnchors = [];
 | 
			
		||||
                        bounds.maxX = nx;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (nx === bounds.maxX) {
 | 
			
		||||
                        endAnchors.push(n);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (ny < bounds.minY) {
 | 
			
		||||
                        startAnchors = [];
 | 
			
		||||
                        bounds.minY = ny;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (ny === bounds.minY) {
 | 
			
		||||
                        startAnchors.push(n);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (ny > bounds.maxY) {
 | 
			
		||||
                        endAnchors = [];
 | 
			
		||||
                        bounds.maxY = ny;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (ny === bounds.maxY) {
 | 
			
		||||
                        endAnchors.push(n);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var startAnchor = startAnchors[0];
 | 
			
		||||
            var endAnchor = endAnchors[0];
 | 
			
		||||
 | 
			
		||||
            var nodeSpace = 0;
 | 
			
		||||
            var nodesToMove = selection.nodes.filter(function(n) {
 | 
			
		||||
                if (n.id !== startAnchor.id && n.id !== endAnchor.id) {
 | 
			
		||||
                    nodeSpace += direction === 'h'?n.w:n.h;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            }).sort(function(A,B) {
 | 
			
		||||
                if (direction === 'h') {
 | 
			
		||||
                    return A.x - B.x
 | 
			
		||||
                } else {
 | 
			
		||||
                    return A.y - B.y
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            var saX = startAnchor.x + startAnchor.w/2;
 | 
			
		||||
            var saY = startAnchor.y + startAnchor.h/2;
 | 
			
		||||
            if (startAnchor.type === "group") {
 | 
			
		||||
                saX = startAnchor.x + startAnchor.w;
 | 
			
		||||
                saY = startAnchor.y + startAnchor.h;
 | 
			
		||||
            }
 | 
			
		||||
            var eaX = endAnchor.x;
 | 
			
		||||
            var eaY = endAnchor.y;
 | 
			
		||||
            if (endAnchor.type !== "group") {
 | 
			
		||||
                eaX -= endAnchor.w/2;
 | 
			
		||||
                eaY -= endAnchor.h/2;
 | 
			
		||||
            }
 | 
			
		||||
            var spaceToFill = direction === 'h'?(eaX - saX - nodeSpace): (eaY - saY - nodeSpace);
 | 
			
		||||
            var spaceBetweenNodes = spaceToFill / (nodesToMove.length + 1);
 | 
			
		||||
 | 
			
		||||
            var tx = saX;
 | 
			
		||||
            var ty = saY;
 | 
			
		||||
            while(nodesToMove.length > 0) {
 | 
			
		||||
                if (direction === 'h') {
 | 
			
		||||
                    tx += spaceBetweenNodes;
 | 
			
		||||
                } else {
 | 
			
		||||
                    ty += spaceBetweenNodes;
 | 
			
		||||
                }
 | 
			
		||||
                var nextNode = nodesToMove.shift();
 | 
			
		||||
                var isGroup = nextNode.type==="group";
 | 
			
		||||
 | 
			
		||||
                var nx = nextNode.x;
 | 
			
		||||
                var ny = nextNode.y;
 | 
			
		||||
                if (!isGroup) {
 | 
			
		||||
                    tx += nextNode.w/2;
 | 
			
		||||
                    ty += nextNode.h/2;
 | 
			
		||||
                }
 | 
			
		||||
                if ((direction === 'h' && nx !== tx) || (direction === 'v' && ny !== ty)) {
 | 
			
		||||
                    if (!isGroup) {
 | 
			
		||||
                        changedNodes.push({
 | 
			
		||||
                            n:nextNode,
 | 
			
		||||
                            ox: nextNode.x,
 | 
			
		||||
                            oy: nextNode.y,
 | 
			
		||||
                            moved: nextNode.moved
 | 
			
		||||
                        });
 | 
			
		||||
                        if (direction === 'h') {
 | 
			
		||||
                            nextNode.x = tx;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            nextNode.y = ty;
 | 
			
		||||
                        }
 | 
			
		||||
                        nextNode.dirty = true;
 | 
			
		||||
                        nextNode.moved = true;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        var groupNodes = RED.group.getNodes(nextNode, true);
 | 
			
		||||
                        var deltaX = direction === 'h'? nx - tx : 0;
 | 
			
		||||
                        var deltaY = direction === 'v'? ny - ty : 0;
 | 
			
		||||
                        groupNodes.forEach(function(gn) {
 | 
			
		||||
                            if (gn.type !== "group" ) {
 | 
			
		||||
                                changedNodes.push({
 | 
			
		||||
                                    n:gn,
 | 
			
		||||
                                    ox: gn.x,
 | 
			
		||||
                                    oy: gn.y,
 | 
			
		||||
                                    moved: gn.moved
 | 
			
		||||
                                });
 | 
			
		||||
                                gn.x = gn.x - deltaX;
 | 
			
		||||
                                gn.y = gn.y - deltaY;
 | 
			
		||||
                                gn.dirty = true;
 | 
			
		||||
                                gn.moved = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (isGroup) {
 | 
			
		||||
                    tx += nextNode.w;
 | 
			
		||||
                    ty += nextNode.h;
 | 
			
		||||
                } else {
 | 
			
		||||
                    tx += nextNode.w/2;
 | 
			
		||||
                    ty += nextNode.h/2;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (changedNodes.length > 0) {
 | 
			
		||||
                RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()});
 | 
			
		||||
                RED.nodes.dirty(true);
 | 
			
		||||
                RED.view.redraw(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function reorderSelection(dir) {
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            var nodesToMove = [];
 | 
			
		||||
            selection.nodes.forEach(function(n) {
 | 
			
		||||
                if (n.type === "group") {
 | 
			
		||||
                    nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true).filter(function(n) {
 | 
			
		||||
                        return n.type !== "group";
 | 
			
		||||
                    }))
 | 
			
		||||
                } else if (n.type !== "subflow"){
 | 
			
		||||
                    nodesToMove.push(n);
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            if (nodesToMove.length > 0) {
 | 
			
		||||
                var z = nodesToMove[0].z;
 | 
			
		||||
                var existingOrder = RED.nodes.getNodeOrder(z);
 | 
			
		||||
                var movedNodes;
 | 
			
		||||
                if (dir === "forwards") {
 | 
			
		||||
                    movedNodes = RED.nodes.moveNodesForwards(nodesToMove);
 | 
			
		||||
                } else if (dir === "backwards") {
 | 
			
		||||
                    movedNodes = RED.nodes.moveNodesBackwards(nodesToMove);
 | 
			
		||||
                } else if (dir === "front") {
 | 
			
		||||
                    movedNodes = RED.nodes.moveNodesToFront(nodesToMove);
 | 
			
		||||
                } else if (dir === "back") {
 | 
			
		||||
                    movedNodes = RED.nodes.moveNodesToBack(nodesToMove);
 | 
			
		||||
                }
 | 
			
		||||
                if (movedNodes.length > 0) {
 | 
			
		||||
                    var newOrder = RED.nodes.getNodeOrder(z);
 | 
			
		||||
                    RED.history.push({t:"reorder",nodes:{z:z,from:existingOrder,to:newOrder},dirty:RED.nodes.dirty()});
 | 
			
		||||
                    RED.nodes.dirty(true);
 | 
			
		||||
                    RED.view.redraw(true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: function() {
 | 
			
		||||
            RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
 | 
			
		||||
            RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); })
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:align-selection-to-grid", alignToGrid);
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());});
 | 
			
		||||
            RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);});
 | 
			
		||||
            RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());});
 | 
			
		||||
@@ -454,6 +745,12 @@ RED.view.tools = (function() {
 | 
			
		||||
            RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});
 | 
			
		||||
            RED.actions.add("core:move-selection-left", function() { moveSelection(-1,0);});
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:move-selection-forwards", function() { reorderSelection('forwards') })
 | 
			
		||||
            RED.actions.add("core:move-selection-backwards", function() { reorderSelection('backwards') })
 | 
			
		||||
            RED.actions.add("core:move-selection-to-front", function() { reorderSelection('front') })
 | 
			
		||||
            RED.actions.add("core:move-selection-to-back", function() { reorderSelection('back') })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:step-selection-up", function() { moveSelection(0,-RED.view.gridSize());});
 | 
			
		||||
            RED.actions.add("core:step-selection-right", function() { moveSelection(RED.view.gridSize(),0);});
 | 
			
		||||
            RED.actions.add("core:step-selection-down", function() { moveSelection(0,RED.view.gridSize());});
 | 
			
		||||
@@ -474,6 +771,20 @@ RED.view.tools = (function() {
 | 
			
		||||
            RED.actions.add("core:go-to-nearest-node-on-right", function() { gotoNearestNode('right')})
 | 
			
		||||
            RED.actions.add("core:go-to-nearest-node-above", function() { gotoNearestNode('up') })
 | 
			
		||||
            RED.actions.add("core:go-to-nearest-node-below", function() { gotoNearestNode('down') })
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:align-selection-to-grid", alignToGrid);
 | 
			
		||||
            RED.actions.add("core:align-selection-to-left", function() { alignSelectionToEdge('left') })
 | 
			
		||||
            RED.actions.add("core:align-selection-to-right", function() { alignSelectionToEdge('right') })
 | 
			
		||||
            RED.actions.add("core:align-selection-to-top", function() { alignSelectionToEdge('top') })
 | 
			
		||||
            RED.actions.add("core:align-selection-to-bottom", function() { alignSelectionToEdge('bottom') })
 | 
			
		||||
            RED.actions.add("core:align-selection-to-middle", function() { alignSelectionToEdge('middle') })
 | 
			
		||||
            RED.actions.add("core:align-selection-to-center", function() { alignSelectionToEdge('center') })
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
 | 
			
		||||
            RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // RED.actions.add("core:add-node", function() { addNode() })
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -345,8 +345,8 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
            activeSubflow = RED.nodes.subflow(event.workspace);
 | 
			
		||||
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow);
 | 
			
		||||
 | 
			
		||||
            if (workspaceScrollPositions[event.workspace]) {
 | 
			
		||||
                chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
 | 
			
		||||
@@ -413,7 +413,7 @@ RED.view = (function() {
 | 
			
		||||
                var nn = result.node;
 | 
			
		||||
 | 
			
		||||
                var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
 | 
			
		||||
                if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
                if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
                    nn.l = showLabel;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -501,6 +501,28 @@ RED.view = (function() {
 | 
			
		||||
        RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
 | 
			
		||||
        RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
 | 
			
		||||
        RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
 | 
			
		||||
 | 
			
		||||
        RED.events.on("view:selection-changed", function(selection) {
 | 
			
		||||
            var hasSelection = (selection.nodes && selection.nodes.length > 0);
 | 
			
		||||
            var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-cut",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-copy",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection);
 | 
			
		||||
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection);
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:delete-selection",deleteSelection);
 | 
			
		||||
        RED.actions.add("core:edit-selected-node",editSelection);
 | 
			
		||||
        RED.actions.add("core:go-to-selection",function() {
 | 
			
		||||
@@ -546,10 +568,44 @@ RED.view = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.view.annotations.init();
 | 
			
		||||
        RED.view.navigator.init();
 | 
			
		||||
        RED.view.tools.init();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        RED.view.annotations.register("red-ui-flow-node-changed",{
 | 
			
		||||
            type: "badge",
 | 
			
		||||
            class: "red-ui-flow-node-changed",
 | 
			
		||||
            element: function() {
 | 
			
		||||
                var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle");
 | 
			
		||||
                changeBadge.setAttribute("cx",5);
 | 
			
		||||
                changeBadge.setAttribute("cy",5);
 | 
			
		||||
                changeBadge.setAttribute("r",5);
 | 
			
		||||
                return changeBadge;
 | 
			
		||||
            },
 | 
			
		||||
            show: function(n) { return n.changed||n.moved }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.view.annotations.register("red-ui-flow-node-error",{
 | 
			
		||||
            type: "badge",
 | 
			
		||||
            class: "red-ui-flow-node-error",
 | 
			
		||||
            element: function(d) {
 | 
			
		||||
                var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
 | 
			
		||||
                errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
 | 
			
		||||
                return errorBadge
 | 
			
		||||
            },
 | 
			
		||||
            tooltip: function(d) {
 | 
			
		||||
                if (d.validationErrors && d.validationErrors.length > 0) {
 | 
			
		||||
                    return RED._("editor.errors.invalidProperties")+"\n  - "+d.validationErrors.join("\n    - ")
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            show: function(n) { return !n.valid }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function updateGrid() {
 | 
			
		||||
        var gridTicks = [];
 | 
			
		||||
        for (var i=0;i<space_width;i+=+gridSize) {
 | 
			
		||||
@@ -619,24 +675,33 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
    function updateActiveNodes() {
 | 
			
		||||
        var activeWorkspace = RED.workspaces.active();
 | 
			
		||||
        if (activeWorkspace !== 0) {
 | 
			
		||||
            activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
 | 
			
		||||
            activeNodes.forEach(function(n,i) {
 | 
			
		||||
                n._index = i;
 | 
			
		||||
            })
 | 
			
		||||
            activeLinks = RED.nodes.filterLinks({
 | 
			
		||||
                source:{z:activeWorkspace},
 | 
			
		||||
                target:{z:activeWorkspace}
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
 | 
			
		||||
            activeGroups = RED.nodes.groups(activeWorkspace)||[];
 | 
			
		||||
            activeGroups.forEach(function(g, i) {
 | 
			
		||||
                g._index = i;
 | 
			
		||||
                if (g.g) {
 | 
			
		||||
                    g._root = g.g;
 | 
			
		||||
                    g._depth = 1;
 | 
			
		||||
                } else {
 | 
			
		||||
                    g._root = g.id;
 | 
			
		||||
                    g._depth = 0;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            activeNodes = [];
 | 
			
		||||
            activeLinks = [];
 | 
			
		||||
            activeGroups = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        activeLinks = RED.nodes.filterLinks({
 | 
			
		||||
            source:{z:activeWorkspace},
 | 
			
		||||
            target:{z:activeWorkspace}
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        activeGroups = RED.nodes.groups(activeWorkspace)||[];
 | 
			
		||||
        activeGroups.forEach(function(g) {
 | 
			
		||||
            if (g.g) {
 | 
			
		||||
                g._root = g.g;
 | 
			
		||||
                g._depth = 1;
 | 
			
		||||
            } else {
 | 
			
		||||
                g._root = g.id;
 | 
			
		||||
                g._depth = 0;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        var changed = false;
 | 
			
		||||
        do {
 | 
			
		||||
            changed = false;
 | 
			
		||||
@@ -661,7 +726,8 @@ RED.view = (function() {
 | 
			
		||||
            if (a._root === b._root) {
 | 
			
		||||
                return a._depth - b._depth;
 | 
			
		||||
            } else {
 | 
			
		||||
                return a._root.localeCompare(b._root);
 | 
			
		||||
                // return a._root.localeCompare(b._root);
 | 
			
		||||
                return a._index - b._index;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -670,7 +736,8 @@ RED.view = (function() {
 | 
			
		||||
            if (a._root === b._root) {
 | 
			
		||||
                return a._depth - b._depth;
 | 
			
		||||
            } else {
 | 
			
		||||
                return a._root.localeCompare(b._root);
 | 
			
		||||
                return a._index - b._index;
 | 
			
		||||
                // return a._root.localeCompare(b._root);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
@@ -1021,7 +1088,7 @@ RED.view = (function() {
 | 
			
		||||
                nn.x = point[0];
 | 
			
		||||
                nn.y = point[1];
 | 
			
		||||
                var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
 | 
			
		||||
                if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
                if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
                    nn.l = showLabel;
 | 
			
		||||
                }
 | 
			
		||||
                if (quickAddLink) {
 | 
			
		||||
@@ -1476,15 +1543,15 @@ RED.view = (function() {
 | 
			
		||||
                            var mouseY = node.n.y;
 | 
			
		||||
                            if (outer[0][0].getIntersectionList) {
 | 
			
		||||
                                var svgRect = outer[0][0].createSVGRect();
 | 
			
		||||
                                svgRect.x = mouseX;
 | 
			
		||||
                                svgRect.y = mouseY;
 | 
			
		||||
                                svgRect.x = mouseX*scaleFactor;
 | 
			
		||||
                                svgRect.y = mouseY*scaleFactor;
 | 
			
		||||
                                svgRect.width = 1;
 | 
			
		||||
                                svgRect.height = 1;
 | 
			
		||||
                                nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // Firefox doesn"t do getIntersectionList and that
 | 
			
		||||
                                // makes us sad
 | 
			
		||||
                                nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
 | 
			
		||||
                                nodes = RED.view.getLinksAtPoint(mouseX*scaleFactor,mouseY*scaleFactor);
 | 
			
		||||
                            }
 | 
			
		||||
                            for (var i=0;i<nodes.length;i++) {
 | 
			
		||||
                                if (d3.select(nodes[i]).classed("red-ui-flow-link-background")) {
 | 
			
		||||
@@ -1751,7 +1818,6 @@ RED.view = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (mouse_mode == RED.state.IMPORT_DRAGGING) {
 | 
			
		||||
            RED.keyboard.remove("escape");
 | 
			
		||||
            updateActiveNodes();
 | 
			
		||||
            RED.nodes.dirty(true);
 | 
			
		||||
        }
 | 
			
		||||
@@ -1786,6 +1852,9 @@ RED.view = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function selectNone() {
 | 
			
		||||
        if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (mouse_mode === RED.state.IMPORT_DRAGGING) {
 | 
			
		||||
            clearSelection();
 | 
			
		||||
            RED.history.pop();
 | 
			
		||||
@@ -1903,93 +1972,94 @@ RED.view = (function() {
 | 
			
		||||
    function updateSelection() {
 | 
			
		||||
        var selection = {};
 | 
			
		||||
        var activeWorkspace = RED.workspaces.active();
 | 
			
		||||
 | 
			
		||||
        var workspaceSelection = RED.workspaces.selection();
 | 
			
		||||
        if (workspaceSelection.length === 0) {
 | 
			
		||||
            selection = getSelection();
 | 
			
		||||
            activeLinks = RED.nodes.filterLinks({
 | 
			
		||||
                source:{z:activeWorkspace},
 | 
			
		||||
                target:{z:activeWorkspace}
 | 
			
		||||
            });
 | 
			
		||||
            var tabOrder = RED.nodes.getWorkspaceOrder();
 | 
			
		||||
            var currentLinks = activeLinks;
 | 
			
		||||
            var addedLinkLinks = {};
 | 
			
		||||
            activeFlowLinks = [];
 | 
			
		||||
            var activeLinkNodeIds = Object.keys(activeLinkNodes);
 | 
			
		||||
            activeLinkNodeIds.forEach(function(n) {
 | 
			
		||||
                activeLinkNodes[n].dirty = true;
 | 
			
		||||
            })
 | 
			
		||||
            activeLinkNodes = {};
 | 
			
		||||
            for (var i=0;i<movingSet.length();i++) {
 | 
			
		||||
                var msn = movingSet.get(i);
 | 
			
		||||
                if ((msn.n.type === "link out" || msn.n.type === "link in") &&
 | 
			
		||||
                    (msn.n.z === activeWorkspace)) {
 | 
			
		||||
                    var linkNode = msn.n;
 | 
			
		||||
                    activeLinkNodes[linkNode.id] = linkNode;
 | 
			
		||||
                    var offFlowLinks = {};
 | 
			
		||||
                    linkNode.links.forEach(function(id) {
 | 
			
		||||
                        var target = RED.nodes.node(id);
 | 
			
		||||
                        if (target) {
 | 
			
		||||
                            if (linkNode.type === "link out") {
 | 
			
		||||
                                if (target.z === linkNode.z) {
 | 
			
		||||
                                    if (!addedLinkLinks[linkNode.id+":"+target.id]) {
 | 
			
		||||
                                        activeLinks.push({
 | 
			
		||||
                                            source:linkNode,
 | 
			
		||||
                                            sourcePort:0,
 | 
			
		||||
                                            target: target,
 | 
			
		||||
                                            link: true
 | 
			
		||||
                                        });
 | 
			
		||||
                                        addedLinkLinks[linkNode.id+":"+target.id] = true;
 | 
			
		||||
                                        activeLinkNodes[target.id] = target;
 | 
			
		||||
                                        target.dirty = true;
 | 
			
		||||
        if (activeWorkspace !== 0) {
 | 
			
		||||
            if (workspaceSelection.length === 0) {
 | 
			
		||||
                selection = getSelection();
 | 
			
		||||
                activeLinks = RED.nodes.filterLinks({
 | 
			
		||||
                    source:{z:activeWorkspace},
 | 
			
		||||
                    target:{z:activeWorkspace}
 | 
			
		||||
                });
 | 
			
		||||
                var tabOrder = RED.nodes.getWorkspaceOrder();
 | 
			
		||||
                var currentLinks = activeLinks;
 | 
			
		||||
                var addedLinkLinks = {};
 | 
			
		||||
                activeFlowLinks = [];
 | 
			
		||||
                var activeLinkNodeIds = Object.keys(activeLinkNodes);
 | 
			
		||||
                activeLinkNodeIds.forEach(function(n) {
 | 
			
		||||
                    activeLinkNodes[n].dirty = true;
 | 
			
		||||
                })
 | 
			
		||||
                activeLinkNodes = {};
 | 
			
		||||
                for (var i=0;i<movingSet.length();i++) {
 | 
			
		||||
                    var msn = movingSet.get(i);
 | 
			
		||||
                    if (((msn.n.type === "link out" && msn.n.mode !== 'return') || msn.n.type === "link in") &&
 | 
			
		||||
                        (msn.n.z === activeWorkspace)) {
 | 
			
		||||
                        var linkNode = msn.n;
 | 
			
		||||
                        activeLinkNodes[linkNode.id] = linkNode;
 | 
			
		||||
                        var offFlowLinks = {};
 | 
			
		||||
                        linkNode.links.forEach(function(id) {
 | 
			
		||||
                            var target = RED.nodes.node(id);
 | 
			
		||||
                            if (target) {
 | 
			
		||||
                                if (linkNode.type === "link out") {
 | 
			
		||||
                                    if (target.z === linkNode.z) {
 | 
			
		||||
                                        if (!addedLinkLinks[linkNode.id+":"+target.id]) {
 | 
			
		||||
                                            activeLinks.push({
 | 
			
		||||
                                                source:linkNode,
 | 
			
		||||
                                                sourcePort:0,
 | 
			
		||||
                                                target: target,
 | 
			
		||||
                                                link: true
 | 
			
		||||
                                            });
 | 
			
		||||
                                            addedLinkLinks[linkNode.id+":"+target.id] = true;
 | 
			
		||||
                                            activeLinkNodes[target.id] = target;
 | 
			
		||||
                                            target.dirty = true;
 | 
			
		||||
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        offFlowLinks[target.z] = offFlowLinks[target.z]||[];
 | 
			
		||||
                                        offFlowLinks[target.z].push(target);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    offFlowLinks[target.z] = offFlowLinks[target.z]||[];
 | 
			
		||||
                                    offFlowLinks[target.z].push(target);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if (target.z === linkNode.z) {
 | 
			
		||||
                                    if (!addedLinkLinks[target.id+":"+linkNode.id]) {
 | 
			
		||||
                                        activeLinks.push({
 | 
			
		||||
                                            source:target,
 | 
			
		||||
                                            sourcePort:0,
 | 
			
		||||
                                            target: linkNode,
 | 
			
		||||
                                            link: true
 | 
			
		||||
                                        });
 | 
			
		||||
                                        addedLinkLinks[target.id+":"+linkNode.id] = true;
 | 
			
		||||
                                        activeLinkNodes[target.id] = target;
 | 
			
		||||
                                        target.dirty = true;
 | 
			
		||||
                                    if (target.z === linkNode.z) {
 | 
			
		||||
                                        if (!addedLinkLinks[target.id+":"+linkNode.id]) {
 | 
			
		||||
                                            activeLinks.push({
 | 
			
		||||
                                                source:target,
 | 
			
		||||
                                                sourcePort:0,
 | 
			
		||||
                                                target: linkNode,
 | 
			
		||||
                                                link: true
 | 
			
		||||
                                            });
 | 
			
		||||
                                            addedLinkLinks[target.id+":"+linkNode.id] = true;
 | 
			
		||||
                                            activeLinkNodes[target.id] = target;
 | 
			
		||||
                                            target.dirty = true;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        offFlowLinks[target.z] = offFlowLinks[target.z]||[];
 | 
			
		||||
                                        offFlowLinks[target.z].push(target);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    offFlowLinks[target.z] = offFlowLinks[target.z]||[];
 | 
			
		||||
                                    offFlowLinks[target.z].push(target);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    var offFlows = Object.keys(offFlowLinks);
 | 
			
		||||
                    // offFlows.sort(function(A,B) {
 | 
			
		||||
                    //     return tabOrder.indexOf(A) - tabOrder.indexOf(B);
 | 
			
		||||
                    // });
 | 
			
		||||
                    if (offFlows.length > 0) {
 | 
			
		||||
                        activeFlowLinks.push({
 | 
			
		||||
                            refresh: Math.floor(Math.random()*10000),
 | 
			
		||||
                            node: linkNode,
 | 
			
		||||
                            links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};})
 | 
			
		||||
                        });
 | 
			
		||||
                        var offFlows = Object.keys(offFlowLinks);
 | 
			
		||||
                        // offFlows.sort(function(A,B) {
 | 
			
		||||
                        //     return tabOrder.indexOf(A) - tabOrder.indexOf(B);
 | 
			
		||||
                        // });
 | 
			
		||||
                        if (offFlows.length > 0) {
 | 
			
		||||
                            activeFlowLinks.push({
 | 
			
		||||
                                refresh: Math.floor(Math.random()*10000),
 | 
			
		||||
                                node: linkNode,
 | 
			
		||||
                                links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};})
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
 | 
			
		||||
                    activeLinks.push(selected_link);
 | 
			
		||||
                    activeLinkNodes[selected_link.source.id] = selected_link.source;
 | 
			
		||||
                    selected_link.source.dirty = true;
 | 
			
		||||
                    activeLinkNodes[selected_link.target.id] = selected_link.target;
 | 
			
		||||
                    selected_link.target.dirty = true;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                selection.flows = workspaceSelection;
 | 
			
		||||
            }
 | 
			
		||||
            if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
 | 
			
		||||
                activeLinks.push(selected_link);
 | 
			
		||||
                activeLinkNodes[selected_link.source.id] = selected_link.source;
 | 
			
		||||
                selected_link.source.dirty = true;
 | 
			
		||||
                activeLinkNodes[selected_link.target.id] = selected_link.target;
 | 
			
		||||
                selected_link.target.dirty = true;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            selection.flows = workspaceSelection;
 | 
			
		||||
        }
 | 
			
		||||
        var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) {
 | 
			
		||||
            if (key === 'nodes' || key === 'flows') {
 | 
			
		||||
@@ -2296,6 +2366,7 @@ RED.view = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            clipboard = JSON.stringify(nns);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-paste", false);
 | 
			
		||||
            if (nodeCount > 0) {
 | 
			
		||||
                RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"});
 | 
			
		||||
            } else if (groupCount > 0) {
 | 
			
		||||
@@ -2333,6 +2404,7 @@ RED.view = (function() {
 | 
			
		||||
    var textDimensionPlaceholder = {};
 | 
			
		||||
    var textDimensionCache = {};
 | 
			
		||||
    function calculateTextDimensions(str,className) {
 | 
			
		||||
        var cacheKey = "!"+str;
 | 
			
		||||
        if (!textDimensionPlaceholder[className]) {
 | 
			
		||||
            textDimensionPlaceholder[className] = document.createElement("span");
 | 
			
		||||
            textDimensionPlaceholder[className].className = className;
 | 
			
		||||
@@ -2341,15 +2413,15 @@ RED.view = (function() {
 | 
			
		||||
            document.getElementById("red-ui-editor").appendChild(textDimensionPlaceholder[className]);
 | 
			
		||||
            textDimensionCache[className] = {};
 | 
			
		||||
        } else {
 | 
			
		||||
            if (textDimensionCache[className][str]) {
 | 
			
		||||
                return textDimensionCache[className][str]
 | 
			
		||||
            if (textDimensionCache[className][cacheKey]) {
 | 
			
		||||
                return textDimensionCache[className][cacheKey]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        textDimensionPlaceholder[className].textContent = (str||"");
 | 
			
		||||
        var w = textDimensionPlaceholder[className].offsetWidth;
 | 
			
		||||
        var h = textDimensionPlaceholder[className].offsetHeight;
 | 
			
		||||
        textDimensionCache[className][str] = [w,h];
 | 
			
		||||
        return textDimensionCache[className][str];
 | 
			
		||||
        textDimensionCache[className][cacheKey] = [w,h];
 | 
			
		||||
        return textDimensionCache[className][cacheKey];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function convertLineBreakCharacter(str) {
 | 
			
		||||
@@ -3443,6 +3515,7 @@ RED.view = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function getGroupAt(x,y) {
 | 
			
		||||
        // x,y expected to be in node-co-ordinate space
 | 
			
		||||
        var candidateGroups = {};
 | 
			
		||||
        for (var i=0;i<activeGroups.length;i++) {
 | 
			
		||||
            var g = activeGroups[i];
 | 
			
		||||
@@ -3594,31 +3667,6 @@ RED.view = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function errorBadgeMouseEnter(e) {
 | 
			
		||||
        var d = this.__data__;
 | 
			
		||||
        if (d.validationErrors && d.validationErrors.length > 0) {
 | 
			
		||||
            clearTimeout(portLabelHoverTimeout);
 | 
			
		||||
            var node = this;
 | 
			
		||||
            portLabelHoverTimeout = setTimeout(function() {
 | 
			
		||||
                var pos = getElementPosition(node);
 | 
			
		||||
                portLabelHoverTimeout = null;
 | 
			
		||||
                portLabelHover = showTooltip(
 | 
			
		||||
                    (pos[0]),
 | 
			
		||||
                    (pos[1]),
 | 
			
		||||
                    RED._("editor.errors.invalidProperties")+"\n  - "+d.validationErrors.join("\n    - "),
 | 
			
		||||
                    "top"
 | 
			
		||||
                );
 | 
			
		||||
            },500);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function errorBadgeMouseLeave() {
 | 
			
		||||
        clearTimeout(portLabelHoverTimeout);
 | 
			
		||||
        if (portLabelHover) {
 | 
			
		||||
            portLabelHover.remove();
 | 
			
		||||
            portLabelHover = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function redrawStatus(d,nodeEl) {
 | 
			
		||||
        if (d.z !== RED.workspaces.active()) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -3627,7 +3675,11 @@ RED.view = (function() {
 | 
			
		||||
            nodeEl = document.getElementById(d.id);
 | 
			
		||||
        }
 | 
			
		||||
        if (nodeEl) {
 | 
			
		||||
            if (!showStatus || !d.status) {
 | 
			
		||||
            // Do not show node status if:
 | 
			
		||||
            // - global flag set
 | 
			
		||||
            // - node has no status
 | 
			
		||||
            // - node is disabled
 | 
			
		||||
            if (!showStatus || !d.status || d.d === true) {
 | 
			
		||||
                nodeEl.__statusGroup__.style.display = "none";
 | 
			
		||||
            } else {
 | 
			
		||||
                nodeEl.__statusGroup__.style.display = "inline";
 | 
			
		||||
@@ -3786,13 +3838,14 @@ RED.view = (function() {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var node = nodeLayer.selectAll(".red-ui-flow-node-group").data(activeNodes,function(d){return d.id});
 | 
			
		||||
            node.exit().remove();
 | 
			
		||||
            node.exit().each(function(d,i) {
 | 
			
		||||
                RED.hooks.trigger("viewRemoveNode",{node:d,el:this})
 | 
			
		||||
            }).remove();
 | 
			
		||||
 | 
			
		||||
            var nodeEnter = node.enter().insert("svg:g")
 | 
			
		||||
                .attr("class", "red-ui-flow-node red-ui-flow-node-group")
 | 
			
		||||
                .classed("red-ui-flow-subflow", activeSubflow != null);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            nodeEnter.each(function(d,i) {
 | 
			
		||||
                this.__outputs__ = [];
 | 
			
		||||
                this.__inputs__ = [];
 | 
			
		||||
@@ -3933,33 +3986,19 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
                nodeContents.appendChild(statusEl);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var changeBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g");
 | 
			
		||||
                changeBadgeG.setAttribute("class","red-ui-flow-node-changed hide");
 | 
			
		||||
                changeBadgeG.setAttribute("transform","translate(20, -2)");
 | 
			
		||||
                node[0][0].__changeBadge__ = changeBadgeG;
 | 
			
		||||
                var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle");
 | 
			
		||||
                changeBadge.setAttribute("r",5);
 | 
			
		||||
                changeBadgeG.appendChild(changeBadge);
 | 
			
		||||
                nodeContents.appendChild(changeBadgeG);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var errorBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g");
 | 
			
		||||
                errorBadgeG.setAttribute("class","red-ui-flow-node-error hide");
 | 
			
		||||
                errorBadgeG.setAttribute("transform","translate(0, -2)");
 | 
			
		||||
                node[0][0].__errorBadge__ = errorBadgeG;
 | 
			
		||||
                var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
 | 
			
		||||
                errorBadge.setAttribute("d","M -5,4 l 10,0 -5,-8 z");
 | 
			
		||||
                errorBadgeG.appendChild(errorBadge);
 | 
			
		||||
                errorBadge.__data__ = d;
 | 
			
		||||
                errorBadge.addEventListener("mouseenter", errorBadgeMouseEnter);
 | 
			
		||||
                errorBadge.addEventListener("mouseleave", errorBadgeMouseLeave);
 | 
			
		||||
                nodeContents.appendChild(errorBadgeG);
 | 
			
		||||
 | 
			
		||||
                node[0][0].appendChild(nodeContents);
 | 
			
		||||
 | 
			
		||||
                RED.hooks.trigger("viewAddNode",{node:d,el:this})
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var nodesReordered = false;
 | 
			
		||||
            node.each(function(d,i) {
 | 
			
		||||
                if (d._reordered) {
 | 
			
		||||
                    nodesReordered = true;
 | 
			
		||||
                    delete d._reordered;
 | 
			
		||||
                }
 | 
			
		||||
                if (d.dirty) {
 | 
			
		||||
                    var self = this;
 | 
			
		||||
                    var thisNode = d3.select(this);
 | 
			
		||||
 | 
			
		||||
                    var isLink = (d.type === "link in" || d.type === "link out")
 | 
			
		||||
@@ -3971,10 +4010,10 @@ RED.view = (function() {
 | 
			
		||||
                    var labelParts;
 | 
			
		||||
                    if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label || this.__outputs__.length !== d.outputs) {
 | 
			
		||||
                        labelParts = getLabelParts(label, "red-ui-flow-node-label");
 | 
			
		||||
                        this.__label__ = label;
 | 
			
		||||
                        if (labelParts.lines.length !== this.__labelLineCount__) {
 | 
			
		||||
                        if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
 | 
			
		||||
                            d.resize = true;
 | 
			
		||||
                        }
 | 
			
		||||
                        this.__label__ = label;
 | 
			
		||||
                        this.__labelLineCount__ = labelParts.lines.length;
 | 
			
		||||
 | 
			
		||||
                        if (hideLabel) {
 | 
			
		||||
@@ -4070,7 +4109,15 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
                        var inputPorts = thisNode.selectAll(".red-ui-flow-port-input");
 | 
			
		||||
                        if ((!isLink || (showAllLinkPorts === -1 && !activeLinkNodes[d.id])) && d.inputs === 0 && !inputPorts.empty()) {
 | 
			
		||||
                            inputPorts.remove();
 | 
			
		||||
                            inputPorts.each(function(d,i) {
 | 
			
		||||
                                RED.hooks.trigger("viewRemovePort",{
 | 
			
		||||
                                    node:d,
 | 
			
		||||
                                    el:self,
 | 
			
		||||
                                    port:d3.select(this)[0][0],
 | 
			
		||||
                                    portType: "input",
 | 
			
		||||
                                    portIndex: 0
 | 
			
		||||
                                })
 | 
			
		||||
                            }).remove();
 | 
			
		||||
                        } else if (((isLink && (showAllLinkPorts===PORT_TYPE_INPUT||activeLinkNodes[d.id]))|| d.inputs === 1) && inputPorts.empty()) {
 | 
			
		||||
                            var inputGroup = thisNode.append("g").attr("class","red-ui-flow-port-input");
 | 
			
		||||
                            var inputGroupPorts;
 | 
			
		||||
@@ -4083,16 +4130,21 @@ RED.view = (function() {
 | 
			
		||||
                            } else {
 | 
			
		||||
                                inputGroupPorts = inputGroup.append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
 | 
			
		||||
                            }
 | 
			
		||||
                            inputGroup[0][0].__port__ = inputGroupPorts[0][0];
 | 
			
		||||
                            inputGroupPorts[0][0].__data__ = this.__data__;
 | 
			
		||||
                            inputGroupPorts[0][0].__portType__ = PORT_TYPE_INPUT;
 | 
			
		||||
                            inputGroupPorts[0][0].__portIndex__ = 0;
 | 
			
		||||
                            inputGroupPorts.on("mousedown",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);})
 | 
			
		||||
                                .on("touchstart",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();})
 | 
			
		||||
                                .on("mouseup",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);} )
 | 
			
		||||
                                .on("touchend",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
 | 
			
		||||
                                .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
 | 
			
		||||
                                .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
 | 
			
		||||
                            RED.hooks.trigger("viewAddPort",{node:d,el: this, port: inputGroup[0][0], portType: "input", portIndex: 0})
 | 
			
		||||
                        }
 | 
			
		||||
                        var numOutputs = d.outputs;
 | 
			
		||||
                        if (isLink && d.type === "link out") {
 | 
			
		||||
                            if (showAllLinkPorts===PORT_TYPE_OUTPUT || activeLinkNodes[d.id]) {
 | 
			
		||||
                            if (d.mode !== "return" && (showAllLinkPorts===PORT_TYPE_OUTPUT || activeLinkNodes[d.id])) {
 | 
			
		||||
                                numOutputs = 1;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                numOutputs = 0;
 | 
			
		||||
@@ -4103,6 +4155,13 @@ RED.view = (function() {
 | 
			
		||||
                        // Remove extra ports
 | 
			
		||||
                        while (this.__outputs__.length > numOutputs) {
 | 
			
		||||
                            var port = this.__outputs__.pop();
 | 
			
		||||
                            RED.hooks.trigger("viewRemovePort",{
 | 
			
		||||
                                node:d,
 | 
			
		||||
                                el:this,
 | 
			
		||||
                                port:port,
 | 
			
		||||
                                portType: "output",
 | 
			
		||||
                                portIndex: this.__outputs__.length
 | 
			
		||||
                            })
 | 
			
		||||
                            port.remove();
 | 
			
		||||
                        }
 | 
			
		||||
                        for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) {
 | 
			
		||||
@@ -4126,6 +4185,7 @@ RED.view = (function() {
 | 
			
		||||
                                    portPort.setAttribute("class","red-ui-flow-port");
 | 
			
		||||
                                }
 | 
			
		||||
                                portGroup.appendChild(portPort);
 | 
			
		||||
                                portGroup.__port__ = portPort;
 | 
			
		||||
                                portPort.__data__ = this.__data__;
 | 
			
		||||
                                portPort.__portType__ = PORT_TYPE_OUTPUT;
 | 
			
		||||
                                portPort.__portIndex__ = portIndex;
 | 
			
		||||
@@ -4138,6 +4198,7 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
                                this.appendChild(portGroup);
 | 
			
		||||
                                this.__outputs__.push(portGroup);
 | 
			
		||||
                                RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex})
 | 
			
		||||
                            } else {
 | 
			
		||||
                                portGroup = this.__outputs__[portIndex];
 | 
			
		||||
                            }
 | 
			
		||||
@@ -4174,10 +4235,10 @@ RED.view = (function() {
 | 
			
		||||
                                                                 );
 | 
			
		||||
                            faIcon.attr("y",(d.h+13)/2);
 | 
			
		||||
                        }
 | 
			
		||||
                        this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)");
 | 
			
		||||
                        this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved));
 | 
			
		||||
                        this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)");
 | 
			
		||||
                        this.__errorBadge__.classList.toggle("hide", d.valid);
 | 
			
		||||
                        // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)");
 | 
			
		||||
                        // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved));
 | 
			
		||||
                        // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)");
 | 
			
		||||
                        // this.__errorBadge__.classList.toggle("hide", d.valid);
 | 
			
		||||
 | 
			
		||||
                        thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) {
 | 
			
		||||
                            var port = d3.select(this);
 | 
			
		||||
@@ -4240,7 +4301,16 @@ RED.view = (function() {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                RED.hooks.trigger("viewRedrawNode",{node:d,el:this})
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (nodesReordered) {
 | 
			
		||||
                node.sort(function(a,b) {
 | 
			
		||||
                    return a._index - b._index;
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var link = linkLayer.selectAll(".red-ui-flow-link").data(
 | 
			
		||||
                activeLinks,
 | 
			
		||||
                function(d) {
 | 
			
		||||
@@ -4355,6 +4425,9 @@ RED.view = (function() {
 | 
			
		||||
                            n.selected = true;
 | 
			
		||||
                            n.dirty = true;
 | 
			
		||||
                            movingSet.add(n);
 | 
			
		||||
                            if (targets.length === 1) {
 | 
			
		||||
                                RED.view.reveal(n.id);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        updateSelection();
 | 
			
		||||
                        redraw();
 | 
			
		||||
@@ -4479,7 +4552,7 @@ RED.view = (function() {
 | 
			
		||||
                    if (a._root === b._root) {
 | 
			
		||||
                        return a._depth - b._depth;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return a._root.localeCompare(b._root);
 | 
			
		||||
                        return a._index - b._index;
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
@@ -4891,7 +4964,7 @@ RED.view = (function() {
 | 
			
		||||
                    counts.push(RED._("clipboard.group",{count:newGroupCount}));
 | 
			
		||||
                }
 | 
			
		||||
                if (newConfigNodeCount > 0) {
 | 
			
		||||
                    counts.push(RED._("clipboard.configNode",{count:newNodeCount}));
 | 
			
		||||
                    counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount}));
 | 
			
		||||
                }
 | 
			
		||||
                if (new_subflows.length > 0) {
 | 
			
		||||
                    counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
 | 
			
		||||
@@ -4963,6 +5036,7 @@ RED.view = (function() {
 | 
			
		||||
                            delete node.d;
 | 
			
		||||
                        }
 | 
			
		||||
                        node.dirty = true;
 | 
			
		||||
                        node.dirtyStatus = true;
 | 
			
		||||
                        node.changed = true;
 | 
			
		||||
                        RED.events.emit("nodes:change",node);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -5014,6 +5088,30 @@ RED.view = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        return selection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function calculateNodeDimensions(node) {
 | 
			
		||||
        var result = [node_width,node_height];
 | 
			
		||||
        try {
 | 
			
		||||
        var isLink = (node.type === "link in" || node.type === "link out")
 | 
			
		||||
        var hideLabel = node.hasOwnProperty('l')?!node.l : isLink;
 | 
			
		||||
        var label = RED.utils.getNodeLabel(node, node.type);
 | 
			
		||||
        var labelParts = getLabelParts(label, "red-ui-flow-node-label");
 | 
			
		||||
        if (hideLabel) {
 | 
			
		||||
            result[1] = Math.max(node_height,(node.outputs || 0) * 15);
 | 
			
		||||
        } else {
 | 
			
		||||
            result[1] = Math.max(6+24*labelParts.lines.length,(node.outputs || 0) * 15, 30);
 | 
			
		||||
        }
 | 
			
		||||
        if (hideLabel) {
 | 
			
		||||
            result[0] = node_height;
 | 
			
		||||
        } else {
 | 
			
		||||
            result[0] = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(node._def.inputs>0?7:0))/20)) );
 | 
			
		||||
        }
 | 
			
		||||
    }catch(err) {
 | 
			
		||||
        console.log("Error",node);
 | 
			
		||||
    }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        state:function(state) {
 | 
			
		||||
@@ -5079,6 +5177,9 @@ RED.view = (function() {
 | 
			
		||||
            return scaleFactor;
 | 
			
		||||
        },
 | 
			
		||||
        getLinksAtPoint: function(x,y) {
 | 
			
		||||
            // x,y must be in SVG co-ordinate space
 | 
			
		||||
            // if they come from a node.x/y, they will need to be scaled using
 | 
			
		||||
            // scaleFactor first.
 | 
			
		||||
            var result = [];
 | 
			
		||||
            var links = outer.selectAll(".red-ui-flow-link-background")[0];
 | 
			
		||||
            for (var i=0;i<links.length;i++) {
 | 
			
		||||
@@ -5242,6 +5343,9 @@ RED.view = (function() {
 | 
			
		||||
            return clipboard
 | 
			
		||||
        },
 | 
			
		||||
        redrawStatus: redrawStatus,
 | 
			
		||||
        showQuickAddDialog:showQuickAddDialog
 | 
			
		||||
        showQuickAddDialog:showQuickAddDialog,
 | 
			
		||||
        calculateNodeDimensions: calculateNodeDimensions,
 | 
			
		||||
        getElementPosition:getElementPosition,
 | 
			
		||||
        showTooltip:showTooltip
 | 
			
		||||
    };
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,21 +21,46 @@ RED.workspaces = (function() {
 | 
			
		||||
    var workspaceIndex = 0;
 | 
			
		||||
 | 
			
		||||
    var viewStack = [];
 | 
			
		||||
    var hideStack = [];
 | 
			
		||||
    var viewStackPos = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function addToViewStack(id) {
 | 
			
		||||
        if (viewStackPos !== viewStack.length) {
 | 
			
		||||
            viewStack.splice(viewStackPos);
 | 
			
		||||
        }
 | 
			
		||||
        viewStack.push(id);
 | 
			
		||||
        viewStackPos = viewStack.length;
 | 
			
		||||
        // console.warn("addToViewStack",id,viewStack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeFromHideStack(id) {
 | 
			
		||||
        hideStack = hideStack.filter(function(v) {
 | 
			
		||||
            if (v === id) {
 | 
			
		||||
                return false;
 | 
			
		||||
            } else if (Array.isArray(v)) {
 | 
			
		||||
                var i = v.indexOf(id);
 | 
			
		||||
                if (i > -1) {
 | 
			
		||||
                    v.splice(i,1);
 | 
			
		||||
                }
 | 
			
		||||
                if (v.length === 0) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addWorkspace(ws,skipHistoryEntry,targetIndex) {
 | 
			
		||||
        if (ws) {
 | 
			
		||||
            if (!ws.closeable) {
 | 
			
		||||
                ws.hideable = true;
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.addTab(ws,targetIndex);
 | 
			
		||||
 | 
			
		||||
            var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
            if (hiddenTabs[ws.id]) {
 | 
			
		||||
                workspace_tabs.hideTab(ws.id);
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.resize();
 | 
			
		||||
        } else {
 | 
			
		||||
            var tabId = RED.nodes.id();
 | 
			
		||||
@@ -43,7 +68,15 @@ RED.workspaces = (function() {
 | 
			
		||||
                workspaceIndex += 1;
 | 
			
		||||
            } while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
 | 
			
		||||
 | 
			
		||||
            ws = {type:"tab",id:tabId,disabled: false,info:"",label:RED._('workspace.defaultName',{number:workspaceIndex})};
 | 
			
		||||
            ws = {
 | 
			
		||||
                type: "tab",
 | 
			
		||||
                id: tabId,
 | 
			
		||||
                disabled: false,
 | 
			
		||||
                info: "",
 | 
			
		||||
                label: RED._('workspace.defaultName',{number:workspaceIndex}),
 | 
			
		||||
                env: [],
 | 
			
		||||
                hideable: true
 | 
			
		||||
            };
 | 
			
		||||
            RED.nodes.addWorkspace(ws,targetIndex);
 | 
			
		||||
            workspace_tabs.addTab(ws,targetIndex);
 | 
			
		||||
            workspace_tabs.activateTab(tabId);
 | 
			
		||||
@@ -55,6 +88,7 @@ RED.workspaces = (function() {
 | 
			
		||||
        RED.view.focus();
 | 
			
		||||
        return ws;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function deleteWorkspace(ws) {
 | 
			
		||||
        if (workspaceTabCount === 1) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -78,165 +112,9 @@ RED.workspaces = (function() {
 | 
			
		||||
            if (subflow) {
 | 
			
		||||
                RED.editor.editSubflow(subflow);
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        } else {
 | 
			
		||||
            RED.editor.editFlow(workspace);
 | 
			
		||||
        }
 | 
			
		||||
        RED.view.state(RED.state.EDITING);
 | 
			
		||||
        var tabflowEditor;
 | 
			
		||||
        var trayOptions = {
 | 
			
		||||
            title: RED._("workspace.editFlow",{name:RED.utils.sanitize(workspace.label)}),
 | 
			
		||||
            buttons: [
 | 
			
		||||
                {
 | 
			
		||||
                    id: "node-dialog-delete",
 | 
			
		||||
                    class: 'leftButton'+((workspaceTabCount === 1)?" disabled":""),
 | 
			
		||||
                    text: RED._("common.label.delete"), //'<i class="fa fa-trash"></i>',
 | 
			
		||||
                    click: function() {
 | 
			
		||||
                        deleteWorkspace(workspace);
 | 
			
		||||
                        RED.tray.close();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id: "node-dialog-cancel",
 | 
			
		||||
                    text: RED._("common.label.cancel"),
 | 
			
		||||
                    click: function() {
 | 
			
		||||
                        RED.tray.close();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id: "node-dialog-ok",
 | 
			
		||||
                    class: "primary",
 | 
			
		||||
                    text: RED._("common.label.done"),
 | 
			
		||||
                    click: function() {
 | 
			
		||||
                        var label = $( "#node-input-name" ).val();
 | 
			
		||||
                        var changed = false;
 | 
			
		||||
                        var changes = {};
 | 
			
		||||
                        if (workspace.label != label) {
 | 
			
		||||
                            changes.label = workspace.label;
 | 
			
		||||
                            changed = true;
 | 
			
		||||
                            workspace.label = label;
 | 
			
		||||
                            workspace_tabs.renameTab(workspace.id,label);
 | 
			
		||||
                        }
 | 
			
		||||
                        var disabled = $("#node-input-disabled").prop("checked");
 | 
			
		||||
                        if (workspace.disabled !== disabled) {
 | 
			
		||||
                            changes.disabled = workspace.disabled;
 | 
			
		||||
                            changed = true;
 | 
			
		||||
                            workspace.disabled = disabled;
 | 
			
		||||
                        }
 | 
			
		||||
                        var info = tabflowEditor.getValue();
 | 
			
		||||
                        if (workspace.info !== info) {
 | 
			
		||||
                            changes.info = workspace.info;
 | 
			
		||||
                            changed = true;
 | 
			
		||||
                            workspace.info = info;
 | 
			
		||||
                        }
 | 
			
		||||
                        $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
 | 
			
		||||
                        $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
 | 
			
		||||
 | 
			
		||||
                        if (changed) {
 | 
			
		||||
                            var historyEvent = {
 | 
			
		||||
                                t: "edit",
 | 
			
		||||
                                changes:changes,
 | 
			
		||||
                                node: workspace,
 | 
			
		||||
                                dirty: RED.nodes.dirty()
 | 
			
		||||
                            }
 | 
			
		||||
                            workspace.changed = true;
 | 
			
		||||
                            RED.history.push(historyEvent);
 | 
			
		||||
                            RED.nodes.dirty(true);
 | 
			
		||||
                            RED.sidebar.config.refresh();
 | 
			
		||||
                            if (changes.hasOwnProperty('disabled')) {
 | 
			
		||||
                                RED.nodes.eachNode(function(n) {
 | 
			
		||||
                                    if (n.z === workspace.id) {
 | 
			
		||||
                                        n.dirty = true;
 | 
			
		||||
                                    }
 | 
			
		||||
                                });
 | 
			
		||||
                                RED.view.redraw();
 | 
			
		||||
                            }
 | 
			
		||||
                            RED.events.emit("flows:change",workspace);
 | 
			
		||||
                        }
 | 
			
		||||
                        RED.tray.close();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            resize: function(dimensions) {
 | 
			
		||||
                var rows = $("#dialog-form>div:not(.node-text-editor-row)");
 | 
			
		||||
                var editorRow = $("#dialog-form>div.node-text-editor-row");
 | 
			
		||||
                var height = $("#dialog-form").height();
 | 
			
		||||
                for (var i=0; i<rows.size(); i++) {
 | 
			
		||||
                    height -= $(rows[i]).outerHeight(true);
 | 
			
		||||
                }
 | 
			
		||||
                height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
 | 
			
		||||
                $(".node-text-editor").css("height",height+"px");
 | 
			
		||||
                tabflowEditor.resize();
 | 
			
		||||
            },
 | 
			
		||||
            open: function(tray) {
 | 
			
		||||
                var trayFooter = tray.find(".red-ui-tray-footer");
 | 
			
		||||
                var trayBody = tray.find('.red-ui-tray-body');
 | 
			
		||||
                var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
 | 
			
		||||
 | 
			
		||||
                var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
 | 
			
		||||
                $('<div class="form-row">'+
 | 
			
		||||
                    '<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
 | 
			
		||||
                    '<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
 | 
			
		||||
                '</div>').appendTo(dialogForm);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                if (!workspace.hasOwnProperty("disabled")) {
 | 
			
		||||
                    workspace.disabled = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $('<input id="node-input-disabled" type="checkbox">').prop("checked",workspace.disabled).appendTo(trayFooterLeft).toggleButton({
 | 
			
		||||
                    enabledIcon: "fa-circle-thin",
 | 
			
		||||
                    disabledIcon: "fa-ban",
 | 
			
		||||
                    invertState: true
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var row = $('<div class="form-row node-text-editor-row">'+
 | 
			
		||||
                    '<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
 | 
			
		||||
                    '<div style="min-height:250px;" class="node-text-editor" id="node-input-info"></div>'+
 | 
			
		||||
                '</div>').appendTo(dialogForm);
 | 
			
		||||
                tabflowEditor = RED.editor.createEditor({
 | 
			
		||||
                    id: 'node-input-info',
 | 
			
		||||
                    mode: 'ace/mode/markdown',
 | 
			
		||||
                    value: ""
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                $('#node-info-input-info-expand').on("click", function(e) {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    var value = tabflowEditor.getValue();
 | 
			
		||||
                    RED.editor.editMarkdown({
 | 
			
		||||
                        value: value,
 | 
			
		||||
                        width: "Infinity",
 | 
			
		||||
                        cursor: tabflowEditor.getCursorPosition(),
 | 
			
		||||
                        complete: function(v,cursor) {
 | 
			
		||||
                            tabflowEditor.setValue(v, -1);
 | 
			
		||||
                            tabflowEditor.gotoLine(cursor.row+1,cursor.column,false);
 | 
			
		||||
                            setTimeout(function() {
 | 
			
		||||
                                tabflowEditor.focus();
 | 
			
		||||
                            },300);
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                $('<input type="text" style="display: none;" />').prependTo(dialogForm);
 | 
			
		||||
                dialogForm.on("submit", function(e) { e.preventDefault();});
 | 
			
		||||
                $("#node-input-name").val(workspace.label);
 | 
			
		||||
                RED.text.bidi.prepareInput($("#node-input-name"));
 | 
			
		||||
                tabflowEditor.getSession().setValue(workspace.info || "", -1);
 | 
			
		||||
                dialogForm.i18n();
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
                if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
 | 
			
		||||
                    RED.view.state(RED.state.DEFAULT);
 | 
			
		||||
                }
 | 
			
		||||
                var selection = RED.view.selection();
 | 
			
		||||
                if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) {
 | 
			
		||||
                    RED.sidebar.info.refresh(workspace);
 | 
			
		||||
                }
 | 
			
		||||
                tabflowEditor.destroy();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        RED.tray.show(trayOptions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -249,11 +127,18 @@ RED.workspaces = (function() {
 | 
			
		||||
                var event = {
 | 
			
		||||
                    old: activeWorkspace
 | 
			
		||||
                }
 | 
			
		||||
                activeWorkspace = tab.id;
 | 
			
		||||
                if (tab) {
 | 
			
		||||
                    $("#red-ui-workspace-chart").show();
 | 
			
		||||
                    activeWorkspace = tab.id;
 | 
			
		||||
                    window.location.hash = 'flow/'+tab.id;
 | 
			
		||||
                    $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!tab.disabled);
 | 
			
		||||
                    } else {
 | 
			
		||||
                    $("#red-ui-workspace-chart").hide();
 | 
			
		||||
                    activeWorkspace = 0;
 | 
			
		||||
                    window.location.hash = '';
 | 
			
		||||
                }
 | 
			
		||||
                event.workspace = activeWorkspace;
 | 
			
		||||
                RED.events.emit("workspace:change",event);
 | 
			
		||||
                window.location.hash = 'flow/'+tab.id;
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!tab.disabled);
 | 
			
		||||
                RED.sidebar.config.refresh();
 | 
			
		||||
                RED.view.focus();
 | 
			
		||||
            },
 | 
			
		||||
@@ -278,7 +163,7 @@ RED.workspaces = (function() {
 | 
			
		||||
                if (tab.disabled) {
 | 
			
		||||
                    $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled');
 | 
			
		||||
                }
 | 
			
		||||
                RED.menu.setDisabled("menu-item-workspace-delete",workspaceTabCount <= 1);
 | 
			
		||||
                RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
 | 
			
		||||
                if (workspaceTabCount === 1) {
 | 
			
		||||
                    showWorkspace();
 | 
			
		||||
                }
 | 
			
		||||
@@ -286,14 +171,23 @@ RED.workspaces = (function() {
 | 
			
		||||
            onremove: function(tab) {
 | 
			
		||||
                if (tab.type === "tab") {
 | 
			
		||||
                    workspaceTabCount--;
 | 
			
		||||
                } else {
 | 
			
		||||
                    hideStack.push(tab.id);
 | 
			
		||||
                }
 | 
			
		||||
                RED.menu.setDisabled("menu-item-workspace-delete",workspaceTabCount <= 1);
 | 
			
		||||
                RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
 | 
			
		||||
                if (workspaceTabCount === 0) {
 | 
			
		||||
                    hideWorkspace();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            onreorder: function(oldOrder, newOrder) {
 | 
			
		||||
                RED.history.push({t:'reorder',order:oldOrder,dirty:RED.nodes.dirty()});
 | 
			
		||||
                RED.history.push({
 | 
			
		||||
                    t:'reorder',
 | 
			
		||||
                    workspaces: {
 | 
			
		||||
                        from:oldOrder,
 | 
			
		||||
                        to:newOrder
 | 
			
		||||
                    },
 | 
			
		||||
                    dirty:RED.nodes.dirty()
 | 
			
		||||
                });
 | 
			
		||||
                RED.nodes.dirty(true);
 | 
			
		||||
                setWorkspaceOrder(newOrder);
 | 
			
		||||
            },
 | 
			
		||||
@@ -312,12 +206,76 @@ RED.workspaces = (function() {
 | 
			
		||||
                    $(".red-ui-sidebar-shade").show();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            onhide: function(tab) {
 | 
			
		||||
                hideStack.push(tab.id);
 | 
			
		||||
                RED.events.emit("workspace:hide",{workspace: tab.id})
 | 
			
		||||
            },
 | 
			
		||||
            onshow: function(tab) {
 | 
			
		||||
                removeFromHideStack(tab.id);
 | 
			
		||||
                RED.events.emit("workspace:show",{workspace: tab.id})
 | 
			
		||||
            },
 | 
			
		||||
            minimumActiveTabWidth: 150,
 | 
			
		||||
            scrollable: true,
 | 
			
		||||
            addButton: "core:add-flow",
 | 
			
		||||
            addButtonCaption: RED._("workspace.addFlow"),
 | 
			
		||||
            searchButton: "core:list-flows",
 | 
			
		||||
            searchButtonCaption: RED._("workspace.listFlows")
 | 
			
		||||
            menu: function() {
 | 
			
		||||
                var menuItems = [
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-search-flows",
 | 
			
		||||
                        label: RED._("workspace.listFlows"),
 | 
			
		||||
                        onselect: "core:list-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-search-subflows",
 | 
			
		||||
                        label: RED._("workspace.listSubflows"),
 | 
			
		||||
                        onselect: "core:list-subflows"
 | 
			
		||||
                    },
 | 
			
		||||
                    null,
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-flow",
 | 
			
		||||
                        label: RED._("workspace.addFlow"),
 | 
			
		||||
                        onselect: "core:add-flow"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-flow-right",
 | 
			
		||||
                        label: RED._("workspace.addFlowToRight"),
 | 
			
		||||
                        onselect: "core:add-flow-to-right"
 | 
			
		||||
                    },
 | 
			
		||||
                    null,
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-flows",
 | 
			
		||||
                        label: RED._("workspace.hideFlow"),
 | 
			
		||||
                        onselect: "core:hide-flow"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-other-flows",
 | 
			
		||||
                        label: RED._("workspace.hideOtherFlows"),
 | 
			
		||||
                        onselect: "core:hide-other-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-show-all-flows",
 | 
			
		||||
                        label: RED._("workspace.showAllFlows"),
 | 
			
		||||
                        onselect: "core:show-all-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-all-flows",
 | 
			
		||||
                        label: RED._("workspace.hideAllFlows"),
 | 
			
		||||
                        onselect: "core:hide-all-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-show-last-flow",
 | 
			
		||||
                        label: RED._("workspace.showLastHiddenFlow"),
 | 
			
		||||
                        onselect: "core:show-last-hidden-flow"
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
                if (hideStack.length > 0) {
 | 
			
		||||
                    menuItems.unshift({
 | 
			
		||||
                        label: RED._("workspace.hiddenFlows",{count: hideStack.length}),
 | 
			
		||||
                        onselect: "core:list-hidden-flows"
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                return menuItems;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        workspaceTabCount = 0;
 | 
			
		||||
    }
 | 
			
		||||
@@ -368,15 +326,104 @@ RED.workspaces = (function() {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
 | 
			
		||||
        RED.actions.add("core:add-flow-to-right",function(opts) { addWorkspace(undefined,undefined,workspace_tabs.activeIndex()+1)});
 | 
			
		||||
        RED.actions.add("core:edit-flow",editWorkspace);
 | 
			
		||||
        RED.actions.add("core:remove-flow",removeWorkspace);
 | 
			
		||||
        RED.actions.add("core:enable-flow",enableWorkspace);
 | 
			
		||||
        RED.actions.add("core:disable-flow",disableWorkspace);
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:hide-flow", function() {
 | 
			
		||||
            var selection = workspace_tabs.selection();
 | 
			
		||||
            if (selection.length === 0) {
 | 
			
		||||
                selection = [{id:activeWorkspace}]
 | 
			
		||||
            }
 | 
			
		||||
            var hiddenTabs = [];
 | 
			
		||||
            selection.forEach(function(ws) {
 | 
			
		||||
                RED.workspaces.hide(ws.id);
 | 
			
		||||
                hideStack.pop();
 | 
			
		||||
                hiddenTabs.push(ws.id);
 | 
			
		||||
            })
 | 
			
		||||
            if (hiddenTabs.length > 0) {
 | 
			
		||||
                hideStack.push(hiddenTabs);
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.clearSelection();
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:hide-other-flows", function() {
 | 
			
		||||
            var selection = workspace_tabs.selection();
 | 
			
		||||
            if (selection.length === 0) {
 | 
			
		||||
                selection = [{id:activeWorkspace}]
 | 
			
		||||
            }
 | 
			
		||||
            var selected = new Set(selection.map(function(ws) { return ws.id }))
 | 
			
		||||
 | 
			
		||||
            var currentTabs = workspace_tabs.listTabs();
 | 
			
		||||
            var hiddenTabs = [];
 | 
			
		||||
            currentTabs.forEach(function(id) {
 | 
			
		||||
                if (!selected.has(id)) {
 | 
			
		||||
                    RED.workspaces.hide(id);
 | 
			
		||||
                    hideStack.pop();
 | 
			
		||||
                    hiddenTabs.push(id);
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            if (hiddenTabs.length > 0) {
 | 
			
		||||
                hideStack.push(hiddenTabs);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:hide-all-flows", function() {
 | 
			
		||||
            var currentTabs = workspace_tabs.listTabs();
 | 
			
		||||
            currentTabs.forEach(function(id) {
 | 
			
		||||
                RED.workspaces.hide(id);
 | 
			
		||||
                hideStack.pop();
 | 
			
		||||
            })
 | 
			
		||||
            if (currentTabs.length > 0) {
 | 
			
		||||
                hideStack.push(currentTabs);
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.clearSelection();
 | 
			
		||||
        })
 | 
			
		||||
        RED.actions.add("core:show-all-flows", function() {
 | 
			
		||||
            var currentTabs = workspace_tabs.listTabs();
 | 
			
		||||
            currentTabs.forEach(function(id) {
 | 
			
		||||
                RED.workspaces.show(id, null, true)
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
        // RED.actions.add("core:toggle-flows", function() {
 | 
			
		||||
        //     var currentTabs = workspace_tabs.listTabs();
 | 
			
		||||
        //     var visibleCount = workspace_tabs.count();
 | 
			
		||||
        //     currentTabs.forEach(function(id) {
 | 
			
		||||
        //         if (visibleCount === 0) {
 | 
			
		||||
        //             RED.workspaces.show(id)
 | 
			
		||||
        //         } else {
 | 
			
		||||
        //             RED.workspaces.hide(id)
 | 
			
		||||
        //         }
 | 
			
		||||
        //     })
 | 
			
		||||
        // })
 | 
			
		||||
        RED.actions.add("core:show-last-hidden-flow", function() {
 | 
			
		||||
            var id = hideStack.pop();
 | 
			
		||||
            if (id) {
 | 
			
		||||
                if (typeof id === 'string') {
 | 
			
		||||
                    RED.workspaces.show(id);
 | 
			
		||||
                } else {
 | 
			
		||||
                    var last = id.pop();
 | 
			
		||||
                    id.forEach(function(i) {
 | 
			
		||||
                        RED.workspaces.show(i, null, true);
 | 
			
		||||
                    })
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        RED.workspaces.show(last);
 | 
			
		||||
                    },150)
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        RED.actions.add("core:list-hidden-flows",function() {
 | 
			
		||||
            RED.actions.invoke("core:search","is:hidden ");
 | 
			
		||||
        })
 | 
			
		||||
        RED.actions.add("core:list-flows",function() {
 | 
			
		||||
            RED.actions.invoke("core:search","type:tab ");
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:list-subflows",function() {
 | 
			
		||||
            RED.actions.invoke("core:search","type:subflow ");
 | 
			
		||||
        })
 | 
			
		||||
        RED.actions.add("core:go-to-previous-location", function() {
 | 
			
		||||
            if (viewStackPos > 0) {
 | 
			
		||||
                if (viewStackPos === viewStack.length) {
 | 
			
		||||
@@ -392,8 +439,6 @@ RED.workspaces = (function() {
 | 
			
		||||
                RED.workspaces.show(viewStack[++viewStackPos],true);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        hideWorkspace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -416,7 +461,7 @@ RED.workspaces = (function() {
 | 
			
		||||
            var changes = { disabled: workspace.disabled };
 | 
			
		||||
            workspace.disabled = disabled;
 | 
			
		||||
            $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
 | 
			
		||||
            if (id === activeWorkspace) {
 | 
			
		||||
            if (!id || (id === activeWorkspace)) {
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
 | 
			
		||||
            }
 | 
			
		||||
            var historyEvent = {
 | 
			
		||||
@@ -445,7 +490,6 @@ RED.workspaces = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function removeWorkspace(ws) {
 | 
			
		||||
        if (!ws) {
 | 
			
		||||
            deleteWorkspace(RED.nodes.workspace(activeWorkspace));
 | 
			
		||||
@@ -474,7 +518,10 @@ RED.workspaces = (function() {
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
        add: addWorkspace,
 | 
			
		||||
        // remove: remove workspace without editor history etc
 | 
			
		||||
        remove: removeWorkspace,
 | 
			
		||||
        // delete: remove workspace and update editor history
 | 
			
		||||
        delete: deleteWorkspace,
 | 
			
		||||
        order: setWorkspaceOrder,
 | 
			
		||||
        edit: editWorkspace,
 | 
			
		||||
        contains: function(id) {
 | 
			
		||||
@@ -489,19 +536,45 @@ RED.workspaces = (function() {
 | 
			
		||||
        selection: function() {
 | 
			
		||||
            return workspace_tabs.selection();
 | 
			
		||||
        },
 | 
			
		||||
        show: function(id,skipStack) {
 | 
			
		||||
        hide: function(id) {
 | 
			
		||||
            if (!id) {
 | 
			
		||||
                id = activeWorkspace;
 | 
			
		||||
            }
 | 
			
		||||
            if (workspace_tabs.contains(id)) {
 | 
			
		||||
                workspace_tabs.hideTab(id);
 | 
			
		||||
                var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
                hiddenTabs[id] = true;
 | 
			
		||||
                RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isHidden: function(id) {
 | 
			
		||||
            return hideStack.includes(id)
 | 
			
		||||
        },
 | 
			
		||||
        show: function(id,skipStack,unhideOnly) {
 | 
			
		||||
            if (!workspace_tabs.contains(id)) {
 | 
			
		||||
                var sf = RED.nodes.subflow(id);
 | 
			
		||||
                if (sf) {
 | 
			
		||||
                    addWorkspace({type:"subflow",id:id,icon:"red/images/subflow_tab.svg",label:sf.name, closeable: true});
 | 
			
		||||
                    addWorkspace(
 | 
			
		||||
                        {type:"subflow",id:id,icon:"red/images/subflow_tab.svg",label:sf.name, closeable: true},
 | 
			
		||||
                        null,
 | 
			
		||||
                        workspace_tabs.activeIndex()+1
 | 
			
		||||
                    );
 | 
			
		||||
                    removeFromHideStack(id);
 | 
			
		||||
                } else {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!skipStack && activeWorkspace !== id) {
 | 
			
		||||
                addToViewStack(activeWorkspace)
 | 
			
		||||
            if (unhideOnly) {
 | 
			
		||||
                workspace_tabs.showTab(id);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!skipStack && activeWorkspace !== id) {
 | 
			
		||||
                    addToViewStack(activeWorkspace)
 | 
			
		||||
                }
 | 
			
		||||
                workspace_tabs.activateTab(id);
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.activateTab(id);
 | 
			
		||||
            var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
            delete hiddenTabs[id];
 | 
			
		||||
            RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
 | 
			
		||||
        },
 | 
			
		||||
        refresh: function() {
 | 
			
		||||
            RED.nodes.eachWorkspace(function(ws) {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,10 @@ RED.user = (function() {
 | 
			
		||||
            opts = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var dialog = $('<div id="node-dialog-login" class="hide">'+
 | 
			
		||||
                       '<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
 | 
			
		||||
                       '<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
 | 
			
		||||
                       '<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
 | 
			
		||||
        var dialog = $('<div id="node-dialog-login" class="hide" style="display: flex; align-items: flex-end;">'+
 | 
			
		||||
                       '<div style="width: 250px; flex-grow: 0;"><img id="node-dialog-login-image" src=""/></div>'+
 | 
			
		||||
                       '<div style="flex-grow: 1;">'+
 | 
			
		||||
                            '<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px; margin-left:20px;"></form>'+
 | 
			
		||||
                       '</div>'+
 | 
			
		||||
                       '</div>');
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +76,7 @@ RED.user = (function() {
 | 
			
		||||
                        }
 | 
			
		||||
                        row.appendTo("#node-dialog-login-fields");
 | 
			
		||||
                    }
 | 
			
		||||
                    $('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
 | 
			
		||||
                    $('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;color:var(--red-ui-text-color-error);" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
 | 
			
		||||
                        (opts.cancelable?'<a href="#" id="node-dialog-login-cancel" class="red-ui-button" style="margin-right: 20px;" tabIndex="'+(i+1)+'">'+RED._("common.label.cancel")+'</a>':'')+
 | 
			
		||||
                        '<input type="submit" id="node-dialog-login-submit" class="red-ui-button" style="width: auto;" tabIndex="'+(i+2)+'" value="'+RED._("user.login")+'"></div>').appendTo("#node-dialog-login-fields");
 | 
			
		||||
 | 
			
		||||
@@ -121,6 +121,24 @@ RED.user = (function() {
 | 
			
		||||
                    i = 0;
 | 
			
		||||
                    for (;i<data.prompts.length;i++) {
 | 
			
		||||
                        var field = data.prompts[i];
 | 
			
		||||
                        var sessionMessage = /[?&]session_message=(.*?)(?:$|&)/.exec(window.location.search);
 | 
			
		||||
                        if (sessionMessage) {
 | 
			
		||||
                            RED.sessionMessages = RED.sessionMessages || [];
 | 
			
		||||
                            RED.sessionMessages.push(sessionMessage[1]);
 | 
			
		||||
                            if (history.pushState) {
 | 
			
		||||
                                var newurl = window.location.protocol+"//"+window.location.host+window.location.pathname
 | 
			
		||||
                                window.history.replaceState({ path: newurl }, "", newurl);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                window.location.search = "";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (RED.sessionMessages) {
 | 
			
		||||
                            var sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
 | 
			
		||||
                            RED.sessionMessages.forEach(function (msg) {
 | 
			
		||||
                                $('<div>').css("color","var(--red-ui-text-color-error)").text(msg).appendTo(sessionMessages);
 | 
			
		||||
                            });
 | 
			
		||||
                            delete RED.sessionMessages;
 | 
			
		||||
                        }
 | 
			
		||||
                        var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
 | 
			
		||||
 | 
			
		||||
                        var loginButton = $('<a href="#" class="red-ui-button"></a>',{style: "padding: 10px"}).appendTo(row).on("click", function() {
 | 
			
		||||
@@ -152,7 +170,7 @@ RED.user = (function() {
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var loginImageSrc = data.image || "red/images/node-red-256.png";
 | 
			
		||||
                var loginImageSrc = data.image || "red/images/node-red-256.svg";
 | 
			
		||||
 | 
			
		||||
                $("#node-dialog-login-image").load(function() {
 | 
			
		||||
                    dialog.dialog("open");
 | 
			
		||||
 
 | 
			
		||||
@@ -149,7 +149,7 @@ body {
 | 
			
		||||
    .red-ui-font-code {
 | 
			
		||||
        font-family: $monospace-font;
 | 
			
		||||
        font-size: $primary-font-size;
 | 
			
		||||
        color: $info-text-code-color;
 | 
			
		||||
        color: $text-color-code;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -158,7 +158,7 @@ body {
 | 
			
		||||
        font-size: $primary-font-size;
 | 
			
		||||
        padding: 0px;
 | 
			
		||||
        margin: 1px;
 | 
			
		||||
        color: $info-text-code-color;
 | 
			
		||||
        color: $text-color-code;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,15 +48,22 @@ $tertiary-text-color: #aaa;//#90f;
 | 
			
		||||
// Heading text
 | 
			
		||||
$header-text-color: #444;//#f00;
 | 
			
		||||
 | 
			
		||||
$text-color-warning: #AD1625;
 | 
			
		||||
$text-color-green: #3a3;
 | 
			
		||||
$text-color-error: #AD1625;
 | 
			
		||||
$text-color-warning: #CAB200;
 | 
			
		||||
$text-color-success: #3a3;
 | 
			
		||||
$text-color-code: #AD1625;
 | 
			
		||||
$text-color-link: #0088cc;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
$primary-border-color: #bbbbbb;//#f00;
 | 
			
		||||
$secondary-border-color: #dddddd;//#0f0;
 | 
			
		||||
$tertiary-border-color: #ccc;//#00f;
 | 
			
		||||
 | 
			
		||||
$form-background: $secondary-background;
 | 
			
		||||
$border-color-error: #DF2935;
 | 
			
		||||
$border-color-warning: #FFCF70;
 | 
			
		||||
$border-color-success: #4B8400;
 | 
			
		||||
 | 
			
		||||
$form-background: $secondary-background;
 | 
			
		||||
$form-placeholder-color: $tertiary-text-color;
 | 
			
		||||
$form-text-color: $primary-text-color;
 | 
			
		||||
$form-text-color-disabled: $secondary-text-color-disabled;
 | 
			
		||||
@@ -133,8 +140,8 @@ $workspace-button-color-focus-outline: $form-input-focus-color;
 | 
			
		||||
 | 
			
		||||
$shade-color: rgba(160,160,160,0.5);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
$popover-background: #333;
 | 
			
		||||
$popover-border: $popover-background;
 | 
			
		||||
$popover-color: #eee;
 | 
			
		||||
$popover-button-border-color: #bbb;
 | 
			
		||||
$popover-button-border-color-hover: #666;
 | 
			
		||||
@@ -190,6 +197,7 @@ $view-select-mode-background: $secondary-background-selected;
 | 
			
		||||
$view-grid-color: #eee;
 | 
			
		||||
 | 
			
		||||
$node-label-color: #333;
 | 
			
		||||
$node-port-label-color: #888;
 | 
			
		||||
$node-border: #999;
 | 
			
		||||
$node-border-unknown: #f33;
 | 
			
		||||
$node-border-placeholder: #aaa;
 | 
			
		||||
@@ -257,16 +265,13 @@ $headerMenuCaret: #C7C7C7;
 | 
			
		||||
 | 
			
		||||
$vcCommitShaColor: #c38888;
 | 
			
		||||
 | 
			
		||||
$info-text-code-color: #AD1625;
 | 
			
		||||
$info-text-link-color: #0088cc;
 | 
			
		||||
 | 
			
		||||
$dnd-background: rgba(0,0,0,0.3);
 | 
			
		||||
$dnd-color: #fff;
 | 
			
		||||
 | 
			
		||||
$notification-border-default: #325C80;
 | 
			
		||||
$notification-border-success: #4B8400;
 | 
			
		||||
$notification-border-warning: #D74108;
 | 
			
		||||
$notification-border-error: $text-color-warning;
 | 
			
		||||
$notification-border-success: $border-color-success;
 | 
			
		||||
$notification-border-warning: $border-color-warning;
 | 
			
		||||
$notification-border-error: $border-color-error;
 | 
			
		||||
 | 
			
		||||
$debug-message-background: $secondary-background;
 | 
			
		||||
$debug-message-background-hover: $secondary-background-selected;
 | 
			
		||||
@@ -282,11 +287,19 @@ $debug-message-text-color-msg-type-number: #2033d6;
 | 
			
		||||
 | 
			
		||||
$debug-message-border: #eee;
 | 
			
		||||
$debug-message-border-hover: #999;
 | 
			
		||||
$debug-message-border-warning: #ffdf9d;
 | 
			
		||||
$debug-message-border-error: #f99;
 | 
			
		||||
$debug-message-border-warning: $border-color-warning;
 | 
			
		||||
$debug-message-border-error: $border-color-error;
 | 
			
		||||
 | 
			
		||||
$group-default-fill: none;
 | 
			
		||||
$group-default-fill-opacity: 1;
 | 
			
		||||
$group-default-stroke: #999;
 | 
			
		||||
$group-default-stroke-opacity: 1;
 | 
			
		||||
$group-default-label-color: #a4a4a4;
 | 
			
		||||
$group-default-label-color: #a4a4a4;
 | 
			
		||||
 | 
			
		||||
$tourGuide-border: #c56c6c;
 | 
			
		||||
$tourGuide-heading-color: #c56c6c;
 | 
			
		||||
 | 
			
		||||
// Deprecated
 | 
			
		||||
$text-color-green: $text-color-success;
 | 
			
		||||
$info-text-code-color: $text-color-code;
 | 
			
		||||
$info-text-link-color: $text-color-link;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,12 +43,24 @@
 | 
			
		||||
    border-bottom: 1px solid $secondary-border-color;
 | 
			
		||||
    box-shadow: 0 2px 6px $shadow;
 | 
			
		||||
}
 | 
			
		||||
.red-ui-debug-filter-row {
 | 
			
		||||
    .red-ui-nodeList {
 | 
			
		||||
        margin: 10px 0;
 | 
			
		||||
#red-ui-sidebar-debug-filter-node-list-row {
 | 
			
		||||
    .red-ui-treeList-label.disabled {
 | 
			
		||||
        font-style: italic;
 | 
			
		||||
        color: $secondary-text-color-disabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .red-ui-treeList-label {
 | 
			
		||||
        &.selected, &.selected .red-ui-treeList-sublabel-text {
 | 
			
		||||
            background: inherit;
 | 
			
		||||
        }
 | 
			
		||||
        &.selected, &.selected .red-ui-treeList-sublabel-text {
 | 
			
		||||
            background: inherit;
 | 
			
		||||
        }
 | 
			
		||||
        &.focus, &.focus .red-ui-treeList-sublabel-text {
 | 
			
		||||
            background: $list-item-background-hover !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red-ui-debug-msg {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    border-bottom: 1px solid $debug-message-border;
 | 
			
		||||
 
 | 
			
		||||
@@ -500,7 +500,7 @@ ul.red-ui-deploy-dialog-confirm-list {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        margin-right: 10px;
 | 
			
		||||
        &.fa-check {
 | 
			
		||||
            color: $text-color-green;
 | 
			
		||||
            color: $text-color-success;
 | 
			
		||||
        }
 | 
			
		||||
        &.fa-exclamation {
 | 
			
		||||
            color: $secondary-text-color;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
    font-size: $primary-font-size;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 100%;
 | 
			
		||||
    width: 200px;
 | 
			
		||||
    width: 230px;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    display: none;
 | 
			
		||||
@@ -46,7 +46,7 @@
 | 
			
		||||
    & > li > a,
 | 
			
		||||
    & > li > a:focus {
 | 
			
		||||
        display: block;
 | 
			
		||||
        padding: 4px 0 4px 32px;
 | 
			
		||||
        padding: 4px 12px 4px 32px;
 | 
			
		||||
        clear: both;
 | 
			
		||||
        font-weight: normal;
 | 
			
		||||
        line-height: 20px;
 | 
			
		||||
@@ -68,6 +68,10 @@
 | 
			
		||||
    & > .disabled > a:hover,
 | 
			
		||||
    & > .disabled > a:focus {
 | 
			
		||||
        color: $menuDisabledColor;
 | 
			
		||||
        .red-ui-popover-key {
 | 
			
		||||
            color: $menuDisabledColor;
 | 
			
		||||
            border-color: $menuDisabledColor;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > .disabled > a:hover,
 | 
			
		||||
@@ -83,6 +87,7 @@
 | 
			
		||||
            max-width: 14px;
 | 
			
		||||
        }
 | 
			
		||||
        .fa {
 | 
			
		||||
            float: left;
 | 
			
		||||
            width: 20px;
 | 
			
		||||
            margin-left: -25px;
 | 
			
		||||
            margin-top: 3px;
 | 
			
		||||
@@ -102,6 +107,20 @@
 | 
			
		||||
                display: none;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .red-ui-menu-label {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            & > :first-child {
 | 
			
		||||
                flex-grow: 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .red-ui-popover-key {
 | 
			
		||||
            border: none;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            font-size: 13px;
 | 
			
		||||
            // float: right;
 | 
			
		||||
            color: $menuColor;
 | 
			
		||||
            border-color: $menuColor;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -113,6 +132,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.red-ui-menu-dropdown > li > a:hover,
 | 
			
		||||
.red-ui-menu-dropdown > li.open > a,
 | 
			
		||||
.red-ui-menu-dropdown > li > a:focus,
 | 
			
		||||
.red-ui-menu-dropdown-submenu:hover > a,
 | 
			
		||||
.red-ui-menu-dropdown-submenu:focus > a {
 | 
			
		||||
@@ -129,6 +149,7 @@
 | 
			
		||||
        margin-top: -6px;
 | 
			
		||||
        margin-left: -1px;
 | 
			
		||||
    }
 | 
			
		||||
    &.open > .red-ui-menu-dropdown,
 | 
			
		||||
    &:hover > .red-ui-menu-dropdown {
 | 
			
		||||
      display: block;
 | 
			
		||||
    }
 | 
			
		||||
@@ -209,4 +230,4 @@ ul.red-ui-menu:not(.red-ui-menu-dropdown) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -222,7 +222,7 @@ button.red-ui-tray-resize-button {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .form-warning {
 | 
			
		||||
        border-color: $text-color-warning;
 | 
			
		||||
        border-color: $text-color-error;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
 | 
			
		||||
.red-ui-flow-port-label {
 | 
			
		||||
    stroke-width: 0;
 | 
			
		||||
    fill: $secondary-text-color;
 | 
			
		||||
    fill: $node-port-label-color;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    dominant-baseline: middle;
 | 
			
		||||
    text-anchor: middle;
 | 
			
		||||
@@ -255,11 +255,11 @@ g.red-ui-flow-node-selected {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@each $current-color in red green yellow blue grey gray {
 | 
			
		||||
    .red-ui-flow-node-status-dot-#{$current-color} {
 | 
			
		||||
    .red-ui-flow-node-status-dot-#{""+$current-color} {
 | 
			
		||||
        fill: map-get($node-status-colors,$current-color);
 | 
			
		||||
        stroke: map-get($node-status-colors,$current-color);
 | 
			
		||||
    }
 | 
			
		||||
    .red-ui-flow-node-status-ring-#{$current-color} {
 | 
			
		||||
    .red-ui-flow-node-status-ring-#{""+$current-color} {
 | 
			
		||||
        fill: $view-background;
 | 
			
		||||
        stroke: map-get($node-status-colors,$current-color);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -395,6 +395,7 @@
 | 
			
		||||
    select[readonly],
 | 
			
		||||
    textarea[readonly] {
 | 
			
		||||
        cursor: not-allowed;
 | 
			
		||||
        color: $form-text-color-disabled;
 | 
			
		||||
        background-color: $form-input-background-disabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user