mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
1330 Commits
prevent-ed
...
update-dro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b7fc69c7 | ||
|
|
71f8de94b0 | ||
|
|
4723378f2f | ||
|
|
8837597ff5 | ||
|
|
473b93f497 | ||
|
|
d9c5144fe2 | ||
|
|
cbc91a9ac8 | ||
|
|
88c946d401 | ||
|
|
ff565bacb4 | ||
|
|
e55301c073 | ||
|
|
dc69226944 | ||
|
|
bf6b18b8a6 | ||
|
|
e1b591d761 | ||
|
|
27463197cd | ||
|
|
854460db56 | ||
|
|
4984af48f1 | ||
|
|
e9dab46de8 | ||
|
|
4605f01c5d | ||
|
|
a0ddf96e03 | ||
|
|
16d25b9d41 | ||
|
|
77c4ccf8fb | ||
|
|
7d9e09f5a7 | ||
|
|
f4c184af4d | ||
|
|
9694c8bdfa | ||
|
|
ca61efc986 | ||
|
|
ffdbd94927 | ||
|
|
43df2318d4 | ||
|
|
21612a5215 | ||
|
|
756485e308 | ||
|
|
efbe38f509 | ||
|
|
daa76e6e5f | ||
|
|
48d2d269a5 | ||
|
|
13cac1b5ef | ||
|
|
479b7e756d | ||
|
|
503ef62cf5 | ||
|
|
b355a37378 | ||
|
|
1acc16c9ef | ||
|
|
4cbf672b26 | ||
|
|
272355a48e | ||
|
|
e2981f2970 | ||
|
|
804551000a | ||
|
|
254b6a1e23 | ||
|
|
3838e4e605 | ||
|
|
953b7584a3 | ||
|
|
3da22882e9 | ||
|
|
1e8f840993 | ||
|
|
4845a1f7eb | ||
|
|
a0952d9a07 | ||
|
|
7fa4e60c82 | ||
|
|
30eead76e6 | ||
|
|
100e5244c8 | ||
|
|
ed0399b855 | ||
|
|
27e9c18a4e | ||
|
|
c6895713ed | ||
|
|
965ca97ad1 | ||
|
|
7785ce0dc0 | ||
|
|
1a47e2fc76 | ||
|
|
b7e96ce6bc | ||
|
|
7a3741165b | ||
|
|
e9d5d20e2d | ||
|
|
867a6ad2da | ||
|
|
03507c2a1f | ||
|
|
aa79aa5479 | ||
|
|
16005a462d | ||
|
|
82c756b091 | ||
|
|
b139eb4a18 | ||
|
|
6af3c8c2a9 | ||
|
|
2c3fbb1467 | ||
|
|
01716119e6 | ||
|
|
a50a37ac26 | ||
|
|
11c4277466 | ||
|
|
7d284ce157 | ||
|
|
aa1d5ad06b | ||
|
|
00a3010933 | ||
|
|
56a4530ec6 | ||
|
|
89e40a0b8f | ||
|
|
66bd1feb47 | ||
|
|
b419e2e303 | ||
|
|
dae4ba8044 | ||
|
|
fe22afea6a | ||
|
|
69753a9940 | ||
|
|
f6e565ba04 | ||
|
|
e4fdf24545 | ||
|
|
43a9a3c3b1 | ||
|
|
bfd98aaf22 | ||
|
|
4e61c54be5 | ||
|
|
39a85c721d | ||
|
|
f9877f8d0b | ||
|
|
92dff4bacd | ||
|
|
338ddf17de | ||
|
|
4e6c8ea367 | ||
|
|
5f92bc83fd | ||
|
|
5e429f3be0 | ||
|
|
2a71175cd4 | ||
|
|
aee531bf16 | ||
|
|
2c99909353 | ||
|
|
50e821d5d7 | ||
|
|
06f3f3c0be | ||
|
|
0b09cf5fa9 | ||
|
|
93102837dd | ||
|
|
e8d81d814c | ||
|
|
f6cf051282 | ||
|
|
328390c2a9 | ||
|
|
6194285b6e | ||
|
|
84a2fbed2e | ||
|
|
5ce3cdb845 | ||
|
|
3e0b5f2fe8 | ||
|
|
94e3fdd7a9 | ||
|
|
69b413040f | ||
|
|
ffecf86281 | ||
|
|
4cb3ccc984 | ||
|
|
47e1389548 | ||
|
|
6d6e6fa416 | ||
|
|
ad615a76c8 | ||
|
|
e48607c743 | ||
|
|
046d56d692 | ||
|
|
604c70ec04 | ||
|
|
59a133cc13 | ||
|
|
0590d81e80 | ||
|
|
c8a02d53e8 | ||
|
|
deccfdf654 | ||
|
|
f2d72b1050 | ||
|
|
3d9bc265dd | ||
|
|
abe0b60bf7 | ||
|
|
54bf3f4402 | ||
|
|
d3219f0600 | ||
|
|
348ec34446 | ||
|
|
33a5b2527c | ||
|
|
443492d6eb | ||
|
|
a7ee31307e | ||
|
|
f67268b89a | ||
|
|
42382e1a03 | ||
|
|
419dfbf08b | ||
|
|
70aed23ef0 | ||
|
|
966064328f | ||
|
|
83696abf9d | ||
|
|
2fd7aee4da | ||
|
|
7555e0644f | ||
|
|
c363f375b6 | ||
|
|
2220956007 | ||
|
|
b3aff3a3e6 | ||
|
|
d0ad62a82b | ||
|
|
892933ff75 | ||
|
|
61fd01b871 | ||
|
|
2eba754801 | ||
|
|
a7b1ce0cf8 | ||
|
|
2854351909 | ||
|
|
fe9354d10b | ||
|
|
71cfa87303 | ||
|
|
802b116b01 | ||
|
|
27b54199f5 | ||
|
|
90ea3c15b3 | ||
|
|
acd500fe3d | ||
|
|
8988176c22 | ||
|
|
35f35705a5 | ||
|
|
a0033697ea | ||
|
|
49a3eded59 | ||
|
|
d50ccea017 | ||
|
|
3ef205fb33 | ||
|
|
b1bfba8b01 | ||
|
|
21832a0bd0 | ||
|
|
a0b4fc8372 | ||
|
|
3812ed5ed3 | ||
|
|
bdb545d6eb | ||
|
|
7650620a78 | ||
|
|
34d8b3ed5e | ||
|
|
e3acc49d5e | ||
|
|
c3da827222 | ||
|
|
1053fc5121 | ||
|
|
cec7a86b54 | ||
|
|
16c49306f3 | ||
|
|
32540dd0e6 | ||
|
|
a3c5b75368 | ||
|
|
f7a43f83e5 | ||
|
|
98ca0f163e | ||
|
|
5a3e6925e5 | ||
|
|
d270da74b4 | ||
|
|
204e0f636b | ||
|
|
1132d39118 | ||
|
|
f2227c8f65 | ||
|
|
55cfd6fa99 | ||
|
|
83b30f1c18 | ||
|
|
aa74d8160a | ||
|
|
f44868384e | ||
|
|
674eb36e15 | ||
|
|
e6882337c1 | ||
|
|
d48594220e | ||
|
|
64cdee6d36 | ||
|
|
a16c72b6a8 | ||
|
|
89839f433b | ||
|
|
3597759692 | ||
|
|
d4b001a74e | ||
|
|
d76b0d39cd | ||
|
|
92911f6714 | ||
|
|
380d3be819 | ||
|
|
6dfc5e0a41 | ||
|
|
d4f18c1731 | ||
|
|
e5a0d4094f | ||
|
|
163c1c5b98 | ||
|
|
912a30b28d | ||
|
|
de0546b251 | ||
|
|
1b375b81f3 | ||
|
|
b4bbae247d | ||
|
|
20ae4a7272 | ||
|
|
7f77a414ec | ||
|
|
2e5a3f4949 | ||
|
|
593726ecb8 | ||
|
|
9745904c5b | ||
|
|
8c53d95610 | ||
|
|
27604fef23 | ||
|
|
2c4dc3334d | ||
|
|
1ebb66f6ca | ||
|
|
aed9a868ab | ||
|
|
e6ca3af1ed | ||
|
|
edc01552f9 | ||
|
|
b4d29d4d4a | ||
|
|
b5785dab9c | ||
|
|
e67afb2611 | ||
|
|
f88e536ee0 | ||
|
|
509d609020 | ||
|
|
a4ec0f7959 | ||
|
|
884c887e0d | ||
|
|
e0059943df | ||
|
|
d11934beeb | ||
|
|
964271f9c7 | ||
|
|
97b773d257 | ||
|
|
b6a25518e3 | ||
|
|
b62c17f94b | ||
|
|
886fb45ad2 | ||
|
|
7b95e64a60 | ||
|
|
af42664d73 | ||
|
|
8af821d380 | ||
|
|
97ee6c6745 | ||
|
|
b5fb15cd9b | ||
|
|
998219ae9a | ||
|
|
e8f0f80f65 | ||
|
|
ff35c46d5d | ||
|
|
05c924a9df | ||
|
|
14ac309b6e | ||
|
|
8aec038c24 | ||
|
|
29b128e5e0 | ||
|
|
bda9f249bc | ||
|
|
c0f1581370 | ||
|
|
9420a52ec7 | ||
|
|
c113b3de13 | ||
|
|
29058c163a | ||
|
|
bb110ea230 | ||
|
|
785f220cd8 | ||
|
|
16570410a5 | ||
|
|
8085eda431 | ||
|
|
6503498f0a | ||
|
|
10ac7fc369 | ||
|
|
da787a9993 | ||
|
|
c873b57094 | ||
|
|
93974ccd92 | ||
|
|
d7aa792f97 | ||
|
|
375fa9da64 | ||
|
|
28c41e17ad | ||
|
|
da3ad40968 | ||
|
|
2464d9ad95 | ||
|
|
011b47a108 | ||
|
|
ea747711c3 | ||
|
|
19a8fa09a8 | ||
|
|
bea08706cc | ||
|
|
7950ee1241 | ||
|
|
a743764345 | ||
|
|
cc1c87387b | ||
|
|
1b5b3f7f88 | ||
|
|
2123514c76 | ||
|
|
379fbd7c7e | ||
|
|
efdc1b1a1d | ||
|
|
52bbd82e18 | ||
|
|
20a9c051be | ||
|
|
830e475969 | ||
|
|
ed4b98b598 | ||
|
|
f75e2f221c | ||
|
|
5a75440668 | ||
|
|
53e092e484 | ||
|
|
53d8b97fff | ||
|
|
c85667cc13 | ||
|
|
1a8b37b4e3 | ||
|
|
40a2d90e08 | ||
|
|
ee269caa4a | ||
|
|
d820686e5a | ||
|
|
aa2a585e00 | ||
|
|
f9e6bccd46 | ||
|
|
3230654ecd | ||
|
|
eab512ef22 | ||
|
|
a5b53ee373 | ||
|
|
ac420247ae | ||
|
|
61198bd7e3 | ||
|
|
9c511b6674 | ||
|
|
9d054543a7 | ||
|
|
fc3ec2a0d7 | ||
|
|
e7ef73222f | ||
|
|
6623e56a1e | ||
|
|
582eca1877 | ||
|
|
2783100f84 | ||
|
|
cb0c484579 | ||
|
|
a1bf270ba6 | ||
|
|
be5694f149 | ||
|
|
4ff364e2c3 | ||
|
|
2fa6f35873 | ||
|
|
2a4fb7123d | ||
|
|
38a77d2b78 | ||
|
|
dc239db256 | ||
|
|
a622d19ba7 | ||
|
|
9842d9116c | ||
|
|
19ea8f8515 | ||
|
|
4ba3c937a8 | ||
|
|
dbd3f0f85b | ||
|
|
48a2876c48 | ||
|
|
10398b05d8 | ||
|
|
3a91fc17fd | ||
|
|
02893d3e78 | ||
|
|
5124bc6bf8 | ||
|
|
3952a23ba3 | ||
|
|
1048b16f3c | ||
|
|
bbbbb1b1e0 | ||
|
|
14b452c996 | ||
|
|
bb91a08939 | ||
|
|
bffa923f05 | ||
|
|
526b3fda91 | ||
|
|
27fc89ba33 | ||
|
|
d70b7ea924 | ||
|
|
1d342a778d | ||
|
|
476016cbcc | ||
|
|
bd2c020e84 | ||
|
|
da7c7ede02 | ||
|
|
34ed9c5cd8 | ||
|
|
47dd08e74a | ||
|
|
6aae50294f | ||
|
|
ec2f6ec46f | ||
|
|
36805b6872 | ||
|
|
d78cb2fec7 | ||
|
|
51edb1ef19 | ||
|
|
2ad3af1864 | ||
|
|
525d7356fe | ||
|
|
bdf37b0546 | ||
|
|
0d330332f1 | ||
|
|
a5f290fd47 | ||
|
|
46f4144172 | ||
|
|
356e332f66 | ||
|
|
d16060bdd9 | ||
|
|
3aeb4bd868 | ||
|
|
b8bcab109a | ||
|
|
b94045fd86 | ||
|
|
87992823d5 | ||
|
|
93b914d4b0 | ||
|
|
61b12f6bbe | ||
|
|
d71b22412b | ||
|
|
d115d6c241 | ||
|
|
e561efb5c5 | ||
|
|
e408c6b376 | ||
|
|
cd95c63daf | ||
|
|
bc6a4a20d2 | ||
|
|
bf30c24e8e | ||
|
|
25205f7440 | ||
|
|
6317420d4a | ||
|
|
6c14ed0ef5 | ||
|
|
f53bdc8257 | ||
|
|
6d41ecdae0 | ||
|
|
7bd61f2c96 | ||
|
|
76338d4d32 | ||
|
|
3f89bc2733 | ||
|
|
b9add237b2 | ||
|
|
87a25df162 | ||
|
|
341f43610a | ||
|
|
67cdf3ef96 | ||
|
|
cd98f448e9 | ||
|
|
b66af1c8e2 | ||
|
|
06bad61569 | ||
|
|
f58766eacf | ||
|
|
8e62b2a749 | ||
|
|
9b86874c2d | ||
|
|
805ed593fb | ||
|
|
c604ac2207 | ||
|
|
fac79fd068 | ||
|
|
da97c5d558 | ||
|
|
ae7b9fe62e | ||
|
|
e52c2911da | ||
|
|
ca37d1ec9d | ||
|
|
07a29ff779 | ||
|
|
1b4a8ebe83 | ||
|
|
abf2eacf18 | ||
|
|
f808f4e2e8 | ||
|
|
ca33d6b799 | ||
|
|
3fd2d07c75 | ||
|
|
940740f15d | ||
|
|
51208fcd0c | ||
|
|
707152d82f | ||
|
|
5538f6dd8a | ||
|
|
d601e2caa4 | ||
|
|
46fdf56c79 | ||
|
|
b76d692a65 | ||
|
|
6600910163 | ||
|
|
a6973bd7ed | ||
|
|
d58127730f | ||
|
|
5494c167fc | ||
|
|
c5ae0be7b1 | ||
|
|
b653914ee0 | ||
|
|
c107c5fc92 | ||
|
|
0980c03129 | ||
|
|
f6c3fdc806 | ||
|
|
2c2628d816 | ||
|
|
56fe2801eb | ||
|
|
87b1ee9642 | ||
|
|
e1c36d232b | ||
|
|
13ee8cec24 | ||
|
|
a977b87cb3 | ||
|
|
14dfb9aef8 | ||
|
|
d520cde57a | ||
|
|
70167d7d1d | ||
|
|
3389c8160b | ||
|
|
c214710f8e | ||
|
|
ac6a4945cb | ||
|
|
fd1a001a23 | ||
|
|
f3c561cd86 | ||
|
|
4e33e785fb | ||
|
|
f55ee6e665 | ||
|
|
edc5e88d5a | ||
|
|
47bf166a6e | ||
|
|
cf26209790 | ||
|
|
e55ebde170 | ||
|
|
a745ddc164 | ||
|
|
18d0fa2259 | ||
|
|
d706c9cb37 | ||
|
|
20d2450cac | ||
|
|
34345461f1 | ||
|
|
aa372a1707 | ||
|
|
03648dc7e8 | ||
|
|
66a667fe58 | ||
|
|
1bb3a0eca5 | ||
|
|
0e0bba25c1 | ||
|
|
af701d65ac | ||
|
|
08927dfb55 | ||
|
|
b27483de9c | ||
|
|
b02f69b77a | ||
|
|
598b0c84ab | ||
|
|
22cc8da088 | ||
|
|
a70618cdef | ||
|
|
faf142cf66 | ||
|
|
1a3cc06935 | ||
|
|
a712a9363b | ||
|
|
67e716466f | ||
|
|
3fae03da98 | ||
|
|
361391ceb8 | ||
|
|
bf0ca38350 | ||
|
|
437c28e2b8 | ||
|
|
c05d18ada1 | ||
|
|
cfb300ec06 | ||
|
|
236e668201 | ||
|
|
211d420fb2 | ||
|
|
c9b902c2b4 | ||
|
|
b8ca4665c1 | ||
|
|
ac8b1e19b7 | ||
|
|
960af87fb0 | ||
|
|
de7339ae97 | ||
|
|
595933d046 | ||
|
|
789426f80e | ||
|
|
0995af62b6 | ||
|
|
c2e03a40b4 | ||
|
|
148e64c3da | ||
|
|
c6289ebb2c | ||
|
|
5f4ece6813 | ||
|
|
c990ec39d6 | ||
|
|
1fdc600ecd | ||
|
|
c855050bcf | ||
|
|
e354d2ce29 | ||
|
|
d218af8619 | ||
|
|
d938e5fb6b | ||
|
|
29ed5b2792 | ||
|
|
e39216e65a | ||
|
|
7ac7f9b4c8 | ||
|
|
4709eb9d49 | ||
|
|
c13b8266dd | ||
|
|
bd58431603 | ||
|
|
3075b82792 | ||
|
|
240082481f | ||
|
|
ea95552285 | ||
|
|
5358b06123 | ||
|
|
99391431da | ||
|
|
d396f50a9a | ||
|
|
affa8ea42b | ||
|
|
d711b01fe5 | ||
|
|
6e7fa6f921 | ||
|
|
343cde75a2 | ||
|
|
2dc446e45b | ||
|
|
884b7fa16a | ||
|
|
173e065b68 | ||
|
|
9a3cb0b2b5 | ||
|
|
6beae5a806 | ||
|
|
66f4008bb8 | ||
|
|
a0636632a1 | ||
|
|
5dfa47ab6c | ||
|
|
e9efe493f9 | ||
|
|
3bd782e62a | ||
|
|
9b49cb2b50 | ||
|
|
963fe87f14 | ||
|
|
ade4679e8c | ||
|
|
40060a470b | ||
|
|
a6e8fbb54a | ||
|
|
410b938442 | ||
|
|
ab7e9f94fa | ||
|
|
28e9ccd372 | ||
|
|
9a66d9addd | ||
|
|
8843bda477 | ||
|
|
3278303eec | ||
|
|
f5fd6e3a36 | ||
|
|
a173e8e70f | ||
|
|
19dcc3a683 | ||
|
|
20d067c1ea | ||
|
|
9526566799 | ||
|
|
0b9dd82c91 | ||
|
|
19213434f9 | ||
|
|
014691346a | ||
|
|
6738b95c29 | ||
|
|
6a8230ec1e | ||
|
|
5679d264b6 | ||
|
|
b20c5f3a8d | ||
|
|
014f206e9c | ||
|
|
068b93befa | ||
|
|
65d8872cea | ||
|
|
bffd1d61b2 | ||
|
|
4788b81220 | ||
|
|
9a07fc03c6 | ||
|
|
954f518030 | ||
|
|
9f8ff71757 | ||
|
|
06dd59dc81 | ||
|
|
37265cf4ef | ||
|
|
2531a5283a | ||
|
|
4cc1a5d846 | ||
|
|
8a63275989 | ||
|
|
2d3e5f4ce0 | ||
|
|
5135545c6c | ||
|
|
fef93818c9 | ||
|
|
7fc64a84e8 | ||
|
|
02f7cdd5aa | ||
|
|
d7dcceef60 | ||
|
|
ae5e1570ae | ||
|
|
50baad9624 | ||
|
|
fc0041bd91 | ||
|
|
283f7f5992 | ||
|
|
1cf7b95891 | ||
|
|
7bdb8db5ff | ||
|
|
de27968e4e | ||
|
|
d31abda28f | ||
|
|
dcfb4c9a79 | ||
|
|
bf065ee11d | ||
|
|
5e3cbadffc | ||
|
|
535ef82e48 | ||
|
|
c368bfea3f | ||
|
|
4498e4100e | ||
|
|
4f1e4faede | ||
|
|
3ca045394a | ||
|
|
9a19a1113e | ||
|
|
1cd550022b | ||
|
|
bad08bafd7 | ||
|
|
f041a21f22 | ||
|
|
712d78ca39 | ||
|
|
93f2910bd2 | ||
|
|
d0ef12c486 | ||
|
|
241fd09053 | ||
|
|
208dd2a457 | ||
|
|
e34ee44b21 | ||
|
|
d5f59307b7 | ||
|
|
64136cc565 | ||
|
|
3e2508c740 | ||
|
|
0853cd65b2 | ||
|
|
01802c817b | ||
|
|
7e10093bb8 | ||
|
|
179032cd4d | ||
|
|
6a6f0d04d6 | ||
|
|
add4d9758c | ||
|
|
a0d3ea62b2 | ||
|
|
54c17c3175 | ||
|
|
80e60538e2 | ||
|
|
84a76909e2 | ||
|
|
033405fdbc | ||
|
|
9444009a9b | ||
|
|
29e9def314 | ||
|
|
8832a1aa20 | ||
|
|
5beb6dbeee | ||
|
|
1261d26b23 | ||
|
|
0b9dd11fff | ||
|
|
08a607aa6a | ||
|
|
e12efc320b | ||
|
|
3ded9de803 | ||
|
|
d5b424910f | ||
|
|
d94d13737f | ||
|
|
b1fa4918e3 | ||
|
|
742aa2fa0d | ||
|
|
ce133c1c04 | ||
|
|
e4dc1779c3 | ||
|
|
22b4ab6bb2 | ||
|
|
7447e88a50 | ||
|
|
a193b79d3d | ||
|
|
da380f7464 | ||
|
|
2dcff51125 | ||
|
|
b50e0533eb | ||
|
|
711545539f | ||
|
|
a6cbceed28 | ||
|
|
269cf02c0b | ||
|
|
837d17ab65 | ||
|
|
eff31c4bdc | ||
|
|
6a8f653b73 | ||
|
|
0cdb36f73d | ||
|
|
db249356e6 | ||
|
|
d509c1a57c | ||
|
|
fb50e2772a | ||
|
|
058c97138a | ||
|
|
828ae29aed | ||
|
|
6a0f45140c | ||
|
|
50a267528d | ||
|
|
220786be60 | ||
|
|
fa78bb3d78 | ||
|
|
9a32ebd0c0 | ||
|
|
4643f5e8cc | ||
|
|
7de0984d6d | ||
|
|
635334f096 | ||
|
|
f0d0990b5a | ||
|
|
43b3589451 | ||
|
|
016a19ba7c | ||
|
|
6802539ccc | ||
|
|
74efaa3c2d | ||
|
|
a5223709ba | ||
|
|
2291dc6132 | ||
|
|
aeb79bce2a | ||
|
|
0ab9b9a5fd | ||
|
|
56e58521bd | ||
|
|
b2548c158d | ||
|
|
5a48d6d4cd | ||
|
|
b10ef4c98c | ||
|
|
3ff038fb98 | ||
|
|
adb498af24 | ||
|
|
fc67a2efc2 | ||
|
|
55771c7241 | ||
|
|
109fa5f04e | ||
|
|
1f412f3d78 | ||
|
|
2b69f52c92 | ||
|
|
6e90798f16 | ||
|
|
3994b404a1 | ||
|
|
0b7e8ec323 | ||
|
|
bb0b547d5a | ||
|
|
1e1acc7ad7 | ||
|
|
1419432b04 | ||
|
|
1a521d7e09 | ||
|
|
5858d2789a | ||
|
|
6a1e4fac5a | ||
|
|
bab6e57a59 | ||
|
|
4ed53fb622 | ||
|
|
e17775c435 | ||
|
|
7ee2b93b10 | ||
|
|
565c212779 | ||
|
|
b54e9d8d55 | ||
|
|
cc611a7a02 | ||
|
|
861c89a0cc | ||
|
|
4268a04a04 | ||
|
|
9bd7131914 | ||
|
|
8485ca254f | ||
|
|
507f9b68eb | ||
|
|
f7b726372f | ||
|
|
14811b5aec | ||
|
|
1a9c34fe40 | ||
|
|
ff8eb0ec2b | ||
|
|
c24f05c2cd | ||
|
|
d2dc1fcc80 | ||
|
|
28907082f1 | ||
|
|
f83174c40a | ||
|
|
ec062d008f | ||
|
|
a587655a5a | ||
|
|
97e05c8784 | ||
|
|
26cb03da42 | ||
|
|
d5a8b1592c | ||
|
|
5b096bfd5e | ||
|
|
a1e242ec1e | ||
|
|
5b9d002f56 | ||
|
|
dd57323889 | ||
|
|
6620679008 | ||
|
|
f93654f680 | ||
|
|
1bef0c32a2 | ||
|
|
f66b48e586 | ||
|
|
a5725c59fd | ||
|
|
4168bbb751 | ||
|
|
b0086edcf9 | ||
|
|
89c2efe17d | ||
|
|
9030b7d27c | ||
|
|
de5111b13f | ||
|
|
8600f4131e | ||
|
|
8a8245b560 | ||
|
|
58e2fcbeee | ||
|
|
282bb6c414 | ||
|
|
931a2344b4 | ||
|
|
dd3c75d298 | ||
|
|
962fc5990e | ||
|
|
eb2f57fc0d | ||
|
|
4a4a15de93 | ||
|
|
ce5b6a8024 | ||
|
|
0773edcaff | ||
|
|
68dcf7bceb | ||
|
|
a007ab7f2e | ||
|
|
d876146ea5 | ||
|
|
50627cd697 | ||
|
|
3a6b1e86dc | ||
|
|
6a4d293352 | ||
|
|
1ad4fe44e2 | ||
|
|
59ea7a4f70 | ||
|
|
6c64ba45c2 | ||
|
|
c68cc4ac19 | ||
|
|
84ed88c8dd | ||
|
|
d7345d5bc6 | ||
|
|
7b01457038 | ||
|
|
54e6d60fe5 | ||
|
|
f0a9b0cf69 | ||
|
|
26ddb5c1b7 | ||
|
|
82f8b64599 | ||
|
|
7f24de442f | ||
|
|
c3536fd7c7 | ||
|
|
8365310ca7 | ||
|
|
aaed9882b8 | ||
|
|
8f5ebfcede | ||
|
|
83279df0fa | ||
|
|
2550da9c6e | ||
|
|
041f00b811 | ||
|
|
21cd4aaeb6 | ||
|
|
70ce1e648d | ||
|
|
eab5a9772b | ||
|
|
74ff0599d1 | ||
|
|
c2710f4f6f | ||
|
|
20187b51b1 | ||
|
|
4be6d57d98 | ||
|
|
c31e622160 | ||
|
|
1828d8a279 | ||
|
|
282d52cf0b | ||
|
|
70ea5c839a | ||
|
|
a77f8cc3e9 | ||
|
|
0b0f1f8701 | ||
|
|
e1f2e0656b | ||
|
|
ea4c0cdbee | ||
|
|
b5e955bd5e | ||
|
|
ba08cf0417 | ||
|
|
7197153fd5 | ||
|
|
b9c1dedab3 | ||
|
|
918943816f | ||
|
|
b4b5d296d9 | ||
|
|
d287b8867b | ||
|
|
0e8d312794 | ||
|
|
c584d51432 | ||
|
|
2366b4508f | ||
|
|
2f1565fbc9 | ||
|
|
7fd0ecf721 | ||
|
|
37d1539fda | ||
|
|
1e518396d6 | ||
|
|
617b98ed49 | ||
|
|
6d2a870812 | ||
|
|
2963f3f1b8 | ||
|
|
17e4bdbff1 | ||
|
|
f3dd5770d9 | ||
|
|
eebab4a921 | ||
|
|
b06494c5be | ||
|
|
a0562bef81 | ||
|
|
33cf34f7c7 | ||
|
|
eff063a748 | ||
|
|
94abaaff1e | ||
|
|
03732869e4 | ||
|
|
41868e2652 | ||
|
|
81bfba3cea | ||
|
|
8a04eb2e29 | ||
|
|
1777fc749d | ||
|
|
fd32ee09ff | ||
|
|
4bb2157cab | ||
|
|
47f20cc86a | ||
|
|
5fc4526c70 | ||
|
|
9fe653f821 | ||
|
|
55da21ed15 | ||
|
|
7ebf84f38c | ||
|
|
d42e75ebd0 | ||
|
|
28825049fe | ||
|
|
1c3644e338 | ||
|
|
2dfabb523b | ||
|
|
3e2d20e536 | ||
|
|
ee7ee083b0 | ||
|
|
fb54c05d9f | ||
|
|
21f807aa66 | ||
|
|
6b088bda12 | ||
|
|
a32ee869ae | ||
|
|
a2d7772958 | ||
|
|
6ec052be18 | ||
|
|
9c71d52d69 | ||
|
|
171c146ec5 | ||
|
|
bc6afa2164 | ||
|
|
3c036257ef | ||
|
|
6633730bf1 | ||
|
|
2964a4da5e | ||
|
|
febc769df5 | ||
|
|
a55554193b | ||
|
|
d2a8338d4a | ||
|
|
e945deeab6 | ||
|
|
722fe02933 | ||
|
|
3dec609459 | ||
|
|
ea483218ea | ||
|
|
c8f3ad8ac7 | ||
|
|
e73b9f646d | ||
|
|
3cea6400d1 | ||
|
|
7916dc9c05 | ||
|
|
3123a5ee51 | ||
|
|
c6a8eee73d | ||
|
|
5b5b06cc06 | ||
|
|
74d431ea36 | ||
|
|
409a559a13 | ||
|
|
6829535350 | ||
|
|
6488111f79 | ||
|
|
923339c1d8 | ||
|
|
3f4d96f4cd | ||
|
|
c52985d245 | ||
|
|
88e6c71aa0 | ||
|
|
4d08e297c4 | ||
|
|
f49f692ffa | ||
|
|
ad2b30691f | ||
|
|
369bad01b8 | ||
|
|
b6ecc6d9ea | ||
|
|
0117df0960 | ||
|
|
6ac905f264 | ||
|
|
10ce681d46 | ||
|
|
e5307f6604 | ||
|
|
8d9b6dd859 | ||
|
|
01821ead0f | ||
|
|
08c6ea94cb | ||
|
|
1451fb9e2f | ||
|
|
245751bb23 | ||
|
|
f07519c705 | ||
|
|
fea1da5542 | ||
|
|
33a978a246 | ||
|
|
32e8f4eac6 | ||
|
|
bfe5a8a986 | ||
|
|
f2cb5ea44e | ||
|
|
861dc0c383 | ||
|
|
60593fed4a | ||
|
|
c7335ed25b | ||
|
|
fae3a5c26a | ||
|
|
1a52c0adfc | ||
|
|
5fda57c730 | ||
|
|
3df3096bb4 | ||
|
|
bb10d5bb94 | ||
|
|
1704ab7454 | ||
|
|
3275a76fb0 | ||
|
|
81937ddc45 | ||
|
|
9fd929ac1e | ||
|
|
44c0bbc61e | ||
|
|
3e6f0acf79 | ||
|
|
7f93d943d7 | ||
|
|
5ac50fae3a | ||
|
|
c48a15c915 | ||
|
|
ee48a2f2bf | ||
|
|
eb940d6d57 | ||
|
|
680d5b8216 | ||
|
|
c9320c190d | ||
|
|
566c667c5d | ||
|
|
ec6e42e655 | ||
|
|
bba6b6f71d | ||
|
|
c261f6625a | ||
|
|
9091935d77 | ||
|
|
a489b270d1 | ||
|
|
51cb61940d | ||
|
|
34e8d2b051 | ||
|
|
0c2ab13c48 | ||
|
|
9489953a8f | ||
|
|
ce2f896b45 | ||
|
|
6635ff9a69 | ||
|
|
41797f8cef | ||
|
|
797cea5394 | ||
|
|
2880d4120e | ||
|
|
b0136d03ea | ||
|
|
9fe73645ad | ||
|
|
58dec7b9b0 | ||
|
|
b0f900e25d | ||
|
|
fc54848318 | ||
|
|
2f9f8cda81 | ||
|
|
667e7ab8dc | ||
|
|
4e55408fed | ||
|
|
88aa61ea77 | ||
|
|
2ccdeb968c | ||
|
|
90045683c9 | ||
|
|
eb49b01cbc | ||
|
|
3a6062775e | ||
|
|
718a7bfc26 | ||
|
|
27ca30aa82 | ||
|
|
54d4079457 | ||
|
|
01b0bf1a36 | ||
|
|
e1cecc9601 | ||
|
|
8e1a21e682 | ||
|
|
d84cdca43e | ||
|
|
1c6dcd373d | ||
|
|
4410ce1486 | ||
|
|
4c13f5a0af | ||
|
|
f5a9942d5e | ||
|
|
ee2d91fb4a | ||
|
|
6ca41ba69d | ||
|
|
7f9d142038 | ||
|
|
44a1f83b26 | ||
|
|
cef3a01042 | ||
|
|
0c042abcab | ||
|
|
d9bbac20f3 | ||
|
|
a48c57dd17 | ||
|
|
77b235655c | ||
|
|
8dc0261993 | ||
|
|
65d2ad68d3 | ||
|
|
52fde088e9 | ||
|
|
ab6d537c3e | ||
|
|
3a6078a56a | ||
|
|
b0c3fefcab | ||
|
|
2bc739194e | ||
|
|
afb06e8c9a | ||
|
|
2c1274ff76 | ||
|
|
e18721a03e | ||
|
|
25120e44ce | ||
|
|
aea32cc279 | ||
|
|
ce0feb2f42 | ||
|
|
12a1c5c2f4 | ||
|
|
d307968880 | ||
|
|
1f98d19f77 | ||
|
|
9a934c941f | ||
|
|
dbc7284b97 | ||
|
|
de63e17a4d | ||
|
|
1bad643b30 | ||
|
|
57c44d4b18 | ||
|
|
7d06133787 | ||
|
|
261998201b | ||
|
|
9a899b929e | ||
|
|
46b15a51d4 | ||
|
|
5fcace8776 | ||
|
|
a12826e719 | ||
|
|
0dc024c722 | ||
|
|
f6f36c5599 | ||
|
|
18bd318da2 | ||
|
|
e1712073c9 | ||
|
|
2478a7194e | ||
|
|
89b8e88df3 | ||
|
|
a7e80f351a | ||
|
|
c39d3db121 | ||
|
|
e7f9e61f2a | ||
|
|
3047ed1253 | ||
|
|
cf0c7ce583 | ||
|
|
206211f9ef | ||
|
|
8220ff8a35 | ||
|
|
927fc5424b | ||
|
|
64b94c68d3 | ||
|
|
3391d18942 | ||
|
|
1414fed776 | ||
|
|
f884cb015a | ||
|
|
7e6a96db87 | ||
|
|
966bbe2856 | ||
|
|
2137211d42 | ||
|
|
cc5533c183 | ||
|
|
19787e5d28 | ||
|
|
6e46666895 | ||
|
|
a87d7276c5 | ||
|
|
c630b4f770 | ||
|
|
f719e5ad49 | ||
|
|
20557f20db | ||
|
|
66144c1eb5 | ||
|
|
054d6870d5 | ||
|
|
04aa4897fe | ||
|
|
72b22a4845 | ||
|
|
3fd2b5f4e1 | ||
|
|
8a4128defb | ||
|
|
9542c22191 | ||
|
|
15b8d79489 | ||
|
|
97470e94f1 | ||
|
|
37a518b655 | ||
|
|
312b2f8ddb | ||
|
|
9caa6a3553 | ||
|
|
6c597bba5b | ||
|
|
0dd771351d | ||
|
|
f7b64b101e | ||
|
|
ec86ec188b | ||
|
|
2b01a3fcd3 | ||
|
|
9540502d06 | ||
|
|
792dedb1f1 | ||
|
|
0bf1343442 | ||
|
|
4d3e3a73fd | ||
|
|
1ffef393c2 | ||
|
|
8b7b3e22d7 | ||
|
|
877aa75e4e | ||
|
|
d21c0758b1 | ||
|
|
21be329008 | ||
|
|
e7b27ce7fb | ||
|
|
fb2c0e5441 | ||
|
|
29898ea68f | ||
|
|
9dcb8a729c | ||
|
|
b66237efa8 | ||
|
|
6d2f855227 | ||
|
|
638aa0372b | ||
|
|
d5baa402c8 | ||
|
|
ec7e594ec1 | ||
|
|
3fad690d1e | ||
|
|
15973768e2 | ||
|
|
0d73a4b013 | ||
|
|
50f11faf1f | ||
|
|
cfb7406fb8 | ||
|
|
9008f063c3 | ||
|
|
368ac4ed5c | ||
|
|
6ac2e703a6 | ||
|
|
cd9a5f112a | ||
|
|
f3847a17f3 | ||
|
|
1fa4aaf706 | ||
|
|
db108a37cf | ||
|
|
a3e41d4f35 | ||
|
|
80e33489d9 | ||
|
|
271b1327c7 | ||
|
|
5d990ff4c5 | ||
|
|
69aacc6256 | ||
|
|
a5066d529f | ||
|
|
5bf034f3c1 | ||
|
|
0743ff371c | ||
|
|
7481b78b16 | ||
|
|
bd589aa140 | ||
|
|
8fa1d4def5 | ||
|
|
fe3626035c | ||
|
|
c4019bd91d | ||
|
|
18e1b670ca | ||
|
|
cd76c934b6 | ||
|
|
db98641a32 | ||
|
|
59745fec0b | ||
|
|
8bcaea7830 | ||
|
|
56ed32e4a1 | ||
|
|
3209777aba | ||
|
|
11f9ad8ca3 | ||
|
|
26fc942c79 | ||
|
|
f196493402 | ||
|
|
1c5fdb6ab6 | ||
|
|
3ed530ed9e | ||
|
|
8db2972288 | ||
|
|
51a0b68d8e | ||
|
|
2fdbe12a7f | ||
|
|
2448e137c8 | ||
|
|
33899763ef | ||
|
|
d8f4f92e1d | ||
|
|
ce679f90ee | ||
|
|
8fb379079b | ||
|
|
b382d048de | ||
|
|
610cb170e7 | ||
|
|
4d9fcaeebf | ||
|
|
aa0225f59f | ||
|
|
2571949e90 | ||
|
|
20d2c11154 | ||
|
|
3f604e9d93 | ||
|
|
234e92db12 | ||
|
|
01c56f6515 | ||
|
|
026be5a990 | ||
|
|
aafb86ef09 | ||
|
|
1a24efe85d | ||
|
|
1ad67d5c73 | ||
|
|
0b3f7dbb1f | ||
|
|
e90007860c | ||
|
|
3b38669c04 | ||
|
|
74ab03288b | ||
|
|
502dacd865 | ||
|
|
31bc99cd61 | ||
|
|
5435c9ebd2 | ||
|
|
ceb9a320ba | ||
|
|
ee8b2a0b58 | ||
|
|
8202f1b7c6 | ||
|
|
4808cac89d | ||
|
|
c1ea3380eb | ||
|
|
694fdebc71 | ||
|
|
1cbd910e5d | ||
|
|
b102ef512e | ||
|
|
220a621dc6 | ||
|
|
0db288e6dc | ||
|
|
f8175fc325 | ||
|
|
5eee38e7de | ||
|
|
fb5b470966 | ||
|
|
876053f858 | ||
|
|
9714ef19de | ||
|
|
613a345771 | ||
|
|
4fe29dd33f | ||
|
|
4b24223290 | ||
|
|
a78da0db1e | ||
|
|
5e4fce1e12 | ||
|
|
4cb2624a5d | ||
|
|
58e045f25d | ||
|
|
26a770e490 | ||
|
|
dfe145a0ed | ||
|
|
6cb4c9224d | ||
|
|
4047612b96 | ||
|
|
1978a360af | ||
|
|
1f46b3fda9 | ||
|
|
47a945d92e | ||
|
|
59ec87a393 | ||
|
|
9423104dad | ||
|
|
614834090e | ||
|
|
2f9523a586 | ||
|
|
3a9f38a873 | ||
|
|
0697c26dd1 | ||
|
|
cfa25dc655 | ||
|
|
b3498a888d | ||
|
|
0528c12782 | ||
|
|
e19b60b202 | ||
|
|
0ed274f994 | ||
|
|
e8378b382b | ||
|
|
e6c12a0c54 | ||
|
|
70620ad12d | ||
|
|
991f13e704 | ||
|
|
14bbe79651 | ||
|
|
2388232179 | ||
|
|
11ded1e497 | ||
|
|
9479b56549 | ||
|
|
90e32f52c9 | ||
|
|
ce6a4845f2 | ||
|
|
0f5cf1d51c | ||
|
|
c2812b05a4 | ||
|
|
5d698d66d0 | ||
|
|
1a149592d6 | ||
|
|
5e2e0b2e39 | ||
|
|
7a4f48e4fb | ||
|
|
2ae2ec2578 | ||
|
|
a790136164 | ||
|
|
e6c454bba5 | ||
|
|
b904c23e4d | ||
|
|
ece3eb2e7b | ||
|
|
18610bb540 | ||
|
|
69d643942c | ||
|
|
6e1b298282 | ||
|
|
c2387777c9 | ||
|
|
32a49a1ef1 | ||
|
|
4b88775183 | ||
|
|
29db82625f | ||
|
|
2b6c9e3439 | ||
|
|
a7e0444e92 | ||
|
|
e9e32550df | ||
|
|
59b059f06f | ||
|
|
42166f5fc4 | ||
|
|
eefe69d136 | ||
|
|
aabaf7c5e2 | ||
|
|
9ea4853c89 | ||
|
|
3b5e21761b | ||
|
|
2d76bf29cf | ||
|
|
a684ec235f | ||
|
|
c21f7abe4e | ||
|
|
8c191263c0 | ||
|
|
47005043a5 | ||
|
|
1e36ba8429 | ||
|
|
2679ff277c | ||
|
|
0e52271ba9 | ||
|
|
57359d1659 | ||
|
|
2253417459 | ||
|
|
9e3f148273 | ||
|
|
7e9042e9f7 | ||
|
|
dee68b903c | ||
|
|
67c5a248ad | ||
|
|
940512fb2c | ||
|
|
e8ddee24a9 | ||
|
|
be4eab65f6 | ||
|
|
fb5ffa1c31 | ||
|
|
aff0bd3f6a | ||
|
|
c0650cc0f5 | ||
|
|
02c7d014cb | ||
|
|
e29479fd25 | ||
|
|
46ae66c8b2 | ||
|
|
20abe4a40c | ||
|
|
55a9a29f76 | ||
|
|
67dd7e30fa | ||
|
|
e9a08af73b | ||
|
|
08b1ef2766 | ||
|
|
667d8673d4 | ||
|
|
d44ea9d558 | ||
|
|
86dfe86813 | ||
|
|
b129e11c8f | ||
|
|
246409970d | ||
|
|
bd7b3bb4d7 | ||
|
|
841f1849c8 | ||
|
|
00e7e4d43c | ||
|
|
ee43a845aa | ||
|
|
a7cc66af93 | ||
|
|
f8701cfed0 | ||
|
|
6b205bf303 | ||
|
|
07729247ac | ||
|
|
792b310fad | ||
|
|
ed2c9d24e8 | ||
|
|
f917212d67 | ||
|
|
6fbcec8b98 | ||
|
|
c30e57c31d | ||
|
|
674c9f0405 | ||
|
|
e16d5cf83d | ||
|
|
df3dc36874 | ||
|
|
7b71d8d212 | ||
|
|
2eaae4b83f | ||
|
|
3c66af9506 | ||
|
|
e5d579c1bb | ||
|
|
ee811ca89b | ||
|
|
8e4933041d | ||
|
|
1f3559e14f | ||
|
|
12ac260dce | ||
|
|
53f99ecc23 | ||
|
|
347410f744 | ||
|
|
7ae3e32abd | ||
|
|
54b2215164 | ||
|
|
889489e33e | ||
|
|
4667e76c6b | ||
|
|
a10d07d1dc | ||
|
|
9f1ac733b7 | ||
|
|
33ea25922d | ||
|
|
b56bd7bb5e | ||
|
|
e7617de1ee | ||
|
|
97fbad4dc5 | ||
|
|
ddf6023983 | ||
|
|
daa84c9415 | ||
|
|
586006de4d | ||
|
|
af8ec9f02b | ||
|
|
dc6abb691e | ||
|
|
d273c38194 | ||
|
|
940a246160 | ||
|
|
6179d1eef2 | ||
|
|
9f121f4c72 | ||
|
|
4deca552fa | ||
|
|
2d066307f4 | ||
|
|
0fdf426a93 | ||
|
|
e47698bfd4 | ||
|
|
e91981207f | ||
|
|
683ae4f067 | ||
|
|
94bc887369 | ||
|
|
d1094da6c7 | ||
|
|
ee1e12eca3 | ||
|
|
307fbef5b2 | ||
|
|
f54f500e2b | ||
|
|
fd66335bd4 | ||
|
|
301343deb0 | ||
|
|
c046c8c4d3 | ||
|
|
0ceca8fc17 | ||
|
|
2ac3b50040 | ||
|
|
70771bf3d6 | ||
|
|
2ed17f0fff | ||
|
|
ca53712ee9 | ||
|
|
69e139b75b | ||
|
|
d268661f29 | ||
|
|
ed379387df | ||
|
|
1eddad82c8 | ||
|
|
d7defc011b | ||
|
|
0e4a7829fe | ||
|
|
e7c6178391 | ||
|
|
40b506b7b4 | ||
|
|
b19a679d00 | ||
|
|
81d4c60cbd | ||
|
|
a95be2aa43 | ||
|
|
5e9a815b06 | ||
|
|
010c8eccc8 | ||
|
|
4b89619ef1 | ||
|
|
e5054d306e | ||
|
|
08eaa9274f | ||
|
|
3306cdede5 | ||
|
|
e7f650a9eb | ||
|
|
2804794f7a | ||
|
|
781eaf058b | ||
|
|
b4c155bdb8 | ||
|
|
7c3e045a57 | ||
|
|
f1fa1bbe4e | ||
|
|
d4f4c7b8c6 | ||
|
|
5eb46c570d | ||
|
|
9e6d501009 | ||
|
|
27e258b19b | ||
|
|
d3615c9661 | ||
|
|
a57c449c25 | ||
|
|
07f2f0cef3 | ||
|
|
c94f0896e1 | ||
|
|
182361c176 | ||
|
|
8dcc530f44 | ||
|
|
b5dfd62c99 | ||
|
|
662b1a8100 | ||
|
|
24178beafc | ||
|
|
e30df544db | ||
|
|
25b6e214dd | ||
|
|
910f6134f6 | ||
|
|
52b4b0419f | ||
|
|
bbc1a82c2a | ||
|
|
2ddbb44992 | ||
|
|
363a8b8588 | ||
|
|
5ae56aaec7 | ||
|
|
37057d1da2 | ||
|
|
6044871438 | ||
|
|
e612bb6a38 | ||
|
|
81ea67d6da | ||
|
|
96cd823fea | ||
|
|
ce9462e6aa | ||
|
|
2e57d80959 | ||
|
|
892d21fb77 | ||
|
|
c3f13eb428 | ||
|
|
91ae0206ac | ||
|
|
19b5eda98c | ||
|
|
6cbfe3d736 | ||
|
|
2274319274 | ||
|
|
a6934b3cba | ||
|
|
5be378e266 | ||
|
|
a9b6eaa31f | ||
|
|
01f9ce0015 | ||
|
|
f4309f5af6 | ||
|
|
1004ce564f | ||
|
|
5a1823d13e | ||
|
|
66fb66edbc | ||
|
|
f61971bc23 | ||
|
|
95a7980ada | ||
|
|
281e9d1357 | ||
|
|
742f05f59d | ||
|
|
79db4f8aa1 | ||
|
|
e440694987 | ||
|
|
b4a12edc61 | ||
|
|
2b04d6834f | ||
|
|
1191574e07 | ||
|
|
327eab6c0d | ||
|
|
0d8a0db883 | ||
|
|
9727062d60 | ||
|
|
09979d3270 | ||
|
|
116839d6f6 | ||
|
|
bed6c48e99 | ||
|
|
4d09e90b90 | ||
|
|
1d91cc6fc1 | ||
|
|
f4d7b71984 | ||
|
|
87cb61750f | ||
|
|
3231247fb6 | ||
|
|
eb53054f49 | ||
|
|
e147602a3a | ||
|
|
b7a016edcf | ||
|
|
371253a4f6 | ||
|
|
08ce6cce97 | ||
|
|
d7a10328c0 | ||
|
|
e7c657f82d | ||
|
|
3bae92b356 | ||
|
|
b937c37be3 | ||
|
|
0908369dba | ||
|
|
12543d2c2a |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -30,5 +30,5 @@ the [forum](https://discourse.nodered.org) or
|
|||||||
|
|
||||||
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
|
||||||
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
|
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
|
||||||
- [ ] I have run `grunt` to verify the unit tests pass
|
- [ ] I have run `npm run test` to verify the unit tests pass
|
||||||
- [ ] I have added suitable unit tests to cover the new/changed functionality
|
- [ ] I have added suitable unit tests to cover the new/changed functionality
|
||||||
|
|||||||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -14,25 +14,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out node-red repository
|
- name: Check out node-red repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: 'node-red'
|
path: 'node-red'
|
||||||
- name: Check out node-red-docker repository
|
- name: Check out node-red-docker repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'node-red/node-red-docker'
|
repository: 'node-red/node-red-docker'
|
||||||
path: 'node-red-docker'
|
path: 'node-red-docker'
|
||||||
- name: Check out node-red.github.io repository
|
- name: Check out node-red.github.io repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'node-red/node-red.github.io'
|
repository: 'node-red/node-red.github.io'
|
||||||
path: 'node-red.github.io'
|
path: 'node-red.github.io'
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- run: node ./node-red/.github/scripts/update-node-red-docker.js
|
- run: node ./node-red/.github/scripts/update-node-red-docker.js
|
||||||
- name: Create Docker Pull Request
|
- name: Create Docker Pull Request
|
||||||
uses: peter-evans/create-pull-request@v2
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||||
committer: GitHub <noreply@github.com>
|
committer: GitHub <noreply@github.com>
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
||||||
- run: node ./node-red/.github/scripts/update-node-red-website.js
|
- run: node ./node-red/.github/scripts/update-node-red-website.js
|
||||||
- name: Create Website Pull Request
|
- name: Create Website Pull Request
|
||||||
uses: peter-evans/create-pull-request@v2
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||||
committer: GitHub <noreply@github.com>
|
committer: GitHub <noreply@github.com>
|
||||||
|
|||||||
12
.github/workflows/tests.yml
vendored
12
.github/workflows/tests.yml
vendored
@@ -12,16 +12,15 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
permissions:
|
permissions:
|
||||||
checks: write # for coverallsapp/github-action to create new checks
|
|
||||||
contents: read # for actions/checkout to fetch code
|
contents: read # for actions/checkout to fetch code
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14, 16]
|
node-version: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
@@ -29,8 +28,3 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
npm run test
|
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 }}
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ docs
|
|||||||
.vscode
|
.vscode
|
||||||
.nyc_output
|
.nyc_output
|
||||||
sync.ffs_db
|
sync.ffs_db
|
||||||
|
package-lock.json
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
/Gruntfile.js
|
|
||||||
/.git/*
|
|
||||||
*.backup
|
|
||||||
/public/*
|
|
||||||
528
CHANGELOG.md
528
CHANGELOG.md
@@ -1,352 +1,306 @@
|
|||||||
#### 3.1.0-beta.1: Beta Release
|
#### 4.0.9: Maintenance Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Add details for the dynamic subscription to match the English docs (#5050) @aikitori
|
||||||
|
- Fix tooltip snapping based on `typedInput` type (#5051) @GogoVega
|
||||||
|
- Prevent symbol usage warning in monaco (#5049) @Steve-Mcl
|
||||||
|
- Show subflow flow context under node section of sidebar (#5025) @knolleary
|
||||||
|
- feat: Add custom label for default deploy button in settings.editorTheme (#5030) @matiseni51
|
||||||
|
- Handle long auto-complete suggests (#5042) @knolleary
|
||||||
|
- Handle undefined username when generating user icon (#5043) @knolleary
|
||||||
|
- Handle dragging node into group and splicing link at same time (#5027) @knolleary
|
||||||
|
- Remember context sidebar tree state when refreshing (#5021) @knolleary
|
||||||
|
- Update sf instance env vars when removed from template (#5023) @knolleary
|
||||||
|
- Do not select group when triggering quick-add within it (#5022) @knolleary
|
||||||
|
- Fix library icon handling within library browser component (#5017) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
- Allow env var access to context (#5016) @knolleary
|
||||||
|
- fix debug status reporting if null (#5018) @dceejay
|
||||||
|
- Fix grunt dev via better ndoemon ignore rules (#5015) @knolleary
|
||||||
|
- Fix typo in CHANGELOG (4.0.7-->4.0.8) (#5007) @natcl
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
- Switch: Avoid exceeding call stack when draining message group in Switch (#5014) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.8: Maintenance Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- NEW: Locking Flows (#3938) @knolleary
|
- Fix config node sort order when importing (#5000) @knolleary
|
||||||
- NEW: Improve UX around hiding flows via context menu (#3930) @knolleary
|
|
||||||
- NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama
|
|
||||||
- NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama
|
|
||||||
- NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary
|
|
||||||
- NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
- Remember compact/pretty flow export user choice (#3974) @Steve-Mcl
|
#### 4.0.7: Maintenance Release
|
||||||
- fix .red-ui-notification class (#4035) @xiaobinqt
|
|
||||||
- Fix border radius on Modules list header (#4038) @bonanitech
|
Editor
|
||||||
- fix workspace reference error in case of empty tabs (#4029) @HiroyasuNishiyama
|
|
||||||
- Disable delete tab menu when single tab exists (#4030) @HiroyasuNishiyama
|
- Fix def can be undefined if the type is missing (#4997) @GogoVega
|
||||||
- Disable hide all menu if all tabs hidden (#4031) @HiroyasuNishiyama
|
- Fix the user list of nested config node (#4995) @GogoVega
|
||||||
- fix hide subflow tooltip (#4033) @HiroyasuNishiyama
|
- Support custom login message and button (#4993) @knolleary
|
||||||
- Fix disabled menu items in project feature (#4027) @kazuhitoyokoi
|
|
||||||
- Let themes change radialMenu text colors (#3995) @bonanitech
|
#### 4.0.6: Maintenance Release
|
||||||
- Add Japanese translations for v3.0.3 (#4012) @kazuhitoyokoi
|
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3997) @kazuhitoyokoi
|
Editor
|
||||||
- Add Japanese translation for v3.1.0-beta.0 (#3916) @kazuhitoyokoi
|
|
||||||
- Hide subflow category after deleting subflow (#3980) @kazuhitoyokoi
|
- Roll up various fixes on config node change history (#4975) @knolleary
|
||||||
- Prevent dbl-click opening node edit dialog with text selected (#3970) @knolleary
|
- Add quotes when installing local tgz to fix spacing in the file path (#4949) @AGhorab-upland
|
||||||
- Handle replacing unknown node inside group or subflow (#3921) @knolleary
|
- Validate json dropped into editor to avoid unhelpful error messages (#4964) @knolleary
|
||||||
- Fix #3939, red border red-ui-typedInput-container (#3949) @Steveorevo
|
- Fix junction insert position via context menu (#4974) @knolleary
|
||||||
- i18n item URL copy notification & add Japanese message (#3946) @HiroyasuNishiyama
|
- Apply zoom scale when calculating annotation positions (#4981) @knolleary
|
||||||
- add Japanese message for item url copy actions (#3947) @HiroyasuNishiyama
|
- Handle the import of an incomplete Subflow (#4811) @GogoVega
|
||||||
- Fix autocomplete entry for responseUrl (#3884) @knolleary
|
- Fix updating the Subflow name during a copy (#4809) @GogoVega
|
||||||
- Fix Japanese translation for JSONata editor (#3872) @HiroyasuNishiyama
|
- Rename variable to avoid confusion in view.js (#4963) @knolleary
|
||||||
- Fix search type with spaces (#3841) @Steve-Mcl
|
- Change groups.length to groups.size (#4959) @hungtcs
|
||||||
- Fix error hanndling of JSONata expression editor for extended functions (#3871) @HiroyasuNishiyama
|
- Remove disabled node types from QuickAddDialog list (#4946) @GogoVega
|
||||||
- Add button type to the adding SSH key button (#3866) @kazuhitoyokoi
|
- Fix `setModulePendingUpdated` with plugins (#4939) @GogoVega
|
||||||
- Check radio button as default in project dialog (#3879) @kazuhitoyokoi
|
- Missing getSubscriptions in the docs while its implemented (#4934) @ersinpw
|
||||||
- Add $clone as supported function (#3874) @HiroyasuNishiyama
|
- Apply `envVarExcludes` setting to `util.getSetting` into the function node (#4925) @GogoVega
|
||||||
- Env var jsonata (#3807) @HiroyasuNishiyama
|
- Fix `envVar` editable list should be sortable (#4932) @GogoVega
|
||||||
- Add Japanese translation for v3.0.2 (#3852) @kazuhitoyokoi
|
- Improve the node name auto-generated with the first available number (#4912) @GogoVega
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Force IPv4 name resolution to have priority (#4019) @dceejay
|
- Get the env config node from the parent subflow (#4960) @GogoVega
|
||||||
- Fix async loading of modules containing both nodes and plugins (#3999) @knolleary
|
- Update dependencies (#4987) @knolleary
|
||||||
- Use main branch as default in project feature (#4036) @kazuhitoyokoi
|
|
||||||
- Rename package var to avoid strict mode error (#4020) @knolleary
|
|
||||||
- Fix typos in settings.js (#4013) @ypid
|
|
||||||
- Ensure credentials object is removed before returning node in getFlow request (#3971) @knolleary
|
|
||||||
- Ignore commit error in project feature (#3987) @kazuhitoyokoi
|
|
||||||
- Update dependencies (#3969) @knolleary
|
|
||||||
- Add check that node sends object rather than primitive type (#3909) @knolleary
|
|
||||||
- Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname (#3912) @knolleary
|
|
||||||
- Fix nodesDir scan when node package has js/html in sub dir to package.json (#3867) @Steve-Mcl
|
|
||||||
- Fix file permissions (#3917) @kazuhitoyokoi
|
|
||||||
- ci: add minimum GitHub token permissions for workflows (#3907) @boahc077
|
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- Catch: fix typo in catch.html (#3965) @we11adam
|
- Performance : make reading single buffer / string file faster by not re-allocating and handling huge buffers (#4980) @Fadoli
|
||||||
- Change: Fix change node overwriting msg with itself (#3899) @dceejay
|
- Make delay node rate limit reset consistent - not send on reset. (#4940) @dceejay
|
||||||
- Comment node: Clarify where the text will appear (#4004) @dirkjanfaber
|
- Fix trigger node date handling for latest time type input (#4915) @dceejay
|
||||||
- CSV: change replace to replaceAll (#3990) @dceejay
|
- Fix delay node not dropping when nodeMessageBufferMaxLength is set (#4973)
|
||||||
- CSV node: check header properties for ' and " (#3920) @dceejay
|
- Ensure node.sep is honoured when generating CSV (#4982) @knolleary
|
||||||
- CSV: Fix for CSV undefined property (#3906) @dceejay
|
|
||||||
- Delay: let delay node handle both flush then reset (#3898) @dceejay
|
|
||||||
- Function: Limit number of ports in function node (#3886) @kazuhitoyokoi
|
|
||||||
- Function: Remove dot from variable name for external module in function node (#3880) @kazuhitoyokoi
|
|
||||||
- Function: add function node monaco types util and promisify (#3868) @Steve-Mcl
|
|
||||||
- HTTP In: Ensure msg.req.headers is enumerable (#3908) @knolleary
|
|
||||||
- HTTP Request: Support form-data arrays (#3991) @hardillb
|
|
||||||
- HTTP Request: Fix httprequest tests to be more lenient on error message (#3922) @knolleary
|
|
||||||
- HTTP Request: Add missing property to node object HTTPRequest (#3842) @hardillb
|
|
||||||
- HTTP Request/Response: Support sortable list on property UI of http request and http response nodes (#3857) @kazuhitoyokoi
|
|
||||||
- HTTP Response: Ensure statusCode is a number (#3894) @hardillb
|
|
||||||
- Inject: Allow Inject node to work with async context stores (#4021) @knolleary
|
|
||||||
- Join/Batch: Add count to join and batch node labels (#4028) @dceejay
|
|
||||||
- MQTT: Fix birth topic handling in MQTT node (#3905) @Steve-Mcl
|
|
||||||
- MQTT: Fix pull-down menus of MQTT configuration node (#3890) @kazuhitoyokoi
|
|
||||||
- MQTT: Prevent invalid mqtt birth topic crashing node-red (#3869) @Steve-Mcl
|
|
||||||
- MQTT: ensure sessionExpiry(Interval) is applied (#3840) @Steve-Mcl
|
|
||||||
- MQTT: Fix mqtt nodes not reconnecting on modified-flows deploy (#3992) @knolleary
|
|
||||||
- MQTT: fix single subscription mqtt node status (#3966) @Steve-Mcl
|
|
||||||
- Range: Add drop mode to range node (#3935) @dceejay
|
|
||||||
- Remove done from describe (#3873) @HiroyasuNishiyama
|
|
||||||
- Split node: avoid duplicate done call for buffer split (#4000) @knolleary
|
|
||||||
- Status: Fix typo in 25-status.html (#3981) @kazuhitoyokoi
|
|
||||||
- TCP Node: ensure newline substitution applies to whole message (#4009) @dceejay
|
|
||||||
- Template: Add information about environment variable to template node (#3882) @kazuhitoyokoi
|
|
||||||
- Trigger: Hide trigger node repeat send option if sending nothing (#4023) @dceejay
|
|
||||||
- Watch: fix watch node test on MacOS/ARM (#3942) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.2: Maintenance Release
|
#### 4.0.5: Maintenance Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Fix workspace chart bottom property (#3812) @bonanitech
|
- Refix link call node can call out of a subflow (#4908) @GogoVega
|
||||||
- Update german translation (#3802) @Dennis14e
|
|
||||||
- Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi
|
#### 4.0.4: Maintenance Release
|
||||||
- Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary
|
|
||||||
- Hide scrollbars until they're needed (#3808) @bonanitech
|
Editor
|
||||||
- Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary
|
|
||||||
- remove console.log (#3820) @Steve-Mcl
|
- Fix `link call` node can call out of a subflow (#4892) @GogoVega
|
||||||
|
- Fix wrong unlock state when event is triggered after deployment (#4889) @GogoVega
|
||||||
|
- i18n(App) update with latest language file changes (#4903) @joebordes
|
||||||
|
- fix typo: depreciated (#4895) @dxdc
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Register subflow module instance node with parent flow (#3818) @knolleary
|
- Update dev dependencies (#4893) @knolleary
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
|
- MQTT: Allow msg.userProperties to have number values (#4900) @hardillb
|
||||||
|
|
||||||
- HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb
|
#### 4.0.3: Maintenance Release
|
||||||
|
|
||||||
#### 3.0.1: Maintenance Release
|
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl
|
- Refresh page title after changing tab name (#4850) @kazuhitoyokoi
|
||||||
- Sys info (diagnostics report) amendments (#3793) @Steve-Mcl
|
- Add Japanese translations for v4.0.2 (again) (#4853) @kazuhitoyokoi
|
||||||
- Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl
|
- Stay in quick-add mode following context menu insert (#4883) @knolleary
|
||||||
- Fix focus issues (#3789) @Steve-Mcl
|
- Do not include Junction type in quick-add for virtual links (#4879) @knolleary
|
||||||
- Ensure all typedInput buttons have button type set (#3788) @knolleary
|
- Multiplayer cursor tracking (#4845) @knolleary
|
||||||
- Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary
|
- Hide add-flow options when disabled via editorTheme (#4869) @knolleary
|
||||||
- Properly position quick-add dialog in all cases (#3786) @knolleary
|
- Fix env-var config select when multiple defined (#4872) @knolleary
|
||||||
- Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary
|
- Fix subflow outbound-link filter (#4857) @GogoVega
|
||||||
- Remove use of Object.hasOwn (#3784) @knolleary
|
- Add French translations for v4.0.2 (#4856) @GogoVega
|
||||||
|
- Fix moving link wires (#4851) @knolleary
|
||||||
#### 3.0.0: Milestone Release
|
- Adjust type search dialog position to prevent x-overflow (#4844) @Steve-Mcl
|
||||||
|
- fix: modulesInUse might be undefined (#4838) @lorenz-maurer
|
||||||
Editor
|
- Add Japanese translations for v4.0.2 (#4849) @kazuhitoyokoi
|
||||||
|
- Fix menu to enable/disable selection when it's a group (#4828) @GogoVega
|
||||||
- Use theme page and header values if settings.js values are not present (#3767) @Steve-Mcl
|
|
||||||
- Focus editor for undo after some actions in menu (#3759) @kazuhitoyokoi
|
|
||||||
- Ensure node icon shade has properly rounded corners (#3763) @knolleary
|
|
||||||
- Fix storing subflow credential type when input has multiple types (#3762) @knolleary
|
|
||||||
- Ensure global-config and flow-config have info in the hierarchy popover (#3752) @Steve-Mcl
|
|
||||||
- Include dirty state in history event (#3748) @Steve-Mcl
|
|
||||||
- Fix display direction of context sub-menu (#3746) @knolleary
|
|
||||||
- Fix clear pinned paths of debug sidebar menu (#3745) @HiroyasuNishiyama
|
|
||||||
- prevent exception generating tooltip for deleted nodes (#3742) @Steve-Mcl
|
|
||||||
- Fix context menu issues ready for v3 beta.5 (#3741) @Steve-Mcl
|
|
||||||
- Do not generate new node-ids when pasting a cut flow (#3729) @knolleary
|
|
||||||
- Fix to prevent node from moving out of workspace (#3731) @HiroyasuNishiyama
|
|
||||||
- Don't let themes change disabled config node background color (#3736) @bonanitech
|
|
||||||
- Move colors left behind in #3692 to CSS variables (#3737) @bonanitech
|
|
||||||
- Fix handling of global debug message (#3733) @HiroyasuNishiyama
|
|
||||||
- Fix label overflow @ config-node palette (#3730) @ralphwetzel
|
|
||||||
- Fix defaulting to monaco if settings does not contain codeEditor (#3732) @knolleary
|
|
||||||
- Disable keyboard shortcut mapping when showing Edit[..]Dialog (#3700) @ralphwetzel
|
|
||||||
- Update add-junction menu to work in more cases (#3727) @knolleary
|
|
||||||
- Ensure importMap is not null when using import UI (#3723) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.4 (#3724) @kazuhitoyokoi
|
|
||||||
- Fix "split with" on virtual links (#3766) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Do not remove unknown credentials of Subflow Modules (#3728) @knolleary
|
- Update dependencies (#4874) @knolleary
|
||||||
- Add missing entries from beta.4 changelog (#3721) @knolleary
|
- GitHub: Add citation file to enable "Cite this repository" feature (#4861) @lobis
|
||||||
|
- Remove use of util.log (#4875) @knolleary
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- Change: Fix change node, not handling from field properly when using context (#3754) @Fadoli
|
- Fix invalid property error in range node example (#4855)
|
||||||
- Link Call: Fix linkcall registry bugs (#3751) @Steve-Mcl
|
- Fix typo in flow example name (#4854) @kazuhitoyokoi
|
||||||
- WebSocket: Fix close timeout of websocket node (#3734) @HiroyasuNishiyama
|
- Move SNI, ALPN and Verify Server cert out of check (#4882) @hardillb
|
||||||
|
- Set status of mqtt nodes to "disconnected" when deregistered from broker (#4878) @Steve-Mcl
|
||||||
|
- MQTT: Ensure will payload is a string (#4873) @knolleary
|
||||||
|
- Let batch node terminate "early" if msg.parts set to end of sequence (#4829) @dceejay
|
||||||
|
- Fix unintentional Capitalisation in Split node name (#4835) @dceejay
|
||||||
|
|
||||||
#### 3.0.0-beta.4: Beta Release
|
#### 4.0.2: Maintenance Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Move all colours to CSS variables (#3692) @bonanitech
|
- Use a more subtle border on the header (#4818) @bonanitech
|
||||||
- Fix clicking on node in workspace to hide context menu (#3696) @knolleary
|
- Improve the editor's French translations (#4824) @GogoVega
|
||||||
- Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama
|
- Clean up orphaned editors (#4821) @Steve-Mcl
|
||||||
- Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl
|
- Fix node validation if the property is not required (#4812) @GogoVega
|
||||||
- Update german translation (#3691) @Dennis14e
|
- Ensure mermaid.min.js is cached properly between loads of the editor (#4817) @knolleary
|
||||||
- List welcome tours in help sidebar (#3717) @knolleary
|
|
||||||
- Ensure 'hidden flow' count doesn't include subflows (#3715) @knolleary
|
|
||||||
- Fix Chinese translate (#3706) @hotlong
|
|
||||||
- Fix use default button for node icon (#3714) @kazuhitoyokoi
|
|
||||||
- Fix select boxes vertical alignment (#3698) @bonanitech
|
|
||||||
- Ensure workspace clean after undoing dropped node (#3708) @Steve-Mcl
|
|
||||||
- Use solid colour as config node icon background to hide text overflow (#3710) @Steve-Mcl
|
|
||||||
- Increase quick-add height to reveal 2 most recent entries (#3711) @Steve-Mcl
|
|
||||||
- Set default editor to monaco in absence of user preference (#3702) @knolleary
|
|
||||||
- Add Japanese translations for v3.0-beta.3 (#3688) @kazuhitoyokoi
|
|
||||||
- Fix handling of spacebar inside JSON visual editor (#3687) @knolleary
|
|
||||||
- Fix menu padding to handle both icons and submenus (#3686) @knolleary
|
|
||||||
- Include scroll offset when positioning quick-add dialog (#3685) @knolleary
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Allow flows to be stopped and started manually (#3719) @knolleary
|
- Allow auth cookie name to be customised (#4815) @knolleary
|
||||||
- Import default export if node is a transpiled es module (#3669) @dschmidt
|
- Guard against undefined sessions in multiplayer (#4816) @knolleary
|
||||||
- Leave Monaco theme commented out by default (#3704) @bonanitech
|
|
||||||
|
|
||||||
Nodes
|
#### 4.0.1: Maintenance Release
|
||||||
|
|
||||||
- CSV: Fix CSV node to handle when outputting text fields (#3716) @dceejay
|
|
||||||
- Delay: Fix delay rate limit last timing when empty (#3709) @dceejay
|
|
||||||
- Link: Ensure link-call cache is updated when link-in is modified (#3695) @Steve-Mcl
|
|
||||||
- Join: Join node in reduce mode doesn't keep existing msg properties (#3670) @dceejay
|
|
||||||
- Template: Add support for evalulating {{env.<var>}} within a template node (#3690) @cow0w
|
|
||||||
|
|
||||||
#### 3.0.0-beta.3: Beta Release
|
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Add Right-Click content menu (#3678) @knolleary
|
- Ensure subflow instance credential property values are extracted (#4802) @knolleary
|
||||||
- Fix disable junction (#3671) @HiroyasuNishiyama
|
- Use `_ADD_` value for both `add new...` and `none` options (#4800) @GogoVega
|
||||||
- Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi
|
- Fix the config node select value assignment (#4788) @GogoVega
|
||||||
- Reset mouse state when switching tabs (#3643) @knolleary
|
- Add tooltip for number of subflow instance on info tab (#4786) @kazuhitoyokoi
|
||||||
- Fix uncorrect fix of junction to subflow conversion (#3666) @HiroyasuNishiyama
|
- Add Japanese translations for v4.0.0 (#4785) @kazuhitoyokoi
|
||||||
- Fix undoing junction to subflow (#3653) @HiroyasuNishiyama
|
|
||||||
- Fix conversion of junction to subflow (#3652) @HiroyasuNishiyama
|
|
||||||
- Fix to include junction to exported nodes (#3650) @HiroyasuNishiyama
|
|
||||||
- Fix z-index value for shade to cover nodes in palette (#3649) @kazuhitoyokoi
|
|
||||||
- Fix to extend escaped subflow category characters (#3647) @HiroyasuNishiyama
|
|
||||||
- Fix to sanitize tab name (#3646) @HiroyasuNishiyama
|
|
||||||
- Fix selector placement (#3644) @bonanitech
|
|
||||||
- Add Japanese translations for v3.0-beta.2 (#3622) @kazuhitoyokoi
|
|
||||||
- Fix new folder menu of save to library dialog (#3633) @HiroyasuNishiyama
|
|
||||||
- Fix layer of palette node (#3638) @HiroyasuNishiyama
|
|
||||||
- Fix to place a node dragged from palette within the workspace (#3637) @HiroyasuNishiyama
|
|
||||||
- Fix typo in CSS (#3628) @bonanitech
|
|
||||||
- Use the correct variable for the gutter text color (#3615) @bonanitech
|
|
||||||
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Support loading node modules from `nodesdir` (#3676) @Steve-Mcl
|
- Ensure group nodes are properly exported in /flow api (#4803) @knolleary
|
||||||
- fix buffer parse error message of evaluateNodeProperty (#3624) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- File: Further simplify file node filename entry UX (v3) (#3677) @Steve-Mcl
|
- Joins: make using msg.parts optional in join node (#4796) @dceejay
|
||||||
- Function: Fix initial cursor position of init/finalize tab of function node (#3674) @HiroyasuNishiyama
|
- HTTP Request: UI proxy should setup agents for both http_proxy and https_proxy (#4794) @Steve-Mcl
|
||||||
- Function: Fix ESM module loading in Function node (#3645) @knolleary
|
- HTTP Request: Remove default user agent (#4791) @Steve-Mcl
|
||||||
- Inject: Fix JSONata evaluation of inject button (#3632) @HiroyasuNishiyama
|
|
||||||
- TCP: Dont delete TCP socket twice (#3630) @Steve-Mcl
|
|
||||||
- MQTT Node: define noproxy variable (#3626) @Steve-Mcl
|
|
||||||
- Debug: i18n debug sidebar node label (#3623) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
#### 3.0.0-beta.2: Beta Release
|
#### 4.0.0: Milestone Release
|
||||||
|
|
||||||
**Migration from 2.x**
|
This marks the next major release of Node-RED. The following changes represent
|
||||||
|
those added since the last beta. Check the beta release details below for the complete
|
||||||
|
list.
|
||||||
|
|
||||||
- The 'slice wires' action has changed from Ctrl-RightMouseButton to Alt-LeftMouseButton
|
Breaking Changes
|
||||||
|
|
||||||
|
- Node-RED now requires Node 18.x or later. At the time of release, we recommend
|
||||||
|
using Node 20.
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Rework Junctions to be more node like in their event handling (#3607) @knolleary
|
- Add `httpStaticCors` (#4761) @knolleary
|
||||||
- Change slicing / slice-junction operations over to mouse button 0 (Left Mouse Button) (#3609) @Steve-Mcl
|
- Update dependencies (#4763) @knolleary
|
||||||
- Do not slice-junction link node wires (#3608) @knolleary
|
- Sync master to dev (#4756) @knolleary
|
||||||
- Handle many-to-one slicing of wires (#3604) @knolleary
|
- Add tooltip and message validation to `typedInput` (#4747) @GogoVega
|
||||||
- Ensure ACE worker options are set (#3611) @Steve-Mcl
|
- Replace bcrypt with @node-rs/bcrypt (#4744) @knolleary
|
||||||
- Remove duplicate history add of ungroup event (#3605) @knolleary
|
- Export Nodes dialog refinement (#4746) @Steve-Mcl
|
||||||
- use text width instead of number of characters for deciding select fi… (#3603) @HiroyasuNishiyama
|
|
||||||
- Update Japanese info of link call node reflecting update of English info (#3600) @HiroyasuNishiyama
|
|
||||||
- Fix typedInput label not visible on themes (#3580) @bonanitech
|
|
||||||
- Fix project switching when junctions are present (#3595) @Steve-Mcl
|
|
||||||
- Fix junction: when wiring from a regular nodes INPUT, backwards to a junction (#3591) @Steve-Mcl
|
|
||||||
- Fix error initialising flow tab editor (#3585) @Steve-Mcl
|
|
||||||
- Add Japanese translations for v3.0-beta.1 (#3576) @kazuhitoyokoi
|
|
||||||
- Fix image paths where `red/image/typedInput/XXXX.png` should be `red/image/typedInput/XXXX.svg` (#3592) @kazuhitoyokoi
|
|
||||||
- Fix browser console error Uncaught TypeError when searching certain terms (#3584) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
|
||||||
|
|
||||||
- fix error on system-info action (#3589) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
Nodes
|
|
||||||
|
|
||||||
- I18n switch rule selector (#3602) @HiroyasuNishiyama
|
|
||||||
- Handle removal of event handlers to allow mqtt client.end() to work (#3594) @PhilDay-CT
|
|
||||||
- update link-call node info according to current behavior (#3597) @HiroyasuNishiyama
|
|
||||||
|
|
||||||
|
|
||||||
#### 3.0.0-beta.1: Beta Release
|
|
||||||
|
|
||||||
**Migration from 2.x**
|
|
||||||
|
|
||||||
- Node-RED now requires Node.js 14.x or later.
|
|
||||||
- New installs of Node-RED will default to the monaco editor.
|
|
||||||
|
|
||||||
|
#### 4.0.0-beta.4: Beta Release
|
||||||
|
|
||||||
Editor
|
Editor
|
||||||
|
|
||||||
- Add Junctions (#3462) @knolleary
|
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
|
||||||
- Allow node name to be auto-generated when added (#3478, #3538) @knolleary
|
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
|
||||||
- Set monaco as default code editor as of v3.x (#3543) @Steve-Mcl
|
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
|
||||||
- Update Monaco to V0.33.0 (#3522) @Steve-Mcl
|
- Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi
|
||||||
- Auto-complete Improvements (#3521) @Steve-Mcl
|
- Translate the number of items selected in the options list (#4730) @GogoVega
|
||||||
- Add a tooltip to debug sidebar messages to reveal full path to node (#3503) @knolleary
|
- Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega
|
||||||
- Fix down arrow triggering menu in search box (#3507) @Steve-Mcl
|
- Deleting a grouped node should update the group (#4714) @GogoVega
|
||||||
- Add Japanese translations for v3.0 (#3512) @kazuhitoyokoi
|
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
|
||||||
- Add feature: Continuous search tools (search previous, search next) (#3405) @Steve-Mcl
|
- Add missing tooltips to Sidebar (#4713) @GogoVega
|
||||||
- Add feature: split-wire-to-links (#3399, #3476) @Steve-Mcl
|
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
|
||||||
- Add copy button to node properties tables (#3390) @knolleary
|
- Update to Monaco 0.49.0 (#4725) @Steve-Mcl
|
||||||
- Add info-tab search options dropdown to the regular search (#3395) @Steve-Mcl
|
- Add Japanese translations for 4.0.0-beta.3 (#4726) @kazuhitoyokoi
|
||||||
- New Feature: Add ability to find modified nodes/flows. (#3392) @Steve-Mcl
|
- Show lock on deploy if user is read-only (#4706) @knolleary
|
||||||
- Code editor ux improvements around remembering state of each code editor in a flow (#3553) @Steve-Mcl
|
|
||||||
- Make it easier to apply themes on SVG icons (#3515) @bonanitech
|
|
||||||
- Add support of property validation message (#3438) @HiroyasuNishiyama
|
|
||||||
- Ensure node validation tooltip is closed when field becomes valid (#3570) @knolleary
|
|
||||||
- Add "search for" buttons to notifications (#3567) @Steve-Mcl
|
|
||||||
- Don't let themes change node config colors (#3564) @bonanitech
|
|
||||||
- Fix gap between typedInput containers borders (#3560) @bonanitech
|
|
||||||
- Fix recording removed links in edit history (#3547) @knolleary
|
|
||||||
- Remove unused SASS vars (#3536) @bonanitech
|
|
||||||
- Add custom style for jQuery widgets borders (#3537) @bonanitech
|
|
||||||
- fix out of scope reference of hasUnusedConfig variable (#3535) @HiroyasuNishiyama
|
|
||||||
- correct "non string" check parenthesis (#3524) @Steve-Mcl
|
|
||||||
- Ensure i18n of scoped package name (#3516) @Steve-Mcl
|
|
||||||
- Prevent shortcut deploy when deploy button shaded (#3517) @Steve-Mcl
|
|
||||||
- Fix: Sidebar "Configuration" filter button tooltip (#3500) @ralphwetzel
|
|
||||||
- Add the ability to customize diff colors even more (#3499) @bonanitech
|
|
||||||
- Do JSON comparison of old value/new value in editor (#3481) @Steve-Mcl
|
|
||||||
- Fix nodes losing their wires when in an iframe (#3484) @zettca
|
|
||||||
- Improve scroll into view (#3468) @Steve-Mcl
|
|
||||||
- Do not show 1st tab if hidden when loading (#3464) @Steve-Mcl
|
|
||||||
|
|
||||||
Runtime
|
Runtime
|
||||||
|
|
||||||
- Fix importing external module from node-red module (#3541) @knolleary
|
- Ensure all CSS variables are in the output file (#3743) @bonanitech
|
||||||
- Add support for multiple static paths with optional static root (#3542) @Steve-Mcl
|
- Add httpAdminCookieOptions (#4718) @knolleary
|
||||||
- Store external token when authenticating if provided (#3460) @ArFe
|
- chore: migrate deprecated `util.isArray` (#4724) @Rotzbua
|
||||||
- Support OAuth/OpenID logout (#3388) @mw75
|
- Add --version cli args (#4707) @knolleary
|
||||||
- Allow adminAuth to auto-login users when using passport strategy (#3519) @knolleary
|
- feat(grunt): fail if files are missing (#4739) @Rotzbua
|
||||||
- Add runtime diagnostics admin endpoint (#3511) @Steve-Mcl
|
- fix(node-red-pi): node-red not started by path (#4736) @Rotzbua
|
||||||
- Don't start if user has no home directory (#3540) @hardillb
|
- fix(editor): remove trailing slash (#4735) @Rotzbua
|
||||||
- Error on invalid encrypted credentials (#3498) @sammachin
|
- fix: remove deprecated mqtt.js (#4733) @Rotzbua
|
||||||
|
|
||||||
Nodes
|
Nodes
|
||||||
|
|
||||||
- Debug: Add message count option to Debug status (#3544 #3551) @rafaelmuynarsk @knolleary
|
- Perform Proxy logic more like cURL (#4616) @Steve-Mcl
|
||||||
- File: Change basic Filename field to a typedInput (#3533) @Steve-Mcl
|
|
||||||
- HTTP Request: Add UI for Http Request node headers (#3488) @Steve-Mcl
|
#### 4.0.0-beta.3: Beta Release
|
||||||
- Inject: let inject optionally fire at start in only at time mode. (#3385) @dceejay
|
|
||||||
- Link Call: Dynamic link call (#3463) @Steve-Mcl
|
Editor
|
||||||
- Link Call: Display link targets of nodes in a regular flow, for Link Call nodes inside a subflow (#3528) @Steve-Mcl
|
|
||||||
- MQTT: MQTT payload auto parsing improvements (#3530) @Steve-Mcl
|
- Improve background-deploy notification handling (#4692) @knolleary
|
||||||
- MQTT: Add client and Runtime MQTT topic validation (#3563) @Steve-Mcl [dev]
|
- Hide workspace tab on middle mouse click (#4657) @Steve-Mcl
|
||||||
- MQTT: save and restore v5 config user props (#3562) @Steve-Mcl
|
- multiplayer: Add user presence indicators (#4666) @knolleary
|
||||||
- MQTT: Fix incorrect MQTT status (#3552) @Steve-Mcl
|
- Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi
|
||||||
- MQTT: fix reference error of msg.status in debug node (#3526) @HiroyasuNishiyama
|
- Add French translations for 4.0.0-beta.2 (#4681) @GogoVega
|
||||||
- MQTT: Add unit tests for MQTT nodes (#3497) @Steve-Mcl
|
- Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi
|
||||||
- MQTT: fix typo of will properties (#3502) @Steve-Mcl
|
- Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary
|
||||||
- MQTT: ensure mqtt v5 props can be set false (#3472) @Steve-Mcl
|
- Add npm install timeout notification (#4662) @hardillb
|
||||||
- Switch: add check for NaN in is of type number to be false (#3409) @dceejay
|
- Fix undo of subflow env property edits (#4667) @knolleary
|
||||||
- TCP: TCP node better split (#3465) @dceejay
|
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
||||||
- Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary
|
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
||||||
- WebSocket: call done after ws disconnects (#3531) @Steve-Mcl
|
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Allow blank strings to be used for env var property substitutions (#4672) @knolleary
|
||||||
|
- Use rfdc for cloning pure JSON values (#4679) @knolleary
|
||||||
|
- fix: remove outdated Node 11+ check (#4314) @Rotzbua
|
||||||
|
- feat(ci): add new nodejs v22 (#4694) @Rotzbua
|
||||||
|
- fix(node): increase required node >=18.5 (#4690) @Rotzbua
|
||||||
|
- fix(dns): remove outdated node check (#4689) @Rotzbua
|
||||||
|
- fix(polyfill): remove import module polyfill (#4688) @Rotzbua
|
||||||
|
- Fix typo (#4686) @Rotzbua
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
||||||
|
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
||||||
|
|
||||||
|
#### 4.0.0-beta.2: Beta Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Introduce multiplayer feature (#4629) @knolleary
|
||||||
|
- Separate the "add new config-node" option into a new (+) button (#4627) @GogoVega
|
||||||
|
- Retain Palette categories collapsed and filter to localStorage (#4634) @knolleary
|
||||||
|
- Ensure palette filter reapplies and clear up unknown categories (#4637) @knolleary
|
||||||
|
- Add support for plugin (only) modules to the palette manager (#4620) @knolleary
|
||||||
|
- Update monaco to latest and node types to 18 LTS (#4615) @Steve-Mcl
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- Fix handling of subflow config-node select type in sf module (#4643) @knolleary
|
||||||
|
- Comms API updates (#4628) @knolleary
|
||||||
|
- Add French translations for 4.0.0-beta.1 (#4621) @GogoVega
|
||||||
|
- Add Japanese translations for 4.0.0-beta.1 (#4612) @kazuhitoyokoi
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
- Fix change node handling of replacing with boolean (#4639) @knolleary
|
||||||
|
|
||||||
|
#### 4.0.0-beta.1: Beta Release
|
||||||
|
|
||||||
|
Editor
|
||||||
|
|
||||||
|
- Click on id in debug panel highlights node or flow (#4439) @ralphwetzel
|
||||||
|
- Support config selection in a subflow env var (#4587) @Steve-Mcl
|
||||||
|
- Add timestamp formatting options to TypedInput (#4468) @knolleary
|
||||||
|
- Allow RED.view.select to select links (#4553) @lgrkvst
|
||||||
|
- Add auto-complete to flow/global/env typedInput types (#4480) @knolleary
|
||||||
|
- Improve the appearance of the Node-RED primary header (#4598) @joepavitt
|
||||||
|
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
- let settings.httpNodeAuth accept single middleware or array of middlewares (#4572) @kevinGodell
|
||||||
|
- Upgrade to JSONata 2.x (#4590) @knolleary
|
||||||
|
- Bump minimum version to node 18 (#4571) @knolleary
|
||||||
|
- npm: Remove production flag on npm invocation (#4347) @ZJvandeWeg
|
||||||
|
- Timer testing fix (#4367) @hlovdal
|
||||||
|
- Bump to 4.0.0-dev (#4322) @knolleary
|
||||||
|
|
||||||
|
Nodes
|
||||||
|
|
||||||
|
- TCP node - when resetting, if no payload, stay disconnected @dceejay
|
||||||
|
- HTML node: add option for collecting attributes and content (#4513) @gorenje
|
||||||
|
- let split node specify property to split on, and join auto join correctly (#4386) @dceejay
|
||||||
|
- Add RFC4180 compliant mode to CSV node (#4540) @Steve-Mcl
|
||||||
|
- Fix change node to return boolean if asked (#4525) @dceejay
|
||||||
|
- Let msg.reset reset Tcp request node connection when in stay connected mode (#4406) @dceejay
|
||||||
|
- Let debug node status msg length be settable via settings (#4402) @dceejay
|
||||||
|
- Feat: Add ability to set headers for WebSocket client (#4436) @marcus-j-davies
|
||||||
|
|
||||||
#### Older Releases
|
#### Older Releases
|
||||||
|
|
||||||
|
|||||||
7
CITATION.cff
Normal file
7
CITATION.cff
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cff-version: 1.2.0
|
||||||
|
message: "If you use this software, please cite it as below."
|
||||||
|
title: "Node-RED"
|
||||||
|
authors:
|
||||||
|
- family-names: "OpenJS Foundation"
|
||||||
|
- family-names: "Contributors"
|
||||||
|
url: "https://nodered.org"
|
||||||
@@ -16,6 +16,9 @@ behavior to the project's core team at team@nodered.org.
|
|||||||
Please raise any bug reports on the relevant project's issue tracker. Be sure to
|
Please raise any bug reports on the relevant project's issue tracker. Be sure to
|
||||||
search the list to see if your issue has already been raised.
|
search the list to see if your issue has already been raised.
|
||||||
|
|
||||||
|
If your issue is more of a question on how to do something with Node-RED, please
|
||||||
|
consider using the [community forum](https://discourse.nodered.org/).
|
||||||
|
|
||||||
A good bug report is one that make it easy for us to understand what you were
|
A good bug report is one that make it easy for us to understand what you were
|
||||||
trying to do and what went wrong.
|
trying to do and what went wrong.
|
||||||
|
|
||||||
@@ -35,14 +38,18 @@ For feature requests, please raise them on the [forum](https://discourse.nodered
|
|||||||
## Pull-Requests
|
## Pull-Requests
|
||||||
|
|
||||||
If you want to raise a pull-request with a new feature, or a refactoring
|
If you want to raise a pull-request with a new feature, or a refactoring
|
||||||
of existing code, it may well get rejected if you haven't discussed it on
|
of existing code, please come and discuss it with us first. We prefer to
|
||||||
the [forum](https://discourse.nodered.org) first.
|
do it that way to make sure your time and effort is well spent on something
|
||||||
|
that fits with our goals.
|
||||||
|
|
||||||
|
If you've got a bug-fix or similar for us, then you are most welcome to
|
||||||
|
get it raised - just make sure you link back to the issue it's fixing and
|
||||||
|
try to include some tests!
|
||||||
|
|
||||||
All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
|
All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
|
||||||
It is an online process and quick to do. If you raise a pull-request without
|
It is an online process and quick to do. If you raise a pull-request without
|
||||||
having signed the CLA, you will be prompted to do so automatically.
|
having signed the CLA, you will be prompted to do so automatically.
|
||||||
|
|
||||||
|
|
||||||
### Code Branches
|
### Code Branches
|
||||||
|
|
||||||
When raising a PR for a fix or a new feature, it is important to target the right branch.
|
When raising a PR for a fix or a new feature, it is important to target the right branch.
|
||||||
|
|||||||
76
Gruntfile.js
76
Gruntfile.js
@@ -143,6 +143,7 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/js/multiplayer.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||||
@@ -151,7 +152,6 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
|
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js",
|
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
|
||||||
@@ -208,38 +208,52 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
||||||
],
|
],
|
||||||
|
nonull: true,
|
||||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
||||||
},
|
},
|
||||||
vendor: {
|
vendor: {
|
||||||
files: {
|
files: [
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [
|
{
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
src: [
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
|
||||||
"node_modules/marked/marked.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||||
"node_modules/dompurify/dist/purify.min.js",
|
"node_modules/marked/marked.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
"node_modules/dompurify/dist/purify.min.js",
|
||||||
"node_modules/i18next/i18next.min.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
||||||
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
"node_modules/i18next/i18next.min.js",
|
||||||
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
"node_modules/jsonata/jsonata-es5.min.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
||||||
],
|
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
|
||||||
// "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
|
],
|
||||||
// // TODO: resolve relative resource paths in
|
nonull: true,
|
||||||
// // bootstrap/FA/jquery
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js"
|
||||||
// ],
|
},
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
|
// {
|
||||||
"node_modules/jsonata/jsonata-es5.min.js",
|
// src: [
|
||||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
// // TODO: resolve relative resource paths in
|
||||||
],
|
// // bootstrap/FA/jquery
|
||||||
"packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
|
// ],
|
||||||
"node_modules/mermaid/dist/mermaid.min.js"
|
// dest: "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css"
|
||||||
]
|
// },
|
||||||
}
|
{
|
||||||
|
src: [
|
||||||
|
"node_modules/jsonata/jsonata-es5.min.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
|
||||||
|
],
|
||||||
|
nonull: true,
|
||||||
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "node_modules/mermaid/dist/mermaid.min.js",
|
||||||
|
nonull: true,
|
||||||
|
dest: "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
uglify: {
|
uglify: {
|
||||||
@@ -408,7 +422,7 @@ module.exports = function(grunt) {
|
|||||||
{
|
{
|
||||||
cwd: 'packages/node_modules/@node-red/editor-client/src',
|
cwd: 'packages/node_modules/@node-red/editor-client/src',
|
||||||
src: [
|
src: [
|
||||||
'types/node/*.ts',
|
'types/node/**/*.ts',
|
||||||
'types/node-red/*.ts',
|
'types/node-red/*.ts',
|
||||||
],
|
],
|
||||||
expand: true,
|
expand: true,
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -1,17 +1,16 @@
|
|||||||
# Node-RED
|
# Node-RED
|
||||||
|
|
||||||
http://nodered.org
|
https://nodered.org
|
||||||
|
|
||||||
[](https://travis-ci.org/node-red/node-red)
|
[](https://github.com/node-red/node-red/actions?query=branch%3Amaster)
|
||||||
[](https://coveralls.io/r/node-red/node-red?branch=master)
|
|
||||||
|
|
||||||
Low-code programming for event-driven applications.
|
Low-code programming for event-driven applications.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
|
Check out https://nodered.org/docs/getting-started/ for full instructions on getting
|
||||||
started.
|
started.
|
||||||
|
|
||||||
1. `sudo npm install -g --unsafe-perm node-red`
|
1. `sudo npm install -g --unsafe-perm node-red`
|
||||||
@@ -20,7 +19,7 @@ started.
|
|||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
||||||
More documentation can be found [here](http://nodered.org/docs).
|
More documentation can be found [here](https://nodered.org/docs).
|
||||||
|
|
||||||
For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||||
|
|
||||||
|
|||||||
16
nodemon.json
Normal file
16
nodemon.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"ignoreRoot": [
|
||||||
|
".git",
|
||||||
|
".nyc_output",
|
||||||
|
".sass-cache",
|
||||||
|
"bower-components",
|
||||||
|
"coverage"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"/Gruntfile.js",
|
||||||
|
"/.git/*",
|
||||||
|
"*.backup",
|
||||||
|
"/public/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
83
package.json
83
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red",
|
"name": "node-red",
|
||||||
"version": "3.1.0-beta.1",
|
"version": "4.0.9",
|
||||||
"description": "Low-code programming for event-driven applications",
|
"description": "Low-code programming for event-driven applications",
|
||||||
"homepage": "http://nodered.org",
|
"homepage": "https://nodered.org",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -26,26 +26,26 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.8.1",
|
"acorn": "8.12.1",
|
||||||
"acorn-walk": "8.2.0",
|
"acorn-walk": "8.3.4",
|
||||||
"ajv": "8.11.2",
|
"ajv": "8.17.1",
|
||||||
"async-mutex": "0.4.0",
|
"async-mutex": "0.5.0",
|
||||||
"basic-auth": "2.0.1",
|
"basic-auth": "2.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.1",
|
"body-parser": "1.20.3",
|
||||||
"cheerio": "1.0.0-rc.10",
|
"cheerio": "1.0.0-rc.10",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"content-type": "1.0.4",
|
"content-type": "1.0.5",
|
||||||
"cookie": "0.5.0",
|
"cookie": "0.7.2",
|
||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.7",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"cronosjs": "1.7.1",
|
"cronosjs": "1.7.1",
|
||||||
"denque": "2.1.0",
|
"denque": "2.1.0",
|
||||||
"express": "4.18.2",
|
"express": "4.21.2",
|
||||||
"express-session": "1.17.3",
|
"express-session": "1.18.1",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "10.1.0",
|
"fs-extra": "11.2.0",
|
||||||
"got": "11.8.5",
|
"got": "12.6.1",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
@@ -54,41 +54,42 @@
|
|||||||
"is-utf8": "0.2.1",
|
"is-utf8": "0.2.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"json-stringify-safe": "5.0.1",
|
"json-stringify-safe": "5.0.1",
|
||||||
"jsonata": "1.8.6",
|
"jsonata": "2.0.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"media-typer": "1.1.0",
|
"media-typer": "1.1.0",
|
||||||
"memorystore": "1.6.7",
|
"memorystore": "1.6.7",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"moment": "2.29.4",
|
"moment": "2.30.1",
|
||||||
"moment-timezone": "0.5.39",
|
"moment-timezone": "0.5.46",
|
||||||
"mqtt": "4.3.7",
|
"mqtt": "5.7.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-red-admin": "^3.0.0",
|
"node-red-admin": "^4.0.1",
|
||||||
"node-watch": "0.7.3",
|
"node-watch": "0.7.4",
|
||||||
"nopt": "5.0.0",
|
"nopt": "5.0.0",
|
||||||
"oauth2orize": "1.11.1",
|
"oauth2orize": "1.12.0",
|
||||||
"on-headers": "1.0.2",
|
"on-headers": "1.0.2",
|
||||||
"passport": "0.6.0",
|
"passport": "0.7.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
"passport-oauth2-client-password": "0.1.2",
|
"passport-oauth2-client-password": "0.1.2",
|
||||||
"raw-body": "2.5.1",
|
"raw-body": "3.0.0",
|
||||||
"semver": "7.3.8",
|
"rfdc": "^1.3.1",
|
||||||
"tar": "6.1.12",
|
"semver": "7.6.3",
|
||||||
"tough-cookie": "4.1.2",
|
"tar": "7.4.3",
|
||||||
|
"tough-cookie": "^5.0.0",
|
||||||
"uglify-js": "3.17.4",
|
"uglify-js": "3.17.4",
|
||||||
"uuid": "8.3.2",
|
"uuid": "9.0.1",
|
||||||
"ws": "7.5.6",
|
"ws": "7.5.10",
|
||||||
"xml2js": "0.4.23"
|
"xml2js": "0.6.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bcrypt": "5.1.0"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dompurify": "2.4.1",
|
"dompurify": "2.5.7",
|
||||||
"grunt": "1.5.3",
|
"grunt": "1.6.1",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-cli": "~1.4.3",
|
"grunt-cli": "~1.5.0",
|
||||||
"grunt-concurrent": "3.0.0",
|
"grunt-concurrent": "3.0.0",
|
||||||
"grunt-contrib-clean": "2.0.1",
|
"grunt-contrib-clean": "2.0.1",
|
||||||
"grunt-contrib-compress": "2.0.0",
|
"grunt-contrib-compress": "2.0.0",
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
"grunt-contrib-watch": "1.1.0",
|
"grunt-contrib-watch": "1.1.0",
|
||||||
"grunt-jsdoc": "2.4.1",
|
"grunt-jsdoc": "2.4.1",
|
||||||
"grunt-jsdoc-to-markdown": "6.0.0",
|
"grunt-jsdoc-to-markdown": "6.0.0",
|
||||||
"grunt-jsonlint": "2.1.3",
|
"grunt-jsonlint": "3.0.0",
|
||||||
"grunt-mkdir": "~1.1.0",
|
"grunt-mkdir": "~1.1.0",
|
||||||
"grunt-npm-command": "~0.1.2",
|
"grunt-npm-command": "~0.1.2",
|
||||||
"grunt-sass": "~3.1.0",
|
"grunt-sass": "~3.1.0",
|
||||||
@@ -108,20 +109,20 @@
|
|||||||
"i18next-http-backend": "1.4.1",
|
"i18next-http-backend": "1.4.1",
|
||||||
"jquery-i18next": "1.2.1",
|
"jquery-i18next": "1.2.1",
|
||||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||||
"marked": "4.2.3",
|
"marked": "4.3.0",
|
||||||
"mermaid": "^9.3.0",
|
"mermaid": "11.3.0",
|
||||||
"minami": "1.2.3",
|
"minami": "1.2.3",
|
||||||
"mocha": "9.2.2",
|
"mocha": "9.2.2",
|
||||||
"node-red-node-test-helper": "^0.3.0",
|
"node-red-node-test-helper": "^0.3.3",
|
||||||
"nodemon": "2.0.20",
|
"nodemon": "3.1.7",
|
||||||
"proxy": "^1.0.2",
|
"proxy": "^1.0.2",
|
||||||
"sass": "1.56.1",
|
"sass": "1.62.1",
|
||||||
"should": "13.2.3",
|
"should": "13.2.3",
|
||||||
"sinon": "11.1.2",
|
"sinon": "11.1.2",
|
||||||
"stoppable": "^1.1.0",
|
"stoppable": "^1.1.0",
|
||||||
"supertest": "6.2.4"
|
"supertest": "6.3.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=18.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ module.exports = {
|
|||||||
store: req.query['store'],
|
store: req.query['store'],
|
||||||
req: apiUtils.getRequestLogObject(req)
|
req: apiUtils.getRequestLogObject(req)
|
||||||
}
|
}
|
||||||
|
if (req.query['keysOnly'] !== undefined) {
|
||||||
|
opts.keysOnly = true
|
||||||
|
}
|
||||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
|
|
||||||
var nodes = require("./nodes");
|
var nodes = require("./nodes");
|
||||||
var flows = require("./flows");
|
var flows = require("./flows");
|
||||||
var flow = require("./flow");
|
var flow = require("./flow");
|
||||||
@@ -37,18 +35,9 @@ module.exports = {
|
|||||||
plugins.init(runtimeAPI);
|
plugins.init(runtimeAPI);
|
||||||
diagnostics.init(settings, runtimeAPI);
|
diagnostics.init(settings, runtimeAPI);
|
||||||
|
|
||||||
var needsPermission = auth.needsPermission;
|
const needsPermission = auth.needsPermission;
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const adminApp = apiUtil.createExpressApp(settings)
|
||||||
|
|
||||||
// Flows
|
// Flows
|
||||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||||
@@ -102,6 +91,7 @@ module.exports = {
|
|||||||
// Plugins
|
// Plugins
|
||||||
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
|
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
|
||||||
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
|
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
|
||||||
|
adminApp.get(/^\/plugins\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("plugins.read"),plugins.getConfig,apiUtil.errorHandler);
|
||||||
|
|
||||||
adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
|
adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
|
||||||
|
|
||||||
|
|||||||
@@ -40,5 +40,31 @@ module.exports = {
|
|||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
apiUtils.rejectHandler(req,res,err);
|
apiUtils.rejectHandler(req,res,err);
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getConfig: function(req, res) {
|
||||||
|
|
||||||
|
let opts = {
|
||||||
|
user: req.user,
|
||||||
|
module: req.params[0],
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.get("accept") === "application/json") {
|
||||||
|
runtimeAPI.nodes.getNodeInfo(opts.module).then(function(result) {
|
||||||
|
res.send(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||||
|
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||||
|
opts.lang = "en-US";
|
||||||
|
}
|
||||||
|
runtimeAPI.plugins.getPluginConfig(opts).then(function(result) {
|
||||||
|
return res.send(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
var apiUtils = require("../util");
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
var settings;
|
var settings;
|
||||||
var theme = require("../editor/theme");
|
var theme = require("../editor/theme");
|
||||||
|
|||||||
@@ -126,6 +126,14 @@ async function login(req,res) {
|
|||||||
if (themeContext.login && themeContext.login.image) {
|
if (themeContext.login && themeContext.login.image) {
|
||||||
response.image = themeContext.login.image;
|
response.image = themeContext.login.image;
|
||||||
}
|
}
|
||||||
|
if (themeContext.login?.message) {
|
||||||
|
response.loginMessage = themeContext.login?.message
|
||||||
|
}
|
||||||
|
if (themeContext.login?.button) {
|
||||||
|
response.prompts = [
|
||||||
|
{ type: "button", ...themeContext.login.button }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.json(response);
|
res.json(response);
|
||||||
}
|
}
|
||||||
@@ -160,20 +168,34 @@ function completeVerify(profile,done) {
|
|||||||
|
|
||||||
|
|
||||||
function genericStrategy(adminApp,strategy) {
|
function genericStrategy(adminApp,strategy) {
|
||||||
var crypto = require("crypto")
|
const crypto = require("crypto")
|
||||||
var session = require('express-session')
|
const session = require('express-session')
|
||||||
var MemoryStore = require('memorystore')(session)
|
const MemoryStore = require('memorystore')(session)
|
||||||
|
|
||||||
adminApp.use(session({
|
const sessionOptions = {
|
||||||
// As the session is only used across the life-span of an auth
|
// As the session is only used across the life-span of an auth
|
||||||
// hand-shake, we can use a instance specific random string
|
// hand-shake, we can use a instance specific random string
|
||||||
secret: crypto.randomBytes(20).toString('hex'),
|
secret: crypto.randomBytes(20).toString('hex'),
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
store: new MemoryStore({
|
store: new MemoryStore({
|
||||||
checkPeriod: 86400000 // prune expired entries every 24h
|
checkPeriod: 86400000 // prune expired entries every 24h
|
||||||
})
|
})
|
||||||
}));
|
}
|
||||||
|
if (settings.httpAdminCookieOptions) {
|
||||||
|
sessionOptions.cookie = {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false,
|
||||||
|
maxAge: null,
|
||||||
|
...settings.httpAdminCookieOptions
|
||||||
|
}
|
||||||
|
if (sessionOptions.cookie.name){
|
||||||
|
sessionOptions.name = sessionOptions.cookie.name
|
||||||
|
delete sessionOptions.cookie.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adminApp.use(session(sessionOptions));
|
||||||
//TODO: all passport references ought to be in ./auth
|
//TODO: all passport references ought to be in ./auth
|
||||||
adminApp.use(passport.initialize());
|
adminApp.use(passport.initialize());
|
||||||
adminApp.use(passport.session());
|
adminApp.use(passport.session());
|
||||||
@@ -205,11 +227,12 @@ function genericStrategy(adminApp,strategy) {
|
|||||||
passport.use(new strategy.strategy(options, verify));
|
passport.use(new strategy.strategy(options, verify));
|
||||||
|
|
||||||
adminApp.get('/auth/strategy',
|
adminApp.get('/auth/strategy',
|
||||||
passport.authenticate(strategy.name, {session:false,
|
passport.authenticate(strategy.name, {
|
||||||
failureMessage: true,
|
session:false,
|
||||||
failureRedirect: settings.httpAdminRoot
|
failWithError: true,
|
||||||
|
failureMessage: true
|
||||||
}),
|
}),
|
||||||
completeGenerateStrategyAuth,
|
completeGenericStrategyAuth,
|
||||||
handleStrategyError
|
handleStrategyError
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -221,14 +244,14 @@ function genericStrategy(adminApp,strategy) {
|
|||||||
passport.authenticate(strategy.name, {
|
passport.authenticate(strategy.name, {
|
||||||
session:false,
|
session:false,
|
||||||
failureMessage: true,
|
failureMessage: true,
|
||||||
failureRedirect: settings.httpAdminRoot
|
failWithError: true
|
||||||
}),
|
}),
|
||||||
completeGenerateStrategyAuth,
|
completeGenericStrategyAuth,
|
||||||
handleStrategyError
|
handleStrategyError
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
function completeGenerateStrategyAuth(req,res) {
|
function completeGenericStrategyAuth(req,res) {
|
||||||
var tokens = req.user.tokens;
|
var tokens = req.user.tokens;
|
||||||
delete req.user.tokens;
|
delete req.user.tokens;
|
||||||
// Successful authentication, redirect home.
|
// Successful authentication, redirect home.
|
||||||
@@ -238,6 +261,8 @@ function handleStrategyError(err, req, res, next) {
|
|||||||
if (res.headersSent) {
|
if (res.headersSent) {
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
|
// Remove the header that passport auto-adds as we don't need it
|
||||||
|
res.removeHeader('WWW-Authenticate')
|
||||||
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
|
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
|
||||||
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
|
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function hasPermission(userScope,permission) {
|
|||||||
}
|
}
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
if (util.isArray(permission)) {
|
if (Array.isArray(permission)) {
|
||||||
// Multiple permissions requested - check each one
|
// Multiple permissions requested - check each one
|
||||||
for (i=0;i<permission.length;i++) {
|
for (i=0;i<permission.length;i++) {
|
||||||
if (!hasPermission(userScope,permission[i])) {
|
if (!hasPermission(userScope,permission[i])) {
|
||||||
@@ -36,7 +36,7 @@ function hasPermission(userScope,permission) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util.isArray(userScope)) {
|
if (Array.isArray(userScope)) {
|
||||||
if (userScope.length === 0) {
|
if (userScope.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
|
|||||||
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
||||||
|
|
||||||
var passport = require("passport");
|
var passport = require("passport");
|
||||||
var crypto = require("crypto");
|
|
||||||
var util = require("util");
|
var util = require("util");
|
||||||
|
|
||||||
var Tokens = require("./tokens");
|
var Tokens = require("./tokens");
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
var util = require("util");
|
var util = require("util");
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
var bcrypt;
|
var bcrypt;
|
||||||
try { bcrypt = require('bcrypt'); }
|
try { bcrypt = require('@node-rs/bcrypt'); }
|
||||||
catch(e) { bcrypt = require('bcryptjs'); }
|
catch(e) { bcrypt = require('bcryptjs'); }
|
||||||
var users = {};
|
var users = {};
|
||||||
var defaultUser = null;
|
var defaultUser = null;
|
||||||
@@ -33,11 +33,11 @@ function authenticate() {
|
|||||||
if (args.length === 2) {
|
if (args.length === 2) {
|
||||||
// Username/password authentication
|
// Username/password authentication
|
||||||
var password = args[1];
|
var password = args[1];
|
||||||
return new Promise(function(resolve,reject) {
|
return bcrypt.compare(password, user.password).then(res => {
|
||||||
bcrypt.compare(password, user.password, function(err, res) {
|
return res ? cleanUser(user) : null
|
||||||
resolve(res?cleanUser(user):null);
|
}).catch(err => {
|
||||||
});
|
return null
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
// Try to extract common profile information
|
// Try to extract common profile information
|
||||||
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
|
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
|
||||||
@@ -74,7 +74,7 @@ function init(config) {
|
|||||||
} else {
|
} else {
|
||||||
var us = config.users;
|
var us = config.users;
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (!util.isArray(us)) {
|
if (!Array.isArray(us)) {
|
||||||
us = [us];
|
us = [us];
|
||||||
}
|
}
|
||||||
for (var i=0;i<us.length;i++) {
|
for (var i=0;i<us.length;i++) {
|
||||||
|
|||||||
@@ -77,6 +77,53 @@ function CommsConnection(ws, user) {
|
|||||||
log.trace("comms.close "+self.session);
|
log.trace("comms.close "+self.session);
|
||||||
removeActiveConnection(self);
|
removeActiveConnection(self);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleAuthPacket = function(msg) {
|
||||||
|
Tokens.get(msg.auth).then(function(client) {
|
||||||
|
if (client) {
|
||||||
|
Users.get(client.user).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
self.user = user;
|
||||||
|
log.audit({event: "comms.auth",user:self.user});
|
||||||
|
completeConnection(msg, client.scope,msg.auth,true);
|
||||||
|
} else {
|
||||||
|
log.audit({event: "comms.auth.fail"});
|
||||||
|
completeConnection(msg, null,null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Users.tokens(msg.auth).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
self.user = user;
|
||||||
|
log.audit({event: "comms.auth",user:self.user});
|
||||||
|
completeConnection(msg, user.permissions,msg.auth,true);
|
||||||
|
} else {
|
||||||
|
log.audit({event: "comms.auth.fail"});
|
||||||
|
completeConnection(msg, null,null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const completeConnection = function(msg, userScope, session, sendAck) {
|
||||||
|
try {
|
||||||
|
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
||||||
|
ws.send(JSON.stringify({auth:"fail"}));
|
||||||
|
ws.close();
|
||||||
|
} else {
|
||||||
|
pendingAuth = false;
|
||||||
|
addActiveConnection(self);
|
||||||
|
self.token = msg.auth;
|
||||||
|
if (sendAck) {
|
||||||
|
ws.send(JSON.stringify({auth:"ok"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
// Just in case the socket closes before we attempt
|
||||||
|
// to send anything.
|
||||||
|
}
|
||||||
|
}
|
||||||
ws.on('message', function(data,flags) {
|
ws.on('message', function(data,flags) {
|
||||||
var msg = null;
|
var msg = null;
|
||||||
try {
|
try {
|
||||||
@@ -86,68 +133,34 @@ function CommsConnection(ws, user) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!pendingAuth) {
|
if (!pendingAuth) {
|
||||||
if (msg.subscribe) {
|
if (msg.auth) {
|
||||||
|
handleAuthPacket(msg)
|
||||||
|
} else if (msg.subscribe) {
|
||||||
self.subscribe(msg.subscribe);
|
self.subscribe(msg.subscribe);
|
||||||
// handleRemoteSubscription(ws,msg.subscribe);
|
// handleRemoteSubscription(ws,msg.subscribe);
|
||||||
|
} else if (msg.topic) {
|
||||||
|
runtimeAPI.comms.receive({
|
||||||
|
user: self.user,
|
||||||
|
client: self,
|
||||||
|
topic: msg.topic,
|
||||||
|
data: msg.data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var completeConnection = function(userScope,session,sendAck) {
|
|
||||||
try {
|
|
||||||
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
|
|
||||||
ws.send(JSON.stringify({auth:"fail"}));
|
|
||||||
ws.close();
|
|
||||||
} else {
|
|
||||||
pendingAuth = false;
|
|
||||||
addActiveConnection(self);
|
|
||||||
self.token = msg.auth;
|
|
||||||
if (sendAck) {
|
|
||||||
ws.send(JSON.stringify({auth:"ok"}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
console.log(err.stack);
|
|
||||||
// Just in case the socket closes before we attempt
|
|
||||||
// to send anything.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.auth) {
|
if (msg.auth) {
|
||||||
Tokens.get(msg.auth).then(function(client) {
|
handleAuthPacket(msg)
|
||||||
if (client) {
|
|
||||||
Users.get(client.user).then(function(user) {
|
|
||||||
if (user) {
|
|
||||||
self.user = user;
|
|
||||||
log.audit({event: "comms.auth",user:self.user});
|
|
||||||
completeConnection(client.scope,msg.auth,true);
|
|
||||||
} else {
|
|
||||||
log.audit({event: "comms.auth.fail"});
|
|
||||||
completeConnection(null,null,false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Users.tokens(msg.auth).then(function(user) {
|
|
||||||
if (user) {
|
|
||||||
self.user = user;
|
|
||||||
log.audit({event: "comms.auth",user:self.user});
|
|
||||||
completeConnection(user.permissions,msg.auth,true);
|
|
||||||
} else {
|
|
||||||
log.audit({event: "comms.auth.fail"});
|
|
||||||
completeConnection(null,null,false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (anonymousUser) {
|
if (anonymousUser) {
|
||||||
log.audit({event: "comms.auth",user:anonymousUser});
|
log.audit({event: "comms.auth",user:anonymousUser});
|
||||||
self.user = anonymousUser;
|
self.user = anonymousUser;
|
||||||
completeConnection(anonymousUser.permissions,null,false);
|
completeConnection(msg, anonymousUser.permissions, null, false);
|
||||||
//TODO: duplicated code - pull non-auth message handling out
|
//TODO: duplicated code - pull non-auth message handling out
|
||||||
if (msg.subscribe) {
|
if (msg.subscribe) {
|
||||||
self.subscribe(msg.subscribe);
|
self.subscribe(msg.subscribe);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.audit({event: "comms.auth.fail"});
|
log.audit({event: "comms.auth.fail"});
|
||||||
completeConnection(null,null,false);
|
completeConnection(msg, null,null,false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
var comms = require("./comms");
|
var comms = require("./comms");
|
||||||
var library = require("./library");
|
|
||||||
var info = require("./settings");
|
var info = require("./settings");
|
||||||
|
|
||||||
var auth = require("../auth");
|
var auth = require("../auth");
|
||||||
@@ -46,14 +44,15 @@ module.exports = {
|
|||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
needsPermission = auth.needsPermission;
|
needsPermission = auth.needsPermission;
|
||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
info.init(runtimeAPI);
|
info.init(settings, runtimeAPI);
|
||||||
comms.init(server,settings,runtimeAPI);
|
comms.init(server,settings,runtimeAPI);
|
||||||
|
|
||||||
var ui = require("./ui");
|
var ui = require("./ui");
|
||||||
|
|
||||||
ui.init(runtimeAPI);
|
ui.init(settings, runtimeAPI);
|
||||||
|
|
||||||
|
const editorApp = apiUtil.createExpressApp(settings)
|
||||||
|
|
||||||
var editorApp = express();
|
|
||||||
if (settings.requireHttps === true) {
|
if (settings.requireHttps === true) {
|
||||||
editorApp.enable('trust proxy');
|
editorApp.enable('trust proxy');
|
||||||
editorApp.use(function (req, res, next) {
|
editorApp.use(function (req, res, next) {
|
||||||
@@ -86,7 +85,7 @@ module.exports = {
|
|||||||
|
|
||||||
//Projects
|
//Projects
|
||||||
var projects = require("./projects");
|
var projects = require("./projects");
|
||||||
projects.init(runtimeAPI);
|
projects.init(settings, runtimeAPI);
|
||||||
editorApp.use("/projects",projects.app());
|
editorApp.use("/projects",projects.app());
|
||||||
|
|
||||||
// Locales
|
// Locales
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
var fs = require('fs');
|
|
||||||
var fspath = require('path');
|
|
||||||
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
// var apiUtil = require('../util');
|
|
||||||
|
|
||||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
|
|
||||||
|
var settings;
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
var needsPermission = require("../auth").needsPermission;
|
var needsPermission = require("../auth").needsPermission;
|
||||||
|
|
||||||
@@ -77,11 +77,12 @@ function getProjectRemotes(req,res) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
},
|
},
|
||||||
app: function() {
|
app: function() {
|
||||||
var app = express();
|
var app = apiUtils.createExpressApp(settings)
|
||||||
|
|
||||||
app.use(function(req,res,next) {
|
app.use(function(req,res,next) {
|
||||||
runtimeAPI.projects.available().then(function(available) {
|
runtimeAPI.projects.available().then(function(available) {
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ var runtimeAPI;
|
|||||||
var sshkeys = require("./sshkeys");
|
var sshkeys = require("./sshkeys");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(settings, _runtimeAPI) {
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
sshkeys.init(runtimeAPI);
|
sshkeys.init(settings, runtimeAPI);
|
||||||
},
|
},
|
||||||
userSettings: function(req, res) {
|
userSettings: function(req, res) {
|
||||||
var opts = {
|
var opts = {
|
||||||
|
|||||||
@@ -15,15 +15,16 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
var express = require("express");
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
|
var settings;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
|
settings = _settings;
|
||||||
},
|
},
|
||||||
app: function() {
|
app: function() {
|
||||||
var app = express();
|
const app = apiUtils.createExpressApp(settings);
|
||||||
|
|
||||||
// List all SSH keys
|
// List all SSH keys
|
||||||
app.get("/", function(req,res) {
|
app.get("/", function(req,res) {
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var util = require("util");
|
var util = require("util");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
|
const apiUtil = require("../util")
|
||||||
|
|
||||||
var defaultContext = {
|
var defaultContext = {
|
||||||
page: {
|
page: {
|
||||||
@@ -27,8 +27,7 @@ var defaultContext = {
|
|||||||
tabicon: {
|
tabicon: {
|
||||||
icon: "red/images/node-red-icon-black.svg",
|
icon: "red/images/node-red-icon-black.svg",
|
||||||
colour: "#8f0000"
|
colour: "#8f0000"
|
||||||
},
|
}
|
||||||
version: require(path.join(__dirname,"../../package.json")).version
|
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
title: "Node-RED",
|
title: "Node-RED",
|
||||||
@@ -40,6 +39,7 @@ var defaultContext = {
|
|||||||
vendorMonaco: ""
|
vendorMonaco: ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var settings;
|
||||||
|
|
||||||
var theme = null;
|
var theme = null;
|
||||||
var themeContext = clone(defaultContext);
|
var themeContext = clone(defaultContext);
|
||||||
@@ -70,7 +70,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
|||||||
var result = [];
|
var result = [];
|
||||||
if (themeValue) {
|
if (themeValue) {
|
||||||
var array = themeValue;
|
var array = themeValue;
|
||||||
if (!util.isArray(array)) {
|
if (!Array.isArray(array)) {
|
||||||
array = [array];
|
array = [array];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,8 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(settings, _runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
themeContext = clone(defaultContext);
|
themeContext = clone(defaultContext);
|
||||||
if (process.env.NODE_ENV == "development") {
|
if (process.env.NODE_ENV == "development") {
|
||||||
@@ -113,7 +114,15 @@ module.exports = {
|
|||||||
var url;
|
var url;
|
||||||
themeSettings = {};
|
themeSettings = {};
|
||||||
|
|
||||||
themeApp = express();
|
themeApp = apiUtil.createExpressApp(settings);
|
||||||
|
|
||||||
|
const defaultServerSettings = {
|
||||||
|
"x-powered-by": false
|
||||||
|
}
|
||||||
|
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||||
|
for (const eOption in serverSettings) {
|
||||||
|
themeApp.set(eOption, serverSettings[eOption]);
|
||||||
|
}
|
||||||
|
|
||||||
if (theme.page) {
|
if (theme.page) {
|
||||||
|
|
||||||
@@ -176,13 +185,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.deployButton) {
|
if (theme.deployButton) {
|
||||||
|
themeSettings.deployButton = {};
|
||||||
|
if (theme.deployButton.label) {
|
||||||
|
themeSettings.deployButton.label = theme.deployButton.label;
|
||||||
|
}
|
||||||
if (theme.deployButton.type == "simple") {
|
if (theme.deployButton.type == "simple") {
|
||||||
themeSettings.deployButton = {
|
themeSettings.deployButton.type = theme.deployButton.type;
|
||||||
type: "simple"
|
|
||||||
}
|
|
||||||
if (theme.deployButton.label) {
|
|
||||||
themeSettings.deployButton.label = theme.deployButton.label;
|
|
||||||
}
|
|
||||||
if (theme.deployButton.icon) {
|
if (theme.deployButton.icon) {
|
||||||
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
||||||
if (url) {
|
if (url) {
|
||||||
@@ -197,14 +205,26 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.login) {
|
if (theme.login) {
|
||||||
|
let themeContextLogin = {}
|
||||||
|
let hasLoginTheme = false
|
||||||
if (theme.login.image) {
|
if (theme.login.image) {
|
||||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||||
if (url) {
|
if (url) {
|
||||||
themeContext.login = {
|
themeContextLogin.image = url
|
||||||
image: url
|
hasLoginTheme = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (theme.login.message) {
|
||||||
|
themeContextLogin.message = theme.login.message
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (theme.login.button) {
|
||||||
|
themeContextLogin.button = theme.login.button
|
||||||
|
hasLoginTheme = true
|
||||||
|
}
|
||||||
|
if (hasLoginTheme) {
|
||||||
|
themeContext.login = themeContextLogin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
themeApp.get("/", async function(req,res) {
|
themeApp.get("/", async function(req,res) {
|
||||||
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
||||||
@@ -224,6 +244,10 @@ module.exports = {
|
|||||||
themeSettings.projects = theme.projects;
|
themeSettings.projects = theme.projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theme.hasOwnProperty("multiplayer")) {
|
||||||
|
themeSettings.multiplayer = theme.multiplayer;
|
||||||
|
}
|
||||||
|
|
||||||
if (theme.hasOwnProperty("keymap")) {
|
if (theme.hasOwnProperty("keymap")) {
|
||||||
themeSettings.keymap = theme.keymap;
|
themeSettings.keymap = theme.keymap;
|
||||||
}
|
}
|
||||||
@@ -329,6 +353,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
theme.codeEditor = theme.codeEditor || {}
|
theme.codeEditor = theme.codeEditor || {}
|
||||||
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
|
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
|
||||||
|
|
||||||
|
theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid)
|
||||||
}
|
}
|
||||||
activeThemeInitialised = true;
|
activeThemeInitialised = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
const crypto = require('crypto')
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
@@ -24,13 +25,16 @@ var apiUtils = require("../util");
|
|||||||
var theme = require("./theme");
|
var theme = require("./theme");
|
||||||
|
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
|
let settings;
|
||||||
var editorClientDir = path.dirname(require.resolve("@node-red/editor-client"));
|
var editorClientDir = path.dirname(require.resolve("@node-red/editor-client"));
|
||||||
var defaultNodeIcon = path.join(editorClientDir,"public","red","images","icons","arrow-in.svg");
|
var defaultNodeIcon = path.join(editorClientDir,"public","red","images","icons","arrow-in.svg");
|
||||||
var editorTemplatePath = path.join(editorClientDir,"templates","index.mst");
|
var editorTemplatePath = path.join(editorClientDir,"templates","index.mst");
|
||||||
var editorTemplate;
|
var editorTemplate;
|
||||||
|
let cacheBuster
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
editorTemplate = fs.readFileSync(editorTemplatePath,"utf8");
|
editorTemplate = fs.readFileSync(editorTemplatePath,"utf8");
|
||||||
Mustache.parse(editorTemplate);
|
Mustache.parse(editorTemplate);
|
||||||
@@ -91,6 +95,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
editor: async function(req,res) {
|
editor: async function(req,res) {
|
||||||
|
if (!cacheBuster) {
|
||||||
|
// settings.instanceId is set asynchronously to the editor-api
|
||||||
|
// being initiaised. So we defer calculating the cacheBuster hash
|
||||||
|
// until the first load of the editor
|
||||||
|
cacheBuster = crypto.createHash('sha1').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
|
||||||
|
}
|
||||||
|
|
||||||
let sessionMessages;
|
let sessionMessages;
|
||||||
if (req.session && req.session.messages) {
|
if (req.session && req.session.messages) {
|
||||||
@@ -99,6 +109,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
res.send(Mustache.render(editorTemplate,{
|
res.send(Mustache.render(editorTemplate,{
|
||||||
sessionMessages,
|
sessionMessages,
|
||||||
|
cacheBuster,
|
||||||
...await theme.context()
|
...await theme.context()
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,11 +24,8 @@
|
|||||||
* @namespace @node-red/editor-api
|
* @namespace @node-red/editor-api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var util = require('util');
|
|
||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var cors = require('cors');
|
|
||||||
|
|
||||||
var auth = require("./auth");
|
var auth = require("./auth");
|
||||||
var apiUtil = require("./util");
|
var apiUtil = require("./util");
|
||||||
@@ -37,7 +34,6 @@ var adminApp;
|
|||||||
var server;
|
var server;
|
||||||
var editor;
|
var editor;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the module.
|
* Initialise the module.
|
||||||
* @param {Object} settings The runtime settings
|
* @param {Object} settings The runtime settings
|
||||||
@@ -49,7 +45,7 @@ var editor;
|
|||||||
function init(settings,_server,storage,runtimeAPI) {
|
function init(settings,_server,storage,runtimeAPI) {
|
||||||
server = _server;
|
server = _server;
|
||||||
if (settings.httpAdminRoot !== false) {
|
if (settings.httpAdminRoot !== false) {
|
||||||
adminApp = express();
|
adminApp = apiUtil.createExpressApp(settings);
|
||||||
|
|
||||||
var cors = require('cors');
|
var cors = require('cors');
|
||||||
var corsHandler = cors({
|
var corsHandler = cors({
|
||||||
@@ -64,14 +60,6 @@ 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);
|
auth.init(settings,storage);
|
||||||
|
|
||||||
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
||||||
@@ -136,10 +124,11 @@ async function stop() {
|
|||||||
editor.stop();
|
editor.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init,
|
||||||
start: start,
|
start,
|
||||||
stop: stop,
|
stop,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @memberof @node-red/editor-api
|
* @memberof @node-red/editor-api
|
||||||
|
|||||||
@@ -14,10 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
|
||||||
var log = require("@node-red/util").log; // TODO: separate module
|
const { log, i18n } = require("@node-red/util");
|
||||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
errorHandler: function(err,req,res,next) {
|
errorHandler: function(err,req,res,next) {
|
||||||
@@ -64,5 +63,17 @@ module.exports = {
|
|||||||
path: req.path,
|
path: req.path,
|
||||||
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
|
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
createExpressApp: function(settings) {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const defaultServerSettings = {
|
||||||
|
"x-powered-by": false
|
||||||
|
}
|
||||||
|
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||||
|
for (let eOption in serverSettings) {
|
||||||
|
app.set(eOption, serverSettings[eOption]);
|
||||||
|
}
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-api",
|
"name": "@node-red/editor-api",
|
||||||
"version": "3.1.0-beta.1",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -16,25 +16,25 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-red/util": "3.1.0-beta.1",
|
"@node-red/util": "4.0.9",
|
||||||
"@node-red/editor-client": "3.1.0-beta.1",
|
"@node-red/editor-client": "4.0.9",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.20.1",
|
"body-parser": "1.20.3",
|
||||||
"clone": "2.1.2",
|
"clone": "2.1.2",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"express-session": "1.17.3",
|
"express-session": "1.18.1",
|
||||||
"express": "4.18.2",
|
"express": "4.21.2",
|
||||||
"memorystore": "1.6.7",
|
"memorystore": "1.6.7",
|
||||||
"mime": "3.0.0",
|
"mime": "3.0.0",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"oauth2orize": "1.11.1",
|
"oauth2orize": "1.12.0",
|
||||||
"passport-http-bearer": "1.0.1",
|
"passport-http-bearer": "1.0.1",
|
||||||
"passport-oauth2-client-password": "0.1.2",
|
"passport-oauth2-client-password": "0.1.2",
|
||||||
"passport": "0.6.0",
|
"passport": "0.7.0",
|
||||||
"ws": "7.5.6"
|
"ws": "7.5.10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bcrypt": "5.1.0"
|
"@node-rs/bcrypt": "1.10.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@
|
|||||||
"selectionToSubflow": "Auswahl in Subflow umwandeln",
|
"selectionToSubflow": "Auswahl in Subflow umwandeln",
|
||||||
"flows": "Flow",
|
"flows": "Flow",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
"rename": "Umbenennen",
|
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"keyboardShortcuts": "Tastenkürzel",
|
"keyboardShortcuts": "Tastenkürzel",
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
@@ -591,6 +590,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ Node",
|
"nodeCount": "__label__ Node",
|
||||||
"nodeCount_plural": "__label__ Nodes",
|
"nodeCount_plural": "__label__ Nodes",
|
||||||
|
"pluginCount": "__count__ Plugin",
|
||||||
|
"pluginCount_plural": "__count__ Plugins",
|
||||||
"moduleCount": "__count__ Modul verfügbar",
|
"moduleCount": "__count__ Modul verfügbar",
|
||||||
"moduleCount_plural": "__count__ Module verfügbar",
|
"moduleCount_plural": "__count__ Module verfügbar",
|
||||||
"inuse": "In Gebrauch",
|
"inuse": "In Gebrauch",
|
||||||
@@ -1076,7 +1077,7 @@
|
|||||||
"git-auth-error": "Git-Authentifizierungsfehler"
|
"git-auth-error": "Git-Authentifizierungsfehler"
|
||||||
},
|
},
|
||||||
"create-success": {
|
"create-success": {
|
||||||
"success": "Sie haben Ihr erstes Projekt erfolgreich erstduellt!",
|
"success": "Sie haben Ihr erstes Projekt erfolgreich erstellt!",
|
||||||
"desc0": "Sie können jetzt Node-RED wie bisher verwenden.",
|
"desc0": "Sie können jetzt Node-RED wie bisher verwenden.",
|
||||||
"desc1": "Im Tab 'Info' in der Seitenleiste wird angezeigt, welches das aktuelle Projekt ist. Über die Schaltfläche rechts neben dem Projektnamen gelangt man zu 'Projekteinstellungen'.",
|
"desc1": "Im Tab 'Info' in der Seitenleiste wird angezeigt, welches das aktuelle Projekt ist. Über die Schaltfläche rechts neben dem Projektnamen gelangt man zu 'Projekteinstellungen'.",
|
||||||
"desc2": "Im Tab 'Commit-Historie' in der Seitenleiste werden alle Dateien angezeigt, die sich in Ihrem Projekt geändert haben, und um sie ins lokale Repository zu übertragen (commit). Es zeigt Ihnen eine vollständige Historie Ihrer Commits an und ermöglicht es Ihnen, Ihre Commits in ein (remote) Server-Repository zu schieben (push)."
|
"desc2": "Im Tab 'Commit-Historie' in der Seitenleiste werden alle Dateien angezeigt, die sich in Ihrem Projekt geändert haben, und um sie ins lokale Repository zu übertragen (commit). Es zeigt Ihnen eine vollständige Historie Ihrer Commits an und ermöglicht es Ihnen, Ihre Commits in ein (remote) Server-Repository zu schieben (push)."
|
||||||
@@ -1172,15 +1173,6 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"title": "System-Informationen"
|
"title": "System-Informationen"
|
||||||
},
|
},
|
||||||
"languages": {
|
|
||||||
"de": "Deutsch",
|
|
||||||
"en-US": "Englisch",
|
|
||||||
"ja": "Japanisch",
|
|
||||||
"ko": "Koreanisch",
|
|
||||||
"ru": "Russisch",
|
|
||||||
"zh-CN": "Chinesisch (Vereinfacht)",
|
|
||||||
"zh-TW": "Chinesisch (Traditionell)"
|
|
||||||
},
|
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "Ungültige JSON-Daten: __error__",
|
"invalid-json": "Ungültige JSON-Daten: __error__",
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
},
|
},
|
||||||
"$formatNumber": {
|
"$formatNumber": {
|
||||||
"args": "number, picture [, options]",
|
"args": "number, picture [, options]",
|
||||||
"desc": "Wandelt `number` in eine Zeichenfolge um und formatiert sie in eine dezimale Darstellung, wie im `picture`-String-Parameter vorgegeben.\n\nDas Verhalten dieser Funktion ist mit der XPath/XQuery-Funktion fn:formatnummer konsistent, wie sie in der XPath F&O 3.1-Spezifikation definiert ist. Der `picture`-String-Parameter definiert, wie die Zahl formatiert ist und hat die gleiche Syntax wie fn:format-number.\n\nDer optionale dritte Parameter `options` wird verwendet, um die standardmäßigen länderspezifischen Formatierungszeichen, wie z.B. das Dezimaltrennzeichen, zu überschreiben. Wenn dieser Parameter vorgegeben wird, muss es sich um ein Objekt handeln, das Name/Wert-Paare enthält, die im Abschnitt mit dem Dezimalformat der XPath F&O 3.1-Spezifikation vorgegeben sind."
|
"desc": "Wandelt `number` in eine Zeichenfolge um und formatiert sie in eine dezimale Darstellung, wie im `picture`-String-Parameter vorgegeben.\n\nDas Verhalten dieser Funktion ist mit der XPath/XQuery-Funktion `fn:formatnummer` konsistent, wie sie in der XPath F&O 3.1-Spezifikation definiert ist. Der `picture`-String-Parameter definiert, wie die Zahl formatiert ist und hat die gleiche Syntax wie `fn:format-number`.\n\nDer optionale dritte Parameter `options` wird verwendet, um die standardmäßigen länderspezifischen Formatierungszeichen, wie z.B. das Dezimaltrennzeichen, zu überschreiben. Wenn dieser Parameter vorgegeben wird, muss es sich um ein Objekt handeln, das Name/Wert-Paare enthält, die im Abschnitt mit dem Dezimalformat der XPath F&O 3.1-Spezifikation vorgegeben sind."
|
||||||
},
|
},
|
||||||
"$formatBase": {
|
"$formatBase": {
|
||||||
"args": "number [, radix]",
|
"args": "number [, radix]",
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
"lock": "Lock",
|
"lock": "Lock",
|
||||||
"unlock": "Unlock",
|
"unlock": "Unlock",
|
||||||
"locked": "Locked",
|
"locked": "Locked",
|
||||||
"unlocked": "Unlocked"
|
"unlocked": "Unlocked",
|
||||||
|
"format": "Format"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "string",
|
"string": "string",
|
||||||
@@ -57,7 +58,6 @@
|
|||||||
"confirmDelete": "Confirm delete",
|
"confirmDelete": "Confirm delete",
|
||||||
"delete": "Are you sure you want to delete '__label__'?",
|
"delete": "Are you sure you want to delete '__label__'?",
|
||||||
"dropFlowHere": "Drop the flow here",
|
"dropFlowHere": "Drop the flow here",
|
||||||
"dropImageHere": "Drop the image here",
|
|
||||||
"addFlow": "Add flow",
|
"addFlow": "Add flow",
|
||||||
"addFlowToRight": "Add flow to the right",
|
"addFlowToRight": "Add flow to the right",
|
||||||
"closeFlow": "Close flow",
|
"closeFlow": "Close flow",
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"displayStatus": "Show node status",
|
"displayStatus": "Show node status",
|
||||||
"displayConfig": "Configuration nodes",
|
"displayConfig": "Configuration nodes",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"importExample": "Import Example Flow",
|
"importExample": "Import example flow",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"search": "Search flows",
|
"search": "Search flows",
|
||||||
"searchInput": "search your flows",
|
"searchInput": "search your flows",
|
||||||
@@ -122,7 +122,6 @@
|
|||||||
"selectionToSubflow": "Selection to Subflow",
|
"selectionToSubflow": "Selection to Subflow",
|
||||||
"flows": "Flows",
|
"flows": "Flows",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"rename": "Rename",
|
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"keyboardShortcuts": "Keyboard shortcuts",
|
"keyboardShortcuts": "Keyboard shortcuts",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
@@ -130,6 +129,11 @@
|
|||||||
"editPalette": "Manage palette",
|
"editPalette": "Manage palette",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"showTips": "Show tips",
|
"showTips": "Show tips",
|
||||||
|
"showNodeHelp": "Show node help",
|
||||||
|
"enableSelectedNodes": "Enable selected nodes",
|
||||||
|
"disableSelectedNodes": "Disable selected nodes",
|
||||||
|
"showSelectedNodeLabels": "Show selected node labels",
|
||||||
|
"hideSelectedNodeLabels": "Hide selected node labels",
|
||||||
"showWelcomeTours": "Show guided tours for new versions",
|
"showWelcomeTours": "Show guided tours for new versions",
|
||||||
"help": "Node-RED website",
|
"help": "Node-RED website",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
@@ -299,7 +303,8 @@
|
|||||||
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
|
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
|
||||||
},
|
},
|
||||||
"conflictNotification1": "Some of the nodes you are importing already exist in your workspace.",
|
"conflictNotification1": "Some of the nodes you are importing already exist in your workspace.",
|
||||||
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them."
|
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them.",
|
||||||
|
"alreadyExists": "This node already exists"
|
||||||
},
|
},
|
||||||
"copyMessagePath": "Path copied",
|
"copyMessagePath": "Path copied",
|
||||||
"copyMessageValue": "Value copied",
|
"copyMessageValue": "Value copied",
|
||||||
@@ -367,8 +372,12 @@
|
|||||||
"deleted": "deleted",
|
"deleted": "deleted",
|
||||||
"flowDeleted": "flow deleted",
|
"flowDeleted": "flow deleted",
|
||||||
"flowAdded": "flow added",
|
"flowAdded": "flow added",
|
||||||
|
"moved": "moved",
|
||||||
"movedTo": "moved to __id__",
|
"movedTo": "moved to __id__",
|
||||||
"movedFrom": "moved from __id__"
|
"movedFrom": "moved from __id__",
|
||||||
|
"none": "none",
|
||||||
|
"position": "position",
|
||||||
|
"wires": "wires"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ node",
|
"nodeCount": "__count__ node",
|
||||||
"nodeCount_plural": "__count__ nodes",
|
"nodeCount_plural": "__count__ nodes",
|
||||||
@@ -377,9 +386,14 @@
|
|||||||
"reviewChanges": "Review Changes",
|
"reviewChanges": "Review Changes",
|
||||||
"noBinaryFileShowed": "Cannot show binary file contents",
|
"noBinaryFileShowed": "Cannot show binary file contents",
|
||||||
"viewCommitDiff": "View Commit Changes",
|
"viewCommitDiff": "View Commit Changes",
|
||||||
|
"commit": "Commit",
|
||||||
"compareChanges": "Compare Changes",
|
"compareChanges": "Compare Changes",
|
||||||
"saveConflict": "Save conflict resolution",
|
"saveConflict": "Save conflict resolution",
|
||||||
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
|
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
|
||||||
|
"localChanges": "Local Changes",
|
||||||
|
"remoteChanges": "Remote Changes",
|
||||||
|
"useLocalChanges": "use local changes",
|
||||||
|
"useRemoteChanges": "use remote changes",
|
||||||
"commonVersionError": "Common Version doesn't contain valid JSON:",
|
"commonVersionError": "Common Version doesn't contain valid JSON:",
|
||||||
"oldVersionError": "Old Version doesn't contain valid JSON:",
|
"oldVersionError": "Old Version doesn't contain valid JSON:",
|
||||||
"newVersionError": "New Version doesn't contain valid JSON:"
|
"newVersionError": "New Version doesn't contain valid JSON:"
|
||||||
@@ -416,6 +430,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
|
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
|
||||||
|
"acrossMultipleGroups": "Cannot create subflow across multiple groups",
|
||||||
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
|
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -504,13 +519,14 @@
|
|||||||
"unassigned": "Unassigned",
|
"unassigned": "Unassigned",
|
||||||
"global": "global",
|
"global": "global",
|
||||||
"workspace": "workspace",
|
"workspace": "workspace",
|
||||||
|
"editor": "edit dialog",
|
||||||
"selectAll": "Select all",
|
"selectAll": "Select all",
|
||||||
"selectNone": "Select none",
|
"selectNone": "Select none",
|
||||||
"selectAllConnected": "Select connected",
|
"selectAllConnected": "Select connected",
|
||||||
"addRemoveNode": "Add/remove node from selection",
|
"addRemoveNode": "Add/remove node from selection",
|
||||||
"editSelected": "Edit selected node",
|
"editSelected": "Edit selected node",
|
||||||
"deleteSelected": "Delete selected nodes or link",
|
"deleteSelected": "Delete selection",
|
||||||
"deleteReconnect": "Delete and Reconnect",
|
"deleteReconnect": "Delete and reconnect",
|
||||||
"importNode": "Import nodes",
|
"importNode": "Import nodes",
|
||||||
"exportNode": "Export nodes",
|
"exportNode": "Export nodes",
|
||||||
"nudgeNode": "Move selected nodes (1px)",
|
"nudgeNode": "Move selected nodes (1px)",
|
||||||
@@ -545,7 +561,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"examples": "Examples"
|
"examples": "Examples"
|
||||||
}
|
},
|
||||||
|
"type": "Type",
|
||||||
|
"name": "Name"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "no information available",
|
"noInfo": "no information available",
|
||||||
@@ -585,6 +603,7 @@
|
|||||||
"editor": {
|
"editor": {
|
||||||
"title": "Manage palette",
|
"title": "Manage palette",
|
||||||
"palette": "Palette",
|
"palette": "Palette",
|
||||||
|
"allCatalogs": "All Catalogs",
|
||||||
"times": {
|
"times": {
|
||||||
"seconds": "seconds ago",
|
"seconds": "seconds ago",
|
||||||
"minutes": "minutes ago",
|
"minutes": "minutes ago",
|
||||||
@@ -606,6 +625,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ node",
|
"nodeCount": "__label__ node",
|
||||||
"nodeCount_plural": "__label__ nodes",
|
"nodeCount_plural": "__label__ nodes",
|
||||||
|
"pluginCount": "__count__ plugin",
|
||||||
|
"pluginCount_plural": "__count__ plugins",
|
||||||
"moduleCount": "__count__ module available",
|
"moduleCount": "__count__ module available",
|
||||||
"moduleCount_plural": "__count__ modules available",
|
"moduleCount_plural": "__count__ modules available",
|
||||||
"inuse": "in use",
|
"inuse": "in use",
|
||||||
@@ -624,6 +645,7 @@
|
|||||||
"tab-nodes": "Nodes",
|
"tab-nodes": "Nodes",
|
||||||
"tab-install": "Install",
|
"tab-install": "Install",
|
||||||
"sort": "sort:",
|
"sort": "sort:",
|
||||||
|
"sortRelevance": "relevance",
|
||||||
"sortAZ": "a-z",
|
"sortAZ": "a-z",
|
||||||
"sortRecent": "recent",
|
"sortRecent": "recent",
|
||||||
"more": "+ __count__ more",
|
"more": "+ __count__ more",
|
||||||
@@ -632,6 +654,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
||||||
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
|
"installTimeout": "<p>Install continuing the background.</p><p>Nodes will appear in palette when complete. Check the log for more information.</p>",
|
||||||
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||||
@@ -646,6 +669,9 @@
|
|||||||
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
|
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
|
||||||
"title": "Remove nodes"
|
"title": "Remove nodes"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>Removed plugin __module__. Please reload the editor to clear left-overs.</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
|
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
|
||||||
"title": "Update nodes"
|
"title": "Update nodes"
|
||||||
@@ -657,7 +683,8 @@
|
|||||||
"review": "Open node information",
|
"review": "Open node information",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"update": "Update"
|
"update": "Update",
|
||||||
|
"understood": "Understood"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,10 +726,9 @@
|
|||||||
"triggerAction": "Trigger action",
|
"triggerAction": "Trigger action",
|
||||||
"find": "Find in workspace",
|
"find": "Find in workspace",
|
||||||
"copyItemUrl": "Copy item url",
|
"copyItemUrl": "Copy item url",
|
||||||
"copyURL2Clipboard": "Copied url to clipboard",
|
"copyURL2Clipboard": "Copied url to clipboard",
|
||||||
"showFlow": "Show",
|
"showFlow": "Show",
|
||||||
"hideFlow": "Hide",
|
"hideFlow": "Hide"
|
||||||
"find": "Find in workspace"
|
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"name": "Help",
|
"name": "Help",
|
||||||
@@ -711,6 +737,7 @@
|
|||||||
"nodeHelp": "Node Help",
|
"nodeHelp": "Node Help",
|
||||||
"showHelp": "Show help",
|
"showHelp": "Show help",
|
||||||
"showInOutline": "Show in outline",
|
"showInOutline": "Show in outline",
|
||||||
|
"hideTopics": "Hide topics",
|
||||||
"showTopics": "Show topics",
|
"showTopics": "Show topics",
|
||||||
"noHelp": "No help topic selected",
|
"noHelp": "No help topic selected",
|
||||||
"changeLog": "Change Log"
|
"changeLog": "Change Log"
|
||||||
@@ -785,6 +812,7 @@
|
|||||||
"branches": "Branches",
|
"branches": "Branches",
|
||||||
"noBranches": "No branches",
|
"noBranches": "No branches",
|
||||||
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
|
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
|
||||||
|
"deleteBranch": "Delete branch",
|
||||||
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
|
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
|
||||||
"deleteUnmergedBranch": "Delete unmerged branch",
|
"deleteUnmergedBranch": "Delete unmerged branch",
|
||||||
"gitRemotes": "Git remotes",
|
"gitRemotes": "Git remotes",
|
||||||
@@ -906,6 +934,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
|
"selected": "__count__ selected",
|
||||||
|
"selected_plural": "__count__ selected",
|
||||||
"type": {
|
"type": {
|
||||||
"str": "string",
|
"str": "string",
|
||||||
"num": "number",
|
"num": "number",
|
||||||
@@ -916,7 +946,14 @@
|
|||||||
"date": "timestamp",
|
"date": "timestamp",
|
||||||
"jsonata": "expression",
|
"jsonata": "expression",
|
||||||
"env": "env variable",
|
"env": "env variable",
|
||||||
"cred": "credential"
|
"cred": "credential",
|
||||||
|
"conf-types": "config node"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "milliseconds since epoch",
|
||||||
|
"object": "JavaScript Date Object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@@ -1199,20 +1236,22 @@
|
|||||||
"title": "System Info"
|
"title": "System Info"
|
||||||
},
|
},
|
||||||
"languages": {
|
"languages": {
|
||||||
"de": "German",
|
"de": "Deutsch",
|
||||||
"en-US": "English",
|
"en-US": "English",
|
||||||
"ja": "Japanese",
|
"es-ES": "Español (España)",
|
||||||
|
"fr": "Français",
|
||||||
|
"ja": "日本語",
|
||||||
"ko": "Korean",
|
"ko": "Korean",
|
||||||
"ru": "Russian",
|
"pt-BR": "Português (Brasil)",
|
||||||
"zh-CN": "Chinese(Simplified)",
|
"ru": "Русский",
|
||||||
"zh-TW": "Chinese(Traditional)"
|
"zh-CN": "简体中文",
|
||||||
|
"zh-TW": "繁體中文"
|
||||||
},
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "Invalid JSON data: __error__",
|
"invalid-json": "Invalid JSON data: __error__",
|
||||||
"invalid-json-prop": "__prop__: invalid JSON data: __error__",
|
"invalid-expr": "Invalid JSONata expression: __error__",
|
||||||
"invalid-prop": "Invalid property expression",
|
"invalid-prop": "Invalid property expression",
|
||||||
"invalid-prop-prop": "__prop__: invalid property expression",
|
|
||||||
"invalid-num": "Invalid number",
|
"invalid-num": "Invalid number",
|
||||||
"invalid-num-prop": "__prop__: invalid number",
|
"invalid-num-prop": "__prop__: invalid number",
|
||||||
"invalid-regexp": "Invalid input pattern",
|
"invalid-regexp": "Invalid input pattern",
|
||||||
@@ -1224,6 +1263,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
|
"showActionList": "Show action list",
|
||||||
"insert": "Insert",
|
"insert": "Insert",
|
||||||
"node": "Node",
|
"node": "Node",
|
||||||
"junction": "Junction",
|
"junction": "Junction",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$string": {
|
"$string": {
|
||||||
"args": "arg[, prettify]",
|
"args": "arg[, prettify]",
|
||||||
"desc": "Casts the `arg` parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function. If `prettify` is true, then \"prettified\" JSON is produced. i.e One line per field and lines will be indented based on the field depth."
|
"desc": "Casts the `arg` parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function. If `prettify` is true, then \"prettified\" JSON is produced. i.e One line per field and lines will be indented based on the field depth."
|
||||||
},
|
},
|
||||||
"$length": {
|
"$length": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
"$now": {
|
"$now": {
|
||||||
"args": "$[picture [, timezone]]",
|
"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"
|
"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": {
|
"$base64encode": {
|
||||||
"args": "string",
|
"args": "string",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args": "array [, function]",
|
"args": "array [, function]",
|
||||||
"desc": "Returns an array containing all the values in the `array` parameter, but sorted into order.\n\nIf a comparator `function` is supplied, then it must be a function that takes two parameters:\n\n`function(left, right)`\n\nThis function gets invoked by the sorting algorithm to compare two values left and right. If the value of left should be placed after the value of right in the desired sort order, then the function must return Boolean `true` to indicate a swap. Otherwise it must return `false`."
|
"desc": "Returns an array containing all the values in the `array` parameter, but sorted into order.\n\nIf a comparator `function` is supplied, then it must be a function that takes two parameters:\n\n`function(left, right)`\n\nThis function gets invoked by the sorting algorithm to compare two values `left` and `right`. If the value of `left` should be placed after the value of `right` in the desired sort order, then the function must return Boolean `true` to indicate a swap. Otherwise it must return `false`."
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -201,11 +201,11 @@
|
|||||||
},
|
},
|
||||||
"$fromMillis": {
|
"$fromMillis": {
|
||||||
"args": "number, [, picture [, timezone]]",
|
"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."
|
"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 according 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": {
|
"$formatNumber": {
|
||||||
"args": "number, picture [, options]",
|
"args": "number, picture [, options]",
|
||||||
"desc": "Casts the `number` to a string and formats it to a decimal representation as specified by the `picture` string.\n\n The behaviour of this function is consistent with the XPath/XQuery function fn:format-number as defined in the XPath F&O 3.1 specification. The picture string parameter defines how the number is formatted and has the same syntax as fn:format-number.\n\nThe optional third argument `options` is used to override the default locale specific formatting characters such as the decimal separator. If supplied, this argument must be an object containing name/value pairs specified in the decimal format section of the XPath F&O 3.1 specification."
|
"desc": "Casts the `number` to a string and formats it to a decimal representation as specified by the `picture` string.\n\n The behaviour of this function is consistent with the XPath/XQuery function `fn:format-number` as defined in the XPath F&O 3.1 specification. The `picture` string parameter defines how the number is formatted and has the same syntax as `fn:format-number`.\n\nThe optional third argument `options` is used to override the default locale specific formatting characters such as the decimal separator. If supplied, this argument must be an object containing name/value pairs specified in the decimal format section of the XPath F&O 3.1 specification."
|
||||||
},
|
},
|
||||||
"$formatBase": {
|
"$formatBase": {
|
||||||
"args": "number [, radix]",
|
"args": "number [, radix]",
|
||||||
@@ -233,15 +233,15 @@
|
|||||||
},
|
},
|
||||||
"$error": {
|
"$error": {
|
||||||
"args": "[str]",
|
"args": "[str]",
|
||||||
"desc": "Throws an error with a message. The optional `str` will replace the default message of `$error() function evaluated`"
|
"desc": "Throws an error with a message. The optional `str` will replace the default message of `$error() function evaluated`"
|
||||||
},
|
},
|
||||||
"$assert": {
|
"$assert": {
|
||||||
"args": "arg, str",
|
"args": "arg, str",
|
||||||
"desc": "If `arg` is true the function returns undefined. If `arg` is false an exception is thrown with `str` as the message of the exception."
|
"desc": "If `arg` is `true` the function returns `undefined`. If `arg` is `false` an exception is thrown with `str` as the message of the exception."
|
||||||
},
|
},
|
||||||
"$single": {
|
"$single": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
"desc": "Returns the one and only value in the `array` parameter that satisfies the `function` predicate (i.e. the `function` returns Boolean `true` when passed the value). Throws an exception if the number of matching values is not exactly one.\n\nThe function should be supplied in the following signature: `function(value [, index [, array]])` where value is each input of the array, index is the position of that value and the whole array is passed as the third argument"
|
"desc": "Returns the one and only value in the `array` parameter that satisfies the `function` predicate (i.e. the `function` returns Boolean `true` when passed the value). Throws an exception if the number of matching values is not exactly one.\n\nThe function should be supplied in the following signature: `function(value [, index [, array]])` where value is each input of the array, index is the position of that value and the whole array is passed as the third argument"
|
||||||
},
|
},
|
||||||
"$encodeUrlComponent": {
|
"$encodeUrlComponent": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
@@ -249,15 +249,15 @@
|
|||||||
},
|
},
|
||||||
"$encodeUrl": {
|
"$encodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "Encodes a Uniform Resource Locator (URL) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character. \n\nExample: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
"desc": "Encodes a Uniform Resource Locator (URL) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.\n\nExample: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||||
},
|
},
|
||||||
"$decodeUrlComponent": {
|
"$decodeUrlComponent": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent. \n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
"desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent.\n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
},
|
},
|
||||||
"$decodeUrl": {
|
"$decodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "Decodes a Uniform Resource Locator (URL) previously created by encodeUrl. \n\nExample: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
"desc": "Decodes a Uniform Resource Locator (URL) previously created by encodeUrl.\n\nExample: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
},
|
},
|
||||||
"$distinct": {
|
"$distinct": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -270,5 +270,9 @@
|
|||||||
"$moment": {
|
"$moment": {
|
||||||
"args": "[str]",
|
"args": "[str]",
|
||||||
"desc": "Gets a date object using the Moment library."
|
"desc": "Gets a date object using the Moment library."
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "Safely clone an object."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1277
packages/node_modules/@node-red/editor-client/locales/es-ES/editor.json
vendored
Normal file
1277
packages/node_modules/@node-red/editor-client/locales/es-ES/editor.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
26
packages/node_modules/@node-red/editor-client/locales/es-ES/infotips.json
vendored
Normal file
26
packages/node_modules/@node-red/editor-client/locales/es-ES/infotips.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"tip0": "Puedes eliminar los nodos o enlaces seleccionados con {{core:delete-selection}}",
|
||||||
|
"tip1": "Busca nodos con {{core:search}}",
|
||||||
|
"tip2": "{{core:toggle-sidebar}} alternará la vista de esta barra lateral",
|
||||||
|
"tip3": "Puedes gestionar tu paleta de nodos con {{core:manage-palette}}",
|
||||||
|
"tip4": "Tus nodos de configuración de flujo aparecen en el panel de la barra lateral. Se puede acceder desde el menú o con {{core:show-config-tab}}",
|
||||||
|
"tip5": "Activa o desactiva estos consejos desde la opción en la configuración",
|
||||||
|
"tip6": "Mueve los nodos seleccionados usando las teclas [izquierda] [arriba] [abajo] y [derecha]. Mantén pulsada [Mayús] para desplazarlos más",
|
||||||
|
"tip7": "Arrastrar un nodo a un cable lo insertará en el enlace",
|
||||||
|
"tip8": "Exporta los nodos seleccionados, o la pestaña actual con {{core:show-export-dialog}}",
|
||||||
|
"tip9": "Importa un flujo arrastrando su JSON al editor, o con {{core:show-import-dialog}}",
|
||||||
|
"tip10": "[shift][clic] y arrastrar en un puerto de nodo para mover todos los cables conectados o sólo el seleccionado",
|
||||||
|
"tip11": "Mostrar la pestaña Información con {{core:show-info-tab}} o la pestaña Depuración con {{core:show-debug-tab}}",
|
||||||
|
"tip12": "[ctrl] [clic] en el área de trabajo para abrir el diálogo de adición rápida",
|
||||||
|
"tip13": "Mantén pulsada [ctrl] cuando [haces clic] en un puerto de nodo para habilitar el enlazado rápido",
|
||||||
|
"tip14": "Mantén pulsada [shift] cuando [haces clic] en un nodo para seleccionar también todos sus nodos conectados",
|
||||||
|
"tip15": "Mantén pulsada [ctrl] cuando [haces clic] en un nodo para añadirlo o eliminarlo de la selección actual",
|
||||||
|
"tip16": "Cambia de pestaña de flujo con {{core:show-previous-tab}} y {{core:show-next-tab}}",
|
||||||
|
"tip17": "Puedes confirmar tus cambios en la bandeja de edición de nodos con {{core:confirm-edit-tray}} o cancelarlos con {{core:cancel-edit-tray}}",
|
||||||
|
"tip18": "Al pulsar {{core:edit-selected-node}} se editará el primer nodo de la selección actual"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
278
packages/node_modules/@node-red/editor-client/locales/es-ES/jsonata.json
vendored
Normal file
278
packages/node_modules/@node-red/editor-client/locales/es-ES/jsonata.json
vendored
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
{
|
||||||
|
"$string": {
|
||||||
|
"args": "arg[, prettify]",
|
||||||
|
"desc": "Convierte el parámetro `arg` a una cadena usando las siguientes reglas de conversión:\n\n - Las cadenas no cambian\n - Las funciones se convierten en una cadena vacía\n - El infinito numérico y NaN arrojan un error porque no se pueden representar como un número JSON\n: todos los demás valores se convierten a una cadena JSON usando la función `JSON.stringify`. Si `prettify` es verdadero, entonces se produce JSON \"prettified\". es decir, una línea por campo y las líneas se indentarán según la profundidad del campo."
|
||||||
|
},
|
||||||
|
"$length": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Devuelve el número de caracteres de la cadena `str`. Se genera un error si `str` no es una cadena."
|
||||||
|
},
|
||||||
|
"$substring": {
|
||||||
|
"args": "str, start[, length]",
|
||||||
|
"desc": "Devuelve una cadena que contiene los caracteres del primer parámetro `str` comenzando en la posición `start` (desplazamiento cero). Si se especifica 'longitud', la subcadena contendrá el máximo de caracteres de 'longitud'. Si 'inicio' es negativo, indica el número de caracteres desde el final de 'cadena'."
|
||||||
|
},
|
||||||
|
"$substringBefore": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Devuelve la subcadena antes de la primera aparición de la secuencia de caracteres `chars` en `str`. Si `str` no contiene `caracteres`, entonces devuelve `str`."
|
||||||
|
},
|
||||||
|
"$substringAfter": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Devuelve la subcadena después de la primera aparición de la secuencia de caracteres `chars` en `str`. Si `str` no contiene `caracteres`, entonces devuelve `str`."
|
||||||
|
},
|
||||||
|
"$uppercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Devuelve una cadena con todos los caracteres de `str` convertidos a mayúsculas."
|
||||||
|
},
|
||||||
|
"$lowercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Devuelve una cadena con todos los caracteres de `str` convertidos a minúsculas."
|
||||||
|
},
|
||||||
|
"$trim": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Normaliza y recorta todos los caracteres de espacio en blanco en `str` aplicando los siguientes pasos:\n\n - Todas las tabulaciones, retornos de carro y avances de línea se reemplazan con espacios.\n- Las secuencias contiguas de espacios se reducen a un solo espacio.\n- Se eliminan los espacios iniciales y finales.\n\n Si no se especifica `str` (es decir, esta función se invoca sin argumentos), entonces el valor de contexto se utiliza como el valor de `str`. Se genera un error si `str` no es una cadena."
|
||||||
|
},
|
||||||
|
"$contains": {
|
||||||
|
"args": "str, pattern",
|
||||||
|
"desc": "Devuelve 'verdadero' si 'cadena' coincide con 'patrón', de lo contrario, devuelve 'falso'. Si no se especifica `str` (es decir, esta función se invoca con un argumento), entonces el valor del contexto se utiliza como valor de `str`. El parámetro `patrón` puede ser una cadena o una expresión regular."
|
||||||
|
},
|
||||||
|
"$split": {
|
||||||
|
"args": "str[, separator][, limit]",
|
||||||
|
"desc": "Divide el parámetro `str` en una matriz de subcadenas. Es un error si `str` no es una cadena. El parámetro opcional `separador` especifica los caracteres dentro de la `cadena` sobre los cuales se debe dividir como una cadena o una expresión regular. Si no se especifica 'separador', se supone que la cadena está vacía y 'cadena' se dividirá en una matriz de caracteres individuales. Es un error si el 'separador' no es una cadena. El parámetro opcional 'límite' es un número que especifica el número máximo de subcadenas que se incluirán en la matriz resultante. Cualquier subcadena adicional se descarta. Si no se especifica `límite`, entonces `str` se divide completamente sin límite para el tamaño de la matriz resultante. Es un error si 'límite' no es un número positivo."
|
||||||
|
},
|
||||||
|
"$join": {
|
||||||
|
"args": "array[, separator]",
|
||||||
|
"desc": "Une una matriz de cadenas de componentes en una única cadena concatenada con cada cadena de componentes separada por el parámetro 'separador' opcional. Es un error si la 'matriz' de entrada contiene un elemento que no es una cadena. Si no se especifica 'separador', se supone que es una cadena vacía, es decir, que no hay 'separador' entre las cadenas componentes. Es un error si el 'separador' no es una cadena."
|
||||||
|
},
|
||||||
|
"$match": {
|
||||||
|
"args": "str, pattern [, limit]",
|
||||||
|
"desc": "Aplica la cadena `str` a la expresión regular `pattern` y devuelve una matriz de objetos, cada objeto contiene información sobre cada aparición de una coincidencia dentro de `str`."
|
||||||
|
},
|
||||||
|
"$replace": {
|
||||||
|
"args": "str, pattern, replacement [, limit]",
|
||||||
|
"desc": "Encuentra apariciones de `patrón` dentro de `str` y las reemplaza con `reemplazo`.\n\nEl parámetro opcional `límite` es el número máximo de reemplazos."
|
||||||
|
},
|
||||||
|
"$now": {
|
||||||
|
"args": "$[picture [, timezone]]",
|
||||||
|
"desc": "Genera una marca de tiempo en formato compatible con ISO 8601 y la devuelve como una cadena. Si se proporcionan los parámetros opcionales `picture` y `zona horaria`, entonces la marca de tiempo actual se formatea como se describe en la función `$fromMillis()`"
|
||||||
|
},
|
||||||
|
"$base64encode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Convierte una cadena ASCII a una representación base 64. Cada carácter de la cadena se trata como un byte de datos binarios. Esto requiere que todos los caracteres de la cadena estén en el rango de 0x00 a 0xFF, que incluye todos los caracteres de las cadenas codificadas con URI. No se admiten caracteres Unicode fuera de ese rango."
|
||||||
|
},
|
||||||
|
"$base64decode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Convierte bytes codificados en base 64 en una cadena, utilizando una página de códigos Unicode UTF-8."
|
||||||
|
},
|
||||||
|
"$number": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Convierte el parámetro `arg` a un número usando las siguientes reglas de conversión:\n\n - Los números no cambian\n - Las cadenas que contienen una secuencia de caracteres que representan un número JSON legal se convierten a ese número\n - Todos los demás valores provocar que se arroje un error."
|
||||||
|
},
|
||||||
|
"$abs": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Devuelve el valor absoluto del parámetro 'número'."
|
||||||
|
},
|
||||||
|
"$floor": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Devuelve el valor de 'número' redondeado hacia abajo al entero más cercano que sea menor o igual a 'número'."
|
||||||
|
},
|
||||||
|
"$ceil": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Devuelve el valor de 'número' redondeado al número entero más cercano que sea mayor o igual a 'número'."
|
||||||
|
},
|
||||||
|
"$round": {
|
||||||
|
"args": "number [, precision]",
|
||||||
|
"desc": "Devuelve el valor del parámetro 'número' redondeado al número de decimales especificado por el parámetro opcional 'precisión'."
|
||||||
|
},
|
||||||
|
"$power": {
|
||||||
|
"args": "base, exponent",
|
||||||
|
"desc": "Devuelve el valor de 'base' elevado a la potencia de 'exponente'."
|
||||||
|
},
|
||||||
|
"$sqrt": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Devuelve la raíz cuadrada del valor del parámetro 'número'."
|
||||||
|
},
|
||||||
|
"$random": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Devuelve un número pseudoaleatorio mayor o igual a cero y menor que uno."
|
||||||
|
},
|
||||||
|
"$millis": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Devuelve el número de milisegundos desde la época Unix (1 de enero de 1970 UTC) como un número. Todas las invocaciones de `$millis()` dentro de una evaluación de una expresión devolverán el mismo valor."
|
||||||
|
},
|
||||||
|
"$sum": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve la suma aritmética de una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
|
||||||
|
},
|
||||||
|
"$max": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve el número máximo en una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
|
||||||
|
},
|
||||||
|
"$min": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve el número mínimo en una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
|
||||||
|
},
|
||||||
|
"$average": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve el valor medio de una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
|
||||||
|
},
|
||||||
|
"$boolean": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Convierte el argumento a un booleano usando las siguientes reglas:\n\n - `Booleano`: sin cambios\n - `cadena`: vacía: `falso`\n - `cadena`: no vacía: `verdadero`\n - `número`: `0`: `falso`\n - `número`: distinto de cero: `verdadero`\n - `nulo`: `falso`\n - `matriz`: vacía: `falso`\n - `array`: contiene un miembro que se convierte en `true`: `true`\n - `array`: todos los miembros se convierten en `false`: `false`\n - `object`: vacío: `false`\n - `objeto`: no vacío: `verdadero`\n - `función`: `falso`"
|
||||||
|
},
|
||||||
|
"$not": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Devuelve booleano NEGADO del argumento. `arg` se convierte antes en un booleano"
|
||||||
|
},
|
||||||
|
"$exists": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Devuelve booleano 'verdadero' si la expresión 'arg' se evalúa como un valor, o 'falso' si la expresión no coincide con nada (por ejemplo, una ruta a una referencia de campo inexistente)."
|
||||||
|
},
|
||||||
|
"$count": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve el número de elementos de la matriz."
|
||||||
|
},
|
||||||
|
"$append": {
|
||||||
|
"args": "array, array",
|
||||||
|
"desc": "Agrega dos matrices"
|
||||||
|
},
|
||||||
|
"$sort": {
|
||||||
|
"args": "array [, function]",
|
||||||
|
"desc": "Devuelve una matriz que contiene todos los valores en el parámetro `array`, pero ordenados.\n\nSi se proporciona una `función` de comparador, entonces debe ser una función que toma dos parámetros:\n\n`function(left , derecha)`\n\nEsta función es invocada por el algoritmo de clasificación para comparar dos valores `izquierda` y `derecha`. Si el valor de `izquierda` debe colocarse después del valor de `derecha` en el orden de clasificación deseado, entonces la función debe devolver un valor booleano 'verdadero' para indicar un intercambio. De lo contrario debe devolver 'falso'."
|
||||||
|
},
|
||||||
|
"$reverse": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve una matriz que contiene todos los valores del parámetro `matriz`, pero en orden inverso."
|
||||||
|
},
|
||||||
|
"$shuffle": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve una matriz que contiene todos los valores del parámetro `array`, pero mezclados en orden aleatorio."
|
||||||
|
},
|
||||||
|
"$zip": {
|
||||||
|
"args": "array, ...",
|
||||||
|
"desc": "Devuelve una matriz convolucionada (comprimida) que contiene matrices agrupadas de valores de los argumentos `matriz1`... `matrizN` del índice 0, 1, 2...."
|
||||||
|
},
|
||||||
|
"$keys": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Devuelve una matriz que contiene las claves del objeto. Si el argumento es una matriz de objetos, entonces la matriz devuelta contiene una lista deduplicada de todas las claves de todos los objetos."
|
||||||
|
},
|
||||||
|
"$lookup": {
|
||||||
|
"args": "object, key",
|
||||||
|
"desc": "Devuelve el valor asociado con la clave en el objeto. Si el primer argumento es una matriz de objetos, entonces se buscan todos los objetos de la matriz y se devuelven los valores asociados con todas las apariciones de la clave."
|
||||||
|
},
|
||||||
|
"$spread": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Divide un objeto que contiene pares clave/valor en una matriz de objetos, cada uno de los cuales tiene un único par clave/valor del objeto de entrada. Si el parámetro es una matriz de objetos, entonces la matriz resultante contiene un objeto para cada par clave/valor en cada objeto de la matriz proporcionada."
|
||||||
|
},
|
||||||
|
"$merge": {
|
||||||
|
"args": "array<object>",
|
||||||
|
"desc": "Fusiona una matriz de objetos en un único objeto que contiene todos los pares clave/valor de cada uno de los objetos en la matriz de entrada. Si alguno de los objetos de entrada contiene la misma clave, entonces el objeto devuelto contendrá el valor del último en la matriz. Es un error si la matriz de entrada contiene un elemento que no es un objeto."
|
||||||
|
},
|
||||||
|
"$sift": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Devuelve un objeto que contiene solo los pares clave/valor del parámetro `objeto` que satisfacen el predicado `función` pasado como segundo parámetro.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función(valor [, clave [, objeto]])`"
|
||||||
|
},
|
||||||
|
"$each": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Devuelve una matriz que contiene los valores devueltos por la función cuando se aplica a cada par clave/valor en el objeto."
|
||||||
|
},
|
||||||
|
"$map": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Devuelve una matriz que contiene los resultados de aplicar el parámetro `función` a cada valor en el parámetro `matriz`.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función( valor [, índice [, matriz]])`"
|
||||||
|
},
|
||||||
|
"$filter": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Devuelve una matriz que contiene solo los valores en el parámetro `matriz` que satisfacen el predicado `función`.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función(valor [ , índice [, matriz]])`"
|
||||||
|
},
|
||||||
|
"$reduce": {
|
||||||
|
"args": "array, function [, init]",
|
||||||
|
"desc": "Devuelve un valor agregado derivado de aplicar el parámetro `función` sucesivamente a cada valor en `matriz` en combinación con el resultado de la aplicación anterior de la función.\n\nLa función debe aceptar dos argumentos y se comporta como un operador infijo entre cada valor dentro de la matriz. La firma de la `función` debe tener la forma: `myfunc($accumulator, $value[, $index[, $array]])`\n\nEl parámetro opcional `init` se utiliza como valor inicial en la agregación."
|
||||||
|
},
|
||||||
|
"$flowContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Recupera una propiedad de contexto de flujo.\n\nEsta es una función definida por Node-RED."
|
||||||
|
},
|
||||||
|
"$globalContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Recupera una propiedad de contexto global.\n\nEsta es una función definida por Node-RED."
|
||||||
|
},
|
||||||
|
"$pad": {
|
||||||
|
"args": "string, width [, char]",
|
||||||
|
"desc": "Devuelve una copia de la `cadena` con relleno adicional, si es necesario, de modo que su número total de caracteres sea al menos el valor absoluto del parámetro `ancho`.\n\nSi `ancho` es un número positivo, entonces la cadena está acolchado hacia la derecha; si es negativo, se rellena hacia la izquierda.\n\nEl argumento opcional `char` especifica los caracteres de relleno que se utilizarán. Si no se especifica, el valor predeterminado es el carácter de espacio."
|
||||||
|
},
|
||||||
|
"$fromMillis": {
|
||||||
|
"args": "number, [, picture [, timezone]]",
|
||||||
|
"desc": "Convierte el `número` que representa milisegundos desde la época Unix (1 de enero de 1970 UTC) en una representación de cadena formateada según la plantilla en picture.\n\nSi se omite el parámetro opcional `picture`, entonces la marca de tiempo es formateado en el formato ISO 8601.\n\nSi se proporciona la cadena opcional `picture`, entonces la marca de tiempo se formatea de acuerdo con la representación especificada en esa cadena. El comportamiento de esta función es consistente con la versión de dos argumentos de la función XPath/XQuery `format-dateTime` tal como se define en la especificación XPath F&O 3.1. El parámetro de plantilla define cómo se formatea la marca de tiempo y tiene la misma sintaxis que `format-dateTime`.\n\nSi se proporciona la cadena opcional `timezone`, entonces la marca de tiempo formateada estará en esa zona horaria. La cadena `timezone` debe tener el formato '±HHMM', donde ± es el signo más o menos y HHMM es el desplazamiento en horas y minutos desde UTC. Desplazamiento positivo para zonas horarias al este de UTC, desplazamiento negativo para zonas horarias al oeste de UTC."
|
||||||
|
},
|
||||||
|
"$formatNumber": {
|
||||||
|
"args": "number, picture [, options]",
|
||||||
|
"desc": "Convierte el `número` en una cadena y lo formatea en una representación decimal según lo especificado en la cadena `picture`.\n\n El comportamiento de esta función es coherente con la función XPath/XQuery `fn:format-number` tal como se define en la especificación XPath F&O 3.1. El parámetro de cadena `picture` define cómo se formatea el número y tiene la misma sintaxis que `fn:formato-número`.\n\nEl tercer argumento opcional `opciones` se utiliza para anular los caracteres de formato específicos de la configuración regional predeterminada, como el decimal. separador. Si se proporciona, este argumento debe ser un objeto que contenga pares de nombre/valor especificados en la sección de formato decimal de la especificación XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$formatBase": {
|
||||||
|
"args": "number [, radix]",
|
||||||
|
"desc": "Convierte el número en una cadena y lo formatea como un número entero representado en la base numérica especificada por el argumento `radix`. Si no se especifica `radix`, el valor predeterminado es la base 10. `radix` puede estar entre 2 y 36; de lo contrario, se genera un error."
|
||||||
|
},
|
||||||
|
"$toMillis": {
|
||||||
|
"args": "timestamp",
|
||||||
|
"desc": "Convierte una cadena de `marca de tiempo` en el formato ISO 8601 al número de milisegundos desde la época Unix (1 de enero de 1970 UTC) como un número. Se genera un error si la cadena no tiene el formato correcto."
|
||||||
|
},
|
||||||
|
"$env": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Devuelve el valor de una variable de entorno.\n\nEsta es una función definida por Node-RED."
|
||||||
|
},
|
||||||
|
"$eval": {
|
||||||
|
"args": "expr [, context]",
|
||||||
|
"desc": "Analiza y evalúa la cadena `expr` que contiene JSON literal o una expresión JSONata utilizando el contexto actual como contexto para la evaluación."
|
||||||
|
},
|
||||||
|
"$formatInteger": {
|
||||||
|
"args": "number, picture",
|
||||||
|
"desc": "Convierte el número en una cadena y lo formatea en una representación entera como lo especifica la cadena `picture`. El parámetro de define cómo se formatea el número y tiene la misma sintaxis que `fn:format-integer` de la especificación XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$parseInteger": {
|
||||||
|
"args": "string, picture",
|
||||||
|
"desc": "Analiza el contenido del parámetro cadena en un número entero (como un número JSON) utilizando el formato especificado por la cadena `picture`. El parámetro tiene el mismo formato que `$formatInteger`."
|
||||||
|
},
|
||||||
|
"$error": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Lanza un error con un mensaje. El parámetro `str` opcional reemplazará el mensaje predeterminado de `$error() función evaluada`"
|
||||||
|
},
|
||||||
|
"$assert": {
|
||||||
|
"args": "arg, str",
|
||||||
|
"desc": "Si `arg` es `verdadero`, la función devuelve indefinido. Si `arg` es `falso`, se lanza una excepción con `str` como mensaje de excepción."
|
||||||
|
},
|
||||||
|
"$single": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Devuelve el único valor en el parámetro `array` que satisface el predicado de `función` (es decir, la `función` devuelve booleano `verdadero` cuando se pasa el valor). Lanza una excepción si el número de valores coincidentes no es exactamente uno.\n\nLa función debe proporcionarse con la siguiente firma: `función(valor [, índice [, matriz]])` donde el valor es cada entrada de la matriz. El índice es la posición de ese valor y toda la matriz se pasa como tercer argumento."
|
||||||
|
},
|
||||||
|
"$encodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Codifica un componente de URL reemplazando cada instancia de ciertos caracteres por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter.\n\nEjemplo: `$encodeUrlComponent(\"?x=prueba\")` => `\"%3Fx%3Dprueba\"`"
|
||||||
|
},
|
||||||
|
"$encodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Codifica una URL reemplazando cada instancia de ciertos caracteres por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter.\n\nEjemplo: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Decodifica un componente de URL creado previamente por encodeUrlComponent.\n\nEjemplo: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Decodifica una URL creado previamente por encodeUrl.\n\nEjemplo: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
|
},
|
||||||
|
"$distinct": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Devuelve una matriz con valores duplicados eliminados de `matriz`"
|
||||||
|
},
|
||||||
|
"$type": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "Devuelve el tipo de `valor` como una cadena. Si `valor` no está definido, esto devolverá indefinido."
|
||||||
|
},
|
||||||
|
"$moment": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Obtiene un objeto de fecha usando la biblioteca Moment."
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "Clona un objeto de forma segura."
|
||||||
|
}
|
||||||
|
}
|
||||||
1419
packages/node_modules/@node-red/editor-client/locales/fr/editor.json
vendored
Normal file
1419
packages/node_modules/@node-red/editor-client/locales/fr/editor.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
23
packages/node_modules/@node-red/editor-client/locales/fr/infotips.json
vendored
Normal file
23
packages/node_modules/@node-red/editor-client/locales/fr/infotips.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"tip0": "Vous pouvez supprimer les noeuds ou les liens sélectionnés avec {{core:delete-selection}}",
|
||||||
|
"tip1": "Rechercher des noeuds à l'aide de {{core:search}}",
|
||||||
|
"tip2": "{{core:toggle-sidebar}} basculera l'affichage de cette barre latérale",
|
||||||
|
"tip3": "Vous pouvez gérer votre palette de noeuds avec {{core:manage-palette}}",
|
||||||
|
"tip4": "Vos noeuds de configuration de flux sont répertoriés dans le panneau de la barre latérale. Ils sont accessibles depuis le menu ou avec {{core:show-config-tab}}",
|
||||||
|
"tip5": "Activer ou désactiver ces conseils à partir de l'option dans les paramètres",
|
||||||
|
"tip6": "Déplacer les noeuds sélectionnés à l'aide des touches [gauche] [haut] [bas] et [droite]. Maintenir la touche [shift] enfoncée pour les pousser plus loin",
|
||||||
|
"tip7": "Faire glisser un noeud sur un fil le raccordera au lien",
|
||||||
|
"tip8": "Exporter les noeuds sélectionnés, ou l'onglet actuel avec {{core:show-export-dialog}}",
|
||||||
|
"tip9": "Importer un flux en faisant glisser son JSON dans l'éditeur, ou avec {{core:show-import-dialog}}",
|
||||||
|
"tip10": "[majuscule] [clic] et faites glisser sur un port de noeud pour déplacer tous les fils attachés ou seulement celui sélectionné",
|
||||||
|
"tip11": "Afficher l'onglet Infos avec {{core:show-info-tab}} ou l'onglet Débogage avec {{core:show-debug-tab}}",
|
||||||
|
"tip12": "[ctrl] [clic] dans l'espace de travail pour ouvrir la boîte de dialogue d'ajout rapide",
|
||||||
|
"tip13": "Maintenir la touche [ctrl] enfoncée lorsque vous [cliquez] sur un port de noeud pour activer le câblage rapide",
|
||||||
|
"tip14": "Maintenir la touche [shift] enfoncée lorsque vous [cliquez] sur un noeud pour sélectionner également tous ses noeuds connectés",
|
||||||
|
"tip15": "Maintenir la touche [ctrl] enfoncée lorsque vous [cliquez] sur un noeud pour l'ajouter ou le supprimer de la sélection actuelle",
|
||||||
|
"tip16": "Changer d'onglet de flux avec {{core:show-previous-tab}} et {{core:show-next-tab}}",
|
||||||
|
"tip17": "Vous pouvez confirmer vos modifications dans le panneau d'édition du noeud avec {{core:confirm-edit-tray}} ou les annuler avec {{core:cancel-edit-tray}}",
|
||||||
|
"tip18": "Appuyer sur {{core:edit-selected-node}} modifiera le premier noeud de la sélection actuelle"
|
||||||
|
}
|
||||||
|
}
|
||||||
278
packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json
vendored
Normal file
278
packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json
vendored
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
{
|
||||||
|
"$string": {
|
||||||
|
"args": "arg[, prettify]",
|
||||||
|
"desc": "Convertit le paramètre `arg` en une chaîne de caractères en utilisant les règles de typage suivantes :\n\n - Les chaînes de caractères sont inchangées\n - Les fonctions sont converties en une chaîne vide\n - L'infini numérique et NaN renvoient une erreur car ils ne peuvent pas être représentés comme un Numéro JSON\n - Toutes les autres valeurs sont converties en une chaîne JSON à l'aide de la fonction `JSON.stringify`. Si `prettify` est vrai, alors le JSON \"prettified\" est produit. c'est-à-dire une ligne par champ et les lignes seront en retrait en fonction de la profondeur du champ."
|
||||||
|
},
|
||||||
|
"$length": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Renvoie le nombre de caractères dans la chaîne `str`. Une erreur est renvoyée si `str` n'est pas une chaîne de caractères."
|
||||||
|
},
|
||||||
|
"$substring": {
|
||||||
|
"args": "str, start[, length]",
|
||||||
|
"desc": "Renvoie une chaîne contenant les caractères du premier paramètre `str` commençant à la position `start` (pas de décalage). Si `length` est spécifié, alors la sous-chaîne contiendra un maximum de caractères `length`. Si `start` est négatif alors il indique le nombre de caractères à partir de la fin de `str`."
|
||||||
|
},
|
||||||
|
"$substringBefore": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Renvoie la sous-chaîne avant la première occurrence de la séquence de caractères `chars` dans `str`. Si `str` ne contient pas `chars`, alors il renvoie `str`."
|
||||||
|
},
|
||||||
|
"$substringAfter": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Renvoie la sous-chaîne après la première occurrence de la séquence de caractères `chars` dans `str`. Si `str` ne contient pas `chars`, alors il renvoie `str`."
|
||||||
|
},
|
||||||
|
"$uppercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Renvoie une chaîne avec tous les caractères de `str` convertis en majuscules."
|
||||||
|
},
|
||||||
|
"$lowercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Renvoie une chaîne avec tous les caractères de `str` convertis en minuscules."
|
||||||
|
},
|
||||||
|
"$trim": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Normalise et supprime tous les caractères d'espacement dans `str` en appliquant les étapes suivantes :\n\n - Toutes les tabulations, retours à la ligne et sauts de ligne sont remplacés par des espaces.\n- Les séquences contiguës d'espaces sont réduites à un seul espace.\n- Les espaces de fin et de début sont supprimés.\n\n Si `str` n'est pas spécifié (c'est-à-dire que cette fonction est invoquée sans argument), alors la valeur de contexte est utilisée comme valeur de `str`. Une erreur est renvoyée si `str` n'est pas une chaîne."
|
||||||
|
},
|
||||||
|
"$contains": {
|
||||||
|
"args": "str, pattern",
|
||||||
|
"desc": "Renvoie `true` si `str` correspond à `pattern`, sinon il renvoie `false`. Si `str` n'est pas spécifié (c'est-à-dire que cette fonction est invoquée avec un argument), alors la valeur de contexte est utilisée comme valeur de `str`. Le paramètre `pattern` peut être une chaîne ou une expression régulière."
|
||||||
|
},
|
||||||
|
"$split": {
|
||||||
|
"args": "str[, separator][, limit]",
|
||||||
|
"desc": "Divise le paramètre `str` en un tableau de sous-chaînes. C'est une erreur si `str` n'est pas une chaîne. Le paramètre facultatif `separator` spécifie les caractères à l'intérieur de `str` à propos desquels il doit être divisé en chaîne ou en expression régulière. Si `separator` n'est pas spécifié, la chaîne vide est supposée et `str` sera divisé en un tableau de caractères uniques. C'est une erreur si `separator` n'est pas une chaîne. Le paramètre facultatif `limit` est un nombre qui spécifie le nombre maximum de sous-chaînes à inclure dans le tableau résultant. Toutes les sous-chaînes supplémentaires sont ignorées. Si `limit` n'est pas spécifié, alors `str` est entièrement divisé sans limite à la taille du tableau résultant. C'est une erreur si `limit` n'est pas un nombre non négatif."
|
||||||
|
},
|
||||||
|
"$join": {
|
||||||
|
"args": "array[, separator]",
|
||||||
|
"desc": "Joint un tableau de chaînes de composants en une seule chaîne concaténée, chaque chaîne de composants étant séparée par le paramètre facultatif `separator`. C'est une erreur si l'entrée `array` contient un élément qui n'est pas une chaîne. Si `séparateur` n'est pas spécifié, il est supposé être la chaîne vide, c'est-à-dire qu'il n'y a pas de `séparateur` entre les chaînes de composants. C'est une erreur si `separator` n'est pas une chaîne."
|
||||||
|
},
|
||||||
|
"$match": {
|
||||||
|
"args": "str, pattern [, limit]",
|
||||||
|
"desc": "Applique la chaîne `str` à l'expression régulière `pattern` et renvoie un tableau d'objets, chaque objet contenant des informations sur chaque occurrence d'une correspondance dans `str`."
|
||||||
|
},
|
||||||
|
"$replace": {
|
||||||
|
"args": "str, pattern, replacement [, limit]",
|
||||||
|
"desc": "Trouve les occurrences de `pattern` dans `str` et les remplace par `replacement`.\n\nLe paramètre facultatif `limit` est le nombre maximum de remplacements."
|
||||||
|
},
|
||||||
|
"$now": {
|
||||||
|
"args": "$[picture [, timezone]]",
|
||||||
|
"desc": "Génère un horodatage au format compatible ISO 8601 et le renvoie sous forme de chaîne. Si les paramètres optionnels d'image et de fuseau horaire sont fournis, alors l'horodatage actuel est formaté comme décrit par la fonction `$fromMillis()`"
|
||||||
|
},
|
||||||
|
"$base64encode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Convertit une chaîne ASCII en une représentation en base 64. Chaque caractère de la chaîne est traité comme un octet de données binaires. Cela nécessite que tous les caractères de la chaîne se trouvent dans la plage 0x00 à 0xFF, qui inclut tous les caractères des chaînes encodées en URI. Les caractères Unicode en dehors de cette plage ne sont pas pris en charge."
|
||||||
|
},
|
||||||
|
"$base64decode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Convertit les octets encodés en base 64 en une chaîne, à l'aide d'une page de codes Unicode UTF-8."
|
||||||
|
},
|
||||||
|
"$number": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Convertit le paramètre `arg` en un nombre en utilisant les règles de conversion suivantes :\n\n - Les nombres sont inchangés\n - Les chaînes qui contiennent une séquence de caractères représentant un nombre JSON légal sont converties en ce nombre\n - Toutes les autres valeurs provoquer l'envoi d'une erreur."
|
||||||
|
},
|
||||||
|
"$abs": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Renvoie la valeur absolue du paramètre `nombre`."
|
||||||
|
},
|
||||||
|
"$floor": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Renvoie la valeur de `number` arrondie à l'entier le plus proche inférieur ou égal à `number`."
|
||||||
|
},
|
||||||
|
"$ceil": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Renvoie la valeur de `number` arrondie à l'entier le plus proche supérieur ou égal à `number`."
|
||||||
|
},
|
||||||
|
"$round": {
|
||||||
|
"args": "number [, precision]",
|
||||||
|
"desc": "Renvoie la valeur du paramètre `number` arrondie au nombre de décimales spécifié par le paramètre facultatif `precision`."
|
||||||
|
},
|
||||||
|
"$power": {
|
||||||
|
"args": "base, exponent",
|
||||||
|
"desc": "Renvoie la valeur de `base` élevée à la puissance de `exponent`."
|
||||||
|
},
|
||||||
|
"$sqrt": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Renvoie la racine carrée de la valeur du paramètre `number`."
|
||||||
|
},
|
||||||
|
"$random": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Renvoie un nombre pseudo-aléatoire supérieur ou égal à zéro et inférieur à un."
|
||||||
|
},
|
||||||
|
"$millis": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Renvoie le nombre de millisecondes depuis l'époque Unix (1er janvier 1970 UTC) sous forme de nombre. Tous les appels de `$millis()` dans une évaluation d'une expression renverront toutes la même valeur."
|
||||||
|
},
|
||||||
|
"$sum": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie la somme arithmétique d'un `tableau` de nombres. C'est une erreur si l'entrée `array` contient un élément qui n'est pas un nombre."
|
||||||
|
},
|
||||||
|
"$max": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie le nombre maximal dans un `tableau` de nombres. C'est une erreur si l'entrée `array` contient un élément qui n'est pas un nombre."
|
||||||
|
},
|
||||||
|
"$min": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie le nombre minimum dans un `tableau` de nombres. C'est une erreur si l'entrée `array` contient un élément qui n'est pas un nombre."
|
||||||
|
},
|
||||||
|
"$average": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie la valeur moyenne d'un `tableau` de nombres. C'est une erreur si l'entrée `array` contient un élément qui n'est pas un nombre."
|
||||||
|
},
|
||||||
|
"$boolean": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Transforme l'argument en booléen en utilisant les règles suivantes :\n\n - `Boolean` : inchangé\n - `string` : vide : `false`\n - `string` : non vide : `true`\n - `number` : `0` : `false`\n - `number` : non nul : `true`\n - `null` : `false`\n - `array` : vide : `false`\n - `array` : contient un membre qui convertit en `true` : `true`\n - `array` : tous les membres sont transformés en `false` : `false`\n - `object` : vide : `false`\n - `object` : non vide : `true`\n - `function` : `false`"
|
||||||
|
},
|
||||||
|
"$not": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Renvoie un booléen résultat de la négation logique de l'argument"
|
||||||
|
},
|
||||||
|
"$exists": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Renvoie la valeur booléenne `true` si l'expression `arg` est évaluée à une valeur, ou `false` si l'expression ne correspond à rien (par exemple, un chemin vers une référence de champ inexistante)."
|
||||||
|
},
|
||||||
|
"$count": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie le nombre d'éléments du tableau"
|
||||||
|
},
|
||||||
|
"$append": {
|
||||||
|
"args": "array, array",
|
||||||
|
"desc": "Combine deux tableaux"
|
||||||
|
},
|
||||||
|
"$sort": {
|
||||||
|
"args": "array [, function]",
|
||||||
|
"desc": "Renvoie un tableau contenant toutes les valeurs du paramètre `array`, mais triées dans l'ordre.\n\nSi un comparateur `function` est fourni, alors il doit s'agir d'une fonction qui prend deux paramètres :\n\n`function(left , droite)`\n\nCette fonction est invoquée par l'algorithme de tri pour comparer deux valeurs à gauche et à droite. Si la valeur de `left` doit être placée après la valeur de `right` dans l'ordre de tri souhaité, la fonction doit renvoyer un booléen `true` pour indiquer un échange. Sinon, il doit renvoyer `false`."
|
||||||
|
},
|
||||||
|
"$reverse": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie un tableau contenant toutes les valeurs du paramètre `array`, mais dans l'ordre inverse."
|
||||||
|
},
|
||||||
|
"$shuffle": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie un tableau contenant toutes les valeurs du paramètre `array`, mais mélangées dans un ordre aléatoire."
|
||||||
|
},
|
||||||
|
"$zip": {
|
||||||
|
"args": "array, ...",
|
||||||
|
"desc": "Renvoie un tableau convolué (zippé) contenant des tableaux groupés de valeurs des arguments `array1`...`arrayN` d'index 0, 1, 2...."
|
||||||
|
},
|
||||||
|
"$keys": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Renvoie un tableau contenant les clés de l'objet. Si l'argument est un tableau d'objets, le tableau renvoyé contient une liste dédupliquée de toutes les clés de tous les objets."
|
||||||
|
},
|
||||||
|
"$lookup": {
|
||||||
|
"args": "object, key",
|
||||||
|
"desc": "Renvoie la valeur associée à la clé dans l'objet. Si le premier argument est un tableau d'objets, tous les objets du tableau sont recherchés et les valeurs associées à toutes les occurrences de key sont renvoyées."
|
||||||
|
},
|
||||||
|
"$spread": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Divise un objet contenant des paires clé/valeur en un tableau d'objets, chacun ayant une seule paire clé/valeur de l'objet d'entrée. Si le paramètre est un tableau d'objets, alors le tableau résultant contient un objet pour chaque paire clé/valeur dans chaque objet du tableau fourni."
|
||||||
|
},
|
||||||
|
"$merge": {
|
||||||
|
"args": "array<object>",
|
||||||
|
"desc": "Fusionne un tableau d'`objets` en un seul `objet` contenant toutes les paires clé/valeur de chacun des objets du tableau d'entrée. Si l'un des objets d'entrée contient la même clé, alors l'`objet` renvoyé contiendra la valeur du dernier dans le tableau. C'est une erreur si le tableau d'entrée contient un élément qui n'est pas un objet."
|
||||||
|
},
|
||||||
|
"$sift": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Renvoie un objet qui contient uniquement les paires clé/valeur du paramètre `object` qui satisfont le prédicat `function` transmis comme second paramètre.\n\nLa `function` qui est fournie comme second paramètre doit avoir la signature suivante :\n\n`fonction(valeur [, clé [, objet]])`"
|
||||||
|
},
|
||||||
|
"$each": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Renvoie un tableau contenant les valeurs renvoyées par la `fonction` lorsqu'elle est appliquée à chaque paire clé/valeur dans l'`objet`."
|
||||||
|
},
|
||||||
|
"$map": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Renvoie un tableau contenant les résultats de l'application du paramètre `function` à chaque valeur du paramètre `array`.\n\nLa `function` fournie comme second paramètre doit avoir la signature suivante :\n\n`function( valeur [, indice [, tableau]])`"
|
||||||
|
},
|
||||||
|
"$filter": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Renvoie un tableau contenant uniquement les valeurs du paramètre `array` qui satisfont le prédicat `function`.\n\nLa `function` fournie comme second paramètre doit avoir la signature suivante :\n\n`function(value [ , indice [, tableau]])`"
|
||||||
|
},
|
||||||
|
"$reduce": {
|
||||||
|
"args": "array, function [, init]",
|
||||||
|
"desc": "Renvoie une valeur agrégée dérivée de l'application successive du paramètre `function` à chaque valeur de `array` en combinaison avec le résultat de l'application précédente de la fonction.\n\nLa fonction doit accepter deux arguments et se comporte comme un opérateur infixe entre chaque valeur dans le `tableau`. La signature de `function` doit être de la forme : `myfunc($accumulator, $value[, $index[, $array]])`\n\nLe paramètre facultatif `init` est utilisé comme valeur initiale dans l'agrégation ."
|
||||||
|
},
|
||||||
|
"$flowContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Récupère une propriété de contexte de flux.\n\nCeci est une fonction définie par Node-RED."
|
||||||
|
},
|
||||||
|
"$globalContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Récupère une propriété de contexte globale.\n\nCeci est une fonction définie par Node-RED."
|
||||||
|
},
|
||||||
|
"$pad": {
|
||||||
|
"args": "string, width [, char]",
|
||||||
|
"desc": "Renvoie une copie de la `chaîne` avec un rembourrage supplémentaire, si nécessaire, de sorte que son nombre total de caractères corresponde au moins à la valeur absolue du paramètre `width`.\n\nSi `width` est un nombre positif, alors la chaîne est rembourré à droite; s'il est négatif, il est rempli vers la gauche.\n\nL'argument optionnel `char` spécifie le(s) caractère(s) de remplissage à utiliser. S'il n'est pas spécifié, la valeur par défaut est le caractère espace."
|
||||||
|
},
|
||||||
|
"$fromMillis": {
|
||||||
|
"args": "number, [, picture [, timezone]]",
|
||||||
|
"desc": "Convertisser le « nombre » représentant les millisecondes depuis l'époque Unix (1er janvier 1970 UTC) en une représentation sous forme de chaîne formatée de l'horodatage tel que spécifié par la chaîne d'image.\n\nSi le paramètre facultatif « image » est omis, l'horodatage est formaté au format ISO 8601.\n\nSi la chaîne facultative `image` est fournie, l'horodatage est formaté selon la représentation spécifiée dans cette chaîne. Le comportement de cette fonction est cohérent avec la version à deux arguments de la fonction XPath/XQuery `format-dateTime` telle que définie dans la spécification XPath F&O 3.1. Le paramètre de chaîne d'image définit la façon dont l'horodatage est formaté et a la même syntaxe que `format-dateTime`.\n\nSi la chaîne facultative `timezone` est fournie, alors l'horodatage formaté sera dans ce fuseau horaire. La chaîne `timezone` doit être au format '±HHMM', où ± est le signe plus ou moins et HHMM est le décalage en heures et minutes par rapport à UTC. Décalage positif pour les fuseaux horaires à l'est de UTC, décalage négatif pour les fuseaux horaires à l'ouest de UTC."
|
||||||
|
},
|
||||||
|
"$formatNumber": {
|
||||||
|
"args": "number, picture [, options]",
|
||||||
|
"desc": "Convertit le `number` en une chaîne et le formate en une représentation décimale comme spécifié par la chaîne `picture`.\n\n Le comportement de cette fonction est cohérent avec la fonction XPath/XQuery `fn:format-number` telle que définie dans le Spécification XPath F&O 3.1. Le paramètre de chaîne d'image définit la façon dont le nombre est formaté et a la même syntaxe que `fn:format-number`.\n\nLe troisième argument facultatif `options` est utilisé pour remplacer les caractères de formatage spécifiques aux paramètres régionaux par défaut, tels que le séparateur décimal. S'il est fourni, cet argument doit être un objet contenant des paires nom/valeur spécifiées dans la section de format décimal de la spécification XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$formatBase": {
|
||||||
|
"args": "number [, radix]",
|
||||||
|
"desc": "Convertit le `number` en une chaîne et le formate en un entier représenté dans la base numérique spécifiée par l'argument `radix`. Si `radix` n'est pas spécifié, la valeur par défaut est la base 10. `radix` peut être compris entre 2 et 36, sinon une erreur est renvoyée."
|
||||||
|
},
|
||||||
|
"$toMillis": {
|
||||||
|
"args": "timestamp",
|
||||||
|
"desc": "Convertit une chaîne `timestamp` au format ISO 8601 en nombre de millisecondes depuis l'époque Unix (1er janvier 1970 UTC) sous forme de nombre. Une erreur est renvoyée si la chaîne n'est pas au format correct."
|
||||||
|
},
|
||||||
|
"$env": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Renvoie la valeur d'une variable d'environnement.\n\nCeci est une fonction définie par Node-RED."
|
||||||
|
},
|
||||||
|
"$eval": {
|
||||||
|
"args": "expr [, context]",
|
||||||
|
"desc": "Analyse et évalue la chaîne `expr` qui contient un JSON littéral ou une expression JSONata en utilisant le contexte actuel comme contexte d'évaluation."
|
||||||
|
},
|
||||||
|
"$formatInteger": {
|
||||||
|
"args": "number, picture",
|
||||||
|
"desc": "Transforme le `nombre` en une chaîne et le formate en une représentation entière comme spécifié par la chaîne `image`. Le paramètre de chaîne d'image définit la façon dont le nombre est formaté et a la même syntaxe que `fn:format-integer` de la spécification XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$parseInteger": {
|
||||||
|
"args": "string, picture",
|
||||||
|
"desc": "Analyse le contenu du paramètre `string` en un entier (comme un nombre JSON) en utilisant le format spécifié par la chaîne `picture`. Le paramètre de chaîne `picture` a le même format que `$formatInteger`."
|
||||||
|
},
|
||||||
|
"$error": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Génère une erreur avec un message. Le `str` facultatif remplacera le message par défaut de la fonction `$error() évaluée`"
|
||||||
|
},
|
||||||
|
"$assert": {
|
||||||
|
"args": "arg, str",
|
||||||
|
"desc": "Si `arg` est vrai, la fonction renvoie undefined. Si `arg` est faux, une exception est lancée avec `str` comme message de l'exception."
|
||||||
|
},
|
||||||
|
"$single": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Renvoie la seule et unique valeur du paramètre `array` qui satisfait le prédicat `function` (c'est-à-dire que la `function` renvoie la valeur booléenne `true` lorsqu'elle est transmise à la valeur). Lève une exception si le nombre de valeurs correspondantes n'est pas exactement un.\n\nLa fonction doit être fournie dans la signature suivante : `function(value [, index [, array]])` où value est chaque entrée du tableau, index est la position de cette valeur et le tableau entier est passé comme troisième argument"
|
||||||
|
},
|
||||||
|
"$encodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Encode un composant URL (Uniform Resource Locator) en remplaçant chaque instance de certains caractères par une, deux, trois ou quatre séquences d'échappement représentant l'encodage UTF-8 du caractère.\n\nExemple : `$encodeUrlComponent(\"?x =test\")` => `\"%3Fx%3Dtest\"`"
|
||||||
|
},
|
||||||
|
"$encodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Encode une URL (Uniform Resource Locator) en remplaçant chaque instance de certains caractères par une, deux, trois ou quatre séquences d'échappement représentant l'encodage UTF-8 du caractère.\n\nExemple : `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0% B5%D0%BB%D0%BB%D1%8B\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Décode un composant URL (Uniform Resource Locator) précédemment créé par encodeUrlComponent.\n\nExemple : `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Décode une URL (Uniform Resource Locator) précédemment créée par encodeUrl.\n\nExemple : `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
|
},
|
||||||
|
"$distinct": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Renvoie un tableau avec les valeurs en double supprimées de `array`"
|
||||||
|
},
|
||||||
|
"$type": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "Renvoie le type de `value` sous forme de chaîne. Si `value` n'est pas défini, cela renverra `undefined`"
|
||||||
|
},
|
||||||
|
"$moment": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Obtient un objet de date à l'aide de la bibliothèque Moment."
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "valeur",
|
||||||
|
"desc": "Cloner un objet en toute sécurité."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,8 @@
|
|||||||
"lock": "固定",
|
"lock": "固定",
|
||||||
"unlock": "固定を解除",
|
"unlock": "固定を解除",
|
||||||
"locked": "固定済み",
|
"locked": "固定済み",
|
||||||
"unlocked": "固定なし"
|
"unlocked": "固定なし",
|
||||||
|
"format": "形式"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "文字列",
|
"string": "文字列",
|
||||||
@@ -57,7 +58,6 @@
|
|||||||
"confirmDelete": "削除の確認",
|
"confirmDelete": "削除の確認",
|
||||||
"delete": "本当に '__label__' を削除しますか?",
|
"delete": "本当に '__label__' を削除しますか?",
|
||||||
"dropFlowHere": "ここにフローをドロップしてください",
|
"dropFlowHere": "ここにフローをドロップしてください",
|
||||||
"dropImageHere": "ここに画像ファイルをドロップしてください",
|
|
||||||
"addFlow": "フローの追加",
|
"addFlow": "フローの追加",
|
||||||
"addFlowToRight": "右側にフローを追加",
|
"addFlowToRight": "右側にフローを追加",
|
||||||
"closeFlow": "フローを閉じる",
|
"closeFlow": "フローを閉じる",
|
||||||
@@ -122,7 +122,6 @@
|
|||||||
"selectionToSubflow": "選択部分をサブフロー化",
|
"selectionToSubflow": "選択部分をサブフロー化",
|
||||||
"flows": "フロー",
|
"flows": "フロー",
|
||||||
"add": "フローを新規追加",
|
"add": "フローを新規追加",
|
||||||
"rename": "フロー名を変更",
|
|
||||||
"delete": "フローを削除",
|
"delete": "フローを削除",
|
||||||
"keyboardShortcuts": "ショートカットキーの説明",
|
"keyboardShortcuts": "ショートカットキーの説明",
|
||||||
"login": "ログイン",
|
"login": "ログイン",
|
||||||
@@ -130,6 +129,11 @@
|
|||||||
"editPalette": "パレットの管理",
|
"editPalette": "パレットの管理",
|
||||||
"other": "その他",
|
"other": "その他",
|
||||||
"showTips": "ヒントを表示",
|
"showTips": "ヒントを表示",
|
||||||
|
"showNodeHelp": "ノードのヘルプを表示",
|
||||||
|
"enableSelectedNodes": "選択したノードを有効化",
|
||||||
|
"disableSelectedNodes": "選択したノードを無効化",
|
||||||
|
"showSelectedNodeLabels": "選択したノードのラベル表示",
|
||||||
|
"hideSelectedNodeLabels": "選択したノードのラベル非表示",
|
||||||
"showWelcomeTours": "新バージョンのガイドツアーを表示",
|
"showWelcomeTours": "新バージョンのガイドツアーを表示",
|
||||||
"help": "Node-REDウェブサイト",
|
"help": "Node-REDウェブサイト",
|
||||||
"projects": "プロジェクト",
|
"projects": "プロジェクト",
|
||||||
@@ -277,8 +281,8 @@
|
|||||||
"selected": "選択したフロー",
|
"selected": "選択したフロー",
|
||||||
"current": "現在のタブ",
|
"current": "現在のタブ",
|
||||||
"all": "全てのタブ",
|
"all": "全てのタブ",
|
||||||
"compact": "インデントのないJSONフォーマット",
|
"compact": "インデントなし",
|
||||||
"formatted": "インデント付きのJSONフォーマット",
|
"formatted": "インデント付き",
|
||||||
"copy": "書き出し",
|
"copy": "書き出し",
|
||||||
"export": "ライブラリに書き出し",
|
"export": "ライブラリに書き出し",
|
||||||
"exportAs": "書き出し先",
|
"exportAs": "書き出し先",
|
||||||
@@ -299,7 +303,8 @@
|
|||||||
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
||||||
},
|
},
|
||||||
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
||||||
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
|
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
|
||||||
|
"alreadyExists": "本ノードは既に存在"
|
||||||
},
|
},
|
||||||
"copyMessagePath": "パスをコピーしました",
|
"copyMessagePath": "パスをコピーしました",
|
||||||
"copyMessageValue": "値をコピーしました",
|
"copyMessageValue": "値をコピーしました",
|
||||||
@@ -367,8 +372,12 @@
|
|||||||
"deleted": "削除",
|
"deleted": "削除",
|
||||||
"flowDeleted": "削除されたフロー",
|
"flowDeleted": "削除されたフロー",
|
||||||
"flowAdded": "追加されたフロー",
|
"flowAdded": "追加されたフロー",
|
||||||
|
"moved": "移動",
|
||||||
"movedTo": "__id__ へ移動",
|
"movedTo": "__id__ へ移動",
|
||||||
"movedFrom": "__id__ から移動"
|
"movedFrom": "__id__ から移動",
|
||||||
|
"none": "なし",
|
||||||
|
"position": "位置",
|
||||||
|
"wires": "ワイヤー"
|
||||||
},
|
},
|
||||||
"nodeCount": "__count__ 個のノード",
|
"nodeCount": "__count__ 個のノード",
|
||||||
"nodeCount_plural": "__count__ 個のノード",
|
"nodeCount_plural": "__count__ 個のノード",
|
||||||
@@ -377,9 +386,14 @@
|
|||||||
"reviewChanges": "変更を表示",
|
"reviewChanges": "変更を表示",
|
||||||
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
|
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
|
||||||
"viewCommitDiff": "コミットの内容を表示",
|
"viewCommitDiff": "コミットの内容を表示",
|
||||||
|
"commit": "コミット",
|
||||||
"compareChanges": "変更を比較",
|
"compareChanges": "変更を比較",
|
||||||
"saveConflict": "解決して保存",
|
"saveConflict": "解決して保存",
|
||||||
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
|
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
|
||||||
|
"localChanges": "ローカルの変更",
|
||||||
|
"remoteChanges": "リモートの変更",
|
||||||
|
"useLocalChanges": "ローカルの変更を使用",
|
||||||
|
"useRemoteChanges": "リモートの変更を使用",
|
||||||
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
|
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
|
||||||
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
|
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
|
||||||
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
|
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
|
||||||
@@ -416,6 +430,7 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
|
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
|
||||||
|
"acrossMultipleGroups": "複数のグループをまたがるサブフローは作成できません",
|
||||||
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
|
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -504,12 +519,13 @@
|
|||||||
"unassigned": "未割当",
|
"unassigned": "未割当",
|
||||||
"global": "グローバル",
|
"global": "グローバル",
|
||||||
"workspace": "ワークスペース",
|
"workspace": "ワークスペース",
|
||||||
|
"editor": "編集ダイアログ",
|
||||||
"selectAll": "全てのノードを選択",
|
"selectAll": "全てのノードを選択",
|
||||||
"selectNone": "選択を外す",
|
"selectNone": "選択を外す",
|
||||||
"selectAllConnected": "接続されたノードを選択",
|
"selectAllConnected": "接続されたノードを選択",
|
||||||
"addRemoveNode": "ノードの選択、選択解除",
|
"addRemoveNode": "ノードの選択、選択解除",
|
||||||
"editSelected": "選択したノードを編集",
|
"editSelected": "選択したノードを編集",
|
||||||
"deleteSelected": "選択したノードや接続を削除",
|
"deleteSelected": "選択部分を削除",
|
||||||
"deleteReconnect": "削除と再接続",
|
"deleteReconnect": "削除と再接続",
|
||||||
"importNode": "フローの読み込み",
|
"importNode": "フローの読み込み",
|
||||||
"exportNode": "フローの書き出し",
|
"exportNode": "フローの書き出し",
|
||||||
@@ -545,7 +561,9 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "ローカル",
|
"local": "ローカル",
|
||||||
"examples": "サンプル"
|
"examples": "サンプル"
|
||||||
}
|
},
|
||||||
|
"type": "型",
|
||||||
|
"name": "名前"
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "情報がありません",
|
"noInfo": "情報がありません",
|
||||||
@@ -585,6 +603,7 @@
|
|||||||
"editor": {
|
"editor": {
|
||||||
"title": "パレットの管理",
|
"title": "パレットの管理",
|
||||||
"palette": "パレット",
|
"palette": "パレット",
|
||||||
|
"allCatalogs": "全カタログ",
|
||||||
"times": {
|
"times": {
|
||||||
"seconds": "数秒前",
|
"seconds": "数秒前",
|
||||||
"minutes": "数分前",
|
"minutes": "数分前",
|
||||||
@@ -606,6 +625,8 @@
|
|||||||
},
|
},
|
||||||
"nodeCount": "__label__ 個のノード",
|
"nodeCount": "__label__ 個のノード",
|
||||||
"nodeCount_plural": "__label__ 個のノード",
|
"nodeCount_plural": "__label__ 個のノード",
|
||||||
|
"pluginCount": "__count__ 個のプラグイン",
|
||||||
|
"pluginCount_plural": "__count__ 個のプラグイン",
|
||||||
"moduleCount": "__count__ 個のモジュール",
|
"moduleCount": "__count__ 個のモジュール",
|
||||||
"moduleCount_plural": "__count__ 個のモジュール",
|
"moduleCount_plural": "__count__ 個のモジュール",
|
||||||
"inuse": "使用中",
|
"inuse": "使用中",
|
||||||
@@ -624,6 +645,7 @@
|
|||||||
"tab-nodes": "現在のノード",
|
"tab-nodes": "現在のノード",
|
||||||
"tab-install": "ノードを追加",
|
"tab-install": "ノードを追加",
|
||||||
"sort": "並べ替え:",
|
"sort": "並べ替え:",
|
||||||
|
"sortRelevance": "関連順",
|
||||||
"sortAZ": "辞書順",
|
"sortAZ": "辞書順",
|
||||||
"sortRecent": "日付順",
|
"sortRecent": "日付順",
|
||||||
"more": "+ さらに __count__ 個",
|
"more": "+ さらに __count__ 個",
|
||||||
@@ -632,6 +654,7 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
|
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
|
||||||
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
|
"installTimeout": "<p>バックグラウンドでインストールが継続されます。</p><p>完了した時にノードが表示されます。詳細はログを確認してください。</p>",
|
||||||
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||||
@@ -646,6 +669,9 @@
|
|||||||
"body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
|
"body": "<p>__module__ を削除します。</p><p>Node-REDからノードを削除します。ノードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
|
||||||
"title": "ノードを削除"
|
"title": "ノードを削除"
|
||||||
},
|
},
|
||||||
|
"removePlugin": {
|
||||||
|
"body": "<p>プラグイン __module__ を削除しました。ブラウザを再読み込みして残った表示を消してください。</p>"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
|
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
|
||||||
"title": "ノードの更新"
|
"title": "ノードの更新"
|
||||||
@@ -657,7 +683,8 @@
|
|||||||
"review": "ノードの情報を参照",
|
"review": "ノードの情報を参照",
|
||||||
"install": "追加",
|
"install": "追加",
|
||||||
"remove": "削除",
|
"remove": "削除",
|
||||||
"update": "更新"
|
"update": "更新",
|
||||||
|
"understood": "了解"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,8 +728,7 @@
|
|||||||
"copyItemUrl": "要素のURLをコピー",
|
"copyItemUrl": "要素のURLをコピー",
|
||||||
"copyURL2Clipboard": "URLをクリップボードにコピーしました",
|
"copyURL2Clipboard": "URLをクリップボードにコピーしました",
|
||||||
"showFlow": "表示",
|
"showFlow": "表示",
|
||||||
"hideFlow": "非表示",
|
"hideFlow": "非表示"
|
||||||
"find": "ワークスペース内を検索"
|
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"name": "ヘルプ",
|
"name": "ヘルプ",
|
||||||
@@ -711,6 +737,7 @@
|
|||||||
"nodeHelp": "ノードヘルプ",
|
"nodeHelp": "ノードヘルプ",
|
||||||
"showHelp": "ヘルプを表示",
|
"showHelp": "ヘルプを表示",
|
||||||
"showInOutline": "アウトラインに表示",
|
"showInOutline": "アウトラインに表示",
|
||||||
|
"hideTopics": "トピックを非表示",
|
||||||
"showTopics": "トピックを表示",
|
"showTopics": "トピックを表示",
|
||||||
"noHelp": "ヘルプのトピックが未選択",
|
"noHelp": "ヘルプのトピックが未選択",
|
||||||
"changeLog": "更新履歴"
|
"changeLog": "更新履歴"
|
||||||
@@ -785,6 +812,7 @@
|
|||||||
"branches": "ブランチ",
|
"branches": "ブランチ",
|
||||||
"noBranches": "ブランチなし",
|
"noBranches": "ブランチなし",
|
||||||
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
|
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
|
||||||
|
"deleteBranch": "ブランチを削除",
|
||||||
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
|
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
|
||||||
"deleteUnmergedBranch": "マージされていないブランチを削除",
|
"deleteUnmergedBranch": "マージされていないブランチを削除",
|
||||||
"gitRemotes": "Gitリモート",
|
"gitRemotes": "Gitリモート",
|
||||||
@@ -906,6 +934,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typedInput": {
|
"typedInput": {
|
||||||
|
"selected": "__count__個を選択",
|
||||||
|
"selected_plural": "__count__個を選択",
|
||||||
"type": {
|
"type": {
|
||||||
"str": "文字列",
|
"str": "文字列",
|
||||||
"num": "数値",
|
"num": "数値",
|
||||||
@@ -916,7 +946,14 @@
|
|||||||
"date": "日時",
|
"date": "日時",
|
||||||
"jsonata": "JSONata式",
|
"jsonata": "JSONata式",
|
||||||
"env": "環境変数",
|
"env": "環境変数",
|
||||||
"cred": "認証情報"
|
"cred": "認証情報",
|
||||||
|
"conf-types": "設定ノード"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"format": {
|
||||||
|
"timestamp": "エポックからの経過ミリ秒",
|
||||||
|
"object": "JavaScript日付オブジェクト"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
@@ -1198,21 +1235,11 @@
|
|||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"title": "システム情報"
|
"title": "システム情報"
|
||||||
},
|
},
|
||||||
"languages": {
|
|
||||||
"de": "ドイツ語",
|
|
||||||
"en-US": "英語",
|
|
||||||
"ja": "日本語",
|
|
||||||
"ko": "韓国語",
|
|
||||||
"ru": "ロシア語",
|
|
||||||
"zh-CN": "中国語(簡体)",
|
|
||||||
"zh-TW": "中国語(繁体)"
|
|
||||||
},
|
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "JSONデータが不正: __error__",
|
"invalid-json": "JSONデータが不正: __error__",
|
||||||
"invalid-json-prop": "__prop__: JSONデータが不正: __error__",
|
"invalid-expr": "不正なJSONata式: __error__",
|
||||||
"invalid-prop": "プロパティ式が不正",
|
"invalid-prop": "プロパティ式が不正",
|
||||||
"invalid-prop-prop": "__prop__: プロパティ式が不正",
|
|
||||||
"invalid-num": "数値が不正",
|
"invalid-num": "数値が不正",
|
||||||
"invalid-num-prop": "__prop__: 数値が不正",
|
"invalid-num-prop": "__prop__: 数値が不正",
|
||||||
"invalid-regexp": "入力パターンが不正",
|
"invalid-regexp": "入力パターンが不正",
|
||||||
@@ -1224,6 +1251,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
|
"showActionList": "動作一覧を表示",
|
||||||
"insert": "挿入",
|
"insert": "挿入",
|
||||||
"node": "ノード",
|
"node": "ノード",
|
||||||
"junction": "分岐点",
|
"junction": "分岐点",
|
||||||
@@ -1231,7 +1259,7 @@
|
|||||||
},
|
},
|
||||||
"env-var": {
|
"env-var": {
|
||||||
"environment": "環境変数",
|
"environment": "環境変数",
|
||||||
"header": "大域環境変数",
|
"header": "グローバル環境変数",
|
||||||
"revert": "破棄"
|
"revert": "破棄"
|
||||||
},
|
},
|
||||||
"action-list": {
|
"action-list": {
|
||||||
@@ -1318,6 +1346,7 @@
|
|||||||
"distribute-selection-vertically": "選択を上下に整列",
|
"distribute-selection-vertically": "選択を上下に整列",
|
||||||
"wire-series-of-nodes": "ノードを一続きに接続",
|
"wire-series-of-nodes": "ノードを一続きに接続",
|
||||||
"wire-node-to-multiple": "ノードを複数に接続",
|
"wire-node-to-multiple": "ノードを複数に接続",
|
||||||
|
"wire-multiple-to-node": "複数からノードへ接続",
|
||||||
"split-wire-with-link-nodes": "ワイヤーをlinkノードで分割",
|
"split-wire-with-link-nodes": "ワイヤーをlinkノードで分割",
|
||||||
"generate-node-names": "ノード名を生成",
|
"generate-node-names": "ノード名を生成",
|
||||||
"show-user-settings": "ユーザ設定を表示",
|
"show-user-settings": "ユーザ設定を表示",
|
||||||
@@ -1382,6 +1411,7 @@
|
|||||||
"copy-item-edit-url": "要素の編集URLをコピー",
|
"copy-item-edit-url": "要素の編集URLをコピー",
|
||||||
"move-flow-to-start": "フローを先頭に移動",
|
"move-flow-to-start": "フローを先頭に移動",
|
||||||
"move-flow-to-end": "フローを末尾に移動",
|
"move-flow-to-end": "フローを末尾に移動",
|
||||||
|
"show-global-env": "グローバル環境変数を表示",
|
||||||
"lock-flow": "フローを固定",
|
"lock-flow": "フローを固定",
|
||||||
"unlock-flow": "フローの固定を解除",
|
"unlock-flow": "フローの固定を解除",
|
||||||
"show-node-help": "ノードのヘルプを表示"
|
"show-node-help": "ノードのヘルプを表示"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
"$now": {
|
"$now": {
|
||||||
"args": "$[picture [, timezone]]",
|
"args": "$[picture [, timezone]]",
|
||||||
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。pictureおよびtimezoneパラメータが指定されている場合、現在時刻を`$fromMillis()`関数の説明に従ってフォーマットします。"
|
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。`picture` および `timezone` パラメータが指定されている場合、現在時刻を `$fromMillis()` 関数の説明に従ってフォーマットします。"
|
||||||
},
|
},
|
||||||
"$base64encode": {
|
"$base64encode": {
|
||||||
"args": "string",
|
"args": "string",
|
||||||
@@ -117,11 +117,11 @@
|
|||||||
},
|
},
|
||||||
"$boolean": {
|
"$boolean": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "以下のルールを用いて、ブーリアン型へ型変換します。:\n\n - `Boolean` : 変換しない\n - `string`: 空 : `false`\n - `string`: 空でない : `true`\n - `number`: `0` : `false`\n - `number`: 0でない : `true`\n - `null` : `false`\n - `array`: 空 : `false`\n - `array`: `true` に型変換された要素を持つ: `true`\n - `array`: 全ての要素が `false` に型変換: `false`\n - `object`: 空 : `false`\n - `object`: 空でない : `true`\n - `function` : `false`"
|
"desc": "以下のルールを用いて、真偽型へ型変換します。:\n\n - `Boolean` : 変換しない\n - `string`: 空 : `false`\n - `string`: 空でない : `true`\n - `number`: `0` : `false`\n - `number`: 0でない : `true`\n - `null` : `false`\n - `array`: 空 : `false`\n - `array`: `true` に型変換された要素を持つ: `true`\n - `array`: 全ての要素が `false` に型変換: `false`\n - `object`: 空 : `false`\n - `object`: 空でない : `true`\n - `function` : `false`"
|
||||||
},
|
},
|
||||||
"$not": {
|
"$not": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "引数の否定をブーリアン型で返します。 `arg` は最初にブーリアン型に型変換されます。"
|
"desc": "引数の否定を真偽型で返します。 `arg` は最初に真偽型に型変換されます。"
|
||||||
},
|
},
|
||||||
"$exists": {
|
"$exists": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args": "array [, function]",
|
"args": "array [, function]",
|
||||||
"desc": "配列 `array` 内の値を並び変えた配列を返します。\n\n比較関数 `function` を用いる場合、比較関数は以下のとおり2つの引数を持つ必要があります。\n\n`function(left, right)`\n\n比較関数は、leftとrightの2つの値を比較するために、値を並び替える処理で呼び出されます。もし、求められる並び順にてleftの値をrightの値より後ろに置きたい場合は、比較関数は置き換えを表すブーリアン型の `true` を返す必要があります。一方、置き換えが不要の場合は `false` を返す必要があります。"
|
"desc": "配列 `array` 内の値を並び変えた配列を返します。\n\n比較関数 `function` を用いる場合、比較関数は以下のとおり2つの引数を持つ必要があります。\n\n`function(left, right)`\n\n比較関数は、`left` と `right` の2つの値を比較するために、値を並び替える処理で呼び出されます。もし、求められる並び順にて `left` の値を `right` の値より後ろに置きたい場合は、比較関数は置き換えを表す真偽型の `true` を返す必要があります。一方、置き換えが不要の場合は `false` を返す必要があります。"
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
},
|
},
|
||||||
"$formatNumber": {
|
"$formatNumber": {
|
||||||
"args": "number, picture [, options]",
|
"args": "number, picture [, options]",
|
||||||
"desc": "`number` を文字列へ変換し、文字列 `picture` に指定した数値表現になるよう書式を変更します。\n\nこの関数の動作は、XPath F&O 3.1の仕様に定義されているXPath/XQuery関数のfn:format-numberの動作と同じです。引数の文字列 picture は、fn:format-numberと同じ構文で数値の書式を定義します。\n\n任意の第三引数 `options` は、小数点記号の様な既定のロケール固有の書式設定文字を上書きするために使用します。この引数を指定する場合、XPath F&O 3.1の仕様の数値形式の項に記述されているname/valueペアを含むオブジェクトでなければなりません。"
|
"desc": "`number` を文字列へ変換し、文字列 `picture` に指定した数値表現になるよう書式を変更します。\n\nこの関数の動作は、XPath F&O 3.1の仕様に定義されているXPath/XQuery関数の `fn:format-number` の動作と同じです。引数の文字列 `picture` は、 `fn:format-number` と同じ構文で数値の書式を定義します。\n\n任意の第三引数 `options` は、小数点記号の様な既定のロケール固有の書式設定文字を上書きするために使用します。この引数を指定する場合、XPath F&O 3.1の仕様の数値形式の項に記述されているname/valueペアを含むオブジェクトでなければなりません。"
|
||||||
},
|
},
|
||||||
"$formatBase": {
|
"$formatBase": {
|
||||||
"args": "number [, radix]",
|
"args": "number [, radix]",
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"$assert": {
|
"$assert": {
|
||||||
"args": "arg, str",
|
"args": "arg, str",
|
||||||
"desc": "`arg`が真の場合、undefinedを返します。偽の場合、`str`をメッセージとする例外を送出します。"
|
"desc": "`arg`が真の場合、`undefined`を返します。偽の場合、`str`をメッセージとする例外を送出します。"
|
||||||
},
|
},
|
||||||
"$single": {
|
"$single": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
},
|
},
|
||||||
"$decodeUrl": {
|
"$decodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。 \n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
"desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。\n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
},
|
},
|
||||||
"$distinct": {
|
"$distinct": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -270,5 +270,9 @@
|
|||||||
"$moment": {
|
"$moment": {
|
||||||
"args": "[str]",
|
"args": "[str]",
|
||||||
"desc": "Momentライブラリを使用して日付オブジェクトを取得します。"
|
"desc": "Momentライブラリを使用して日付オブジェクトを取得します。"
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "オブジェクトを安全に複製します。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"tip0": "{{core:delete-selection}}를 사용하여 선택된 노드나 링크를 삭제할 수 있습니다.",
|
"tip0": "{{core:delete-selection}}를 사용하여 선택된 노드나 링크를 삭제할 수 있습니다.",
|
||||||
"tip1": "{{core:search}}를 활용하여 노드를 검색할 수 있습니다.",
|
"tip1": "{{core:search}}를 활용하여 노드를 검색할 수 있습니다.",
|
||||||
"tip2": "{{core:toggle-sidebar}}를 사용하여 사이드바를 표시/비표시 전환 할 수 있습니다.",
|
"tip2": "{{core:toggle-sidebar}}를 사용하여 사이드바를 표시/비표시 전환 할 수 있습니다.",
|
||||||
"tip3": "{{core:manage-palette}}를 사용하여 노드 팔레트를 관리 할 수 있습니다.",
|
"tip3": "{{core:manage-palette}}를 사용하여 노드 팔레트를 관리 할 수 있습니다.",
|
||||||
"tip4": "플로우 안의 설정노드가 사이드바에 표시됩니다. 메뉴 혹은 {{core:show-config-tab}}를 사용하여 엑세스 할 수 있습니다.",
|
"tip4": "플로우 안의 설정노드가 사이드바에 표시됩니다. 메뉴 혹은 {{core:show-config-tab}}를 사용하여 엑세스 할 수 있습니다.",
|
||||||
"tip5": "설정에서 이 팁을 활성화/비활성화 할 수 있습니다.",
|
"tip5": "설정에서 이 팁을 활성화/비활성화 할 수 있습니다.",
|
||||||
"tip6": "[left] [up] [down] [right] 키를 사용하여 선택된 노드를 움직일 수 있습니다. [shift]키를 누른 채로 움직이면 이동폭이 늘어납니다.",
|
"tip6": "[left] [up] [down] [right] 키를 사용하여 선택된 노드를 움직일 수 있습니다. [shift]키를 누른 채로 움직이면 이동폭이 늘어납니다.",
|
||||||
"tip7": "노드를 와이어 사이로 드래그 하여 연결할 수도 있습니다.",
|
"tip7": "노드를 와이어 사이로 드래그 하여 연결할 수도 있습니다.",
|
||||||
"tip8": "{{core:show-export-dialog}}를 사용하여 선택한 노드 또는 현재탭을 내보낼 수 있습니다.",
|
"tip8": "{{core:show-export-dialog}}를 사용하여 선택한 노드 또는 현재탭을 내보낼 수 있습니다.",
|
||||||
"tip9": "JSON파일을 에디터로 드래그하거나 {{core:show-import-dialog}}를 사용하여 플로우 가져올 수 있습니다.",
|
"tip9": "JSON파일을 에디터로 드래그하거나 {{core:show-import-dialog}}를 사용하여 플로우 가져올 수 있습니다.",
|
||||||
"tip10": "[shift] [click] 하고서 드래그하여 선택한 와이어를 이동할 수 있습니다.",
|
"tip10": "[shift] [click] 하고서 드래그하여 선택한 와이어를 이동할 수 있습니다.",
|
||||||
"tip11": "{{core:show-info-tab}}를 사용하여 정보탭을 표시하거나 {{core:show-debug-tab}}를 사용하여 디버그탭을 표시할 수 있습니다.",
|
"tip11": "{{core:show-info-tab}}를 사용하여 정보탭을 표시하거나 {{core:show-debug-tab}}를 사용하여 디버그탭을 표시할 수 있습니다.",
|
||||||
"tip12": "작업공간에서 [ctrl] [click]을 사용하여 빠른추가 대회상자를 열 수 있습니다.",
|
"tip12": "작업공간에서 [ctrl] [click]을 사용하여 빠른추가 대회상자를 열 수 있습니다.",
|
||||||
"tip13": "[ctrl]을 누른 상태로 노드의 포트를 클릭하여 빠르게 연결할 수 있습니다.",
|
"tip13": "[ctrl]을 누른 상태로 노드의 포트를 클릭하여 빠르게 연결할 수 있습니다.",
|
||||||
"tip14": "[shift]를 누른 상태로 노드를 클릭하여 연결된 모든 노드를 선택할 수 있습니다.",
|
"tip14": "[shift]를 누른 상태로 노드를 클릭하여 연결된 모든 노드를 선택할 수 있습니다.",
|
||||||
"tip15": "[ctrl]을 누른 상태로 노드를 클릭하여 현재 선택영역에 노드를 추가/제거 할 수 있습니다.",
|
"tip15": "[ctrl]을 누른 상태로 노드를 클릭하여 현재 선택영역에 노드를 추가/제거 할 수 있습니다.",
|
||||||
"tip16": "{{core:show-previous-tab}}와 {{core:show-next-tab}}를 사용하여 탭을 전환할 수 있습니다.",
|
"tip16": "{{core:show-previous-tab}}와 {{core:show-next-tab}}를 사용하여 탭을 전환할 수 있습니다.",
|
||||||
"tip17": "노드 편집 창에서 {{core : confirm-edit-tray}}로 변경 사항을 확인하거나 {{core : cancel-edit-tray}}로 취소 할 수 있습니다.",
|
"tip17": "노드 편집 창에서 {{core : confirm-edit-tray}}로 변경 사항을 확인하거나 {{core : cancel-edit-tray}}로 취소 할 수 있습니다.",
|
||||||
"tip18": "{{core : edit-selected-node}}를 누르면 현재 선택 영역의 첫 번째 노드가 편집됩니다."
|
"tip18": "{{core : edit-selected-node}}를 누르면 현재 선택 영역의 첫 번째 노드가 편집됩니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,222 +1,222 @@
|
|||||||
{
|
{
|
||||||
"$string": {
|
"$string": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "다음과 같은 규칙을 사용하여 인수 *arg*를 문자열로 변환합니다. \n\n - 문자열은 변경되지 않습니다. \n - 함수는 빈 문자열로 변환됩니다. \n - 무한대와 NaN은 JSON수치로 표현할 수 없기 때문에 오류처리 됩니다. \n - 다른 모든 값은 `JSON.stringify` 함수를 사용하여 JSON 문자열로 변환됩니다."
|
"desc": "다음과 같은 규칙을 사용하여 인수 *arg*를 문자열로 변환합니다.\n\n - 문자열은 변경되지 않습니다.\n - 함수는 빈 문자열로 변환됩니다.\n - 무한대와 NaN은 JSON수치로 표현할 수 없기 때문에 오류처리 됩니다.\n - 다른 모든 값은 `JSON.stringify` 함수를 사용하여 JSON 문자열로 변환됩니다."
|
||||||
},
|
},
|
||||||
"$length": {
|
"$length": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "문자열 `str`의 문자 수를 반환합니다. `str`가 문자열이 아닌 경우 에러를 반환합니다."
|
"desc": "문자열 `str`의 문자 수를 반환합니다. `str`가 문자열이 아닌 경우 에러를 반환합니다."
|
||||||
},
|
},
|
||||||
"$substring": {
|
"$substring": {
|
||||||
"args": "str, start[, length]",
|
"args": "str, start[, length]",
|
||||||
"desc": "(zero-offset)의 `start`에서 시작하는 첫번째 인수 `str`의 문자열을 반환합니다. 만약 `length`가 지정된 경우, 부분 문자열은 최대 `length`의 크기를 갖습니다. 만약 `start` 인수가 음수이면 `str`의 끝에서부터의 문자수를 나타냅니다."
|
"desc": "(zero-offset)의 `start`에서 시작하는 첫번째 인수 `str`의 문자열을 반환합니다. 만약 `length`가 지정된 경우, 부분 문자열은 최대 `length`의 크기를 갖습니다. 만약 `start` 인수가 음수이면 `str`의 끝에서부터의 문자수를 나타냅니다."
|
||||||
},
|
},
|
||||||
"$substringBefore": {
|
"$substringBefore": {
|
||||||
"args": "str, chars",
|
"args": "str, chars",
|
||||||
"desc": "`str`에 `chars`문자가 처음으로 나오기 전까지의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
|
"desc": "`str`에 `chars`문자가 처음으로 나오기 전까지의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
|
||||||
},
|
},
|
||||||
"$substringAfter": {
|
"$substringAfter": {
|
||||||
"args": "str, chars",
|
"args": "str, chars",
|
||||||
"desc": "`str`에 `chars`문자가 처음으로 나온 이후의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
|
"desc": "`str`에 `chars`문자가 처음으로 나온 이후의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
|
||||||
},
|
},
|
||||||
"$uppercase": {
|
"$uppercase": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "`str`의 문자를 대문자로 반환합니다."
|
"desc": "`str`의 문자를 대문자로 반환합니다."
|
||||||
},
|
},
|
||||||
"$lowercase": {
|
"$lowercase": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "`str`의 문자를 소문자로 반환합니다."
|
"desc": "`str`의 문자를 소문자로 반환합니다."
|
||||||
},
|
},
|
||||||
"$trim": {
|
"$trim": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "다음의 순서대로 `str`의 모든 공백을 자르고 정규화 합니다:\n\n - 모든 탭, 캐리지 리턴 및 줄 바꿈은 공백으로 대체됩니다. \n- 연속된 공백은 하나로 줄입니다.\n- 후행 및 선행 공백은 삭제됩니다.\n\n 만일 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `str`이 문자열이 아니면 에러가 발생합니다."
|
"desc": "다음의 순서대로 `str`의 모든 공백을 자르고 정규화 합니다:\n\n - 모든 탭, 캐리지 리턴 및 줄 바꿈은 공백으로 대체됩니다.\n- 연속된 공백은 하나로 줄입니다.\n- 후행 및 선행 공백은 삭제됩니다.\n\n 만일 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `str`이 문자열이 아니면 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$contains": {
|
"$contains": {
|
||||||
"args": "str, pattern",
|
"args": "str, pattern",
|
||||||
"desc": "`str`이 `pattern`과 일치하면 `true`를, 일치하지 않으면 `false`를 반환합니다. 만약 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `pattern` 인수는 문자열이나 정규표현으로 할 수 있습니다."
|
"desc": "`str`이 `pattern`과 일치하면 `true`를, 일치하지 않으면 `false`를 반환합니다. 만약 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `pattern` 인수는 문자열이나 정규표현으로 할 수 있습니다."
|
||||||
},
|
},
|
||||||
"$split": {
|
"$split": {
|
||||||
"args": "str[, separator][, limit]",
|
"args": "str[, separator][, limit]",
|
||||||
"desc": "`str`인수를 분할하여 부분문자열로 배열합니다. `str`이 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 `separator`는 `str`을 분할하는 문자를 문자열 또는 정규표현으로 지정합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주하여 `str`은 단일 문자의 배열로 분리됩니다. `separator`가 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 'limit`는 결과의 배열이 갖는 부분문자열의 최대수를 지정합니다. 이 수를 넘는 부분문자열은 파기됩니다. `limit`가 지정되지 않으면`str`은 결과 배열의 크기의 제한없이 완전히 분리됩니다. `limit`이 음수인 경우 에러가 발생합니다."
|
"desc": "`str`인수를 분할하여 부분문자열로 배열합니다. `str`이 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 `separator`는 `str`을 분할하는 문자를 문자열 또는 정규표현으로 지정합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주하여 `str`은 단일 문자의 배열로 분리됩니다. `separator`가 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 'limit`는 결과의 배열이 갖는 부분문자열의 최대수를 지정합니다. 이 수를 넘는 부분문자열은 파기됩니다. `limit`가 지정되지 않으면`str`은 결과 배열의 크기의 제한없이 완전히 분리됩니다. `limit`이 음수인 경우 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$join": {
|
"$join": {
|
||||||
"args": "array[, separator]",
|
"args": "array[, separator]",
|
||||||
"desc": "문자열의 배열을 생략가능한 인수 `separator`로 구분한 하나의 문자열로 연결합니다. 배열 `array`가 문자열이 아닌 요소를 포함하는 경우, 에러가 발생합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주합니다(예: 문자열간의 `separator`없음). `separator`가 문자열이 아닌 경우, 에러가 발생합니다."
|
"desc": "문자열의 배열을 생략가능한 인수 `separator`로 구분한 하나의 문자열로 연결합니다. 배열 `array`가 문자열이 아닌 요소를 포함하는 경우, 에러가 발생합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주합니다(예: 문자열간의 `separator`없음). `separator`가 문자열이 아닌 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$match": {
|
"$match": {
|
||||||
"args": "str, pattern [, limit]",
|
"args": "str, pattern [, limit]",
|
||||||
"desc": "`str`문자열에 `pattern`를 적용하여, 오브젝트 배열을 반환합니다. 배열요소의 오브젝트는 `str`중 일치하는 부분의 정보를 보유합니다."
|
"desc": "`str`문자열에 `pattern`를 적용하여, 오브젝트 배열을 반환합니다. 배열요소의 오브젝트는 `str`중 일치하는 부분의 정보를 보유합니다."
|
||||||
},
|
},
|
||||||
"$replace": {
|
"$replace": {
|
||||||
"args": "str, pattern, replacement [, limit]",
|
"args": "str, pattern, replacement [, limit]",
|
||||||
"desc": "`str`문자열에서 `pattern` 패턴을 검색하여, `replacement`로 대체합니다.\n\n임의이ㅡ 인수 `limit`는 대체 횟수의 상한값을 지정합니다."
|
"desc": "`str`문자열에서 `pattern` 패턴을 검색하여, `replacement`로 대체합니다.\n\n임의이ㅡ 인수 `limit`는 대체 횟수의 상한값을 지정합니다."
|
||||||
},
|
},
|
||||||
"$now": {
|
"$now": {
|
||||||
"args": "",
|
"args": "",
|
||||||
"desc": "ISO 8601 호환 형식으로 타임 스탬프를 생성하고 이를 문자열로 반환합니다."
|
"desc": "ISO 8601 호환 형식으로 타임 스탬프를 생성하고 이를 문자열로 반환합니다."
|
||||||
},
|
},
|
||||||
"$base64encode": {
|
"$base64encode": {
|
||||||
"args": "string",
|
"args": "string",
|
||||||
"desc": "ASCII 문자열을 base 64 표현으로 변환합니다. 문자열의 각 문자는 이진 데이터의 바이트로 처리됩니다. 이렇게 하려면 문자열의 모든 문자가 URI로 인코딩 된 문자열을 포함하고, 0x00에서 0xFF 범위에 있어야합니다. 해당 범위를 벗어난 유니 코드 문자는 지원되지 않습니다"
|
"desc": "ASCII 문자열을 base 64 표현으로 변환합니다. 문자열의 각 문자는 이진 데이터의 바이트로 처리됩니다. 이렇게 하려면 문자열의 모든 문자가 URI로 인코딩 된 문자열을 포함하고, 0x00에서 0xFF 범위에 있어야합니다. 해당 범위를 벗어난 유니 코드 문자는 지원되지 않습니다"
|
||||||
},
|
},
|
||||||
"$base64decode": {
|
"$base64decode": {
|
||||||
"args": "string",
|
"args": "string",
|
||||||
"desc": "UTF-8코드페이지를 이용하여, Base 64형식의 바이트값을 문자열로 변환합니다."
|
"desc": "UTF-8코드페이지를 이용하여, Base 64형식의 바이트값을 문자열로 변환합니다."
|
||||||
},
|
},
|
||||||
"$number": {
|
"$number": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "`arg`를 다음과 같은 규칙을 사요하여 숫자로 변환합니다. :\n\n - 숫자는 변경되지 않습니다.\n – 올바른 JSON의 숫자는 숫자 그대로 변환됩니다.\n – 그 외의 형식은 에러를 발생합니다."
|
"desc": "`arg`를 다음과 같은 규칙을 사요하여 숫자로 변환합니다. :\n\n - 숫자는 변경되지 않습니다.\n – 올바른 JSON의 숫자는 숫자 그대로 변환됩니다.\n – 그 외의 형식은 에러를 발생합니다."
|
||||||
},
|
},
|
||||||
"$abs": {
|
"$abs": {
|
||||||
"args": "number",
|
"args": "number",
|
||||||
"desc": "`number`의 절대값을 반환합니다."
|
"desc": "`number`의 절대값을 반환합니다."
|
||||||
},
|
},
|
||||||
"$floor": {
|
"$floor": {
|
||||||
"args": "number",
|
"args": "number",
|
||||||
"desc": "`number`를 `number`보다 같거나 작은 정수로 내림하여 반환합니다."
|
"desc": "`number`를 `number`보다 같거나 작은 정수로 내림하여 반환합니다."
|
||||||
},
|
},
|
||||||
"$ceil": {
|
"$ceil": {
|
||||||
"args": "number",
|
"args": "number",
|
||||||
"desc": "`number`를 `number`와 같거나 큰 정수로 올림하여 반환합니다."
|
"desc": "`number`를 `number`와 같거나 큰 정수로 올림하여 반환합니다."
|
||||||
},
|
},
|
||||||
"$round": {
|
"$round": {
|
||||||
"args": "number [, precision]",
|
"args": "number [, precision]",
|
||||||
"desc": "인수 `number`를 반올림한 값을 반환합니다. 임의의 인수 `precision`에는 반올립에서 사용할 소수점이하의 자릿수를 지정합니다."
|
"desc": "인수 `number`를 반올림한 값을 반환합니다. 임의의 인수 `precision`에는 반올립에서 사용할 소수점이하의 자릿수를 지정합니다."
|
||||||
},
|
},
|
||||||
"$power": {
|
"$power": {
|
||||||
"args": "base, exponent",
|
"args": "base, exponent",
|
||||||
"desc": "기수 `base`의 값을 지수 `exponent`만큼의 거듭 제곱으로 반환합니다."
|
"desc": "기수 `base`의 값을 지수 `exponent`만큼의 거듭 제곱으로 반환합니다."
|
||||||
},
|
},
|
||||||
"$sqrt": {
|
"$sqrt": {
|
||||||
"args": "number",
|
"args": "number",
|
||||||
"desc": "인수 `number`의 제곱근을 반환합니다."
|
"desc": "인수 `number`의 제곱근을 반환합니다."
|
||||||
},
|
},
|
||||||
"$random": {
|
"$random": {
|
||||||
"args": "",
|
"args": "",
|
||||||
"desc": "0이상 1미만의 의사난수를 반환합니다."
|
"desc": "0이상 1미만의 의사난수를 반환합니다."
|
||||||
},
|
},
|
||||||
"$millis": {
|
"$millis": {
|
||||||
"args": "",
|
"args": "",
|
||||||
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC)부터 경과된 밀리 초 수를 숫자로 반환합니다. 평가대상식에 포함되는 $millis()의 모든 호출은 모두 같은 값을 반환합니다."
|
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC)부터 경과된 밀리 초 수를 숫자로 반환합니다. 평가대상식에 포함되는 $millis()의 모든 호출은 모두 같은 값을 반환합니다."
|
||||||
},
|
},
|
||||||
"$sum": {
|
"$sum": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "숫자 배열 `array`의 합계를 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
"desc": "숫자 배열 `array`의 합계를 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$max": {
|
"$max": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "숫자 배열 `array`에서 최대값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
"desc": "숫자 배열 `array`에서 최대값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$min": {
|
"$min": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "숫자 배열 `array`에서 최소값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
"desc": "숫자 배열 `array`에서 최소값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$average": {
|
"$average": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "숫자 배열 `array`에서 평균값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
"desc": "숫자 배열 `array`에서 평균값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$boolean": {
|
"$boolean": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "`arg` 값을 다음의 규칙에 의해 Boolean으로 변환합니다::\n\n - `Boolean` : 변환하지 않음\n - `string`: 비어있음 : `false`\n - `string`: 비어있지 않음 : `true`\n - `number`: `0` : `false`\n - `number`: 0이 아님 : `true`\n - `null` : `false`\n - `array`: 비어있음 : `false`\n - `array`: `true`로 변환된 요소를 가짐 : `true`\n - `array`: 모든 요소가 `false`로 변환 : `false`\n - `object`: 비어있음 : `false`\n - `object`: 비어있지 않음 : `true`\n - `function` : `false`"
|
"desc": "`arg` 값을 다음의 규칙에 의해 Boolean으로 변환합니다::\n\n - `Boolean` : 변환하지 않음\n - `string`: 비어있음 : `false`\n - `string`: 비어있지 않음 : `true`\n - `number`: `0` : `false`\n - `number`: 0이 아님 : `true`\n - `null` : `false`\n - `array`: 비어있음 : `false`\n - `array`: `true`로 변환된 요소를 가짐 : `true`\n - `array`: 모든 요소가 `false`로 변환 : `false`\n - `object`: 비어있음 : `false`\n - `object`: 비어있지 않음 : `true`\n - `function` : `false`"
|
||||||
},
|
},
|
||||||
"$not": {
|
"$not": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "인수의 부정을 Boolean으로 변환합니다. `arg`는 가장먼저boolean으로 변환됩니다."
|
"desc": "인수의 부정을 Boolean으로 변환합니다. `arg`는 가장먼저boolean으로 변환됩니다."
|
||||||
},
|
},
|
||||||
"$exists": {
|
"$exists": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "`arg` 식의 평가값이 존재하는 경우 `true`, 식의 평가결과가 미정의인 경우 (예: 존재하지 않는 참조필드로의 경로)는 `false`를 반환합니다."
|
"desc": "`arg` 식의 평가값이 존재하는 경우 `true`, 식의 평가결과가 미정의인 경우 (예: 존재하지 않는 참조필드로의 경로)는 `false`를 반환합니다."
|
||||||
},
|
},
|
||||||
"$count": {
|
"$count": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "`array`의 요소 갯수를 반환합니다."
|
"desc": "`array`의 요소 갯수를 반환합니다."
|
||||||
},
|
},
|
||||||
"$append": {
|
"$append": {
|
||||||
"args": "array, array",
|
"args": "array, array",
|
||||||
"desc": "두개의 `array`를 병합합니다."
|
"desc": "두개의 `array`를 병합합니다."
|
||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args": "array [, function]",
|
"args": "array [, function]",
|
||||||
"desc": "배열 `array`의 모든 값을 순서대로 정렬하여 반환합니다. \n\n 비교함수 `function`을 이용하는 경우, 비교함수는 아래와 같은 두개의 인수를 가져야 합니다. \n\n `function(left,right)` \n\n 비교함수는 left와 right의 두개의 값을 비교하기에, 값을 정렬하는 처리에서 호출됩니다. 만약 요구되는 정렬에서 left값을 right값보다 뒤로 두고싶은 경우에는, 비교함수는 치환을 나타내는 Boolean형의 ``true`를, 그렇지 않은 경우에는 `false`를 반환해야 합니다."
|
"desc": "배열 `array`의 모든 값을 순서대로 정렬하여 반환합니다.\n\n 비교함수 `function`을 이용하는 경우, 비교함수는 아래와 같은 두개의 인수를 가져야 합니다.\n\n `function(left,right)`\n\n 비교함수는 `left`와 `right`의 두개의 값을 비교하기에, 값을 정렬하는 처리에서 호출됩니다. 만약 요구되는 정렬에서 left값을 `right`값보다 뒤로 두고싶은 경우에는, 비교함수는 치환을 나타내는 Boolean형의 `true`를, 그렇지 않은 경우에는 `false`를 반환해야 합니다."
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "`array`에 포함된 모든 값의 순서를 역순으로 변환하여 반환합니다."
|
"desc": "`array`에 포함된 모든 값의 순서를 역순으로 변환하여 반환합니다."
|
||||||
},
|
},
|
||||||
"$shuffle": {
|
"$shuffle": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
"desc": "`array`에 포함된 모든 값의 순서를 랜덤으로 반환합니다."
|
"desc": "`array`에 포함된 모든 값의 순서를 랜덤으로 반환합니다."
|
||||||
},
|
},
|
||||||
"$zip": {
|
"$zip": {
|
||||||
"args": "array, ...",
|
"args": "array, ...",
|
||||||
"desc": "배열 `array1` ... arrayN`의 위치 0, 1, 2…. 의 값으로 구성된 convolved (zipped) 배열을 반환합니다."
|
"desc": "배열 `array1` ... arrayN`의 위치 0, 1, 2…. 의 값으로 구성된 convolved (zipped) 배열을 반환합니다."
|
||||||
},
|
},
|
||||||
"$keys": {
|
"$keys": {
|
||||||
"args": "object",
|
"args": "object",
|
||||||
"desc": "`object` 키를 포함하는 배열을 반환합니다. 인수가 오브젝트의 배열이면 반환되는 배열은 모든 오브젝트에있는 모든 키의 중복되지 않은 목록이 됩니다."
|
"desc": "`object` 키를 포함하는 배열을 반환합니다. 인수가 오브젝트의 배열이면 반환되는 배열은 모든 오브젝트에있는 모든 키의 중복되지 않은 목록이 됩니다."
|
||||||
},
|
},
|
||||||
"$lookup": {
|
"$lookup": {
|
||||||
"args": "object, key",
|
"args": "object, key",
|
||||||
"desc": "`object` 내의 `key`가 갖는 값을 반환합니다. 최초의 인수가 객체의 배열 인 경우, 배열 내의 모든 오브젝트를 검색하여, 존재하는 모든 키가 갖는 값을 반환합니다."
|
"desc": "`object` 내의 `key`가 갖는 값을 반환합니다. 최초의 인수가 객체의 배열 인 경우, 배열 내의 모든 오브젝트를 검색하여, 존재하는 모든 키가 갖는 값을 반환합니다."
|
||||||
},
|
},
|
||||||
"$spread": {
|
"$spread": {
|
||||||
"args": "object",
|
"args": "object",
|
||||||
"desc": "`object`의 키/값 쌍별로 각 요소가 하나인 오브젝트 배열로 분할합니다. 만일 오브젝트 배열인 경우, 배열의 결과는 각 오브젝트에서 얻은 키/값 쌍의 오브젝트를 갖습니다."
|
"desc": "`object`의 키/값 쌍별로 각 요소가 하나인 오브젝트 배열로 분할합니다. 만일 오브젝트 배열인 경우, 배열의 결과는 각 오브젝트에서 얻은 키/값 쌍의 오브젝트를 갖습니다."
|
||||||
},
|
},
|
||||||
"$merge": {
|
"$merge": {
|
||||||
"args": "array<object>",
|
"args": "array<object>",
|
||||||
"desc": "`object`배열을 하나의 `object`로 병합합니다. 병합결과의 오브젝트는 입력배열내의 각 오브젝트의 키/값 쌍을 포함합니다. 입력 오브젝트가 같은 키를 가질경우, 반환 된 `object`에는 배열 마지막의 오브젝트의 키/값이 격납됩니다. 입력 배열이 오브젝트가 아닌 요소를 포함하는 경우, 에러가 발생합니다."
|
"desc": "`object`배열을 하나의 `object`로 병합합니다. 병합결과의 오브젝트는 입력배열내의 각 오브젝트의 키/값 쌍을 포함합니다. 입력 오브젝트가 같은 키를 가질경우, 반환 된 `object`에는 배열 마지막의 오브젝트의 키/값이 격납됩니다. 입력 배열이 오브젝트가 아닌 요소를 포함하는 경우, 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$sift": {
|
"$sift": {
|
||||||
"args": "object, function",
|
"args": "object, function",
|
||||||
"desc": "함수 `function`을 충족시키는 `object` 인수 키/값 쌍만 포함하는 오브젝트를 반환합니다. \n\n 함수 `function` 다음과 같은 인수를 가져야 합니다 : \n\n `function(value [, key [, object]])`"
|
"desc": "함수 `function`을 충족시키는 `object` 인수 키/값 쌍만 포함하는 오브젝트를 반환합니다.\n\n 함수 `function` 다음과 같은 인수를 가져야 합니다 :\n\n `function(value [, key [, object]])`"
|
||||||
},
|
},
|
||||||
"$each": {
|
"$each": {
|
||||||
"args": "object, function",
|
"args": "object, function",
|
||||||
"desc": "`object`의 각 키/값 쌍에, 함수`function`을 적용한 값의 배열을 반환합니다."
|
"desc": "`object`의 각 키/값 쌍에, 함수`function`을 적용한 값의 배열을 반환합니다."
|
||||||
},
|
},
|
||||||
"$map": {
|
"$map": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
"desc": "`array`의 각 값에 `function`을 적용한 결과로 이루어진 배열을 반환합니다. \n\n 함수 `function`은 다음과 같은 인수를 가져야 합니다. \n\n `function(value[, index[, array]])`"
|
"desc": "`array`의 각 값에 `function`을 적용한 결과로 이루어진 배열을 반환합니다.\n\n 함수 `function`은 다음과 같은 인수를 가져야 합니다.\n\n `function(value[, index[, array]])`"
|
||||||
},
|
},
|
||||||
"$filter": {
|
"$filter": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
"desc": "`array`의 값중, 함수 `function`의 조건을 만족하는 값으로 이루어진 배열을 반환합니다. \n\n 함수 `function`은 다음과 같은 형식을 가져야 합니다. \n\n `function(value[, index[, array]])`"
|
"desc": "`array`의 값중, 함수 `function`의 조건을 만족하는 값으로 이루어진 배열을 반환합니다.\n\n 함수 `function`은 다음과 같은 형식을 가져야 합니다.\n\n `function(value[, index[, array]])`"
|
||||||
},
|
},
|
||||||
"$reduce": {
|
"$reduce": {
|
||||||
"args": "array, function [, init]",
|
"args": "array, function [, init]",
|
||||||
"desc": "배열의 각 요소값에 함수 `function`을 연속적으로 적용하여 얻어지는 집계값을 반환합니다. `function`의 적용에는 직전의 `function`의 적용결과와 요소값이 인수로 주어집니다. \n\n 함수 `function`은 인수를 두개 뽑아, 배열의 각 요소 사이에 배치하는 중치연산자처럼 작용해야 합니다. \n\n 임의의 인수 `init`에는 집약시의 초기값을 설정합니다."
|
"desc": "배열의 각 요소값에 함수 `function`을 연속적으로 적용하여 얻어지는 집계값을 반환합니다. `function`의 적용에는 직전의 `function`의 적용결과와 요소값이 인수로 주어집니다.\n\n 함수 `function`은 인수를 두개 뽑아, 배열의 각 요소 사이에 배치하는 중치연산자처럼 작용해야 합니다.\n\n 임의의 인수 `init`에는 집약시의 초기값을 설정합니다."
|
||||||
},
|
},
|
||||||
"$flowContext": {
|
"$flowContext": {
|
||||||
"args": "string[, string]",
|
"args": "string[, string]",
|
||||||
"desc": "플로우 컨텍스트 속성을 취득합니다."
|
"desc": "플로우 컨텍스트 속성을 취득합니다."
|
||||||
},
|
},
|
||||||
"$globalContext": {
|
"$globalContext": {
|
||||||
"args": "string[, string]",
|
"args": "string[, string]",
|
||||||
"desc": "플로우의 글로벌 컨텍스트 속성을 취득합니다."
|
"desc": "플로우의 글로벌 컨텍스트 속성을 취득합니다."
|
||||||
},
|
},
|
||||||
"$pad": {
|
"$pad": {
|
||||||
"args": "string, width [, char]",
|
"args": "string, width [, char]",
|
||||||
"desc": "문자수가 인수 `width`의 절대값이상이 되도록, 필요한 경우 여분의 패딩을 사용하여 `string`의 복사본을 반환합니다. \n\n `width`가 양수인 경우, 오른쪽으로 채워지고, 음수이면 왼쪽으로 채워집니다. \n\n 임의의 `char`인수에는 이 함수에서 사용할 패딩을 지정합니다. 지정하지 않는 경우에는, 기본값으로 공백을 사용합니다."
|
"desc": "문자수가 인수 `width`의 절대값이상이 되도록, 필요한 경우 여분의 패딩을 사용하여 `string`의 복사본을 반환합니다.\n\n `width`가 양수인 경우, 오른쪽으로 채워지고, 음수이면 왼쪽으로 채워집니다.\n\n 임의의 `char`인수에는 이 함수에서 사용할 패딩을 지정합니다. 지정하지 않는 경우에는, 기본값으로 공백을 사용합니다."
|
||||||
},
|
},
|
||||||
"$fromMillis": {
|
"$fromMillis": {
|
||||||
"args": "number",
|
"args": "number",
|
||||||
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초를 나타내는 숫자를 ISO 8601 형식의 타임 스탬프 문자열로 변환합니다."
|
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초를 나타내는 숫자를 ISO 8601 형식의 타임 스탬프 문자열로 변환합니다."
|
||||||
},
|
},
|
||||||
"$formatNumber": {
|
"$formatNumber": {
|
||||||
"args": "number, picture [, options]",
|
"args": "number, picture [, options]",
|
||||||
"desc": "`number`를 문자열로 변환하고 `picture` 문자열에 지정된 표현으로 서식을 변경합니다. \n\n 이 함수의 동작은 XPath F&O 3.1사양에 정의된 XPath/XQuery함수의 fn:format-number의 동작과 같습니다. 인수의 문자열 picture은 fn:format-number 과 같은 구문으로 수치의 서식을 정의합니다. \n\n 임의의 제3 인수 `option`은 소수점기호와 같은 기본 로케일 고유의 서식설정문자를 덮어쓰는데에 사용됩니다. 이 인수를 지정할 경우, XPath F&O 3.1사양의 수치형식에 기술되어있는 name/value 쌍을 포함하는 오브젝트여야 합니다."
|
"desc": "`number`를 문자열로 변환하고 `picture` 문자열에 지정된 표현으로 서식을 변경합니다.\n\n 이 함수의 동작은 XPath F&O 3.1사양에 정의된 XPath/XQuery함수의 `fn:format-number`의 동작과 같습니다. 인수의 문자열 `picture`은 `fn:format-number` 과 같은 구문으로 수치의 서식을 정의합니다.\n\n 임의의 제3 인수 `option`은 소수점기호와 같은 기본 로케일 고유의 서식설정문자를 덮어쓰는데에 사용됩니다. 이 인수를 지정할 경우, XPath F&O 3.1사양의 수치형식에 기술되어있는 name/value 쌍을 포함하는 오브젝트여야 합니다."
|
||||||
},
|
},
|
||||||
"$formatBase": {
|
"$formatBase": {
|
||||||
"args": "number [, radix]",
|
"args": "number [, radix]",
|
||||||
"desc": "`number`를 인수 `radix`에 지정한 값을 기수로하는 문자열로 변환합니다. `radix`가 지정되지 않은 경우, 기수 10이 기본값으로 설정됩니다. `radix`에는 2~36의 값을 설정할 수 있고, 그 외의 값의 경우에는 에러가 발생합니다."
|
"desc": "`number`를 인수 `radix`에 지정한 값을 기수로하는 문자열로 변환합니다. `radix`가 지정되지 않은 경우, 기수 10이 기본값으로 설정됩니다. `radix`에는 2~36의 값을 설정할 수 있고, 그 외의 값의 경우에는 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$toMillis": {
|
"$toMillis": {
|
||||||
"args": "timestamp",
|
"args": "timestamp",
|
||||||
"desc": "ISO 8601 형식의 `timestamp`를 Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초 수로 변환합니다. 문자열이 올바른 형식이 아닌 경우 에러가 발생합니다."
|
"desc": "ISO 8601 형식의 `timestamp`를 Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초 수로 변환합니다. 문자열이 올바른 형식이 아닌 경우 에러가 발생합니다."
|
||||||
},
|
},
|
||||||
"$env": {
|
"$env": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "환경변수를 값으로 반환합니다.\n\n 이 함수는 Node-RED 정의 함수입니다."
|
"desc": "환경변수를 값으로 반환합니다.\n\n 이 함수는 Node-RED 정의 함수입니다."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1195
packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json
vendored
Normal file
1195
packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
23
packages/node_modules/@node-red/editor-client/locales/pt-BR/infotips.json
vendored
Normal file
23
packages/node_modules/@node-red/editor-client/locales/pt-BR/infotips.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"tip0": "Você pode remover os nós ou links selecionados com {{core:delete-selection}}",
|
||||||
|
"tip1": "Procure por nós usando {{core:search}}",
|
||||||
|
"tip2": "{{core:toggle-sidebar}} irá alternar a visualização desta barra lateral",
|
||||||
|
"tip3": "Você pode gerenciar sua paleta de nós com {{core:manage-palette}}",
|
||||||
|
"tip4": "Seus nós de configuração de fluxo são listados no painel da barra lateral. Pode ser acessado a partir do menu ou com{{core:show-config-tab}}",
|
||||||
|
"tip5": "Habilite ou desabilite essas dicas na opção nas configurações",
|
||||||
|
"tip6": "Mova os nós selecionados usando o [left] [up] [down] e [right] chaves. Segure [shift] para empurrá-los ainda mais",
|
||||||
|
"tip7": "Arrastar um nó para um fio o unirá no link",
|
||||||
|
"tip8": "Exporte os nós selecionados ou a guia atual com {{core:show-export-dialog}}",
|
||||||
|
"tip9": "Importe um fluxo arrastando seu JSON para o editor ou com {{core:show-import-dialog}}",
|
||||||
|
"tip10": "[shift] [click] e arraste em uma porta de nó para mover todos os fios conectados ou apenas o selecionado",
|
||||||
|
"tip11": "Mostre a guia Informações com {{core:show-info-tab}} ou a guia Depurar com {{core:show-debug-tab}}",
|
||||||
|
"tip12": "[ctrl] [click] na área de trabalho para abrir a caixa de diálogo de adição rápida",
|
||||||
|
"tip13": "Mantenha pressionado [ctrl] enquanto você [click] em uma porta de nó para habilitar a ligação rápida",
|
||||||
|
"tip14": "Mantenha pressionado [shift] enquanto você [click] em um nó para também selecionar todos os seus nós conectados",
|
||||||
|
"tip15": "Mantenha pressionado [ctrl] enquanto você [click] em um nó para adicioná-lo ou removê-lo da seleção atual",
|
||||||
|
"tip16": "Alternar guias de fluxo com {{core:show-previous-tab}} e {{core:show-next-tab}}",
|
||||||
|
"tip17": "Você pode confirmar suas alterações na bandeja de edição do nó com {{core:confirm-edit-tray}} ou cancele-os com {{core:cancel-edit-tray}}",
|
||||||
|
"tip18": "Pressionando {{core:edit-selected-node}} irá editar o primeiro nó na seleção atual"
|
||||||
|
}
|
||||||
|
}
|
||||||
274
packages/node_modules/@node-red/editor-client/locales/pt-BR/jsonata.json
vendored
Normal file
274
packages/node_modules/@node-red/editor-client/locales/pt-BR/jsonata.json
vendored
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
{
|
||||||
|
"$string": {
|
||||||
|
"args": "arg[, prettify]",
|
||||||
|
"desc": "Converte o tipo do parâmetro `arg` em uma cadeia de caracteres usando as seguintes regras de conversão de tipo:\n\n - Cadeia de caracteres não são alteradas\n - As funções são convertidas para uma cadeia de caracteres vazia\n - os tipos numérico infinito e NaN geram um erro porque não podem ser representados como um número JSON\n - Todos os outros valores são convertidos para uma cadeia de caracteres JSON usando a função `JSON.stringify`. Se `prettify` for verdadeira, então o JSON \"prettified\" é produzido. Isto é, uma linha por campo e as linhas serão indentadas com base na profundidade do campo."
|
||||||
|
},
|
||||||
|
"$length": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Retorna o número de caracteres na cadeia de caracteres `str`. Um erro é gerado se `str` não for uma cadeia de caracteres."
|
||||||
|
},
|
||||||
|
"$substring": {
|
||||||
|
"args": "str, start[, length]",
|
||||||
|
"desc": "Retorna uma cadeia de caracteres contendo os caracteres no primeiro parâmetro `str` começando na posição `start` (deslocamento zero). Se` length` for especificado, então a sub cadeia de caracteres conterá o máximo `length` de caracteres. Se` start` for negativo isso indica o número de caracteres a partir do fim de `str`."
|
||||||
|
},
|
||||||
|
"$substringBefore": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Retorna a sub cadeia de caracteres antes da primeira ocorrência da sequência de caracteres `chars` em `string`. Se` string` não contiver `chars`, então retorna `str`."
|
||||||
|
},
|
||||||
|
"$substringAfter": {
|
||||||
|
"args": "str, chars",
|
||||||
|
"desc": "Retorna a sub cadeia de caracteres após a primeira ocorrência da sequência de caracteres `chars` em `string`. Se `string` não contiver `chars`, então retorna `str`."
|
||||||
|
},
|
||||||
|
"$uppercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Retorna uma cadeia de caracteres com todos os caracteres de `string` convertidos em maiúsculas."
|
||||||
|
},
|
||||||
|
"$lowercase": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Retorna uma cadeia de caracteres com todos os caracteres de `string` convertidos em minúsculas."
|
||||||
|
},
|
||||||
|
"$trim": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Normaliza e retira todos os caracteres de espaço em branco em `str` aplicando as seguintes etapas:\n\n - Todas as tabulações, retornos de carro e avanços de linha são substituídos por espaços.\n- Sequências contíguas de espaços são reduzidas a um único espaço.\n- Espaços à direita e à esquerda são removidos.\n\n Se `str` não for especificado (isto é, esta função é chamada sem argumentos), então o valor do contexto é usado como o valor de `str`. Um erro é gerado se `str` não for uma cadeia de caracteres."
|
||||||
|
},
|
||||||
|
"$contains": {
|
||||||
|
"args": "str, pattern",
|
||||||
|
"desc": "Retorna `true` se `str` tiver correspondente em `pattern`, caso contrário, retorna `false`. Se `str` não for especificado (isto é, esta função é chamada com um argumento), então o valor do contexto é usado como o valor de `str`. O parâmetro `pattern` pode ser uma cadeia de caracteres ou uma expressão regular."
|
||||||
|
},
|
||||||
|
"$split": {
|
||||||
|
"args": "str[, separator][, limit]",
|
||||||
|
"desc": "Divide o parâmetro `str` em uma matriz de sub cadeia de caracteres. É um erro se `str` não for uma cadeia de caracteres. O parâmetro opcional `separator` especifica os caracteres dentro de `str` sobre os quais devem ser divididos como uma cadeia de caracteres ou expressão regular. Se `separator` não for especificado, a cadeia de caracteres vazia será assumida e `str` será dividido em uma matriz de caracteres únicos. É um erro se `separador` não for uma cadeia de caracteres. O parâmetro opcional `limit` é um número que especifica o número máximo de sub cadeia de caracteres a serem incluídas na matriz resultante. Quaisquer sub cadeia de caracteres adicionais são descartadas. Se `limit` não for especificado, então `str` será totalmente dividido sem limite para o tamanho da matriz resultante . É um erro se `limit` não for um número não negativo."
|
||||||
|
},
|
||||||
|
"$join": {
|
||||||
|
"args": "array[, separator]",
|
||||||
|
"desc": "Une uma matriz de cadeias de caracteres de componentes em uma única cadeia de caracteres concatenada com cada cadeia de caracteres de componente separada pelo parâmetro opcional `separator`. É um erro se a `matriz` de entrada contiver um item que não seja uma cadeia de caracteres. Se `separator` for não especificado, assume-se que é uma cadeia de caracteres vazia, ou seja, nenhum `separator` entre as cadeias de caracteres do componente. É um erro se `separator` não for uma cadeia de caracteres."
|
||||||
|
},
|
||||||
|
"$match": {
|
||||||
|
"args": "str, pattern [, limit]",
|
||||||
|
"desc": "Aplica a cadeia de caracteres `str` à expressão regular `pattern` e retorna uma matriz de objetos, com cada objeto contendo informações sobre cada ocorrência de uma correspondência dentro de `str`."
|
||||||
|
},
|
||||||
|
"$replace": {
|
||||||
|
"args": "str, pattern, replacement [, limit]",
|
||||||
|
"desc": "Encontra ocorrências de `pattern` dentro de `str` e as substitui por `replacement`.\n\nO parâmetro opcional `limit` é o número máximo de substituições."
|
||||||
|
},
|
||||||
|
"$now": {
|
||||||
|
"args": "$[picture [, timezone]]",
|
||||||
|
"desc": "Gera um carimbo de data/hora em formato compatível com ISO 8601 e o retorna como uma cadeia de caracteres. Se os parâmetros opcionais de imagem e fuso horário forem fornecidos, o carimbo de data/hora atual é formatado conforme descrito pela função `$fromMillis()`"
|
||||||
|
},
|
||||||
|
"$base64encode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Converte uma cadeia de caracteres ASCII em uma representação de base 64. Cada caractere na cadeia de caracteres é tratado como um byte de dados binários. Isso requer que todos os caracteres na cadeia de caracteres estejam no intervalo de 0x00 a 0xFF, o que inclui todos os caracteres em cadeias de caracteres codificadas em URI. Caracteres Unicode fora desse intervalo não são suportados."
|
||||||
|
},
|
||||||
|
"$base64decode": {
|
||||||
|
"args": "string",
|
||||||
|
"desc": "Converte bytes codificados de base 64 em uma cadeia de caracteres, usando uma página de código UTF-8 Unicode."
|
||||||
|
},
|
||||||
|
"$number": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Converte o parâmetro `arg` em um número usando as seguintes regras de conversão:\n\n - Os números permanecem inalterados\n - Cadeias de caracteres que contêm uma sequência de caracteres que representam um número JSON válido são convertidos para esse número\n - Todos os outros valores causam a geração de um erro."
|
||||||
|
},
|
||||||
|
"$abs": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Retorna o valor absoluto do parâmetro `number`."
|
||||||
|
},
|
||||||
|
"$floor": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Retorna o valor de `number` arredondado para baixo para o inteiro mais próximo que seja menor ou igual a `number`."
|
||||||
|
},
|
||||||
|
"$ceil": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Retorna o valor de `number` arredondado para o número inteiro mais próximo que é maior ou igual a `number`."
|
||||||
|
},
|
||||||
|
"$round": {
|
||||||
|
"args": "number [, precision]",
|
||||||
|
"desc": "Retorna o valor do parâmetro `number` arredondado para o número de casas decimais especificado pelo parâmetro opcional `precision`."
|
||||||
|
},
|
||||||
|
"$power": {
|
||||||
|
"args": "base, exponent",
|
||||||
|
"desc": "Retorna o valor de `base` elevado à potência de `exponent`."
|
||||||
|
},
|
||||||
|
"$sqrt": {
|
||||||
|
"args": "number",
|
||||||
|
"desc": "Retorna a raiz quadrada do valor do parâmetro `number`."
|
||||||
|
},
|
||||||
|
"$random": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Retorna um número pseudoaleatório maior ou igual a zero e menor que um."
|
||||||
|
},
|
||||||
|
"$millis": {
|
||||||
|
"args": "",
|
||||||
|
"desc": "Retorna o número de milissegundos desde o Unix Epoch (1º de janeiro de 1970 UTC) como um número. Todas as invocações de `$millis()` dentro de uma avaliação de uma expressão retornarão todas o mesmo valor."
|
||||||
|
},
|
||||||
|
"$sum": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna a soma aritmética de uma `array` de números. É um erro se o `array` de entrada contiver um item que não seja um número."
|
||||||
|
},
|
||||||
|
"$max": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna o número máximo em uma `array` de números. É um erro se o `array` de entrada contiver um item que não seja um número."
|
||||||
|
},
|
||||||
|
"$min": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna o número mínimo em uma `array` de números. É um erro se o `array` de entrada contiver um item que não seja um número."
|
||||||
|
},
|
||||||
|
"$average": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna o valor médio de uma `array` de números. É um erro se o `array` de entrada contiver um item que não seja um número."
|
||||||
|
},
|
||||||
|
"$boolean": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Converte o argumento em um booliano usando as seguintes regras:\n\n - `Boolean` : inalterado\n - `string`: vazio : `false`\n - `string`: não-vazio : `true`\n - `number`: `0` : `false`\n - `number`: não-zero : `true`\n - `null` : `false`\n - `array`: vazio : `false`\n - `array`: contém um membro que converte de tipo para `true` : `true`\n - `array`: todos os membros convertidos de tipo para `false` : `false`\n - `object`: vazio : `false`\n - `object`: não-vazio : `true`\n - `function` : `false`"
|
||||||
|
},
|
||||||
|
"$not": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Retorna booliano NOT no argumento. `Arg` é convertido de tipo primeiro para um booliano"
|
||||||
|
},
|
||||||
|
"$exists": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Retorna booliano `true` se a expressão `arg` for avaliada como um valor, ou `false` se a expressão não corresponder a nada (por exemplo, um caminho para uma referência de campo inexistente)."
|
||||||
|
},
|
||||||
|
"$count": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna o número de itens na matriz"
|
||||||
|
},
|
||||||
|
"$append": {
|
||||||
|
"args": "array, array",
|
||||||
|
"desc": "Anexa duas matrizes"
|
||||||
|
},
|
||||||
|
"$sort": {
|
||||||
|
"args": "array [, function]",
|
||||||
|
"desc": "Retorna uma matriz contendo todos os valores no parâmetro `array`, mas classificados em ordem.\n\nSe um comparador `function` for fornecido, então deve ser uma função que leva dois parâmetros:\n\n`function(left, right)`\n\nEsta função é invocada pelo algoritmo de classificação para comparar dois valores à esquerda e à direita. Se o valor de esquerda deve ser colocado após o valor de direita na ordem de classificação desejada, a função deve retornar o booliano `true` para indicar uma troca. Caso contrário, deve retornar `false`."
|
||||||
|
},
|
||||||
|
"$reverse": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna uma matriz contendo todos os valores do parâmetro `array`, mas na ordem reversa."
|
||||||
|
},
|
||||||
|
"$shuffle": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna uma matriz contendo todos os valores do parâmetro `array`, mas misturados em ordem aleatória."
|
||||||
|
},
|
||||||
|
"$zip": {
|
||||||
|
"args": "array, ...",
|
||||||
|
"desc": "Retorna uma matriz convolucional (compactada) contendo matrizes agrupadas de valores dos argumentos `array1`… `arrayN` do índice 0, 1, 2 ...."
|
||||||
|
},
|
||||||
|
"$keys": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Retorna uma matriz contendo as chaves do objeto. Se o argumento for uma matriz de objetos, então a matriz retornada contém uma lista não duplicada de todas as chaves em todos os objetos."
|
||||||
|
},
|
||||||
|
"$lookup": {
|
||||||
|
"args": "object, key",
|
||||||
|
"desc": "Retorna o valor associado à chave no objeto. Se o primeiro argumento for uma matriz de objetos, todos os objetos na matriz são pesquisados e os valores associados a todas as ocorrências da chave são retornados."
|
||||||
|
},
|
||||||
|
"$spread": {
|
||||||
|
"args": "object",
|
||||||
|
"desc": "Divide um objeto que contém pares de chave/valor em uma matriz de objetos, cada um com um único par de chave/valor do objeto de entrada. Se o parâmetro for uma matriz de objetos, a matriz resultante conterá um objeto para cada par de chave/valor em todo objeto na matriz fornecida."
|
||||||
|
},
|
||||||
|
"$merge": {
|
||||||
|
"args": "array<object>",
|
||||||
|
"desc": "Mescla uma matriz de `objects` em um único `object` contendo todos os pares de chave/valor de cada um dos objetos na matriz de entrada. Se qualquer um dos objetos de entrada contiver a mesma chave, então o `object` retornado conterá o valor do último na matriz. É um erro se a matriz de entrada contiver um item que não seja um objeto."
|
||||||
|
},
|
||||||
|
"$sift": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Retorna um objeto que contém apenas os pares de chave/valor do parâmetro `object` que satisfazem o predicado `function` passado como o segundo parâmetro.\n\nA `function` que é fornecida como o segundo parâmetro deve ter o seguinte assinatura:\n\n`function(value [, key [, object]])`"
|
||||||
|
},
|
||||||
|
"$each": {
|
||||||
|
"args": "object, function",
|
||||||
|
"desc": "Retorna uma matriz contendo os valores retornados por `function` quando aplicado a cada par chave/valor no `object`."
|
||||||
|
},
|
||||||
|
"$map": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Retorna uma matriz contendo os resultados da aplicação do parâmetro `function` a cada valor no parâmetro `array`.\n\nA `function` que é fornecido como o segundo parâmetro deve ter a seguinte assinatura:\n\n`function(value [, index [, array]])`"
|
||||||
|
},
|
||||||
|
"$filter": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Retorna uma matriz contendo apenas os valores no parâmetro `array` que satisfazem o predicado `function`.\n\nThe `function` que é fornecido como o segundo parâmetro deve ter a seguinte assinatura:\n\n`function(value [, index [, array]])`"
|
||||||
|
},
|
||||||
|
"$reduce": {
|
||||||
|
"args": "array, function [, init]",
|
||||||
|
"desc": "Retorna um valor agregado derivado da aplicação do parâmetro `function` sucessivamente a cada valor em `array` em combinação com o resultado da aplicação anterior da função.\n\nA função deve aceitar dois argumentos e se comportar como um operador inserido entre cada valor dentro de `array`. A assinatura da `function` deve estar no formato: `myfunc($accumulator, $value[, $index[, $array]])`\n\nO parâmetro opcional `init` é usado como o valor inicial na agregação."
|
||||||
|
},
|
||||||
|
"$flowContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Recupera uma propriedade de contexto de fluxo.\n\nEsta é uma função definida pelo Node-RED."
|
||||||
|
},
|
||||||
|
"$globalContext": {
|
||||||
|
"args": "string[, string]",
|
||||||
|
"desc": "Recupera uma propriedade de contexto global.\n\nEsta é uma função definida pelo Node-RED."
|
||||||
|
},
|
||||||
|
"$pad": {
|
||||||
|
"args": "string, width [, char]",
|
||||||
|
"desc": "Retorna uma cópia da `string` com preenchimento extra, se necessário, de forma que seu número total de caracteres seja pelo menos o valor absoluto do parâmetro `width`.\n\nSe `width` for um número positivo, a cadeia de caracteres será preenchida à direita; se negativo, é preenchida à esquerda.\n\nO argumento opcional `char` especifica os caracteres de preenchimento a serem usados. Se não for especificado, o padrão é o caractere de espaço."
|
||||||
|
},
|
||||||
|
"$fromMillis": {
|
||||||
|
"args": "number, [, picture [, timezone]]",
|
||||||
|
"desc": "Converta o `number` que representa os milissegundos desde a época do Unix (1 January, 1970 UTC) em uma representação de cadeia de caracteres formatada do carimbo de data/hora conforme especificado pela cadeia de caracteres de imagem.\n\nSe o parâmetro opcional `image` for omitido, o carimbo de data/hora será formatado no formato ISO 8601.\n\nSe a cadeia de caracteresopcional `picture` for fornecida, o carimbo de data/hora é formatado de acordo com a representação especificada nessa cadeia de caracteres. O comportamento desta função é consistente com a versão de dois argumentos da função XPath/XQuery `format-dateTime` conforme definido na especificação XPath F&O 3.1. O parâmetro de cadeia de caracteres de imagem define como o carimbo de data/hora é formatado e tem a mesma sintaxe de `format-dateTime`.\n\nSe a cadeia de caracteres opcional `timezone` for fornecida, o carimbo de data/hora formatado estará nesse fuso horário. A cadeia de caracteres `timezone` deve estar no formato '± HHMM', onde ± é o sinal de mais ou menos e HHMM é o deslocamento em horas e minutos do UTC. Deslocamento positivo para fusos horários a leste do UTC, deslocamento negativo para fusos horários a oeste do UTC."
|
||||||
|
},
|
||||||
|
"$formatNumber": {
|
||||||
|
"args": "number, picture [, options]",
|
||||||
|
"desc": "Converte o tipo de `number` em uma cadeia de caracteres e o formata em uma representação decimal conforme especificado pela cadeia de caracteres `picture`.\n\n O comportamento desta função é consistente com a função XPath/XQuery `fn:format-number` conforme definido na especificação XPath F&O 3.1. O parâmetro de cadeia de caracteres de imagem define como o número é formatado e tem a mesma sintaxe de `fn:format-number`.\n\nO terceiro argumento opcional `options` é usado para substituir os caracteres de formatação específicos da localidade padrão, como o separador decimal. Se fornecido, este argumento deve ser um objeto contendo pares de nome/valor especificados na seção de formato decimal da especificação XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$formatBase": {
|
||||||
|
"args": "number [, radix]",
|
||||||
|
"desc": "Converte o `number` em uma cadeia de caracteres e o formata em um inteiro representado na base do número especificada pelo argumento `radix`. Se `radix` não for especificado, o padrão é a base 10. `radix` pode estar entre 2 e 36, caso contrário, um erro será gerado."
|
||||||
|
},
|
||||||
|
"$toMillis": {
|
||||||
|
"args": "timestamp",
|
||||||
|
"desc": "Converta o tipo de uma cadeia de caracteres `timestamp` no formato ISO 8601 para o número de milissegundos desde a época do Unix (1 January, 1970 UTC) como um número. Um erro é gerado se a cadeia de caracteres não estiver no formato correto."
|
||||||
|
},
|
||||||
|
"$env": {
|
||||||
|
"args": "arg",
|
||||||
|
"desc": "Retorna o valor de uma variável de ambiente.\n\nEsta é uma função definida pelo Node-RED."
|
||||||
|
},
|
||||||
|
"$eval": {
|
||||||
|
"args": "expr [, context]",
|
||||||
|
"desc": "Analisa e avalia a cadeia de caracteres `expr` que contém um JSON literal ou uma expressão JSONata usando o contexto atual como o contexto para avaliação."
|
||||||
|
},
|
||||||
|
"$formatInteger": {
|
||||||
|
"args": "number, picture",
|
||||||
|
"desc": "Converte o tipo de `number` em uma cadeia de caracteres e o formata em uma representação inteira conforme especificado pela cadeia de caracteres `picture`. O parâmetro da cadeia de caracteres de imagem define como o número é formatado e tem a mesma sintaxe de `fn:format-integer` do Especificação XPath F&O 3.1."
|
||||||
|
},
|
||||||
|
"$parseInteger": {
|
||||||
|
"args": "string, picture",
|
||||||
|
"desc": "Examina e troca o conteúdo do parâmetro `string` para um inteiro (como um número JSON) usando o formato especificado pela cadeia de caracteres `picture`. O parâmetro da cadeia de caracteres `picture` tem o mesmo formato que `$formatInteger`."
|
||||||
|
},
|
||||||
|
"$error": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Gera um erro com uma mensagem. O (parâmetro) opcional `str` substituirá a mensagem padrão de `$error() function evaluated`"
|
||||||
|
},
|
||||||
|
"$assert": {
|
||||||
|
"args": "arg, str",
|
||||||
|
"desc": "Se `arg` for verdadeiro, a função retorna indefinido. Se `arg` for falso, uma exceção é gerada com `str` como a mensagem da exceção."
|
||||||
|
},
|
||||||
|
"$single": {
|
||||||
|
"args": "array, function",
|
||||||
|
"desc": "Retorna o único valor no parâmetro `array` que satisfaz o predicado `function` (isto é, O (parâmetro) `function` retorna o booliano `true` quando passado o valor). Gera uma exceção se o número de valores correspondentes não for exatamente um .\n\nA função deve ser fornecida na seguinte assinatura: `function(value [, index [, array]])` onde 'value' é cada entrada da matriz, 'index' é a posição desse valor e toda a matriz é passada como o terceiro argumento"
|
||||||
|
},
|
||||||
|
"$encodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Codifica um componente Localizador Uniforme de Recursos (URL) substituindo cada instância de certos caracteres por uma, duas, três ou quatro sequências de escape que representam a codificação UTF-8 do caractere.\n\nExemplo: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||||
|
},
|
||||||
|
"$encodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Codifica um Localizador Uniforme de Recursos (URL) substituindo cada instância de certos caracteres por uma, duas, três ou quatro sequências de escape que representam a codificação UTF-8 do caractere.\n\nExemplo: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrlComponent": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Decodifica um componente Localizador Uniforme de Recursos (URL) criado anteriormente por encodeUrlComponent.\n\nExemplo: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
|
},
|
||||||
|
"$decodeUrl": {
|
||||||
|
"args": "str",
|
||||||
|
"desc": "Decodifica um Localizador Uniforme de Recursos (URL) criado anteriormente por encodeUrl.\n\nExemplo: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
|
},
|
||||||
|
"$distinct": {
|
||||||
|
"args": "array",
|
||||||
|
"desc": "Retorna uma matriz com valores duplicados removidos da `array`"
|
||||||
|
},
|
||||||
|
"$type": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "Retorna o tipo de `value` como uma cadeia de caracteres. Se `value` for indefinido, retornará `undefined`"
|
||||||
|
},
|
||||||
|
"$moment": {
|
||||||
|
"args": "[str]",
|
||||||
|
"desc": "Obtém um objeto de dados usando a biblioteca 'Moment'."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,7 +95,6 @@
|
|||||||
"selectionToSubflow": "Выделение в подпоток",
|
"selectionToSubflow": "Выделение в подпоток",
|
||||||
"flows": "Потоки",
|
"flows": "Потоки",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"rename": "Переименовать",
|
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"keyboardShortcuts": "Сочетания клавиш",
|
"keyboardShortcuts": "Сочетания клавиш",
|
||||||
"login": "Войти",
|
"login": "Войти",
|
||||||
@@ -1129,14 +1128,5 @@
|
|||||||
"appearance": "Внешний вид",
|
"appearance": "Внешний вид",
|
||||||
"preview": "Предпросмотр редактора",
|
"preview": "Предпросмотр редактора",
|
||||||
"defaultValue": "Значение по умолчанию"
|
"defaultValue": "Значение по умолчанию"
|
||||||
},
|
|
||||||
"languages" : {
|
|
||||||
"de": "Немецкий",
|
|
||||||
"en-US": "Английский",
|
|
||||||
"ja": "Японский",
|
|
||||||
"ko": "Корейский",
|
|
||||||
"ru": "Русский",
|
|
||||||
"zh-CN": "Китайский (упрощенный)",
|
|
||||||
"zh-TW": "Китайский (традиционный)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,52 +52,52 @@
|
|||||||
"desc": "Находит вхождения шаблона `pattern` в строке `str` и заменяет их на строку `replacement`.\n\nНеобязательный параметр `limit` - это максимальное количество замен."
|
"desc": "Находит вхождения шаблона `pattern` в строке `str` и заменяет их на строку `replacement`.\n\nНеобязательный параметр `limit` - это максимальное количество замен."
|
||||||
},
|
},
|
||||||
"$now": {
|
"$now": {
|
||||||
"args":"",
|
"args": "",
|
||||||
"desc":"Создает отметку времени в формате, совместимом с ISO 8601, и возвращает ее как строку."
|
"desc": "Создает отметку времени в формате, совместимом с ISO 8601, и возвращает ее как строку."
|
||||||
},
|
},
|
||||||
"$base64encode": {
|
"$base64encode": {
|
||||||
"args":"string",
|
"args": "string",
|
||||||
"desc":"Преобразует ASCII-строку в base-64 кодировку. Каждый символ в строке обрабатывается как байт двоичных данных. Для этого необходимо, чтобы все символы в строке находились в диапазоне от 0x00 до 0xFF, который включает все символы строк в URI-кодировке. Символы Юникода за пределами этого диапазона не поддерживаются."
|
"desc": "Преобразует ASCII-строку в base-64 кодировку. Каждый символ в строке обрабатывается как байт двоичных данных. Для этого необходимо, чтобы все символы в строке находились в диапазоне от 0x00 до 0xFF, который включает все символы строк в URI-кодировке. Символы Юникода за пределами этого диапазона не поддерживаются."
|
||||||
},
|
},
|
||||||
"$base64decode": {
|
"$base64decode": {
|
||||||
"args":"string",
|
"args": "string",
|
||||||
"desc":"Преобразует байты в кодировке base-64 в строку, используя кодовую страницу Юникод UTF-8."
|
"desc": "Преобразует байты в кодировке base-64 в строку, используя кодовую страницу Юникод UTF-8."
|
||||||
},
|
},
|
||||||
"$number": {
|
"$number": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "Преобразует параметр `arg` в число с использованием следующих правил приведения:\n\n - Числа возвращаются как есть\n - Строки, которые содержат последовательность символов, представляющих допустимое в JSON число, преобразуются в это число\n - Все остальные значения вызывают ошибку."
|
"desc": "Преобразует параметр `arg` в число с использованием следующих правил приведения:\n\n - Числа возвращаются как есть\n - Строки, которые содержат последовательность символов, представляющих допустимое в JSON число, преобразуются в это число\n - Все остальные значения вызывают ошибку."
|
||||||
},
|
},
|
||||||
"$abs": {
|
"$abs": {
|
||||||
"args":"number",
|
"args": "number",
|
||||||
"desc":"Возвращает абсолютное значение числа `number`."
|
"desc": "Возвращает абсолютное значение числа `number`."
|
||||||
},
|
},
|
||||||
"$floor": {
|
"$floor": {
|
||||||
"args":"number",
|
"args": "number",
|
||||||
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое меньше или равно `number`."
|
"desc": "Возвращает значение числа `number`, округленное до ближайшего целого числа, которое меньше или равно `number`."
|
||||||
},
|
},
|
||||||
"$ceil": {
|
"$ceil": {
|
||||||
"args":"number",
|
"args": "number",
|
||||||
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое больше или равно `number`."
|
"desc": "Возвращает значение числа `number`, округленное до ближайшего целого числа, которое больше или равно `number`."
|
||||||
},
|
},
|
||||||
"$round": {
|
"$round": {
|
||||||
"args":"number [, precision]",
|
"args": "number [, precision]",
|
||||||
"desc":"Возвращает значение числа `number`, округленное до количества десятичных знаков, указанных необязательным параметром `precision`."
|
"desc": "Возвращает значение числа `number`, округленное до количества десятичных знаков, указанных необязательным параметром `precision`."
|
||||||
},
|
},
|
||||||
"$power": {
|
"$power": {
|
||||||
"args":"base, exponent",
|
"args": "base, exponent",
|
||||||
"desc":"Возвращает значение числа `base`, возведенное в степень `exponent`."
|
"desc": "Возвращает значение числа `base`, возведенное в степень `exponent`."
|
||||||
},
|
},
|
||||||
"$sqrt": {
|
"$sqrt": {
|
||||||
"args":"number",
|
"args": "number",
|
||||||
"desc":"Возвращает квадратный корень из значения числа `number`."
|
"desc": "Возвращает квадратный корень из значения числа `number`."
|
||||||
},
|
},
|
||||||
"$random": {
|
"$random": {
|
||||||
"args":"",
|
"args": "",
|
||||||
"desc":"Возвращает псевдослучайное число, которе больше или равно нулю и меньше единицы."
|
"desc": "Возвращает псевдослучайное число, которе больше или равно нулю и меньше единицы."
|
||||||
},
|
},
|
||||||
"$millis": {
|
"$millis": {
|
||||||
"args":"",
|
"args": "",
|
||||||
"desc":"Возвращает число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу) в виде числа. Все вызовы `$millis()` в пределах выполнения выражения будут возвращать одно и то же значение."
|
"desc": "Возвращает число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу) в виде числа. Все вызовы `$millis()` в пределах выполнения выражения будут возвращать одно и то же значение."
|
||||||
},
|
},
|
||||||
"$sum": {
|
"$sum": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
},
|
},
|
||||||
"$boolean": {
|
"$boolean": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "Приводит аргумент к логическому значению, используя следующие правила: \n\n - Логические значения возвращаются как есть\n - пустая строка: `false`\n - непустая строка: `true`\n - число равное `0`: `false`\n - ненулевое число: `true`\n - `null` : `false`\n - пустой массив: `false`\n - массив, который содержит хотя бы один элемент, приводимый к `true`: `true`\n - массив, все элементы которого приводятся к `false`: `false`\n - пустой объект: `false`\n - непустой объект: `true`\n - функция: `false`"
|
"desc": "Приводит аргумент к логическому значению, используя следующие правила:\n\n - Логические значения возвращаются как есть\n - пустая строка: `false`\n - непустая строка: `true`\n - число равное `0`: `false`\n - ненулевое число: `true`\n - `null` : `false`\n - пустой массив: `false`\n - массив, который содержит хотя бы один элемент, приводимый к `true`: `true`\n - массив, все элементы которого приводятся к `false`: `false`\n - пустой объект: `false`\n - непустой объект: `true`\n - функция: `false`"
|
||||||
},
|
},
|
||||||
"$not": {
|
"$not": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
@@ -136,20 +136,20 @@
|
|||||||
"desc": "Присоединяет один массив к другому"
|
"desc": "Присоединяет один массив к другому"
|
||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args":"array [, function]",
|
"args": "array [, function]",
|
||||||
"desc":"Возвращает массив, содержащий все значения параметра `array`, но отсортированные по порядку.\n\nЕсли указан компаратор `function`, то это должна быть функция, которая принимает два параметра:\n\n`function(val1, val2)`\n\nЭту функцию вызывает алгоритм сортировки для сравнения двух значений: val1 и val2. Если значение val1 следует поместить после значения val2 в желаемом порядке сортировки, то функция должна возвращать логическое значение `true`, чтобы обозначить замену. В противном случае она должна вернуть `false`."
|
"desc": "Возвращает массив, содержащий все значения параметра `array`, но отсортированные по порядку.\n\nЕсли указан компаратор `function`, то это должна быть функция, которая принимает два параметра:\n\n`function(val1, val2)`\n\nЭту функцию вызывает алгоритм сортировки для сравнения двух значений: val1 и val2. Если значение val1 следует поместить после значения val2 в желаемом порядке сортировки, то функция должна возвращать логическое значение `true`, чтобы обозначить замену. В противном случае она должна вернуть `false`."
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args":"array",
|
"args": "array",
|
||||||
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но в обратном порядке."
|
"desc": "Возвращает массив, содержащий все значения из параметра `array`, но в обратном порядке."
|
||||||
},
|
},
|
||||||
"$shuffle": {
|
"$shuffle": {
|
||||||
"args":"array",
|
"args": "array",
|
||||||
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но перемешанный в случайном порядке."
|
"desc": "Возвращает массив, содержащий все значения из параметра `array`, но перемешанный в случайном порядке."
|
||||||
},
|
},
|
||||||
"$zip": {
|
"$zip": {
|
||||||
"args":"array, ...",
|
"args": "array, ...",
|
||||||
"desc":"Возвращает свернутый (сжатый) массив, содержащий сгруппированные массивы значений из аргументов `array1` … `arrayN` по индексам 0, 1, 2...."
|
"desc": "Возвращает свернутый (сжатый) массив, содержащий сгруппированные массивы значений из аргументов `array1` … `arrayN` по индексам 0, 1, 2...."
|
||||||
},
|
},
|
||||||
"$keys": {
|
"$keys": {
|
||||||
"args": "object",
|
"args": "object",
|
||||||
@@ -168,24 +168,24 @@
|
|||||||
"desc": "Объединяет массив объектов в один объект, содержащий все пары ключ / значение каждого из объектов входного массива. Если какой-либо из входных объектов содержит один и тот же ключ, возвращаемый объект будет содержать значение последнего в массиве. Вызывает ошибку, если входной массив содержит элемент, который не является объектом."
|
"desc": "Объединяет массив объектов в один объект, содержащий все пары ключ / значение каждого из объектов входного массива. Если какой-либо из входных объектов содержит один и тот же ключ, возвращаемый объект будет содержать значение последнего в массиве. Вызывает ошибку, если входной массив содержит элемент, который не является объектом."
|
||||||
},
|
},
|
||||||
"$sift": {
|
"$sift": {
|
||||||
"args":"object, function",
|
"args": "object, function",
|
||||||
"desc":"Возвращает объект, который содержит только пары ключ / значение из параметра `object`, которые удовлетворяют предикату `function`, переданному в качестве второго параметра.\n\n`function`, которая передается в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, key [, object]])`"
|
"desc": "Возвращает объект, который содержит только пары ключ / значение из параметра `object`, которые удовлетворяют предикату `function`, переданному в качестве второго параметра.\n\n`function`, которая передается в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, key [, object]])`"
|
||||||
},
|
},
|
||||||
"$each": {
|
"$each": {
|
||||||
"args":"object, function",
|
"args": "object, function",
|
||||||
"desc":"Возвращает массив, который содержит значения, возвращаемые функцией `function` при применении к каждой паре ключ/значение из объекта `object`."
|
"desc": "Возвращает массив, который содержит значения, возвращаемые функцией `function` при применении к каждой паре ключ/значение из объекта `object`."
|
||||||
},
|
},
|
||||||
"$map": {
|
"$map": {
|
||||||
"args":"array, function",
|
"args": "array, function",
|
||||||
"desc":"Возвращает массив, содержащий результаты применения функции `function` к каждому значению массива `array`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
"desc": "Возвращает массив, содержащий результаты применения функции `function` к каждому значению массива `array`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
||||||
},
|
},
|
||||||
"$filter": {
|
"$filter": {
|
||||||
"args":"array, function",
|
"args": "array, function",
|
||||||
"desc":"Возвращает массив, содержащий только те значения из массива `array`, которые удовлетворяют предикату `function`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
"desc": "Возвращает массив, содержащий только те значения из массива `array`, которые удовлетворяют предикату `function`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
||||||
},
|
},
|
||||||
"$reduce": {
|
"$reduce": {
|
||||||
"args":"array, function [, init]",
|
"args": "array, function [, init]",
|
||||||
"desc":"Возвращает агрегированное значение, полученное в результате последовательного применения функции `function` к каждому значению в массиве в сочетании с результатом от предыдущего применения функции.\n\nФункция должна принимать два аргумента и вести себя как инфиксный оператор между каждым значением в массиве `array`. Сигнатура `function` должна иметь форму: `myfunc($accumulator, $value[, $index[, $array]])`\n\nНеобязательный параметр `init` используется в качестве начального значения в агрегации."
|
"desc": "Возвращает агрегированное значение, полученное в результате последовательного применения функции `function` к каждому значению в массиве в сочетании с результатом от предыдущего применения функции.\n\nФункция должна принимать два аргумента и вести себя как инфиксный оператор между каждым значением в массиве `array`. Сигнатура `function` должна иметь форму: `myfunc($accumulator, $value[, $index[, $array]])`\n\nНеобязательный параметр `init` используется в качестве начального значения в агрегации."
|
||||||
},
|
},
|
||||||
"$flowContext": {
|
"$flowContext": {
|
||||||
"args": "string[, string]",
|
"args": "string[, string]",
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"$assert": {
|
"$assert": {
|
||||||
"args": "arg, str",
|
"args": "arg, str",
|
||||||
"desc": "Если значение `arg` равно true, функция возвращает значение undefined. Если значение `arg` равно false, генерируется исключение с `str` в качестве сообщения об исключении."
|
"desc": "Если значение `arg` равно `true`, функция возвращает значение undefined. Если значение `arg` равно `false`, генерируется исключение с `str` в качестве сообщения об исключении."
|
||||||
},
|
},
|
||||||
"$single": {
|
"$single": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
},
|
},
|
||||||
"$decodeUrl": {
|
"$decodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrl. \n\nПример: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrl.\n\nПример: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
},
|
},
|
||||||
"$distinct": {
|
"$distinct": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -117,7 +117,7 @@
|
|||||||
},
|
},
|
||||||
"$boolean": {
|
"$boolean": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
"desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值 `Boolean` 。\n – 将空的字符串 `string` 转换为 `false` \n – 将不为空的字符串 `string` 转换为 `true` \n – 将为0的数字 `number` 转换成 `false` \n –将不为0的数字 `number` 转换成 `true` \n –将 `null` 转换成 `false` \n –将空的数组 `array` 转换成 `false` \n –如果数组 `array` 中含有可以转换成 `true` 的要素则转换成 `true` \n –如果 `array` 中没有可转换成 `true` 的要素则转换成 `false` \n – 空的对象 `object` 转换成 `false` \n – 非空的对象 `object` 转换成 `true` \n –将函数 `function` 转换成 `false` "
|
"desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值 `Boolean` 。\n – 将空的字符串 `string` 转换为 `false`\n – 将不为空的字符串 `string` 转换为 `true`\n – 将为0的数字 `number` 转换成 `false`\n –将不为0的数字 `number` 转换成 `true`\n –将 `null` 转换成 `false`\n –将空的数组 `array` 转换成 `false`\n –如果数组 `array` 中含有可以转换成 `true` 的要素则转换成 `true`\n –如果 `array` 中没有可转换成 `true` 的要素则转换成 `false`\n – 空的对象 `object` 转换成 `false`\n – 非空的对象 `object` 转换成 `true`\n –将函数 `function` 转换成 `false`"
|
||||||
},
|
},
|
||||||
"$not": {
|
"$not": {
|
||||||
"args": "arg",
|
"args": "arg",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args": "array [, function]",
|
"args": "array [, function]",
|
||||||
"desc": "输出排序后的数组 `array` 。\n\n如果使用了比较函数 `function` ,则下述两个参数需要被指定。\n\n `function(left, right)` \n\n该比较函数是为了比较left和right两个值而被排序算法调用的。如果用户希望left的值被置于right的值之后,那么该函数必须输出布尔值 `true` 来表示位置交换。而在不需要位置交换时函数必须输出 `false` 。"
|
"desc": "输出排序后的数组 `array` 。\n\n如果使用了比较函数 `function` ,则下述两个参数需要被指定。\n\n `function(left, right)`\n\n该比较函数是为了比较`left`和`right`两个值而被排序算法调用的。如果用户希望`left`的值被置于`right`的值之后,那么该函数必须输出布尔值 `true` 来表示位置交换。而在不需要位置交换时函数必须输出 `false` 。"
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
},
|
},
|
||||||
"$sift": {
|
"$sift": {
|
||||||
"args": "object, function",
|
"args": "object, function",
|
||||||
"desc": "输出参数 `object` 中符合 `function` 的键值对。\n\n `function` 必须含有下述参数。\n\n `function(value [, key [, object]])` "
|
"desc": "输出参数 `object` 中符合 `function` 的键值对。\n\n `function` 必须含有下述参数。\n\n `function(value [, key [, object]])`"
|
||||||
},
|
},
|
||||||
"$each": {
|
"$each": {
|
||||||
"args": "object, function",
|
"args": "object, function",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
},
|
},
|
||||||
"$map": {
|
"$map": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
"desc": "将函数 `function` 应用于数组 `array` 中所有的值并输出由返回值组成的数组。\n\n `function` 中必须含有下述参数。\n\n`function(value [, index [, array]])` "
|
"desc": "将函数 `function` 应用于数组 `array` 中所有的值并输出由返回值组成的数组。\n\n `function` 中必须含有下述参数。\n\n`function(value [, index [, array]])`"
|
||||||
},
|
},
|
||||||
"$filter": {
|
"$filter": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"$assert": {
|
"$assert": {
|
||||||
"args": "arg, str",
|
"args": "arg, str",
|
||||||
"desc": "如果 `arg` 为真,则该函数返回。 如果arg为假,则抛出带有str的异常作为异常消息。"
|
"desc": "如果 `arg` 为真,则该函数返回。 如果`arg`为假,则抛出带有`str`的异常作为异常消息。"
|
||||||
},
|
},
|
||||||
"$single": {
|
"$single": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
@@ -253,11 +253,11 @@
|
|||||||
},
|
},
|
||||||
"$decodeUrlComponent": {
|
"$decodeUrlComponent": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器(URL)组件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器(URL)组件。\n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
},
|
},
|
||||||
"$decodeUrl": {
|
"$decodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "解码先前由encodeUrl创建的统一资源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
"desc": "解码先前由encodeUrl创建的统一资源定位符(URL)。\n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
},
|
},
|
||||||
"$distinct": {
|
"$distinct": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -265,10 +265,14 @@
|
|||||||
},
|
},
|
||||||
"$type": {
|
"$type": {
|
||||||
"args": "value",
|
"args": "value",
|
||||||
"desc": "以字符串形式返回 `值` 的类型。 如果该 `值` 未定义,则将返回 `未定义` "
|
"desc": "以字符串形式返回 `值` 的类型。 如果该 `值` 未定义,则将返回 `未定义`"
|
||||||
},
|
},
|
||||||
"$moment": {
|
"$moment": {
|
||||||
"args": "[str]",
|
"args": "[str]",
|
||||||
"desc": "使用Moment库获取日期对象。"
|
"desc": "使用Moment库获取日期对象。"
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "安全克隆对象."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
"position": "位置",
|
"position": "位置",
|
||||||
"enable": "啟用",
|
"enable": "啟用",
|
||||||
"disable": "禁用",
|
"disable": "禁用",
|
||||||
"upload": "上傳"
|
"upload": "上傳",
|
||||||
|
"lock": "鎖定",
|
||||||
|
"unlock": "解鎖",
|
||||||
|
"locked": "鎖定",
|
||||||
|
"unlocked": "解鎖"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"string": "字符串",
|
"string": "字符串",
|
||||||
@@ -38,11 +42,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"event": {
|
"event": {
|
||||||
|
"loadPlugins": "加載插件",
|
||||||
"loadPalette": "加載控制板",
|
"loadPalette": "加載控制板",
|
||||||
"loadNodeCatalogs": "加載節點目錄",
|
"loadNodeCatalogs": "加載節點目錄",
|
||||||
"loadNodes": "加載 __count__ 個節點",
|
"loadNodes": "加載 __count__ 個節點",
|
||||||
"loadFlows": "加載流程",
|
"loadFlows": "加載流程",
|
||||||
"importFlows": "往工作區中加載流程"
|
"importFlows": "往工作區中加載流程",
|
||||||
|
"importError": "<p>加載流程錯誤</p><p>__message__</p>",
|
||||||
|
"loadingProject": "加載項目"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"defaultName": "流程__number__",
|
"defaultName": "流程__number__",
|
||||||
@@ -51,18 +58,35 @@
|
|||||||
"delete": "確定想要刪除 '__label__'?",
|
"delete": "確定想要刪除 '__label__'?",
|
||||||
"dropFlowHere": "把流程放到這裡",
|
"dropFlowHere": "把流程放到這裡",
|
||||||
"addFlow": "新增流程",
|
"addFlow": "新增流程",
|
||||||
"listFlows": "流程列表",
|
"addFlowToRight": "在右側新增流程",
|
||||||
|
"hideFlow": "隱藏流程",
|
||||||
|
"hideOtherFlows": "隱藏其它流程",
|
||||||
|
"showAllFlows": "顯示所有流程",
|
||||||
|
"hideAllFlows": "隱藏所有流程",
|
||||||
|
"hiddenFlows": "列出 __count__ 個隱藏流程",
|
||||||
|
"hiddenFlows_plural": "列出 __count__ 個隱藏流程",
|
||||||
|
"showLastHiddenFlow": "顯示最後一個隱藏流程",
|
||||||
|
" ": "流程列表",
|
||||||
|
"listSubflows": "列出子流程",
|
||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"enabled": "有效",
|
"enabled": "有效",
|
||||||
"disabled": "無效",
|
"disabled": "無效",
|
||||||
"info": "詳細描述",
|
"info": "詳細描述",
|
||||||
"selectNodes": "點擊節點用於選擇"
|
"selectNodes": "點擊節點用於選擇",
|
||||||
|
"enableFlow": "啟用流程",
|
||||||
|
"disableFlow": "禁用流程",
|
||||||
|
"lockFlow": "鎖定流程",
|
||||||
|
"unlockFlow": "解除鎖定",
|
||||||
|
"moveToStart": "移動到起始",
|
||||||
|
"moveToEnd": "移動到末尾"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"label": {
|
"label": {
|
||||||
"view": {
|
"view": {
|
||||||
"view": "顯示",
|
"view": "顯示",
|
||||||
"grid": "格線",
|
"grid": "格線",
|
||||||
|
"storeZoom": "加載時還原縮放尺寸",
|
||||||
|
"storePosition": "加載時還原滾動位置",
|
||||||
"showGrid": "顯示格線",
|
"showGrid": "顯示格線",
|
||||||
"snapGrid": "對齊格線",
|
"snapGrid": "對齊格線",
|
||||||
"gridSize": "格線尺寸",
|
"gridSize": "格線尺寸",
|
||||||
@@ -80,12 +104,14 @@
|
|||||||
"palette": {
|
"palette": {
|
||||||
"show": "顯示控制板"
|
"show": "顯示控制板"
|
||||||
},
|
},
|
||||||
|
"edit": "編輯",
|
||||||
"settings": "設置",
|
"settings": "設置",
|
||||||
"userSettings": "使用者設置",
|
"userSettings": "使用者設置",
|
||||||
"nodes": "節點",
|
"nodes": "節點",
|
||||||
"displayStatus": "顯示節點狀態",
|
"displayStatus": "顯示節點狀態",
|
||||||
"displayConfig": "修改節點配置",
|
"displayConfig": "修改節點配置",
|
||||||
"import": "匯入",
|
"import": "匯入",
|
||||||
|
"importExample": "導入示例流程",
|
||||||
"export": "匯出",
|
"export": "匯出",
|
||||||
"search": "搜尋流程",
|
"search": "搜尋流程",
|
||||||
"searchInput": "搜尋流程",
|
"searchInput": "搜尋流程",
|
||||||
@@ -94,7 +120,6 @@
|
|||||||
"selectionToSubflow": "將選擇部分更改為子流程",
|
"selectionToSubflow": "將選擇部分更改為子流程",
|
||||||
"flows": "流程",
|
"flows": "流程",
|
||||||
"add": "增加",
|
"add": "增加",
|
||||||
"rename": "重新命名",
|
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"keyboardShortcuts": "鍵盤快速鍵",
|
"keyboardShortcuts": "鍵盤快速鍵",
|
||||||
"login": "登入",
|
"login": "登入",
|
||||||
@@ -102,24 +127,48 @@
|
|||||||
"editPalette": "節點管理",
|
"editPalette": "節點管理",
|
||||||
"other": "其他",
|
"other": "其他",
|
||||||
"showTips": "顯示小提示",
|
"showTips": "顯示小提示",
|
||||||
"help": "Node-RED website",
|
"showWelcomeTours": "顯示新版本向導",
|
||||||
|
"help": "Node-RED 文檔主頁",
|
||||||
"projects": "專案",
|
"projects": "專案",
|
||||||
"projects-new": "新專案",
|
"projects-new": "新專案",
|
||||||
"projects-open": "開啟專案",
|
"projects-open": "開啟專案",
|
||||||
"projects-settings": "專案設定",
|
"projects-settings": "專案設定",
|
||||||
"showNodeLabelDefault": "顯示新添加節點的標籤",
|
"showNodeLabelDefault": "顯示新添加節點的標籤",
|
||||||
|
"codeEditor": "代碼編輯器",
|
||||||
"groups": "組",
|
"groups": "組",
|
||||||
"groupSelection": "選擇組",
|
"groupSelection": "選擇組",
|
||||||
"ungroupSelection": "取消選擇組",
|
"ungroupSelection": "取消選擇組",
|
||||||
"groupMergeSelection": "合并選擇",
|
"groupMergeSelection": "合并選擇",
|
||||||
"groupRemoveSelection": "從組中移除"
|
"groupRemoveSelection": "從組中移除",
|
||||||
|
"arrange": "布局",
|
||||||
|
"alignLeft": "左對齊",
|
||||||
|
"alignCenter": "居中對齊",
|
||||||
|
"alignRight": "右對齊",
|
||||||
|
"alignTop": "頂部對齊",
|
||||||
|
"alignMiddle": "垂直居中對齊",
|
||||||
|
"alignBottom": "底部對齊",
|
||||||
|
"distributeHorizontally": "横向分布",
|
||||||
|
"distributeVertically": "垂直分布",
|
||||||
|
"moveToBack": "置於底層",
|
||||||
|
"moveToFront": "置於頂層",
|
||||||
|
"moveBackwards": "向後移動",
|
||||||
|
"moveForwards": "向前移動",
|
||||||
|
"showNodeHelp":"顯示節點幫助",
|
||||||
|
"enableSelectedNodes":"啟用當前選中節點",
|
||||||
|
"disableSelectedNodes":"禁用當前選中節點",
|
||||||
|
"showSelectedNodeLabels":"顯示選中的節點標簽",
|
||||||
|
"hideSelectedNodeLabels":"隱藏選中的節點標簽"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"toggle-navigator": "切換導航器",
|
"toggle-navigator": "切換導航器",
|
||||||
"zoom-out": "縮小",
|
"zoom-out": "縮小",
|
||||||
"zoom-reset": "重置縮放",
|
"zoom-reset": "重置縮放",
|
||||||
"zoom-in": "放大"
|
"zoom-in": "放大",
|
||||||
|
"search-flows": "搜索流程",
|
||||||
|
"search-prev": "上一個",
|
||||||
|
"search-next": "下一個",
|
||||||
|
"search-counter": "\"__term__\" __result__ of __count__"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"loggedInAs": "作為 __name__ 登入",
|
"loggedInAs": "作為 __name__ 登入",
|
||||||
@@ -135,12 +184,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
|
"state": {
|
||||||
|
"flowsStopped": "流程已停止",
|
||||||
|
"flowsStarted": "流程已啟動"
|
||||||
|
},
|
||||||
"warning": "<strong>警告</strong>: __message__",
|
"warning": "<strong>警告</strong>: __message__",
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"undeployedChanges": "節點中存在未部署的更改",
|
"undeployedChanges": "節點中存在未部署的更改",
|
||||||
"nodeActionDisabled": "節點動作在子流程中被禁用",
|
"nodeActionDisabled": "節點動作在子流程中被禁用",
|
||||||
"nodeActionDisabledSubflow": "子流程中禁用了節點操作",
|
"nodeActionDisabledSubflow": "子流程中禁用了節點操作",
|
||||||
"missing-types": "流程由於缺少節點類型而停止。請檢查日誌的詳細資訊",
|
"missing-types": "流程由於缺少節點類型而停止。請檢查日誌的詳細資訊",
|
||||||
|
"missing-modules": "<p>流程因缺少模塊而停止。</p>",
|
||||||
"safe-mode": "<p>流程在安全模式下停止。</p><p>您可以修改流程並部署更改以重新啟動。</p>",
|
"safe-mode": "<p>流程在安全模式下停止。</p><p>您可以修改流程並部署更改以重新啟動。</p>",
|
||||||
"restartRequired": "Node-RED必須重新啟動,以啟用升級的模組",
|
"restartRequired": "Node-RED必須重新啟動,以啟用升級的模組",
|
||||||
"credentials_load_failed": "<p>流程由於無法解密證書而停止。</p> <p>流程證書文件已加密,但是項目的加密密鑰丟失或無效。</p>",
|
"credentials_load_failed": "<p>流程由於無法解密證書而停止。</p> <p>流程證書文件已加密,但是項目的加密密鑰丟失或無效。</p>",
|
||||||
@@ -151,7 +205,7 @@
|
|||||||
"project_not_found": "<p>找不到項目的'__project__'</p>",
|
"project_not_found": "<p>找不到項目的'__project__'</p>",
|
||||||
"git_merge_conflict": "<p>自動合併更改失敗。</p><p>修復未合併的衝突,然後提交結果。</p>"
|
"git_merge_conflict": "<p>自動合併更改失敗。</p><p>修復未合併的衝突,然後提交結果。</p>"
|
||||||
},
|
},
|
||||||
"error": "<strong>Error</strong>: __message__",
|
"error": "<strong>錯誤</strong>: __message__",
|
||||||
"errors": {
|
"errors": {
|
||||||
"lostConnection": "丟失與伺服器的連接,重新連接...",
|
"lostConnection": "丟失與伺服器的連接,重新連接...",
|
||||||
"lostConnectionReconnect": "丟失與伺服器的連接,__time__ 秒後重新連接",
|
"lostConnectionReconnect": "丟失與伺服器的連接,__time__ 秒後重新連接",
|
||||||
@@ -208,6 +262,8 @@
|
|||||||
"download": "下載",
|
"download": "下載",
|
||||||
"importUnrecognised": "匯入了無法識別的類型:",
|
"importUnrecognised": "匯入了無法識別的類型:",
|
||||||
"importUnrecognised_plural": "匯入了無法識別的類型:",
|
"importUnrecognised_plural": "匯入了無法識別的類型:",
|
||||||
|
"importDuplicate": "導入了重復節點:",
|
||||||
|
"importDuplicate_plural": "導入了重復節點:",
|
||||||
"nodesExported": "節點匯出到了剪貼簿",
|
"nodesExported": "節點匯出到了剪貼簿",
|
||||||
"nodesImported": "已匯入:",
|
"nodesImported": "已匯入:",
|
||||||
"nodeCopied": "已複製 __count__ 個節點",
|
"nodeCopied": "已複製 __count__ 個節點",
|
||||||
@@ -259,6 +315,10 @@
|
|||||||
"modifiedFlowsDesc": "只部署包含已更改節點的流程",
|
"modifiedFlowsDesc": "只部署包含已更改節點的流程",
|
||||||
"modifiedNodes": "已更改的節點",
|
"modifiedNodes": "已更改的節點",
|
||||||
"modifiedNodesDesc": "只部署已經更改的節點",
|
"modifiedNodesDesc": "只部署已經更改的節點",
|
||||||
|
"startFlows": "啟動",
|
||||||
|
"startFlowsDesc": "啟動流程",
|
||||||
|
"stopFlows": "停止",
|
||||||
|
"stopFlowsDesc": "停止流程",
|
||||||
"restartFlows": "重新啟動流程",
|
"restartFlows": "重新啟動流程",
|
||||||
"restartFlowsDesc": "重新啟動當前部署的流程",
|
"restartFlowsDesc": "重新啟動當前部署的流程",
|
||||||
"successfulDeploy": "部署成功",
|
"successfulDeploy": "部署成功",
|
||||||
@@ -337,14 +397,28 @@
|
|||||||
"output": "輸出:",
|
"output": "輸出:",
|
||||||
"status": "狀態節點",
|
"status": "狀態節點",
|
||||||
"deleteSubflow": "刪除子流程",
|
"deleteSubflow": "刪除子流程",
|
||||||
|
"confirmDelete": "您確定要刪除此子流程?",
|
||||||
"info": "詳細描述",
|
"info": "詳細描述",
|
||||||
"category": "類別",
|
"category": "類別",
|
||||||
|
"module": "模塊",
|
||||||
|
"license": "許可",
|
||||||
|
"licenseNone": "無",
|
||||||
|
"licenseOther": "其它",
|
||||||
|
"type": "節點類型",
|
||||||
|
"version": "版本",
|
||||||
|
"versionPlaceholder": "x.y.z",
|
||||||
|
"keys": "關鍵字",
|
||||||
|
"keysPlaceholder": "使用英文逗號分隔關鍵字",
|
||||||
|
"author": "作者",
|
||||||
|
"authorPlaceholder": "名字 <email@example.com>",
|
||||||
|
"desc": "描述",
|
||||||
"env": {
|
"env": {
|
||||||
"restore": "恢復為默認子流程",
|
"restore": "恢復為默認子流程",
|
||||||
"remove": "類別刪除環境變量"
|
"remove": "類別刪除環境變量"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"noNodesSelected": "<strong>無法創建子流程</strong>: 未選擇節點",
|
"noNodesSelected": "<strong>無法創建子流程</strong>: 未選擇節點",
|
||||||
|
"acrossMultipleGroups": "無法跨多個組創建子流",
|
||||||
"multipleInputsToSelection": "<strong>無法創建子流程</strong>: 多個輸入到了選擇"
|
"multipleInputsToSelection": "<strong>無法創建子流程</strong>: 多個輸入到了選擇"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -367,12 +441,12 @@
|
|||||||
"editConfig": "編輯 __type__ 配置",
|
"editConfig": "編輯 __type__ 配置",
|
||||||
"addNewType": "添加新的 __type__ 節點",
|
"addNewType": "添加新的 __type__ 節點",
|
||||||
"nodeProperties": "節點屬性",
|
"nodeProperties": "節點屬性",
|
||||||
"label": "Label",
|
"label": "標簽",
|
||||||
"color": "顏色",
|
"color": "顏色",
|
||||||
"portLabels": "埠標籤",
|
"portLabels": "埠標籤",
|
||||||
"labelInputs": "輸入",
|
"labelInputs": "輸入",
|
||||||
"labelOutputs": "輸出",
|
"labelOutputs": "輸出",
|
||||||
"settingIcon": "Icon",
|
"settingIcon": "圖標",
|
||||||
"default": "默認",
|
"default": "默認",
|
||||||
"noDefaultLabel": "無",
|
"noDefaultLabel": "無",
|
||||||
"defaultLabel": "使用默認標籤",
|
"defaultLabel": "使用默認標籤",
|
||||||
@@ -385,6 +459,7 @@
|
|||||||
"icon": "圖標",
|
"icon": "圖標",
|
||||||
"inputType": "輸入類型",
|
"inputType": "輸入類型",
|
||||||
"selectType": "選擇類型...",
|
"selectType": "選擇類型...",
|
||||||
|
"loadCredentials": "加載節點憑證",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"input": "輸入",
|
"input": "輸入",
|
||||||
"select": "選擇",
|
"select": "選擇",
|
||||||
@@ -419,7 +494,8 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"scopeChange": "更改範圍將使其他流程中的節點無法使用",
|
"scopeChange": "更改範圍將使其他流程中的節點無法使用",
|
||||||
"invalidProperties": "無效的屬性:"
|
"invalidProperties": "無效的屬性:",
|
||||||
|
"credentialLoadFailed": "無法加載節點憑據"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keyboard": {
|
"keyboard": {
|
||||||
@@ -431,11 +507,14 @@
|
|||||||
"unassigned": "未分配",
|
"unassigned": "未分配",
|
||||||
"global": "全局",
|
"global": "全局",
|
||||||
"workspace": "工作區",
|
"workspace": "工作區",
|
||||||
|
"editor": "編輯對話框",
|
||||||
"selectAll": "選擇所有節點",
|
"selectAll": "選擇所有節點",
|
||||||
|
"selectNone": "取消所有選擇",
|
||||||
"selectAllConnected": "選擇所有連接的節點",
|
"selectAllConnected": "選擇所有連接的節點",
|
||||||
"addRemoveNode": "從選擇中添加/刪除節點",
|
"addRemoveNode": "從選擇中添加/刪除節點",
|
||||||
"editSelected": "編輯選定節點",
|
"editSelected": "編輯選定節點",
|
||||||
"deleteSelected": "刪除選定節點或連結",
|
"deleteSelected": "刪除選定節點或連結",
|
||||||
|
"deleteReconnect": "刪除並重新連接",
|
||||||
"importNode": "匯入節點",
|
"importNode": "匯入節點",
|
||||||
"exportNode": "匯出節點",
|
"exportNode": "匯出節點",
|
||||||
"nudgeNode": "移動所選節點(1px)",
|
"nudgeNode": "移動所選節點(1px)",
|
||||||
@@ -445,10 +524,14 @@
|
|||||||
"copyNode": "複製所選節點",
|
"copyNode": "複製所選節點",
|
||||||
"cutNode": "剪切所選節點",
|
"cutNode": "剪切所選節點",
|
||||||
"pasteNode": "粘貼節點",
|
"pasteNode": "粘貼節點",
|
||||||
|
"copyGroupStyle": "復製組樣式",
|
||||||
|
"pasteGroupStyle": "粘貼組樣式",
|
||||||
"undoChange": "撤銷上次執行的更改",
|
"undoChange": "撤銷上次執行的更改",
|
||||||
|
"redoChange": "重做",
|
||||||
"searchBox": "打開搜尋框",
|
"searchBox": "打開搜尋框",
|
||||||
"managePalette": "管理面板",
|
"managePalette": "管理面板",
|
||||||
"actionList": "動作列表"
|
"actionList": "動作列表",
|
||||||
|
"splitWireWithLinks": "使用Link節點拆分已選項"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"library": "庫",
|
"library": "庫",
|
||||||
@@ -466,12 +549,11 @@
|
|||||||
"types": {
|
"types": {
|
||||||
"local": "本地",
|
"local": "本地",
|
||||||
"examples": "例子"
|
"examples": "例子"
|
||||||
},
|
}
|
||||||
"exportToLibrary": "將節點匯出到庫"
|
|
||||||
},
|
},
|
||||||
"palette": {
|
"palette": {
|
||||||
"noInfo": "無可用資訊",
|
"noInfo": "無可用資訊",
|
||||||
"filter": "過濾節點",
|
"filter": "過濾已安裝模組",
|
||||||
"search": "搜尋模組",
|
"search": "搜尋模組",
|
||||||
"addCategory": "添加新的...",
|
"addCategory": "添加新的...",
|
||||||
"label": {
|
"label": {
|
||||||
@@ -501,11 +583,13 @@
|
|||||||
"nodeEnabled_plural": "啟用多個節點:",
|
"nodeEnabled_plural": "啟用多個節點:",
|
||||||
"nodeDisabled": "禁用節點:",
|
"nodeDisabled": "禁用節點:",
|
||||||
"nodeDisabled_plural": "禁用多個節點:",
|
"nodeDisabled_plural": "禁用多個節點:",
|
||||||
"nodeUpgraded": "節點模組__module__升級到__version__版本"
|
"nodeUpgraded": "節點模組__module__升級到__version__版本",
|
||||||
|
"unknownNodeRegistered": "加載節點錯誤: <ul><li>__type__<br>__error__</li></ul>"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"title": "面板管理",
|
"title": "面板管理",
|
||||||
"palette": "Palette",
|
"palette": "控製板",
|
||||||
|
"allCatalogs": "所有目錄",
|
||||||
"times": {
|
"times": {
|
||||||
"seconds": "秒前",
|
"seconds": "秒前",
|
||||||
"minutes": "分前",
|
"minutes": "分前",
|
||||||
@@ -545,10 +629,12 @@
|
|||||||
"tab-nodes": "節點",
|
"tab-nodes": "節點",
|
||||||
"tab-install": "安裝",
|
"tab-install": "安裝",
|
||||||
"sort": "排序:",
|
"sort": "排序:",
|
||||||
|
"sortRelevance": "關聯",
|
||||||
"sortAZ": "a-z順序",
|
"sortAZ": "a-z順序",
|
||||||
"sortRecent": "日期順序",
|
"sortRecent": "日期順序",
|
||||||
"more": "增加 __count__ 個",
|
"more": "增加 __count__ 個",
|
||||||
"upload": "上傳模塊tgz文件",
|
"upload": "上傳模塊tgz文件",
|
||||||
|
"refresh": "更新模塊列表",
|
||||||
"errors": {
|
"errors": {
|
||||||
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊",
|
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊",
|
||||||
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
|
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
|
||||||
@@ -617,7 +703,11 @@
|
|||||||
"empty": "空的",
|
"empty": "空的",
|
||||||
"globalConfig": "全局配置節點",
|
"globalConfig": "全局配置節點",
|
||||||
"triggerAction": "觸發動作",
|
"triggerAction": "觸發動作",
|
||||||
"find": "在工作區中查找"
|
"find": "在工作區中查找",
|
||||||
|
"copyItemUrl": "復製地址",
|
||||||
|
"copyURL2Clipboard": "復製地址到剪貼板",
|
||||||
|
"showFlow": "顯示流程",
|
||||||
|
"hideFlow": "隱藏流程"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"name": "幫助",
|
"name": "幫助",
|
||||||
@@ -627,7 +717,8 @@
|
|||||||
"showHelp": "顯示幫助",
|
"showHelp": "顯示幫助",
|
||||||
"showInOutline": "在大綱中顯示",
|
"showInOutline": "在大綱中顯示",
|
||||||
"showTopics": "顯示主題",
|
"showTopics": "顯示主題",
|
||||||
"noHelp": "未選擇幫助主題"
|
"noHelp": "未選擇幫助主題",
|
||||||
|
"changeLog": "更新日誌"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"name": "配置節點",
|
"name": "配置節點",
|
||||||
@@ -828,31 +919,37 @@
|
|||||||
"json": "JSON",
|
"json": "JSON",
|
||||||
"bin": "二進位流",
|
"bin": "二進位流",
|
||||||
"date": "時間戳記",
|
"date": "時間戳記",
|
||||||
"jsonata": "expression",
|
"jsonata": "表達式",
|
||||||
"env": "env variable",
|
"env": "環境變量",
|
||||||
"cred": "證書"
|
"cred": "證書"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editableList": {
|
"editableList": {
|
||||||
"add": "添加"
|
"add": "添加",
|
||||||
|
"addTitle": "添加項"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"empty": "找不到匹配",
|
"history": "搜索歷史",
|
||||||
|
"clear": "清除所有",
|
||||||
|
"empty": "找不到匹配項",
|
||||||
"addNode": "添加一個節點...",
|
"addNode": "添加一個節點...",
|
||||||
"options": {
|
"options": {
|
||||||
"configNodes": "配置節點",
|
"configNodes": "配置節點",
|
||||||
"unusedConfigNodes": "未使用的配置節點",
|
"unusedConfigNodes": "未使用的配置節點",
|
||||||
"invalidNodes": "無效的節點",
|
"invalidNodes": "無效的節點",
|
||||||
"uknownNodes": "未知的節點",
|
"uknownNodes": "未知的節點",
|
||||||
"unusedSubflows": "未使用的子流程"
|
"unusedSubflows": "未使用的子流程",
|
||||||
|
"hiddenFlows": "隱藏的流程",
|
||||||
|
"modifiedNodes": "已修改的節點或流程",
|
||||||
|
"thisFlow": "當前流程"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"expressionEditor": {
|
"expressionEditor": {
|
||||||
"functions": "功能",
|
"functions": "功能",
|
||||||
"functionReference": "Function reference",
|
"functionReference": "功能參考",
|
||||||
"insert": "插入",
|
"insert": "插入",
|
||||||
"title": "JSONata運算式編輯器",
|
"title": "JSONata運算式編輯器",
|
||||||
"test": "Test",
|
"test": "測試",
|
||||||
"data": "示例消息",
|
"data": "示例消息",
|
||||||
"result": "結果",
|
"result": "結果",
|
||||||
"format": "格式表達方法",
|
"format": "格式表達方法",
|
||||||
@@ -863,20 +960,28 @@
|
|||||||
"invalid-expr": "無效的JSONata運算式:\n __message__",
|
"invalid-expr": "無效的JSONata運算式:\n __message__",
|
||||||
"invalid-msg": "無效的示例JSON消息:\n __message__",
|
"invalid-msg": "無效的示例JSON消息:\n __message__",
|
||||||
"context-unsupported": "無法測試上下文函數\n $flowContext 或 $globalContext",
|
"context-unsupported": "無法測試上下文函數\n $flowContext 或 $globalContext",
|
||||||
|
"env-unsupported": "無法測試 $env 函數",
|
||||||
|
"moment-unsupported": "無法測試 $moment 函數",
|
||||||
|
"clone-unsupported": "無法測試 $clone 函數",
|
||||||
"eval": "評估運算式錯誤:\n __message__"
|
"eval": "評估運算式錯誤:\n __message__"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"monaco": {
|
||||||
|
"setTheme": "設置主題"
|
||||||
|
},
|
||||||
"jsEditor": {
|
"jsEditor": {
|
||||||
"title": "JavaScript 編輯器"
|
"title": "JavaScript 編輯器"
|
||||||
},
|
},
|
||||||
"textEditor": {
|
"textEditor": {
|
||||||
"title": "Text 編輯器"
|
"title": "文本編輯器"
|
||||||
},
|
},
|
||||||
"jsonEditor": {
|
"jsonEditor": {
|
||||||
"title": "JSON編輯器",
|
"title": "JSON編輯器",
|
||||||
"format": "格式化JSON",
|
"format": "格式化JSON",
|
||||||
"rawMode": "編輯 JSON",
|
"rawMode": "編輯 JSON",
|
||||||
"uiMode": "Visual編輯器",
|
"uiMode": "可視化編輯器",
|
||||||
|
"rawMode-readonly": "原始JSON",
|
||||||
|
"uiMode-readonly": "可視化",
|
||||||
"insertAbove": "在上方插入",
|
"insertAbove": "在上方插入",
|
||||||
"insertBelow": "在下方插入",
|
"insertBelow": "在下方插入",
|
||||||
"addItem": "添加項目",
|
"addItem": "添加項目",
|
||||||
@@ -892,9 +997,9 @@
|
|||||||
"title": "Markdown 編輯器",
|
"title": "Markdown 編輯器",
|
||||||
"expand": "展開",
|
"expand": "展開",
|
||||||
"format": "F使用markdown格式化",
|
"format": "F使用markdown格式化",
|
||||||
"heading1": "Heading 1",
|
"heading1": "標題 1",
|
||||||
"heading2": "Heading 2",
|
"heading2": "標題 2",
|
||||||
"heading3": "Heading 3",
|
"heading3": "標題 3",
|
||||||
"bold": "粗體",
|
"bold": "粗體",
|
||||||
"italic": "斜體",
|
"italic": "斜體",
|
||||||
"code": "程式碼",
|
"code": "程式碼",
|
||||||
@@ -903,7 +1008,10 @@
|
|||||||
"quote": "引用",
|
"quote": "引用",
|
||||||
"link": "連結",
|
"link": "連結",
|
||||||
"horizontal-rule": "分隔線",
|
"horizontal-rule": "分隔線",
|
||||||
"toggle-preview": "預覽"
|
"toggle-preview": "切換預覽",
|
||||||
|
"mermaid": {
|
||||||
|
"summary": "美人魚圖"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"bufferEditor": {
|
"bufferEditor": {
|
||||||
"title": "緩衝區編輯器",
|
"title": "緩衝區編輯器",
|
||||||
@@ -1038,7 +1146,8 @@
|
|||||||
"not-git": "不是git倉庫",
|
"not-git": "不是git倉庫",
|
||||||
"no-resource": "找不到存儲庫",
|
"no-resource": "找不到存儲庫",
|
||||||
"cant-get-ssh-key-path": "錯誤! 無法獲取所選的SSH密鑰路徑。",
|
"cant-get-ssh-key-path": "錯誤! 無法獲取所選的SSH密鑰路徑。",
|
||||||
"unexpected_error": "意外的錯誤"
|
"unexpected_error": "意外的錯誤",
|
||||||
|
"clearContext": "更改項目時清除上下文"
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"confirm": "您確定要刪除此項目嗎?"
|
"confirm": "您確定要刪除此項目嗎?"
|
||||||
@@ -1068,7 +1177,7 @@
|
|||||||
"create-default-file-set": {
|
"create-default-file-set": {
|
||||||
"no-active": "沒有活動項目就無法創建默認文件集",
|
"no-active": "沒有活動項目就無法創建默認文件集",
|
||||||
"no-empty": "無法在非空項目上創建默認文件集",
|
"no-empty": "無法在非空項目上創建默認文件集",
|
||||||
"git-error": "git error"
|
"git-error": "git錯誤"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"no-username-email": "您的Git客戶端未配置用戶名/電子郵件。",
|
"no-username-email": "您的Git客戶端未配置用戶名/電子郵件。",
|
||||||
@@ -1079,18 +1188,45 @@
|
|||||||
"editor-tab": {
|
"editor-tab": {
|
||||||
"properties": "屬性",
|
"properties": "屬性",
|
||||||
"envProperties": "環境變量",
|
"envProperties": "環境變量",
|
||||||
|
"module": "模塊屬性",
|
||||||
"description": "描述",
|
"description": "描述",
|
||||||
"appearance": "外觀",
|
"appearance": "外觀",
|
||||||
"preview": "UI預覽",
|
"preview": "UI預覽",
|
||||||
"defaultValue": "默認值",
|
"defaultValue": "默認值"
|
||||||
"env": "環境變量"
|
|
||||||
},
|
},
|
||||||
"languages": {
|
"tourGuide": {
|
||||||
"de": "德語",
|
"takeATour": "查看更新內容",
|
||||||
"en-US": "英語",
|
"start": "開始",
|
||||||
"ja": "日語",
|
"next": "下一個",
|
||||||
"ko": "韓語",
|
"welcomeTours": "歡迎使用 Node-RED"
|
||||||
"zh-CN": "簡體中文",
|
},
|
||||||
"zh-TW": "繁體中文"
|
"diagnostics": {
|
||||||
|
"title": "系统信息"
|
||||||
|
},
|
||||||
|
"validator": {
|
||||||
|
"errors": {
|
||||||
|
"invalid-json": "無效的 JSON 數據: __error__",
|
||||||
|
"invalid-expr": "無效的 JSONata 表達式: __error__",
|
||||||
|
"invalid-prop": "無效的屬性表達式",
|
||||||
|
"invalid-num": "無效的數字",
|
||||||
|
"invalid-regexp": "輸入格式無效",
|
||||||
|
"invalid-regex-prop": "__prop__: 輸入格式無效",
|
||||||
|
"missing-required-prop": "__prop__: 缺少屬性值",
|
||||||
|
"invalid-config": "__prop__: 無效的配置節點",
|
||||||
|
"missing-config": "__prop__: 缺少配置節點",
|
||||||
|
"validation-error": "__prop__: 驗證錯誤: __node__, __id__: __error__"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"showActionList":"顯示動作列表",
|
||||||
|
"insert": "插入",
|
||||||
|
"node": "節點",
|
||||||
|
"junction": "連接點",
|
||||||
|
"linkNodes": "鏈接節點"
|
||||||
|
},
|
||||||
|
"env-var": {
|
||||||
|
"environment": "環境配置",
|
||||||
|
"header": "全局環境變量",
|
||||||
|
"revert": "重置"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
},
|
},
|
||||||
"$sort": {
|
"$sort": {
|
||||||
"args": "array [, function]",
|
"args": "array [, function]",
|
||||||
"desc": "輸出排序後的陣列`array`。\n\n如果使用了比較函數`function`,則下述兩個參數需要被指定。\n\n`function(left, right)`\n\n該比較函數是為了比較left和right兩個值而被排序演算法調用的。如果使用者希望left的值被置於right的值之後,那麼該函數必須輸出布林值`true`來表示位置交換。而在不需要位置交換時函數必須輸出`false`。"
|
"desc": "輸出排序後的陣列`array`。\n\n如果使用了比較函數`function`,則下述兩個參數需要被指定。\n\n`function(left, right)`\n\n該比較函數是為了比較`left`和`right`兩個值而被排序演算法調用的。如果使用者希望left的值被置於`right`的值之後,那麼該函數必須輸出布林值`true`來表示位置交換。而在不需要位置交換時函數必須輸出`false`。"
|
||||||
},
|
},
|
||||||
"$reverse": {
|
"$reverse": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
"$assert": {
|
"$assert": {
|
||||||
"args": "arg, str",
|
"args": "arg, str",
|
||||||
"desc": "如果`arg`為真,則該函數返回。 如果arg為假,則拋出帶有str的異常作為異常消息。"
|
"desc": "如果`arg`為真,則該函數返回。 如果`arg`為假,則拋出帶有`str`的異常作為異常消息。"
|
||||||
},
|
},
|
||||||
"$single": {
|
"$single": {
|
||||||
"args": "array, function",
|
"args": "array, function",
|
||||||
@@ -253,11 +253,11 @@
|
|||||||
},
|
},
|
||||||
"$decodeUrlComponent": {
|
"$decodeUrlComponent": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。\n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||||
},
|
},
|
||||||
"$decodeUrl": {
|
"$decodeUrl": {
|
||||||
"args": "str",
|
"args": "str",
|
||||||
"desc": "解碼先前由encodeUrl創建的統一資源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
"desc": "解碼先前由encodeUrl創建的統一資源定位符(URL)。\n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||||
},
|
},
|
||||||
"$distinct": {
|
"$distinct": {
|
||||||
"args": "array",
|
"args": "array",
|
||||||
@@ -270,5 +270,9 @@
|
|||||||
"$moment": {
|
"$moment": {
|
||||||
"args": "[str]",
|
"args": "[str]",
|
||||||
"desc": "使用Moment庫獲取日期對象。"
|
"desc": "使用Moment庫獲取日期對象。"
|
||||||
|
},
|
||||||
|
"$clone": {
|
||||||
|
"args": "value",
|
||||||
|
"desc": "安全克隆對象."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@node-red/editor-client",
|
"name": "@node-red/editor-client",
|
||||||
"version": "3.1.0-beta.1",
|
"version": "4.0.9",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ RED.comms = (function() {
|
|||||||
var reconnectAttempts = 0;
|
var reconnectAttempts = 0;
|
||||||
var active = false;
|
var active = false;
|
||||||
|
|
||||||
|
RED.events.on('login', function(username) {
|
||||||
|
// User has logged in
|
||||||
|
// Need to upgrade the connection to be authenticated
|
||||||
|
if (ws && ws.readyState == 1) {
|
||||||
|
const auth_tokens = RED.settings.get("auth-tokens");
|
||||||
|
ws.send(JSON.stringify({auth:auth_tokens.access_token}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function connectWS() {
|
function connectWS() {
|
||||||
active = true;
|
active = true;
|
||||||
var wspath;
|
var wspath;
|
||||||
@@ -56,6 +65,7 @@ RED.comms = (function() {
|
|||||||
ws.send(JSON.stringify({subscribe:t}));
|
ws.send(JSON.stringify({subscribe:t}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit('connect')
|
||||||
}
|
}
|
||||||
|
|
||||||
ws = new WebSocket(wspath);
|
ws = new WebSocket(wspath);
|
||||||
@@ -180,9 +190,53 @@ RED.comms = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function send(topic, msg) {
|
||||||
|
if (ws && ws.readyState == 1) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
topic,
|
||||||
|
data: msg
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventHandlers = {};
|
||||||
|
function on(evt,func) {
|
||||||
|
eventHandlers[evt] = eventHandlers[evt]||[];
|
||||||
|
eventHandlers[evt].push(func);
|
||||||
|
}
|
||||||
|
function off(evt,func) {
|
||||||
|
const handler = eventHandlers[evt];
|
||||||
|
if (handler) {
|
||||||
|
for (let i=0;i<handler.length;i++) {
|
||||||
|
if (handler[i] === func) {
|
||||||
|
handler.splice(i,1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function emit() {
|
||||||
|
const evt = arguments[0]
|
||||||
|
const args = Array.prototype.slice.call(arguments,1);
|
||||||
|
if (eventHandlers[evt]) {
|
||||||
|
let cpyHandlers = [...eventHandlers[evt]];
|
||||||
|
for (let i=0;i<cpyHandlers.length;i++) {
|
||||||
|
try {
|
||||||
|
cpyHandlers[i].apply(null, args);
|
||||||
|
} catch(err) {
|
||||||
|
console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
|
||||||
|
console.warn(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connect: connectWS,
|
connect: connectWS,
|
||||||
subscribe: subscribe,
|
subscribe: subscribe,
|
||||||
unsubscribe:unsubscribe
|
unsubscribe:unsubscribe,
|
||||||
|
on,
|
||||||
|
off,
|
||||||
|
send
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -39,15 +39,16 @@
|
|||||||
console.warn(evt,args);
|
console.warn(evt,args);
|
||||||
}
|
}
|
||||||
if (handlers[evt]) {
|
if (handlers[evt]) {
|
||||||
for (var i=0;i<handlers[evt].length;i++) {
|
let cpyHandlers = [...handlers[evt]];
|
||||||
|
|
||||||
|
for (var i=0;i<cpyHandlers.length;i++) {
|
||||||
try {
|
try {
|
||||||
handlers[evt][i].apply(null, args);
|
cpyHandlers[i].apply(null, args);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("RED.events.emit error: ["+evt+"] "+(err.toString()));
|
console.warn("RED.events.emit error: ["+evt+"] "+(err.toString()));
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -29,7 +29,14 @@ RED.history = (function() {
|
|||||||
}
|
}
|
||||||
return RED.nodes.junction(id);
|
return RED.nodes.junction(id);
|
||||||
}
|
}
|
||||||
|
function ensureUnlocked(id, flowsToLock) {
|
||||||
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
|
const isLocked = flow ? flow.locked : false;
|
||||||
|
if (flow && isLocked) {
|
||||||
|
flow.locked = false;
|
||||||
|
flowsToLock.add(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
function undoEvent(ev) {
|
function undoEvent(ev) {
|
||||||
var i;
|
var i;
|
||||||
var len;
|
var len;
|
||||||
@@ -59,18 +66,46 @@ RED.history = (function() {
|
|||||||
t: 'replace',
|
t: 'replace',
|
||||||
config: RED.nodes.createCompleteNodeSet(),
|
config: RED.nodes.createCompleteNodeSet(),
|
||||||
changed: {},
|
changed: {},
|
||||||
rev: RED.nodes.version()
|
moved: {},
|
||||||
|
complete: true,
|
||||||
|
rev: RED.nodes.version(),
|
||||||
|
dirty: RED.nodes.dirty()
|
||||||
};
|
};
|
||||||
|
var selectedTab = RED.workspaces.active();
|
||||||
|
inverseEv.config.forEach(n => {
|
||||||
|
const node = RED.nodes.node(n.id)
|
||||||
|
if (node) {
|
||||||
|
inverseEv.changed[n.id] = node.changed
|
||||||
|
inverseEv.moved[n.id] = node.moved
|
||||||
|
}
|
||||||
|
})
|
||||||
RED.nodes.clear();
|
RED.nodes.clear();
|
||||||
var imported = RED.nodes.import(ev.config);
|
var imported = RED.nodes.import(ev.config);
|
||||||
|
// Clear all change flags from the import
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
|
||||||
|
const flowsToLock = new Set()
|
||||||
|
|
||||||
imported.nodes.forEach(function(n) {
|
imported.nodes.forEach(function(n) {
|
||||||
if (ev.changed[n.id]) {
|
if (ev.changed[n.id]) {
|
||||||
|
ensureUnlocked(n.z, flowsToLock)
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
inverseEv.changed[n.id] = true;
|
|
||||||
}
|
}
|
||||||
|
if (ev.moved[n.id]) {
|
||||||
|
ensureUnlocked(n.z, flowsToLock)
|
||||||
|
n.moved = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
flowsToLock.forEach(flow => {
|
||||||
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
|
||||||
RED.nodes.version(ev.rev);
|
RED.nodes.version(ev.rev);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
RED.palette.refresh();
|
||||||
|
RED.workspaces.refresh();
|
||||||
|
RED.workspaces.show(selectedTab, true);
|
||||||
|
RED.sidebar.config.refresh();
|
||||||
} else {
|
} else {
|
||||||
var importMap = {};
|
var importMap = {};
|
||||||
ev.config.forEach(function(n) {
|
ev.config.forEach(function(n) {
|
||||||
@@ -378,7 +413,8 @@ RED.history = (function() {
|
|||||||
if (ev.addToGroup) {
|
if (ev.addToGroup) {
|
||||||
RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),false);
|
RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),false);
|
||||||
inverseEv.removeFromGroup = ev.addToGroup;
|
inverseEv.removeFromGroup = ev.addToGroup;
|
||||||
} else if (ev.removeFromGroup) {
|
}
|
||||||
|
if (ev.removeFromGroup) {
|
||||||
RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
|
RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
|
||||||
inverseEv.addToGroup = ev.removeFromGroup;
|
inverseEv.addToGroup = ev.removeFromGroup;
|
||||||
}
|
}
|
||||||
@@ -417,10 +453,64 @@ RED.history = (function() {
|
|||||||
RED.events.emit("nodes:change",newConfigNode);
|
RED.events.emit("nodes:change",newConfigNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (i === "env" && ev.node.type.indexOf("subflow:") === 0) {
|
||||||
|
// Subflow can have config node in node.env
|
||||||
|
let nodeList = ev.node.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(ev.node), 1);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeList = ev.changes.env || [];
|
||||||
|
nodeList = nodeList.reduce((list, prop) => {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
list.push(prop.value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
nodeList.forEach(function(id) {
|
||||||
|
const configNode = RED.nodes.node(id);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(ev.node) === -1) {
|
||||||
|
configNode.users.push(ev.node);
|
||||||
|
RED.events.emit("nodes:change", configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (i === "credentials" && ev.changes[i]) {
|
||||||
|
// Reset - Only want to keep the changes
|
||||||
|
inverseEv.changes[i] = {};
|
||||||
|
for (const [key, value] of Object.entries(ev.changes[i])) {
|
||||||
|
// Edge case: node.credentials is cleared after a deploy, so we can't
|
||||||
|
// capture values for the inverse event when undoing past a deploy
|
||||||
|
if (ev.node.credentials) {
|
||||||
|
inverseEv.changes[i][key] = ev.node.credentials[key];
|
||||||
|
}
|
||||||
|
ev.node.credentials[key] = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ev.node[i] = ev.changes[i];
|
||||||
}
|
}
|
||||||
ev.node[i] = ev.changes[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ev.node.dirty = true;
|
||||||
|
ev.node.changed = ev.changed;
|
||||||
|
|
||||||
var eventType;
|
var eventType;
|
||||||
switch(ev.node.type) {
|
switch(ev.node.type) {
|
||||||
case 'tab': eventType = "flows"; break;
|
case 'tab': eventType = "flows"; break;
|
||||||
@@ -497,6 +587,24 @@ RED.history = (function() {
|
|||||||
RED.editor.updateNodeProperties(ev.node,outputMap);
|
RED.editor.updateNodeProperties(ev.node,outputMap);
|
||||||
RED.editor.validateNode(ev.node);
|
RED.editor.validateNode(ev.node);
|
||||||
}
|
}
|
||||||
|
// If it's a Config Node, validate user nodes too.
|
||||||
|
// NOTE: The Config Node must be validated before validating users.
|
||||||
|
if (ev.node.users) {
|
||||||
|
const validatedNodes = new Set();
|
||||||
|
const userStack = ev.node.users.slice();
|
||||||
|
|
||||||
|
validatedNodes.add(ev.node.id);
|
||||||
|
while (userStack.length) {
|
||||||
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
|
}
|
||||||
|
RED.editor.validateNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ev.links) {
|
if (ev.links) {
|
||||||
inverseEv.createdLinks = [];
|
inverseEv.createdLinks = [];
|
||||||
for (i=0;i<ev.links.length;i++) {
|
for (i=0;i<ev.links.length;i++) {
|
||||||
@@ -511,8 +619,6 @@ RED.history = (function() {
|
|||||||
inverseEv.links.push(ev.createdLinks[i]);
|
inverseEv.links.push(ev.createdLinks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ev.node.dirty = true;
|
|
||||||
ev.node.changed = ev.changed;
|
|
||||||
} else if (ev.t == "createSubflow") {
|
} else if (ev.t == "createSubflow") {
|
||||||
inverseEv = {
|
inverseEv = {
|
||||||
t: "deleteSubflow",
|
t: "deleteSubflow",
|
||||||
@@ -648,6 +754,12 @@ RED.history = (function() {
|
|||||||
ev.groups[i].nodes = [];
|
ev.groups[i].nodes = [];
|
||||||
RED.nodes.addGroup(ev.groups[i]);
|
RED.nodes.addGroup(ev.groups[i]);
|
||||||
RED.group.addToGroup(ev.groups[i],nodes);
|
RED.group.addToGroup(ev.groups[i],nodes);
|
||||||
|
if (ev.groups[i].g) {
|
||||||
|
const parentGroup = RED.nodes.group(ev.groups[i].g)
|
||||||
|
if (parentGroup) {
|
||||||
|
RED.group.addToGroup(parentGroup, ev.groups[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ev.t == "addToGroup") {
|
} else if (ev.t == "addToGroup") {
|
||||||
|
|||||||
560
packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
vendored
Normal file
560
packages/node_modules/@node-red/editor-client/src/js/multiplayer.js
vendored
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
RED.multiplayer = (function () {
|
||||||
|
|
||||||
|
// activeSessionId - used to identify sessions across websocket reconnects
|
||||||
|
let activeSessionId
|
||||||
|
|
||||||
|
let headerWidget
|
||||||
|
// Map of session id to { session:'', user:{}, location:{}}
|
||||||
|
let sessions = {}
|
||||||
|
// Map of username to { user:{}, sessions:[] }
|
||||||
|
let users = {}
|
||||||
|
|
||||||
|
function addUserSession (session) {
|
||||||
|
if (sessions[session.session]) {
|
||||||
|
// This is an existing connection that has been authenticated
|
||||||
|
const existingSession = sessions[session.session]
|
||||||
|
if (existingSession.user.username !== session.user.username) {
|
||||||
|
removeUserHeaderButton(users[existingSession.user.username])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions[session.session] = session
|
||||||
|
const user = users[session.user.username] = users[session.user.username] || {
|
||||||
|
user: session.user,
|
||||||
|
sessions: []
|
||||||
|
}
|
||||||
|
if (session.user.profileColor === undefined) {
|
||||||
|
session.user.profileColor = (1 + Math.floor(Math.random() * 5))
|
||||||
|
}
|
||||||
|
session.location = session.location || {}
|
||||||
|
user.sessions.push(session)
|
||||||
|
|
||||||
|
if (session.session === activeSessionId) {
|
||||||
|
// This is the current user session - do not add a extra button for them
|
||||||
|
} else {
|
||||||
|
if (user.sessions.length === 1) {
|
||||||
|
if (user.button) {
|
||||||
|
clearTimeout(user.inactiveTimeout)
|
||||||
|
clearTimeout(user.removeTimeout)
|
||||||
|
user.button.removeClass('inactive')
|
||||||
|
} else {
|
||||||
|
addUserHeaderButton(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions[session.session].location = session.location
|
||||||
|
updateUserLocation(session.session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserSession (sessionId, isDisconnected) {
|
||||||
|
removeUserLocation(sessionId)
|
||||||
|
const session = sessions[sessionId]
|
||||||
|
delete sessions[sessionId]
|
||||||
|
const user = users[session.user.username]
|
||||||
|
const i = user.sessions.indexOf(session)
|
||||||
|
user.sessions.splice(i, 1)
|
||||||
|
if (isDisconnected) {
|
||||||
|
removeUserHeaderButton(user)
|
||||||
|
} else {
|
||||||
|
if (user.sessions.length === 0) {
|
||||||
|
// Give the user 5s to reconnect before marking inactive
|
||||||
|
user.inactiveTimeout = setTimeout(() => {
|
||||||
|
user.button.addClass('inactive')
|
||||||
|
// Give the user further 20 seconds to reconnect before removing them
|
||||||
|
// from the user toolbar entirely
|
||||||
|
user.removeTimeout = setTimeout(() => {
|
||||||
|
removeUserHeaderButton(user)
|
||||||
|
}, 20000)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserHeaderButton (user) {
|
||||||
|
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
|
||||||
|
.attr('data-username', user.user.username)
|
||||||
|
.prependTo("#red-ui-multiplayer-user-list");
|
||||||
|
var button = user.button.find("button")
|
||||||
|
RED.popover.tooltip(button, user.user.username)
|
||||||
|
button.on('click', function () {
|
||||||
|
const location = user.sessions[0].location
|
||||||
|
revealUser(location)
|
||||||
|
})
|
||||||
|
|
||||||
|
const userProfile = RED.user.generateUserIcon(user.user)
|
||||||
|
userProfile.appendTo(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserHeaderButton (user) {
|
||||||
|
user.button.remove()
|
||||||
|
delete user.button
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocation () {
|
||||||
|
const location = {
|
||||||
|
workspace: RED.workspaces.active()
|
||||||
|
}
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
for (let i = editStack.length - 1; i >= 0; i--) {
|
||||||
|
if (editStack[i].id) {
|
||||||
|
location.node = editStack[i].id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInWorkspace) {
|
||||||
|
const chart = $('#red-ui-workspace-chart')
|
||||||
|
const chartOffset = chart.offset()
|
||||||
|
const scaleFactor = RED.view.scale()
|
||||||
|
location.cursor = {
|
||||||
|
x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
|
||||||
|
y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
let publishLocationTimeout
|
||||||
|
let lastPosition = [0,0]
|
||||||
|
let isInWorkspace = false
|
||||||
|
|
||||||
|
function publishLocation () {
|
||||||
|
if (!publishLocationTimeout) {
|
||||||
|
publishLocationTimeout = setTimeout(() => {
|
||||||
|
const location = getLocation()
|
||||||
|
if (location.workspace !== 0) {
|
||||||
|
log('send', 'multiplayer/location', location)
|
||||||
|
RED.comms.send('multiplayer/location', location)
|
||||||
|
}
|
||||||
|
publishLocationTimeout = null
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function revealUser(location, skipWorkspace) {
|
||||||
|
if (location.node) {
|
||||||
|
// Need to check if this is a known node, so we can fall back to revealing
|
||||||
|
// the workspace instead
|
||||||
|
const node = RED.nodes.node(location.node)
|
||||||
|
if (node) {
|
||||||
|
RED.view.reveal(location.node)
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
|
} else if (!skipWorkspace && location.workspace) {
|
||||||
|
RED.view.reveal(location.workspace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceTrays = {}
|
||||||
|
function getWorkspaceTray(workspaceId) {
|
||||||
|
// console.log('get tray for',workspaceId)
|
||||||
|
if (!workspaceTrays[workspaceId]) {
|
||||||
|
const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
|
||||||
|
const users = []
|
||||||
|
const userIcons = {}
|
||||||
|
|
||||||
|
const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
|
||||||
|
const userCountSpan = userCountIcon.find('span span')
|
||||||
|
userCountIcon.hide()
|
||||||
|
userCountSpan.text('')
|
||||||
|
userCountIcon.appendTo(tray)
|
||||||
|
const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
|
||||||
|
const content = $('<div>')
|
||||||
|
users.forEach(sessionId => {
|
||||||
|
$('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
userCountTooltip.close()
|
||||||
|
})).appendTo(content)
|
||||||
|
})
|
||||||
|
return content
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateUserCount = function () {
|
||||||
|
const maxShown = 2
|
||||||
|
const children = tray.children()
|
||||||
|
children.each(function (index, element) {
|
||||||
|
const i = users.length - index
|
||||||
|
if (i > maxShown) {
|
||||||
|
$(this).hide()
|
||||||
|
} else if (i >= 0) {
|
||||||
|
$(this).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (users.length < maxShown + 1) {
|
||||||
|
userCountIcon.hide()
|
||||||
|
} else {
|
||||||
|
userCountSpan.text('+'+(users.length - maxShown))
|
||||||
|
userCountIcon.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workspaceTrays[workspaceId] = {
|
||||||
|
attached: false,
|
||||||
|
tray,
|
||||||
|
users,
|
||||||
|
userIcons,
|
||||||
|
addUser: function (sessionId) {
|
||||||
|
if (users.indexOf(sessionId) === -1) {
|
||||||
|
// console.log(`addUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
users.push(sessionId)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
|
||||||
|
RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
|
||||||
|
userLocationIcon.prependTo(tray)
|
||||||
|
RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
|
||||||
|
userIcons[sessionId] = userLocationIcon
|
||||||
|
updateUserCount()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeUser: function (sessionId) {
|
||||||
|
// console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
|
||||||
|
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||||
|
const index = users.indexOf(sessionId)
|
||||||
|
if (index > -1) {
|
||||||
|
users.splice(index, 1)
|
||||||
|
userIcons[sessionId].remove()
|
||||||
|
delete userIcons[sessionId]
|
||||||
|
}
|
||||||
|
updateUserCount()
|
||||||
|
},
|
||||||
|
updateUserCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const trayDef = workspaceTrays[workspaceId]
|
||||||
|
if (!trayDef.attached) {
|
||||||
|
const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
|
||||||
|
if (workspaceTab.length > 0) {
|
||||||
|
trayDef.attached = true
|
||||||
|
trayDef.tray.appendTo(workspaceTab)
|
||||||
|
trayDef.users.forEach(sessionId => {
|
||||||
|
trayDef.userIcons[sessionId].on('click', function (evt) {
|
||||||
|
revealUser(sessions[sessionId].location, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return workspaceTrays[workspaceId]
|
||||||
|
}
|
||||||
|
function attachWorkspaceTrays () {
|
||||||
|
let viewTouched = false
|
||||||
|
for (let sessionId of Object.keys(sessions)) {
|
||||||
|
const location = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).updateUserCount()
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserToNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node) {
|
||||||
|
if (!node._multiplayer) {
|
||||||
|
node._multiplayer = {
|
||||||
|
users: [sessionId]
|
||||||
|
}
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
} else {
|
||||||
|
if (node._multiplayer.users.indexOf(sessionId) === -1) {
|
||||||
|
node._multiplayer.users.push(sessionId)
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeUserFromNode(sessionId, nodeId) {
|
||||||
|
const node = RED.nodes.node(nodeId)
|
||||||
|
if (node && node._multiplayer) {
|
||||||
|
const i = node._multiplayer.users.indexOf(sessionId)
|
||||||
|
if (i > -1) {
|
||||||
|
node._multiplayer.users.splice(i, 1)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length === 0) {
|
||||||
|
delete node._multiplayer
|
||||||
|
} else {
|
||||||
|
node._multiplayer_refresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUserLocation (sessionId) {
|
||||||
|
updateUserLocation(sessionId, {})
|
||||||
|
removeUserCursor(sessionId)
|
||||||
|
}
|
||||||
|
function removeUserCursor (sessionId) {
|
||||||
|
// return
|
||||||
|
if (sessions[sessionId]?.cursor) {
|
||||||
|
sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
|
||||||
|
delete sessions[sessionId].cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUserLocation (sessionId, location) {
|
||||||
|
let viewTouched = false
|
||||||
|
const oldLocation = sessions[sessionId].location
|
||||||
|
if (location) {
|
||||||
|
if (oldLocation.workspace !== location.workspace) {
|
||||||
|
// console.log('removing', sessionId, oldLocation.workspace)
|
||||||
|
workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
|
||||||
|
}
|
||||||
|
if (oldLocation.node !== location.node) {
|
||||||
|
removeUserFromNode(sessionId, oldLocation.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
sessions[sessionId].location = location
|
||||||
|
} else {
|
||||||
|
location = sessions[sessionId].location
|
||||||
|
}
|
||||||
|
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
|
||||||
|
if (location.workspace) {
|
||||||
|
getWorkspaceTray(location.workspace).addUser(sessionId)
|
||||||
|
if (location.cursor && location.workspace === RED.workspaces.active()) {
|
||||||
|
if (!sessions[sessionId].cursor) {
|
||||||
|
const user = sessions[sessionId].user
|
||||||
|
const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
|
||||||
|
cursorIcon.appendChild(createAnnotationUser(user, true))
|
||||||
|
$(cursorIcon).css({
|
||||||
|
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
|
||||||
|
transition: 'transform 0.1s linear'
|
||||||
|
})
|
||||||
|
$("#red-ui-workspace-chart svg").append(cursorIcon)
|
||||||
|
sessions[sessionId].cursor = cursorIcon
|
||||||
|
} else {
|
||||||
|
const cursorIcon = sessions[sessionId].cursor
|
||||||
|
$(cursorIcon).css({
|
||||||
|
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (sessions[sessionId].cursor) {
|
||||||
|
removeUserCursor(sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (location.node) {
|
||||||
|
addUserToNode(sessionId, location.node)
|
||||||
|
viewTouched = true
|
||||||
|
}
|
||||||
|
if (viewTouched) {
|
||||||
|
RED.view.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function refreshUserLocations () {
|
||||||
|
// for (const session of Object.keys(sessions)) {
|
||||||
|
// if (session !== activeSessionId) {
|
||||||
|
// updateUserLocation(session)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
function createAnnotationUser(user, pointer = false) {
|
||||||
|
const radius = 20
|
||||||
|
const halfRadius = radius/2
|
||||||
|
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
let shapePath
|
||||||
|
if (!pointer) {
|
||||||
|
shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
|
||||||
|
} else {
|
||||||
|
shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
|
||||||
|
}
|
||||||
|
badge.setAttribute('d', shapePath)
|
||||||
|
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
|
||||||
|
group.appendChild(badge)
|
||||||
|
if (user && user.profileColor !== undefined) {
|
||||||
|
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
|
||||||
|
}
|
||||||
|
if (user && user.image) {
|
||||||
|
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
|
||||||
|
image.setAttribute("width", radius)
|
||||||
|
image.setAttribute("height", radius)
|
||||||
|
image.setAttribute("href", user.image)
|
||||||
|
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
|
||||||
|
group.appendChild(image)
|
||||||
|
} else if (user && user.anonymous) {
|
||||||
|
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||||
|
anonIconHead.setAttribute("cx", radius/2)
|
||||||
|
anonIconHead.setAttribute("cy", radius/2 - 2)
|
||||||
|
anonIconHead.setAttribute("r", 2.4)
|
||||||
|
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||||
|
group.appendChild(anonIconHead)
|
||||||
|
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||||
|
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
|
||||||
|
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||||
|
group.appendChild(anonIconBody)
|
||||||
|
} else {
|
||||||
|
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||||
|
if (user.username || user.email) {
|
||||||
|
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||||
|
label.textContent = (user.username || user.email).substring(0,2)
|
||||||
|
} else {
|
||||||
|
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||||
|
label.textContent = 'nr'
|
||||||
|
}
|
||||||
|
label.setAttribute("text-anchor", "middle")
|
||||||
|
label.setAttribute("x",radius/2);
|
||||||
|
label.setAttribute("y",radius/2 + 3);
|
||||||
|
group.appendChild(label)
|
||||||
|
}
|
||||||
|
const border = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
border.setAttribute('d', shapePath)
|
||||||
|
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
|
||||||
|
group.appendChild(border)
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RED.view.annotations.register("red-ui-multiplayer",{
|
||||||
|
type: 'badge',
|
||||||
|
align: 'left',
|
||||||
|
class: "red-ui-multiplayer-annotation",
|
||||||
|
show: "_multiplayer",
|
||||||
|
refresh: "_multiplayer_refresh",
|
||||||
|
element: function(node) {
|
||||||
|
const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||||
|
containerGroup.setAttribute("transform","translate(0,-4)")
|
||||||
|
if (node._multiplayer) {
|
||||||
|
let y = 0
|
||||||
|
for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
|
||||||
|
const user = sessions[node._multiplayer.users[i]].user
|
||||||
|
const group = createAnnotationUser(user)
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 15
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
if (node._multiplayer.users.length > 2) {
|
||||||
|
const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
|
||||||
|
group.setAttribute("transform","translate("+y+",0)")
|
||||||
|
y += 12
|
||||||
|
containerGroup.appendChild(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return containerGroup;
|
||||||
|
},
|
||||||
|
tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
|
||||||
|
// if (!activeSessionId) {
|
||||||
|
activeSessionId = RED.nodes.id()
|
||||||
|
// RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
|
||||||
|
// log('Session ID (new)', activeSessionId)
|
||||||
|
// } else {
|
||||||
|
log('Session ID', activeSessionId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
|
||||||
|
|
||||||
|
RED.comms.on('connect', () => {
|
||||||
|
const location = getLocation()
|
||||||
|
const connectInfo = {
|
||||||
|
session: activeSessionId
|
||||||
|
}
|
||||||
|
if (location.workspace !== 0) {
|
||||||
|
connectInfo.location = location
|
||||||
|
}
|
||||||
|
RED.comms.send('multiplayer/connect', connectInfo)
|
||||||
|
})
|
||||||
|
RED.comms.subscribe('multiplayer/#', (topic, msg) => {
|
||||||
|
log('recv', topic, msg)
|
||||||
|
if (topic === 'multiplayer/init') {
|
||||||
|
// We have just reconnected, runtime has sent state to
|
||||||
|
// initialise the world
|
||||||
|
sessions = {}
|
||||||
|
users = {}
|
||||||
|
$('#red-ui-multiplayer-user-list').empty()
|
||||||
|
|
||||||
|
msg.sessions.forEach(session => {
|
||||||
|
addUserSession(session)
|
||||||
|
})
|
||||||
|
} else if (topic === 'multiplayer/connection-added') {
|
||||||
|
addUserSession(msg)
|
||||||
|
} else if (topic === 'multiplayer/connection-removed') {
|
||||||
|
removeUserSession(msg.session, msg.disconnected)
|
||||||
|
} else if (topic === 'multiplayer/location') {
|
||||||
|
const session = msg.session
|
||||||
|
delete msg.session
|
||||||
|
updateUserLocation(session, msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
RED.events.on('workspace:change', (event) => {
|
||||||
|
getWorkspaceTray(event.workspace)
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:open', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:close', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('editor:change', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('login', () => {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
RED.events.on('flows:loaded', () => {
|
||||||
|
attachWorkspaceTrays()
|
||||||
|
})
|
||||||
|
RED.events.on('workspace:close', (event) => {
|
||||||
|
// A subflow tab has been closed. Need to mark its tray as detached
|
||||||
|
if (workspaceTrays[event.workspace]) {
|
||||||
|
workspaceTrays[event.workspace].attached = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RED.events.on('logout', () => {
|
||||||
|
const disconnectInfo = {
|
||||||
|
session: activeSessionId
|
||||||
|
}
|
||||||
|
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
||||||
|
RED.settings.removeLocal('multiplayer:sessionId')
|
||||||
|
})
|
||||||
|
|
||||||
|
const chart = $('#red-ui-workspace-chart')
|
||||||
|
chart.on('mousemove', function (evt) {
|
||||||
|
lastPosition[0] = evt.clientX
|
||||||
|
lastPosition[1] = evt.clientY
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('scroll', function (evt) {
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('mouseenter', function () {
|
||||||
|
isInWorkspace = true
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
chart.on('mouseleave', function () {
|
||||||
|
isInWorkspace = false
|
||||||
|
publishLocation()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
if (RED.multiplayer.DEBUG) {
|
||||||
|
console.log('[multiplayer]', ...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
RED.plugins = (function() {
|
RED.plugins = (function() {
|
||||||
var plugins = {};
|
var plugins = {};
|
||||||
var pluginsByType = {};
|
var pluginsByType = {};
|
||||||
|
var moduleList = {};
|
||||||
|
|
||||||
function registerPlugin(id,definition) {
|
function registerPlugin(id,definition) {
|
||||||
plugins[id] = definition;
|
plugins[id] = definition;
|
||||||
@@ -38,9 +39,43 @@ RED.plugins = (function() {
|
|||||||
function getPluginsByType(type) {
|
function getPluginsByType(type) {
|
||||||
return pluginsByType[type] || [];
|
return pluginsByType[type] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPluginList(list) {
|
||||||
|
for(let i=0;i<list.length;i++) {
|
||||||
|
let p = list[i];
|
||||||
|
addPlugin(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPlugin(p) {
|
||||||
|
|
||||||
|
moduleList[p.module] = moduleList[p.module] || {
|
||||||
|
name:p.module,
|
||||||
|
version:p.version,
|
||||||
|
local:p.local,
|
||||||
|
sets:{},
|
||||||
|
plugin: true,
|
||||||
|
id: p.id
|
||||||
|
};
|
||||||
|
if (p.pending_version) {
|
||||||
|
moduleList[p.module].pending_version = p.pending_version;
|
||||||
|
}
|
||||||
|
moduleList[p.module].sets[p.name] = p;
|
||||||
|
|
||||||
|
RED.events.emit("registry:plugin-module-added",p.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(module) {
|
||||||
|
return moduleList[module];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
registerPlugin: registerPlugin,
|
registerPlugin: registerPlugin,
|
||||||
getPlugin: getPlugin,
|
getPlugin: getPlugin,
|
||||||
getPluginsByType: getPluginsByType
|
getPluginsByType: getPluginsByType,
|
||||||
|
|
||||||
|
setPluginList: setPluginList,
|
||||||
|
addPlugin: addPlugin,
|
||||||
|
getModule: getModule
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ var RED = (function() {
|
|||||||
cache: false,
|
cache: false,
|
||||||
url: 'plugins',
|
url: 'plugins',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
RED.plugins.setPluginList(data);
|
||||||
loader.reportProgress(RED._("event.loadPlugins"), 13)
|
loader.reportProgress(RED._("event.loadPlugins"), 13)
|
||||||
RED.i18n.loadPluginCatalogs(function() {
|
RED.i18n.loadPluginCatalogs(function() {
|
||||||
loadPlugins(function() {
|
loadPlugins(function() {
|
||||||
@@ -263,6 +264,7 @@ var RED = (function() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
RED.view.reveal(nodeToShow.id)
|
RED.view.reveal(nodeToShow.id)
|
||||||
window.location.hash = currentHash
|
window.location.hash = currentHash
|
||||||
|
RED.view.select(nodeToShow.id)
|
||||||
if (showEditDialog) {
|
if (showEditDialog) {
|
||||||
RED.editor.edit(nodeToShow)
|
RED.editor.edit(nodeToShow)
|
||||||
}
|
}
|
||||||
@@ -273,6 +275,7 @@ var RED = (function() {
|
|||||||
if (nodeToShow) {
|
if (nodeToShow) {
|
||||||
RED.view.reveal(nodeToShow.id)
|
RED.view.reveal(nodeToShow.id)
|
||||||
window.location.hash = currentHash
|
window.location.hash = currentHash
|
||||||
|
RED.view.select(nodeToShow.id)
|
||||||
if (showEditDialog) {
|
if (showEditDialog) {
|
||||||
RED.editor.editGroup(nodeToShow)
|
RED.editor.editGroup(nodeToShow)
|
||||||
}
|
}
|
||||||
@@ -295,6 +298,7 @@ var RED = (function() {
|
|||||||
RED.workspaces.show(workspaces[0]);
|
RED.workspaces.show(workspaces[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RED.events.emit('flows:loaded')
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
RED.notify(
|
RED.notify(
|
||||||
@@ -496,6 +500,15 @@ var RED = (function() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (notificationId === 'restart-required') {
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.close"),
|
||||||
|
click: function() {
|
||||||
|
persistentNotifications[notificationId].hideNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||||
persistentNotifications[notificationId] = RED.notify(text,options);
|
persistentNotifications[notificationId] = RED.notify(text,options);
|
||||||
@@ -523,6 +536,45 @@ var RED = (function() {
|
|||||||
RED.view.redrawStatus(node);
|
RED.view.redrawStatus(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
|
||||||
|
if (topic == "notification/plugin/added") {
|
||||||
|
RED.settings.refreshSettings(function(err, data) {
|
||||||
|
let addedPlugins = [];
|
||||||
|
msg.forEach(function(m) {
|
||||||
|
let id = m.id;
|
||||||
|
RED.plugins.addPlugin(m);
|
||||||
|
|
||||||
|
m.plugins.forEach((p) => {
|
||||||
|
addedPlugins.push(p.id);
|
||||||
|
})
|
||||||
|
|
||||||
|
RED.i18n.loadNodeCatalog(id, function() {
|
||||||
|
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"text/html",
|
||||||
|
"Accept-Language": lang
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'plugins/'+id,
|
||||||
|
success: function(data) {
|
||||||
|
appendPluginConfig(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (addedPlugins.length) {
|
||||||
|
let pluginList = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||||
|
// ToDo: Adapt notification (node -> plugin)
|
||||||
|
RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let pendingNodeRemovedNotifications = []
|
||||||
|
let pendingNodeRemovedTimeout
|
||||||
|
|
||||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||||
var i,m;
|
var i,m;
|
||||||
var typeList;
|
var typeList;
|
||||||
@@ -560,8 +612,15 @@ var RED = (function() {
|
|||||||
m = msg[i];
|
m = msg[i];
|
||||||
info = RED.nodes.removeNodeSet(m.id);
|
info = RED.nodes.removeNodeSet(m.id);
|
||||||
if (info.added) {
|
if (info.added) {
|
||||||
typeList = "<ul><li>"+m.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
pendingNodeRemovedNotifications = pendingNodeRemovedNotifications.concat(m.types.map(RED.utils.sanitize))
|
||||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
if (pendingNodeRemovedTimeout) {
|
||||||
|
clearTimeout(pendingNodeRemovedTimeout)
|
||||||
|
}
|
||||||
|
pendingNodeRemovedTimeout = setTimeout(function () {
|
||||||
|
typeList = "<ul><li>"+pendingNodeRemovedNotifications.join("</li><li>")+"</li></ul>";
|
||||||
|
RED.notify(RED._("palette.event.nodeRemoved", {count:pendingNodeRemovedNotifications.length})+typeList,"success");
|
||||||
|
pendingNodeRemovedNotifications = []
|
||||||
|
}, 200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadIconList();
|
loadIconList();
|
||||||
@@ -700,7 +759,7 @@ var RED = (function() {
|
|||||||
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
|
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
|
||||||
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
|
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
|
||||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"},
|
{id:"menu-item-workspace-edit",label:RED._("menu.label.edit"),onselect:"core:edit-flow"},
|
||||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
|
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
|
||||||
]});
|
]});
|
||||||
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||||
@@ -729,7 +788,7 @@ var RED = (function() {
|
|||||||
}
|
}
|
||||||
menuOptions.push({id:"menu-item-help",
|
menuOptions.push({id:"menu-item-help",
|
||||||
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
|
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
|
||||||
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
|
href: RED.settings.theme("menu.menu-item-help.url","https://nodered.org/docs")
|
||||||
});
|
});
|
||||||
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
|
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
|
||||||
|
|
||||||
@@ -781,6 +840,10 @@ var RED = (function() {
|
|||||||
|
|
||||||
RED.nodes.init();
|
RED.nodes.init();
|
||||||
RED.runtime.init()
|
RED.runtime.init()
|
||||||
|
|
||||||
|
if (RED.settings.theme("multiplayer.enabled",false)) {
|
||||||
|
RED.multiplayer.init()
|
||||||
|
}
|
||||||
RED.comms.connect();
|
RED.comms.connect();
|
||||||
|
|
||||||
$("#red-ui-main-container").show();
|
$("#red-ui-main-container").show();
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
if (key.startsWith("auth-tokens")) {
|
||||||
localStorage.setItem(key, JSON.stringify(value));
|
localStorage.setItem(key+this.authTokensSuffix, JSON.stringify(value));
|
||||||
} else {
|
} else {
|
||||||
RED.utils.setMessageProperty(userSettings,key,value);
|
RED.utils.setMessageProperty(userSettings,key,value);
|
||||||
saveUserSettings();
|
saveUserSettings();
|
||||||
@@ -52,8 +52,8 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
if (key.startsWith("auth-tokens")) {
|
||||||
return JSON.parse(localStorage.getItem(key));
|
return JSON.parse(localStorage.getItem(key+this.authTokensSuffix));
|
||||||
} else {
|
} else {
|
||||||
var v;
|
var v;
|
||||||
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
|
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
|
||||||
@@ -71,8 +71,8 @@ RED.settings = (function () {
|
|||||||
if (!hasLocalStorage()) {
|
if (!hasLocalStorage()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === "auth-tokens") {
|
if (key.startsWith("auth-tokens")) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key+this.authTokensSuffix);
|
||||||
} else {
|
} else {
|
||||||
delete userSettings[key];
|
delete userSettings[key];
|
||||||
saveUserSettings();
|
saveUserSettings();
|
||||||
@@ -99,6 +99,8 @@ RED.settings = (function () {
|
|||||||
|
|
||||||
var init = function (options, done) {
|
var init = function (options, done) {
|
||||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||||
|
var path=window.location.pathname.slice(0,-1);
|
||||||
|
RED.settings.authTokensSuffix=path.replace(/\//g, '-');
|
||||||
if (accessTokenMatch) {
|
if (accessTokenMatch) {
|
||||||
var accessToken = accessTokenMatch[1];
|
var accessToken = accessTokenMatch[1];
|
||||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ RED.actionList = (function() {
|
|||||||
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
|
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
|
||||||
searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
|
searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
|
||||||
change: function() {
|
change: function() {
|
||||||
filterTerm = $(this).val().trim();
|
filterTerm = $(this).val().trim().toLowerCase();
|
||||||
filterTerms = filterTerm.split(" ");
|
filterTerms = filterTerm.split(" ");
|
||||||
searchResults.editableList('filter');
|
searchResults.editableList('filter');
|
||||||
searchResults.find("li.selected").removeClass("selected");
|
searchResults.find("li.selected").removeClass("selected");
|
||||||
@@ -205,7 +205,9 @@ RED.actionList = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
RED.actions.add("core:show-action-list",show);
|
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
||||||
|
RED.actions.add("core:show-action-list",show);
|
||||||
|
}
|
||||||
|
|
||||||
RED.events.on("editor:open",function() { disabled = true; });
|
RED.events.on("editor:open",function() { disabled = true; });
|
||||||
RED.events.on("editor:close",function() { disabled = false; });
|
RED.events.on("editor:close",function() { disabled = false; });
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ RED.clipboard = (function() {
|
|||||||
var currentPopoverError;
|
var currentPopoverError;
|
||||||
var activeTab;
|
var activeTab;
|
||||||
var libraryBrowser;
|
var libraryBrowser;
|
||||||
|
var clipboardTabs;
|
||||||
|
|
||||||
var activeLibraries = {};
|
var activeLibraries = {};
|
||||||
|
|
||||||
@@ -37,13 +38,13 @@ RED.clipboard = (function() {
|
|||||||
// IE11 workaround
|
// IE11 workaround
|
||||||
// IE does not support data uri scheme for downloading data
|
// IE does not support data uri scheme for downloading data
|
||||||
var blob = new Blob([data], {
|
var blob = new Blob([data], {
|
||||||
type: "data:text/plain;charset=utf-8"
|
type: "data:application/json;charset=utf-8"
|
||||||
});
|
});
|
||||||
navigator.msSaveBlob(blob, file);
|
navigator.msSaveBlob(blob, file);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var element = document.createElement('a');
|
var element = document.createElement('a');
|
||||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
|
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
|
||||||
element.setAttribute('download', file);
|
element.setAttribute('download', file);
|
||||||
element.style.display = 'none';
|
element.style.display = 'none';
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
@@ -215,6 +216,13 @@ RED.clipboard = (function() {
|
|||||||
open: function( event, ui ) {
|
open: function( event, ui ) {
|
||||||
RED.keyboard.disable();
|
RED.keyboard.disable();
|
||||||
},
|
},
|
||||||
|
beforeClose: function(e) {
|
||||||
|
if (clipboardTabs && activeTab === "red-ui-clipboard-dialog-export-tab-clipboard") {
|
||||||
|
const jsonTabIndex = clipboardTabs.getTabIndex('red-ui-clipboard-dialog-export-tab-clipboard-json')
|
||||||
|
const activeTabIndex = clipboardTabs.activeIndex()
|
||||||
|
RED.settings.set("editor.dialog.export.json-view", activeTabIndex === jsonTabIndex )
|
||||||
|
}
|
||||||
|
},
|
||||||
close: function(e) {
|
close: function(e) {
|
||||||
RED.keyboard.enable();
|
RED.keyboard.enable();
|
||||||
if (popover) {
|
if (popover) {
|
||||||
@@ -228,12 +236,23 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
exportNodesDialog =
|
exportNodesDialog =
|
||||||
'<div class="form-row">'+
|
'<div class="form-row">'+
|
||||||
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
|
'<div style="display: flex; justify-content: space-between;">'+
|
||||||
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
|
'<div class="form-row">'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.export"></label>'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
'<span id="red-ui-clipboard-dialog-export-rng-group" class="button-group">'+
|
||||||
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
'<a id="red-ui-clipboard-dialog-export-rng-selected" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
|
||||||
'</span>'+
|
'<a id="red-ui-clipboard-dialog-export-rng-flow" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-rng-full" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'<div class="form-row">'+
|
||||||
|
'<label style="width:auto;margin-right: 10px;" data-i18n="common.label.format"></label>'+
|
||||||
|
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
||||||
|
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
||||||
|
'</span>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="red-ui-clipboard-dialog-box">'+
|
'<div class="red-ui-clipboard-dialog-box">'+
|
||||||
'<div class="red-ui-clipboard-dialog-tabs">'+
|
'<div class="red-ui-clipboard-dialog-tabs">'+
|
||||||
@@ -248,15 +267,9 @@ RED.clipboard = (function() {
|
|||||||
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
|
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
|
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
|
||||||
'<div class="form-row" style="height:calc(100% - 40px)">'+
|
'<div class="form-row" style="height:calc(100% - 10px)">'+
|
||||||
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
|
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row" style="text-align: right;">'+
|
|
||||||
'<span id="red-ui-clipboard-dialog-export-fmt-group" class="button-group">'+
|
|
||||||
'<a id="red-ui-clipboard-dialog-export-fmt-mini" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
|
|
||||||
'<a id="red-ui-clipboard-dialog-export-fmt-full" class="red-ui-button red-ui-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
|
|
||||||
'</span>'+
|
|
||||||
'</div>'+
|
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+
|
'<div class="form-row" id="red-ui-clipboard-dialog-export-tab-library-filename">'+
|
||||||
@@ -321,6 +334,30 @@ RED.clipboard = (function() {
|
|||||||
},100);
|
},100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if the provided string looks like valid flow json
|
||||||
|
* @param {string} flowString the string to validate
|
||||||
|
* @returns If valid, returns the node array
|
||||||
|
*/
|
||||||
|
function validateFlowString(flowString) {
|
||||||
|
const res = JSON.parse(flowString)
|
||||||
|
if (!Array.isArray(res)) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.notArray"));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (typeof res[i] !== "object") {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'id')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
||||||
|
}
|
||||||
|
if (!Object.hasOwn(res[i], 'type')) {
|
||||||
|
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
var validateImportTimeout;
|
var validateImportTimeout;
|
||||||
function validateImport() {
|
function validateImport() {
|
||||||
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
|
||||||
@@ -338,21 +375,7 @@ RED.clipboard = (function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!/^\[[\s\S]*\]$/m.test(v)) {
|
validateFlowString(v)
|
||||||
throw new Error(RED._("clipboard.import.errors.notArray"));
|
|
||||||
}
|
|
||||||
var res = JSON.parse(v);
|
|
||||||
for (var i=0;i<res.length;i++) {
|
|
||||||
if (typeof res[i] !== "object") {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('id')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingId",{index:i}));
|
|
||||||
}
|
|
||||||
if (!res[i].hasOwnProperty('type')) {
|
|
||||||
throw new Error(RED._("clipboard.import.errors.missingType",{index:i}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentPopoverError = null;
|
currentPopoverError = null;
|
||||||
popover.close(true);
|
popover.close(true);
|
||||||
importInput.removeClass("input-error");
|
importInput.removeClass("input-error");
|
||||||
@@ -569,7 +592,7 @@ RED.clipboard = (function() {
|
|||||||
|
|
||||||
dialogContainer.empty();
|
dialogContainer.empty();
|
||||||
dialogContainer.append($(exportNodesDialog));
|
dialogContainer.append($(exportNodesDialog));
|
||||||
|
clipboardTabs = null
|
||||||
var tabs = RED.tabs.create({
|
var tabs = RED.tabs.create({
|
||||||
id: "red-ui-clipboard-dialog-export-tabs",
|
id: "red-ui-clipboard-dialog-export-tabs",
|
||||||
vertical: true,
|
vertical: true,
|
||||||
@@ -630,7 +653,7 @@ RED.clipboard = (function() {
|
|||||||
$("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
$("#red-ui-clipboard-dialog-tab-library-name").on('paste',function() { setTimeout(validateExportFilename,10)});
|
||||||
$("#red-ui-clipboard-dialog-export").button("enable");
|
$("#red-ui-clipboard-dialog-export").button("enable");
|
||||||
|
|
||||||
var clipboardTabs = RED.tabs.create({
|
clipboardTabs = RED.tabs.create({
|
||||||
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
|
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
|
||||||
onchange: function(tab) {
|
onchange: function(tab) {
|
||||||
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
|
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
|
||||||
@@ -647,6 +670,9 @@ RED.clipboard = (function() {
|
|||||||
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
|
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
|
||||||
label: RED._("editor.types.json")
|
label: RED._("editor.types.json")
|
||||||
});
|
});
|
||||||
|
if (RED.settings.get("editor.dialog.export.json-view") === true) {
|
||||||
|
clipboardTabs.activateTab("red-ui-clipboard-dialog-export-tab-clipboard-json");
|
||||||
|
}
|
||||||
|
|
||||||
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
|
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
|
||||||
data: []
|
data: []
|
||||||
@@ -731,7 +757,7 @@ RED.clipboard = (function() {
|
|||||||
nodes.unshift(parentNode);
|
nodes.unshift(parentNode);
|
||||||
nodes = RED.nodes.createExportableNodeSet(nodes);
|
nodes = RED.nodes.createExportableNodeSet(nodes);
|
||||||
} else if (type === 'full') {
|
} else if (type === 'full') {
|
||||||
nodes = RED.nodes.createCompleteNodeSet(false);
|
nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
|
||||||
}
|
}
|
||||||
if (nodes !== null) {
|
if (nodes !== null) {
|
||||||
if (format === "red-ui-clipboard-dialog-export-fmt-full") {
|
if (format === "red-ui-clipboard-dialog-export-fmt-full") {
|
||||||
@@ -819,7 +845,7 @@ RED.clipboard = (function() {
|
|||||||
flow.forEach(function(node) {
|
flow.forEach(function(node) {
|
||||||
if (node.type === "tab") {
|
if (node.type === "tab") {
|
||||||
flows[node.id] = {
|
flows[node.id] = {
|
||||||
element: getFlowLabel(node,false),
|
element: getFlowLabel(node),
|
||||||
deferBuild: type !== "flow",
|
deferBuild: type !== "flow",
|
||||||
expanded: type === "flow",
|
expanded: type === "flow",
|
||||||
children: []
|
children: []
|
||||||
@@ -982,16 +1008,16 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function importNodes(nodesStr,addFlow) {
|
function importNodes(nodesStr,addFlow) {
|
||||||
var newNodes = nodesStr;
|
let newNodes = nodesStr;
|
||||||
if (typeof nodesStr === 'string') {
|
if (typeof nodesStr === 'string') {
|
||||||
try {
|
try {
|
||||||
nodesStr = nodesStr.trim();
|
nodesStr = nodesStr.trim();
|
||||||
if (nodesStr.length === 0) {
|
if (nodesStr.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newNodes = JSON.parse(nodesStr);
|
newNodes = validateFlowString(nodesStr)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
const e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||||
e.code = "NODE_RED";
|
e.code = "NODE_RED";
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -1000,7 +1026,6 @@ RED.clipboard = (function() {
|
|||||||
try {
|
try {
|
||||||
RED.view.importNodes(newNodes, importOptions);
|
RED.view.importNodes(newNodes, importOptions);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.log(error.importConfig)
|
|
||||||
// Thrown for import_conflict
|
// Thrown for import_conflict
|
||||||
confirmImport(error.importConfig, newNodes, importOptions);
|
confirmImport(error.importConfig, newNodes, importOptions);
|
||||||
}
|
}
|
||||||
@@ -1170,9 +1195,9 @@ RED.clipboard = (function() {
|
|||||||
function getNodeElement(n, isConflicted, isSelected, parent) {
|
function getNodeElement(n, isConflicted, isSelected, parent) {
|
||||||
var element;
|
var element;
|
||||||
if (n.type === "tab") {
|
if (n.type === "tab") {
|
||||||
element = getFlowLabel(n, isSelected);
|
element = getFlowLabel(n, isConflicted);
|
||||||
} else {
|
} else {
|
||||||
element = getNodeLabel(n, isConflicted, isSelected);
|
element = getNodeLabel(n, isConflicted, isSelected, parent);
|
||||||
}
|
}
|
||||||
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
|
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
|
||||||
controls.on("click", function(evt) { evt.stopPropagation(); });
|
controls.on("click", function(evt) { evt.stopPropagation(); });
|
||||||
@@ -1222,14 +1247,14 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFlowLabel(n) {
|
function getFlowLabel(n, isConflicted) {
|
||||||
n = JSON.parse(JSON.stringify(n));
|
n = JSON.parse(JSON.stringify(n));
|
||||||
n._def = RED.nodes.getType(n.type) || {};
|
n._def = RED.nodes.getType(n.type) || {};
|
||||||
if (n._def) {
|
if (n._def) {
|
||||||
n._ = n._def._;
|
n._ = n._def._;
|
||||||
}
|
}
|
||||||
|
|
||||||
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
|
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow red-ui-node-list-item"});
|
||||||
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
|
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
|
||||||
var label = (typeof n === "string")? n : n.label;
|
var label = (typeof n === "string")? n : n.label;
|
||||||
var newlineIndex = label.indexOf("\\n");
|
var newlineIndex = label.indexOf("\\n");
|
||||||
@@ -1237,11 +1262,17 @@ RED.clipboard = (function() {
|
|||||||
label = label.substring(0,newlineIndex)+"...";
|
label = label.substring(0,newlineIndex)+"...";
|
||||||
}
|
}
|
||||||
contentDiv.text(label);
|
contentDiv.text(label);
|
||||||
|
|
||||||
|
if (!!isConflicted) {
|
||||||
|
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
|
||||||
|
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
|
||||||
|
}
|
||||||
|
|
||||||
// A conflicted flow should not be imported by default.
|
// A conflicted flow should not be imported by default.
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeLabel(n, isConflicted) {
|
function getNodeLabel(n, isConflicted, isSelected, parent) {
|
||||||
n = JSON.parse(JSON.stringify(n));
|
n = JSON.parse(JSON.stringify(n));
|
||||||
n._def = RED.nodes.getType(n.type) || {};
|
n._def = RED.nodes.getType(n.type) || {};
|
||||||
if (n._def) {
|
if (n._def) {
|
||||||
@@ -1249,6 +1280,11 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
var div = $('<div>',{class:"red-ui-node-list-item"});
|
var div = $('<div>',{class:"red-ui-node-list-item"});
|
||||||
RED.utils.createNodeIcon(n,true).appendTo(div);
|
RED.utils.createNodeIcon(n,true).appendTo(div);
|
||||||
|
|
||||||
|
if (!parent && !!isConflicted) {
|
||||||
|
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
|
||||||
|
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
|
||||||
|
}
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1316,6 +1352,7 @@ RED.clipboard = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
console.warn('Import failed: ', err)
|
||||||
// Ensure any errors throw above doesn't stop the drop target from
|
// Ensure any errors throw above doesn't stop the drop target from
|
||||||
// being hidden.
|
// being hidden.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
this.menu = RED.popover.menu({
|
this.menu = RED.popover.menu({
|
||||||
tabSelect: true,
|
tabSelect: true,
|
||||||
width: 300,
|
width: Math.max(300, this.element.width()),
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
class: "red-ui-autoComplete-container",
|
class: "red-ui-autoComplete-container",
|
||||||
options: completions,
|
options: completions,
|
||||||
|
|||||||
@@ -174,12 +174,24 @@
|
|||||||
this.uiContainer.width(m[1]);
|
this.uiContainer.width(m[1]);
|
||||||
}
|
}
|
||||||
if (this.options.sortable) {
|
if (this.options.sortable) {
|
||||||
|
var isCanceled = false; // Flag to track if an item has been canceled from being dropped into a different list
|
||||||
|
var noDrop = false; // Flag to track if an item is being dragged into a different list
|
||||||
var handle = (typeof this.options.sortable === 'string')?
|
var handle = (typeof this.options.sortable === 'string')?
|
||||||
this.options.sortable :
|
this.options.sortable :
|
||||||
".red-ui-editableList-item-handle";
|
".red-ui-editableList-item-handle";
|
||||||
var sortOptions = {
|
var sortOptions = {
|
||||||
axis: "y",
|
axis: "y",
|
||||||
update: function( event, ui ) {
|
update: function( event, ui ) {
|
||||||
|
// dont trigger update if the item is being canceled
|
||||||
|
const targetList = $(event.target);
|
||||||
|
const draggedItem = ui.item;
|
||||||
|
const draggedItemParent = draggedItem.parent();
|
||||||
|
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
noDrop = true;
|
||||||
|
}
|
||||||
|
if (isCanceled || noDrop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (that.options.sortItems) {
|
if (that.options.sortItems) {
|
||||||
that.options.sortItems(that.items());
|
that.options.sortItems(that.items());
|
||||||
}
|
}
|
||||||
@@ -189,8 +201,32 @@
|
|||||||
tolerance: "pointer",
|
tolerance: "pointer",
|
||||||
forcePlaceholderSize:true,
|
forcePlaceholderSize:true,
|
||||||
placeholder: "red-ui-editabelList-item-placeholder",
|
placeholder: "red-ui-editabelList-item-placeholder",
|
||||||
start: function(e, ui){
|
start: function (event, ui) {
|
||||||
ui.placeholder.height(ui.item.height()-4);
|
isCanceled = false;
|
||||||
|
ui.placeholder.height(ui.item.height() - 4);
|
||||||
|
ui.item.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
},
|
||||||
|
stop: function (event, ui) {
|
||||||
|
ui.item.css('cursor', 'auto');
|
||||||
|
},
|
||||||
|
receive: function (event, ui) {
|
||||||
|
if (ui.item.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
isCanceled = true;
|
||||||
|
$(ui.sender).sortable('cancel');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
over: function (event, ui) {
|
||||||
|
// if the dragged item is constrained, prevent it from being dropped into a different list
|
||||||
|
const targetList = $(event.target);
|
||||||
|
const draggedItem = ui.item;
|
||||||
|
const draggedItemParent = draggedItem.parent();
|
||||||
|
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||||
|
noDrop = true;
|
||||||
|
draggedItem.css('cursor', 'no-drop'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
} else {
|
||||||
|
noDrop = false;
|
||||||
|
draggedItem.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (this.options.connectWith) {
|
if (this.options.connectWith) {
|
||||||
@@ -417,6 +453,9 @@
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
cancel: function() {
|
||||||
|
this.element.sortable("cancel");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ RED.popover = (function() {
|
|||||||
closePopup(true);
|
closePopup(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (trigger === 'hover' && options.interactive) {
|
if (/*trigger === 'hover' && */options.interactive) {
|
||||||
div.on('mouseenter', function(e) {
|
div.on('mouseenter', function(e) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
active = true;
|
active = true;
|
||||||
@@ -445,9 +445,12 @@ RED.popover = (function() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
create: createPopover,
|
create: createPopover,
|
||||||
tooltip: function(target,content, action) {
|
tooltip: function(target,content, action, interactive) {
|
||||||
var label = function() {
|
var label = function() {
|
||||||
var label = content;
|
var label = content;
|
||||||
|
if (typeof content === 'function') {
|
||||||
|
label = content()
|
||||||
|
}
|
||||||
if (action) {
|
if (action) {
|
||||||
var shortcut = RED.keyboard.getShortcut(action);
|
var shortcut = RED.keyboard.getShortcut(action);
|
||||||
if (shortcut && shortcut.key) {
|
if (shortcut && shortcut.key) {
|
||||||
@@ -463,6 +466,7 @@ RED.popover = (function() {
|
|||||||
size: "small",
|
size: "small",
|
||||||
direction: "bottom",
|
direction: "bottom",
|
||||||
content: label,
|
content: label,
|
||||||
|
interactive,
|
||||||
delay: { show: 750, hide: 50 }
|
delay: { show: 750, hide: 50 }
|
||||||
});
|
});
|
||||||
popover.setContent = function(newContent) {
|
popover.setContent = function(newContent) {
|
||||||
|
|||||||
@@ -365,7 +365,10 @@ RED.tabs = (function() {
|
|||||||
|
|
||||||
var thisTabA = thisTab.find("a");
|
var thisTabA = thisTab.find("a");
|
||||||
if (options.onclick) {
|
if (options.onclick) {
|
||||||
options.onclick(tabs[thisTabA.attr('href').slice(1)]);
|
options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
|
||||||
|
if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activateTab(thisTabA);
|
activateTab(thisTabA);
|
||||||
if (fireSelectionChanged) {
|
if (fireSelectionChanged) {
|
||||||
@@ -548,6 +551,8 @@ RED.tabs = (function() {
|
|||||||
ul.find("li.red-ui-tab a")
|
ul.find("li.red-ui-tab a")
|
||||||
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||||
.on("mouseup",onTabClick)
|
.on("mouseup",onTabClick)
|
||||||
|
// prevent browser-default middle-click behaviour
|
||||||
|
.on("auxclick", function(evt) { evt.preventDefault() })
|
||||||
.on("click", function(evt) {evt.preventDefault(); })
|
.on("click", function(evt) {evt.preventDefault(); })
|
||||||
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
|
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
|
||||||
|
|
||||||
@@ -816,6 +821,8 @@ RED.tabs = (function() {
|
|||||||
}
|
}
|
||||||
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||||
link.on("mouseup",onTabClick);
|
link.on("mouseup",onTabClick);
|
||||||
|
// prevent browser-default middle-click behaviour
|
||||||
|
link.on("auxclick", function(evt) { evt.preventDefault() })
|
||||||
link.on("click", function(evt) { evt.preventDefault(); })
|
link.on("click", function(evt) { evt.preventDefault(); })
|
||||||
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
|
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
|
||||||
|
|
||||||
@@ -845,7 +852,6 @@ RED.tabs = (function() {
|
|||||||
|
|
||||||
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
|
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
|
||||||
if (options.onselect) {
|
if (options.onselect) {
|
||||||
$('<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);
|
$('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,25 +54,27 @@
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
var autoComplete = function(options) {
|
function getMatch(value, searchValue) {
|
||||||
function getMatch(value, searchValue) {
|
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
const len = idx > -1 ? searchValue.length : 0;
|
||||||
const len = idx > -1 ? searchValue.length : 0;
|
return {
|
||||||
return {
|
index: idx,
|
||||||
index: idx,
|
found: idx > -1,
|
||||||
found: idx > -1,
|
pre: value.substring(0,idx),
|
||||||
pre: value.substring(0,idx),
|
match: value.substring(idx,idx+len),
|
||||||
match: value.substring(idx,idx+len),
|
post: value.substring(idx+len),
|
||||||
post: value.substring(idx+len),
|
exact: idx === 0 && value.length === searchValue.length
|
||||||
}
|
|
||||||
}
|
|
||||||
function generateSpans(match) {
|
|
||||||
const els = [];
|
|
||||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
|
||||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
|
||||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
|
||||||
return els;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function generateSpans(match) {
|
||||||
|
const els = [];
|
||||||
|
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||||
|
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||||
|
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||||
|
return els;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgAutoComplete = function(options) {
|
||||||
return function(val) {
|
return function(val) {
|
||||||
var matches = [];
|
var matches = [];
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
@@ -82,7 +84,7 @@
|
|||||||
const srcMatch = getMatch(optSrc, val);
|
const srcMatch = getMatch(optSrc, val);
|
||||||
if (valMatch.found || srcMatch.found) {
|
if (valMatch.found || srcMatch.found) {
|
||||||
const element = $('<div>',{style: "display: flex"});
|
const element = $('<div>',{style: "display: flex"});
|
||||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
valEl.append(generateSpans(valMatch));
|
valEl.append(generateSpans(valMatch));
|
||||||
valEl.appendTo(element);
|
valEl.appendTo(element);
|
||||||
if (optSrc) {
|
if (optSrc) {
|
||||||
@@ -102,6 +104,210 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getEnvVars (obj, envVars = {}) {
|
||||||
|
contextKnownKeys.env = contextKnownKeys.env || {}
|
||||||
|
if (contextKnownKeys.env[obj.id]) {
|
||||||
|
return contextKnownKeys.env[obj.id]
|
||||||
|
}
|
||||||
|
let parent
|
||||||
|
if (obj.type === 'tab' || obj.type === 'subflow') {
|
||||||
|
RED.nodes.eachConfig(function (conf) {
|
||||||
|
if (conf.type === "global-config") {
|
||||||
|
parent = conf;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (obj.g) {
|
||||||
|
parent = RED.nodes.group(obj.g)
|
||||||
|
} else if (obj.z) {
|
||||||
|
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
getEnvVars(parent, envVars)
|
||||||
|
}
|
||||||
|
if (obj.env) {
|
||||||
|
obj.env.forEach(env => {
|
||||||
|
envVars[env.name] = obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
contextKnownKeys.env[obj.id] = envVars
|
||||||
|
return envVars
|
||||||
|
}
|
||||||
|
|
||||||
|
const envAutoComplete = function (val) {
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
if (editStack.length === 0) {
|
||||||
|
done([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editingNode = editStack.pop()
|
||||||
|
if (!editingNode) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const envVarsMap = getEnvVars(editingNode)
|
||||||
|
const envVars = Object.keys(envVarsMap)
|
||||||
|
const matches = []
|
||||||
|
const i = val.lastIndexOf('${')
|
||||||
|
let searchKey = val
|
||||||
|
let isSubkey = false
|
||||||
|
if (i > -1) {
|
||||||
|
if (val.lastIndexOf('}') < i) {
|
||||||
|
searchKey = val.substring(i+2)
|
||||||
|
isSubkey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
envVars.forEach(v => {
|
||||||
|
let valMatch = getMatch(v, searchKey);
|
||||||
|
if (valMatch.found) {
|
||||||
|
const optSrc = envVarsMap[v]
|
||||||
|
const element = $('<div>',{style: "display: flex"});
|
||||||
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
|
valEl.append(generateSpans(valMatch))
|
||||||
|
valEl.appendTo(element)
|
||||||
|
|
||||||
|
if (optSrc) {
|
||||||
|
const optEl = $('<div>').css({ "font-size": "0.8em" });
|
||||||
|
let label
|
||||||
|
if (optSrc.type === 'global-config') {
|
||||||
|
label = RED._('sidebar.context.global')
|
||||||
|
} else if (optSrc.type === 'group') {
|
||||||
|
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
|
||||||
|
} else {
|
||||||
|
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
optEl.append(generateSpans({ match: label }));
|
||||||
|
optEl.appendTo(element);
|
||||||
|
}
|
||||||
|
matches.push({
|
||||||
|
value: isSubkey ? val + v + '}' : v,
|
||||||
|
label: element,
|
||||||
|
i: valMatch.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
matches.sort(function(A,B){return A.i-B.i})
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextKnownKeys = {}
|
||||||
|
let contextCache = {}
|
||||||
|
if (RED.events) {
|
||||||
|
RED.events.on("editor:close", function () {
|
||||||
|
contextCache = {}
|
||||||
|
contextKnownKeys = {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextAutoComplete = function() {
|
||||||
|
const that = this
|
||||||
|
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
||||||
|
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
|
||||||
|
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Map()
|
||||||
|
if (searchKey.length > 0) {
|
||||||
|
try {
|
||||||
|
RED.utils.normalisePropertyExpression(searchKey)
|
||||||
|
} catch (err) {
|
||||||
|
// Not a valid context key, so don't try looking up
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
|
||||||
|
if (contextCache[url]) {
|
||||||
|
// console.log('CACHED', url)
|
||||||
|
done()
|
||||||
|
} else {
|
||||||
|
// console.log('GET', url)
|
||||||
|
$.getJSON(url, function(data) {
|
||||||
|
// console.log(data)
|
||||||
|
contextCache[url] = true
|
||||||
|
const result = data[store] || {}
|
||||||
|
const keys = result.keys || []
|
||||||
|
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
||||||
|
keys.forEach(keyInfo => {
|
||||||
|
const key = keyInfo.key
|
||||||
|
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
|
||||||
|
contextKnownKeys[scope][store].set(keyPrefix + key, keyInfo)
|
||||||
|
} else {
|
||||||
|
contextKnownKeys[scope][store].set(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]", keyInfo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getContextKeys = function(key, done) {
|
||||||
|
const keyParts = key.split('.')
|
||||||
|
const partialKey = keyParts.pop()
|
||||||
|
let scope = that.propertyType
|
||||||
|
if (scope === 'flow') {
|
||||||
|
// Get the flow id of the node we're editing
|
||||||
|
const editStack = RED.editor.getEditStack()
|
||||||
|
if (editStack.length === 0) {
|
||||||
|
done(new Map())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const editingNode = editStack.pop()
|
||||||
|
if (editingNode.z) {
|
||||||
|
scope = `${scope}/${editingNode.z}`
|
||||||
|
} else {
|
||||||
|
done(new Map())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
|
||||||
|
const searchKey = keyParts.join('.')
|
||||||
|
|
||||||
|
getContextKeysFromRuntime(scope, store, searchKey, function() {
|
||||||
|
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
|
||||||
|
getContextKeysFromRuntime(scope, store, key, function() {
|
||||||
|
done(contextKnownKeys[scope][store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
done(contextKnownKeys[scope][store])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(val, done) {
|
||||||
|
getContextKeys(val, function (keys) {
|
||||||
|
const matches = []
|
||||||
|
keys.forEach((keyInfo, v) => {
|
||||||
|
let optVal = v
|
||||||
|
let valMatch = getMatch(optVal, val);
|
||||||
|
if (!valMatch.found && val.length > 0) {
|
||||||
|
if (val.endsWith('.')) {
|
||||||
|
// Search key ends in '.' - but doesn't match. Check again
|
||||||
|
// with [" at the end instead so we match bracket notation
|
||||||
|
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
|
||||||
|
// } else if (val.endsWith('[') && /^array/.test(keyInfo.format)) {
|
||||||
|
// console.log('this case')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valMatch.found) {
|
||||||
|
const element = $('<div>',{style: "display: flex"});
|
||||||
|
const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" });
|
||||||
|
// if (keyInfo.format) {
|
||||||
|
// valMatch.post += ' ' + keyInfo.format
|
||||||
|
// }
|
||||||
|
if (valMatch.exact && /^array/.test(keyInfo.format)) {
|
||||||
|
valMatch.post += `[0-${keyInfo.length}]`
|
||||||
|
optVal += '['
|
||||||
|
|
||||||
|
}
|
||||||
|
valEl.append(generateSpans(valMatch))
|
||||||
|
valEl.appendTo(element)
|
||||||
|
matches.push({
|
||||||
|
value: optVal,
|
||||||
|
label: element,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
|
||||||
|
done(matches);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
||||||
var msgCompletions = [
|
var msgCompletions = [
|
||||||
{ value: "payload" },
|
{ value: "payload" },
|
||||||
@@ -166,66 +372,93 @@
|
|||||||
{ value: "_session", source: ["websocket out","tcp out"] },
|
{ value: "_session", source: ["websocket out","tcp out"] },
|
||||||
]
|
]
|
||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
msg: { value: "msg", label: "msg.", validate: RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions) },
|
||||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
flow: { value: "flow", label: "flow.", hasValue: true,
|
||||||
options:[],
|
options: [],
|
||||||
validate:RED.utils.validatePropertyExpression,
|
validate: RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete
|
||||||
},
|
},
|
||||||
global: {value:"global",label:"global.",hasValue:true,
|
global: {
|
||||||
options:[],
|
value: "global", label: "global.", hasValue: true,
|
||||||
validate:RED.utils.validatePropertyExpression,
|
options: [],
|
||||||
|
validate: RED.utils.validatePropertyExpression,
|
||||||
parse: contextParse,
|
parse: contextParse,
|
||||||
export: contextExport,
|
export: contextExport,
|
||||||
valueLabel: contextLabel
|
valueLabel: contextLabel,
|
||||||
|
autoComplete: contextAutoComplete
|
||||||
},
|
},
|
||||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
str: { value: "str", label: "string", icon: "red/images/typedInput/az.svg" },
|
||||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
num: { value: "num", label: "number", icon: "red/images/typedInput/09.svg", validate: function (v, o) {
|
||||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
|
return RED.utils.validateTypedProperty(v, "num", o);
|
||||||
|
} },
|
||||||
|
bool: { value: "bool", label: "boolean", icon: "red/images/typedInput/bool.svg", options: ["true", "false"] },
|
||||||
json: {
|
json: {
|
||||||
value:"json",
|
value: "json",
|
||||||
label:"JSON",
|
label: "JSON",
|
||||||
icon:"red/images/typedInput/json.svg",
|
icon: "red/images/typedInput/json.svg",
|
||||||
validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}},
|
validate: function (v, o) {
|
||||||
expand: function() {
|
return RED.utils.validateTypedProperty(v, "json", o);
|
||||||
|
},
|
||||||
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
var value = this.value();
|
var value = this.value();
|
||||||
try {
|
try {
|
||||||
value = JSON.stringify(JSON.parse(value),null,4);
|
value = JSON.stringify(JSON.parse(value), null, 4);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
}
|
}
|
||||||
RED.editor.editJSON({
|
RED.editor.editJSON({
|
||||||
value: value,
|
value: value,
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
var value = v;
|
var value = v;
|
||||||
try {
|
try {
|
||||||
value = JSON.stringify(JSON.parse(v));
|
value = JSON.stringify(JSON.parse(v));
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
}
|
}
|
||||||
that.value(value);
|
that.value(value);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
|
re: { value: "re", label: "regular expression", icon: "red/images/typedInput/re.svg" },
|
||||||
date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
|
date: {
|
||||||
|
value: "date",
|
||||||
|
label: "timestamp",
|
||||||
|
icon: "fa fa-clock-o",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'milliseconds since epoch',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'YYYY-MM-DDTHH:mm:ss.sssZ',
|
||||||
|
value: 'iso'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JavaScript Date Object',
|
||||||
|
value: 'object'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
jsonata: {
|
jsonata: {
|
||||||
value: "jsonata",
|
value: "jsonata",
|
||||||
label: "expression",
|
label: "expression",
|
||||||
icon: "red/images/typedInput/expr.svg",
|
icon: "red/images/typedInput/expr.svg",
|
||||||
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
|
validate: function (v, o) {
|
||||||
expand:function() {
|
return RED.utils.validateTypedProperty(v, "jsonata", o);
|
||||||
|
},
|
||||||
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.editor.editExpression({
|
RED.editor.editExpression({
|
||||||
value: this.value().replace(/\t/g,"\n"),
|
value: this.value().replace(/\t/g, "\n"),
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
that.value(v.replace(/\n/g,"\t"));
|
that.value(v.replace(/\n/g, "\t"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -234,13 +467,13 @@
|
|||||||
value: "bin",
|
value: "bin",
|
||||||
label: "buffer",
|
label: "buffer",
|
||||||
icon: "red/images/typedInput/bin.svg",
|
icon: "red/images/typedInput/bin.svg",
|
||||||
expand: function() {
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.editor.editBuffer({
|
RED.editor.editBuffer({
|
||||||
value: this.value(),
|
value: this.value(),
|
||||||
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
|
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
|
||||||
focus: true,
|
focus: true,
|
||||||
complete: function(v) {
|
complete: function (v) {
|
||||||
that.value(v);
|
that.value(v);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -249,15 +482,16 @@
|
|||||||
env: {
|
env: {
|
||||||
value: "env",
|
value: "env",
|
||||||
label: "env variable",
|
label: "env variable",
|
||||||
icon: "red/images/typedInput/env.svg"
|
icon: "red/images/typedInput/env.svg",
|
||||||
|
autoComplete: envAutoComplete
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
value: "node",
|
value: "node",
|
||||||
label: "node",
|
label: "node",
|
||||||
icon: "red/images/typedInput/target.svg",
|
icon: "red/images/typedInput/target.svg",
|
||||||
valueLabel: function(container,value) {
|
valueLabel: function (container, value) {
|
||||||
var node = RED.nodes.node(value);
|
var node = RED.nodes.node(value);
|
||||||
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).css({
|
var nodeDiv = $('<div>', { class: "red-ui-search-result-node" }).css({
|
||||||
"margin-top": "2px",
|
"margin-top": "2px",
|
||||||
"margin-left": "3px"
|
"margin-left": "3px"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
@@ -266,133 +500,190 @@
|
|||||||
"margin-left": "6px"
|
"margin-left": "6px"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
if (node) {
|
if (node) {
|
||||||
var colour = RED.utils.getNodeColor(node.type,node._def);
|
var colour = RED.utils.getNodeColor(node.type, node._def);
|
||||||
var icon_url = RED.utils.getNodeIcon(node._def,node);
|
var icon_url = RED.utils.getNodeIcon(node._def, node);
|
||||||
if (node.type === 'tab') {
|
if (node.type === 'tab') {
|
||||||
colour = "#C0DEED";
|
colour = "#C0DEED";
|
||||||
}
|
}
|
||||||
nodeDiv.css('backgroundColor',colour);
|
nodeDiv.css('backgroundColor', colour);
|
||||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
var iconContainer = $('<div/>', { class: "red-ui-palette-icon-container" }).appendTo(nodeDiv);
|
||||||
RED.utils.createIconElement(icon_url, iconContainer, true);
|
RED.utils.createIconElement(icon_url, iconContainer, true);
|
||||||
var l = RED.utils.getNodeLabel(node,node.id);
|
var l = RED.utils.getNodeLabel(node, node.id);
|
||||||
nodeLabel.text(l);
|
nodeLabel.text(l);
|
||||||
} else {
|
} else {
|
||||||
nodeDiv.css({
|
nodeDiv.css({
|
||||||
'backgroundColor': '#eee',
|
'backgroundColor': '#eee',
|
||||||
'border-style' : 'dashed'
|
'border-style': 'dashed'
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expand: function() {
|
expand: function () {
|
||||||
var that = this;
|
var that = this;
|
||||||
RED.tray.hide();
|
RED.tray.hide();
|
||||||
RED.view.selectNodes({
|
RED.view.selectNodes({
|
||||||
single: true,
|
single: true,
|
||||||
selected: [that.value()],
|
selected: [that.value()],
|
||||||
onselect: function(selection) {
|
onselect: function (selection) {
|
||||||
that.value(selection.id);
|
that.value(selection.id);
|
||||||
RED.tray.show();
|
RED.tray.show();
|
||||||
},
|
},
|
||||||
oncancel: function() {
|
oncancel: function () {
|
||||||
RED.tray.show();
|
RED.tray.show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cred:{
|
cred: {
|
||||||
value:"cred",
|
value: "cred",
|
||||||
label:"credential",
|
label: "credential",
|
||||||
icon:"fa fa-lock",
|
icon: "fa fa-lock",
|
||||||
inputType: "password",
|
inputType: "password",
|
||||||
valueLabel: function(container,value) {
|
valueLabel: function (container, value) {
|
||||||
var that = this;
|
var that = this;
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
container.css("flex-grow",0);
|
container.css("flex-grow", 0);
|
||||||
this.elementDiv.hide();
|
this.elementDiv.hide();
|
||||||
var buttons = $('<div>').css({
|
var buttons = $('<div>').css({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right:"6px",
|
right: "6px",
|
||||||
top: "6px",
|
top: "6px",
|
||||||
"pointer-events":"all"
|
"pointer-events": "all"
|
||||||
}).appendTo(container);
|
}).appendTo(container);
|
||||||
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
|
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
|
||||||
width:"20px"
|
width: "20px"
|
||||||
}).appendTo(buttons).on("click", function(evt) {
|
}).appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
var cursorPosition = that.input[0].selectionStart;
|
var cursorPosition = that.input[0].selectionStart;
|
||||||
var currentType = that.input.attr("type");
|
var currentType = that.input.attr("type");
|
||||||
if (currentType === "text") {
|
if (currentType === "text") {
|
||||||
that.input.attr("type","password");
|
that.input.attr("type", "password");
|
||||||
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||||
},50);
|
}, 50);
|
||||||
} else {
|
} else {
|
||||||
that.input.attr("type","text");
|
that.input.attr("type", "text");
|
||||||
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
|
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||||
},50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}).hide();
|
}).hide();
|
||||||
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-2px").appendTo(eyeButton);
|
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left", "-2px").appendTo(eyeButton);
|
||||||
|
|
||||||
if (value === "__PWRD__") {
|
if (value === "__PWRD__") {
|
||||||
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
|
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
|
||||||
padding:"6px 6px",
|
padding: "6px 6px",
|
||||||
borderRadius:"4px"
|
borderRadius: "4px"
|
||||||
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
|
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
|
||||||
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function(evt) {
|
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
innerContainer.hide();
|
innerContainer.hide();
|
||||||
container.css("background","none");
|
container.css("background", "none");
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
that.input.val("");
|
that.input.val("");
|
||||||
that.element.val("");
|
that.element.val("");
|
||||||
that.elementDiv.show();
|
that.elementDiv.show();
|
||||||
editButton.hide();
|
editButton.hide();
|
||||||
cancelButton.show();
|
cancelButton.show();
|
||||||
eyeButton.show();
|
eyeButton.show();
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
that.input.focus();
|
that.input.focus();
|
||||||
},50);
|
}, 50);
|
||||||
});
|
});
|
||||||
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
|
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left", "3px").appendTo(buttons).on("click", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
innerContainer.show();
|
innerContainer.show();
|
||||||
container.css("background","");
|
container.css("background", "");
|
||||||
that.input.val("__PWRD__");
|
that.input.val("__PWRD__");
|
||||||
that.element.val("__PWRD__");
|
that.element.val("__PWRD__");
|
||||||
that.elementDiv.hide();
|
that.elementDiv.hide();
|
||||||
editButton.show();
|
editButton.show();
|
||||||
cancelButton.hide();
|
cancelButton.hide();
|
||||||
eyeButton.hide();
|
eyeButton.hide();
|
||||||
that.input.attr("type","password");
|
that.input.attr("type", "password");
|
||||||
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
||||||
|
|
||||||
}).hide();
|
}).hide();
|
||||||
} else {
|
} else {
|
||||||
container.css("background","none");
|
container.css("background", "none");
|
||||||
container.css("pointer-events","none");
|
container.css("pointer-events", "none");
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
eyeButton.show();
|
eyeButton.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'conf-types': {
|
||||||
|
value: "conf-types",
|
||||||
|
label: "config",
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
// hasValue: false,
|
||||||
|
valueLabel: function (container, value) {
|
||||||
|
// get the selected option (for access to the "name" and "module" properties)
|
||||||
|
const _options = this._optionsCache || this.typeList.find(opt => opt.value === value)?.options || []
|
||||||
|
const selectedOption = _options.find(opt => opt.value === value) || {
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
module: ''
|
||||||
|
}
|
||||||
|
container.attr("title", selectedOption.title) // set tooltip to the full path/id of the module/node
|
||||||
|
container.text(selectedOption.name) // apply the "name" of the selected option
|
||||||
|
// set "line-height" such as to make the "name" appear further up, giving room for the "module" to be displayed below the value
|
||||||
|
container.css("line-height", "1.4em")
|
||||||
|
// add the module name in smaller, lighter font below the value
|
||||||
|
$('<div></div>').text(selectedOption.module).css({
|
||||||
|
// "font-family": "var(--red-ui-monospace-font)",
|
||||||
|
color: "var(--red-ui-tertiary-text-color)",
|
||||||
|
"font-size": "0.8em",
|
||||||
|
"line-height": "1em",
|
||||||
|
opacity: 0.8
|
||||||
|
}).appendTo(container);
|
||||||
|
},
|
||||||
|
// hasValue: false,
|
||||||
|
options: function () {
|
||||||
|
if (this._optionsCache) {
|
||||||
|
return this._optionsCache
|
||||||
|
}
|
||||||
|
const configNodes = RED.nodes.registry.getNodeDefinitions({configOnly: true, filter: (def) => def.type !== "global-config"}).map((def) => {
|
||||||
|
// create a container with with 2 rows (row 1 for the name, row 2 for the module name in smaller, lighter font)
|
||||||
|
const container = $('<div style="display: flex; flex-direction: column; justify-content: space-between; row-gap: 1px;">')
|
||||||
|
const row1Name = $('<div>').text(def.type)
|
||||||
|
const row2Module = $('<div style="font-size: 0.8em; color: var(--red-ui-tertiary-text-color);">').text(def.set.module)
|
||||||
|
container.append(row1Name, row2Module)
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: def.type,
|
||||||
|
name: def.type,
|
||||||
|
enabled: def.set.enabled ?? true,
|
||||||
|
local: def.set.local,
|
||||||
|
title: def.set.id, // tooltip e.g. "node-red-contrib-foo/bar"
|
||||||
|
module: def.set.module,
|
||||||
|
icon: container[0].outerHTML.trim(), // the typeInput will interpret this as html text and render it in the anchor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this._optionsCache = configNodes
|
||||||
|
return configNodes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// For a type with options, check value is a valid selection
|
// For a type with options, check value is a valid selection
|
||||||
// If !opt.multiple, returns the valid option object
|
// If !opt.multiple, returns the valid option object
|
||||||
// if opt.multiple, returns an array of valid option objects
|
// if opt.multiple, returns an array of valid option objects
|
||||||
// If not valid, returns null;
|
// If not valid, returns null;
|
||||||
|
|
||||||
function isOptionValueValid(opt, currentVal) {
|
function isOptionValueValid(opt, currentVal) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof _options === "function") {
|
||||||
|
_options = _options.call(this)
|
||||||
|
}
|
||||||
if (!opt.multiple) {
|
if (!opt.multiple) {
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
op = opt.options[i];
|
op = _options[i];
|
||||||
if (typeof op === "string" && op === currentVal) {
|
if (typeof op === "string" && op === currentVal) {
|
||||||
return {value:currentVal}
|
return {value:currentVal}
|
||||||
} else if (op.value === currentVal) {
|
} else if (op.value === currentVal) {
|
||||||
@@ -409,8 +700,8 @@
|
|||||||
currentValues[v] = true;
|
currentValues[v] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
op = opt.options[i];
|
op = _options[i];
|
||||||
var val = typeof op === "string" ? op : op.value;
|
var val = typeof op === "string" ? op : op.value;
|
||||||
if (currentValues.hasOwnProperty(val)) {
|
if (currentValues.hasOwnProperty(val)) {
|
||||||
delete currentValues[val];
|
delete currentValues[val];
|
||||||
@@ -425,6 +716,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var nlsd = false;
|
var nlsd = false;
|
||||||
|
let contextStoreOptions;
|
||||||
|
|
||||||
$.widget( "nodered.typedInput", {
|
$.widget( "nodered.typedInput", {
|
||||||
_create: function() {
|
_create: function() {
|
||||||
@@ -436,7 +728,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var contextStores = RED.settings.context.stores;
|
var contextStores = RED.settings.context.stores;
|
||||||
var contextOptions = contextStores.map(function(store) {
|
contextStoreOptions = contextStores.map(function(store) {
|
||||||
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
||||||
}).sort(function(A,B) {
|
}).sort(function(A,B) {
|
||||||
if (A.value === RED.settings.context.default) {
|
if (A.value === RED.settings.context.default) {
|
||||||
@@ -447,13 +739,17 @@
|
|||||||
return A.value.localeCompare(B.value);
|
return A.value.localeCompare(B.value);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (contextOptions.length < 2) {
|
if (contextStoreOptions.length < 2) {
|
||||||
allOptions.flow.options = [];
|
allOptions.flow.options = [];
|
||||||
allOptions.global.options = [];
|
allOptions.global.options = [];
|
||||||
} else {
|
} else {
|
||||||
allOptions.flow.options = contextOptions;
|
allOptions.flow.options = contextStoreOptions;
|
||||||
allOptions.global.options = contextOptions;
|
allOptions.global.options = contextStoreOptions;
|
||||||
}
|
}
|
||||||
|
// Translate timestamp options
|
||||||
|
allOptions.date.options.forEach(opt => {
|
||||||
|
opt.label = RED._("typedInput.date.format." + (opt.value || 'timestamp'), {defaultValue: opt.label})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
nlsd = true;
|
nlsd = true;
|
||||||
var that = this;
|
var that = this;
|
||||||
@@ -542,7 +838,7 @@
|
|||||||
that.element.trigger('paste',evt);
|
that.element.trigger('paste',evt);
|
||||||
});
|
});
|
||||||
this.input.on('keydown', function(evt) {
|
this.input.on('keydown', function(evt) {
|
||||||
if (that.typeMap[that.propertyType].autoComplete) {
|
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
||||||
@@ -732,12 +1028,12 @@
|
|||||||
}
|
}
|
||||||
if (menu.opts.multiple) {
|
if (menu.opts.multiple) {
|
||||||
var selected = {};
|
var selected = {};
|
||||||
this.value().split(",").forEach(function(f) {
|
this.value().split(",").forEach(function(f) {
|
||||||
selected[f] = true;
|
selected[f] = true;
|
||||||
})
|
});
|
||||||
menu.find('input[type="checkbox"]').each(function() {
|
menu.find('input[type="checkbox"]').each(function() {
|
||||||
$(this).prop("checked",selected[$(this).data('value')])
|
$(this).prop("checked", selected[$(this).data('value')] || false);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -828,7 +1124,7 @@
|
|||||||
this.input.trigger('change',[this.propertyType,this.value()]);
|
this.input.trigger('change',[this.propertyType,this.value()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.optionSelectLabel.text(o.length+" selected");
|
this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -836,7 +1132,9 @@
|
|||||||
if (this.optionMenu) {
|
if (this.optionMenu) {
|
||||||
this.optionMenu.remove();
|
this.optionMenu.remove();
|
||||||
}
|
}
|
||||||
this.menu.remove();
|
if (this.menu) {
|
||||||
|
this.menu.remove();
|
||||||
|
}
|
||||||
this.uiSelect.remove();
|
this.uiSelect.remove();
|
||||||
},
|
},
|
||||||
types: function(types) {
|
types: function(types) {
|
||||||
@@ -869,7 +1167,7 @@
|
|||||||
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
|
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
|
||||||
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
|
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
|
||||||
if (!firstCall) {
|
if (!firstCall) {
|
||||||
this.type(this.typeList[0].value);
|
this.type(this.typeList[0]?.value || ""); // permit empty typeList
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.propertyType = null;
|
this.propertyType = null;
|
||||||
@@ -906,6 +1204,11 @@
|
|||||||
var selectedOption = [];
|
var selectedOption = [];
|
||||||
var valueToCheck = value;
|
var valueToCheck = value;
|
||||||
if (opt.options) {
|
if (opt.options) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof opt.options === "function") {
|
||||||
|
_options = opt.options.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
if (opt.hasValue && opt.parse) {
|
if (opt.hasValue && opt.parse) {
|
||||||
var parts = opt.parse(value);
|
var parts = opt.parse(value);
|
||||||
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
||||||
@@ -919,8 +1222,8 @@
|
|||||||
checkValues = valueToCheck.split(",");
|
checkValues = valueToCheck.split(",");
|
||||||
}
|
}
|
||||||
checkValues.forEach(function(valueToCheck) {
|
checkValues.forEach(function(valueToCheck) {
|
||||||
for (var i=0;i<opt.options.length;i++) {
|
for (var i=0;i<_options.length;i++) {
|
||||||
var op = opt.options[i];
|
var op = _options[i];
|
||||||
if (typeof op === "string") {
|
if (typeof op === "string") {
|
||||||
if (op === valueToCheck || op === ""+valueToCheck) {
|
if (op === valueToCheck || op === ""+valueToCheck) {
|
||||||
selectedOption.push(that.activeOptions[op]);
|
selectedOption.push(that.activeOptions[op]);
|
||||||
@@ -955,7 +1258,7 @@
|
|||||||
},
|
},
|
||||||
type: function(type) {
|
type: function(type) {
|
||||||
if (!arguments.length) {
|
if (!arguments.length) {
|
||||||
return this.propertyType;
|
return this.propertyType || this.options?.default || '';
|
||||||
} else {
|
} else {
|
||||||
var that = this;
|
var that = this;
|
||||||
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
||||||
@@ -965,6 +1268,9 @@
|
|||||||
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
||||||
var previousType = this.typeMap[this.propertyType];
|
var previousType = this.typeMap[this.propertyType];
|
||||||
previousValue = this.input.val();
|
previousValue = this.input.val();
|
||||||
|
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete("destroy");
|
||||||
|
}
|
||||||
|
|
||||||
if (previousType && this.typeChanged) {
|
if (previousType && this.typeChanged) {
|
||||||
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
||||||
@@ -1011,7 +1317,9 @@
|
|||||||
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
||||||
}
|
}
|
||||||
if (previousType.autoComplete) {
|
if (previousType.autoComplete) {
|
||||||
this.input.autoComplete("destroy");
|
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||||
|
this.input.autoComplete("destroy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.propertyType = type;
|
this.propertyType = type;
|
||||||
@@ -1051,6 +1359,10 @@
|
|||||||
this.optionMenu = null;
|
this.optionMenu = null;
|
||||||
}
|
}
|
||||||
if (opt.options) {
|
if (opt.options) {
|
||||||
|
let _options = opt.options
|
||||||
|
if (typeof _options === "function") {
|
||||||
|
_options = opt.options.call(this);
|
||||||
|
}
|
||||||
if (this.optionExpandButton) {
|
if (this.optionExpandButton) {
|
||||||
this.optionExpandButton.hide();
|
this.optionExpandButton.hide();
|
||||||
this.optionExpandButton.shown = false;
|
this.optionExpandButton.shown = false;
|
||||||
@@ -1067,7 +1379,7 @@
|
|||||||
this.valueLabelContainer.hide();
|
this.valueLabelContainer.hide();
|
||||||
}
|
}
|
||||||
this.activeOptions = {};
|
this.activeOptions = {};
|
||||||
opt.options.forEach(function(o) {
|
_options.forEach(function(o) {
|
||||||
if (typeof o === 'string') {
|
if (typeof o === 'string') {
|
||||||
that.activeOptions[o] = {label:o,value:o};
|
that.activeOptions[o] = {label:o,value:o};
|
||||||
} else {
|
} else {
|
||||||
@@ -1087,7 +1399,7 @@
|
|||||||
if (validValues) {
|
if (validValues) {
|
||||||
that._updateOptionSelectLabel(validValues)
|
that._updateOptionSelectLabel(validValues)
|
||||||
} else {
|
} else {
|
||||||
op = opt.options[0];
|
op = _options[0] || {value:""}; // permit zero options
|
||||||
if (typeof op === "string") {
|
if (typeof op === "string") {
|
||||||
this.value(op);
|
this.value(op);
|
||||||
that._updateOptionSelectLabel({value:op});
|
that._updateOptionSelectLabel({value:op});
|
||||||
@@ -1106,7 +1418,7 @@
|
|||||||
that._updateOptionSelectLabel(validValues);
|
that._updateOptionSelectLabel(validValues);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var selectedOption = this.optionValue||opt.options[0];
|
var selectedOption = this.optionValue||_options[0];
|
||||||
if (opt.parse) {
|
if (opt.parse) {
|
||||||
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
||||||
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
||||||
@@ -1139,8 +1451,18 @@
|
|||||||
} else {
|
} else {
|
||||||
this.optionSelectTrigger.hide();
|
this.optionSelectTrigger.hide();
|
||||||
}
|
}
|
||||||
|
if (opt.autoComplete) {
|
||||||
|
let searchFunction = opt.autoComplete
|
||||||
|
if (searchFunction.length === 0) {
|
||||||
|
searchFunction = opt.autoComplete.call(this)
|
||||||
|
}
|
||||||
|
this.input.autoComplete({
|
||||||
|
search: searchFunction,
|
||||||
|
minLength: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.optionMenu = this._createMenu(opt.options,opt,function(v){
|
this.optionMenu = this._createMenu(_options,opt,function(v){
|
||||||
if (!opt.multiple) {
|
if (!opt.multiple) {
|
||||||
that._updateOptionSelectLabel(that.activeOptions[v]);
|
that._updateOptionSelectLabel(that.activeOptions[v]);
|
||||||
if (!opt.hasValue) {
|
if (!opt.hasValue) {
|
||||||
@@ -1181,8 +1503,12 @@
|
|||||||
this.valueLabelContainer.hide();
|
this.valueLabelContainer.hide();
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
if (opt.autoComplete) {
|
if (opt.autoComplete) {
|
||||||
|
let searchFunction = opt.autoComplete
|
||||||
|
if (searchFunction.length === 0) {
|
||||||
|
searchFunction = opt.autoComplete.call(this)
|
||||||
|
}
|
||||||
this.input.autoComplete({
|
this.input.autoComplete({
|
||||||
search: opt.autoComplete,
|
search: searchFunction,
|
||||||
minLength: 0
|
minLength: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1231,26 +1557,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validate: function() {
|
validate: function(options) {
|
||||||
var result;
|
let valid = true;
|
||||||
var value = this.value();
|
const value = this.value();
|
||||||
var type = this.type();
|
const type = this.type();
|
||||||
if (this.typeMap[type] && this.typeMap[type].validate) {
|
if (this.typeMap[type] && this.typeMap[type].validate) {
|
||||||
var val = this.typeMap[type].validate;
|
const validate = this.typeMap[type].validate;
|
||||||
if (typeof val === 'function') {
|
if (typeof validate === 'function') {
|
||||||
result = val(value);
|
valid = validate(value, {});
|
||||||
} else {
|
} else {
|
||||||
result = val.test(value);
|
// Regex
|
||||||
|
valid = validate.test(value);
|
||||||
|
if (!valid) {
|
||||||
|
valid = RED._("validator.errors.invalid-regexp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((typeof valid === "string") || !valid) {
|
||||||
|
this.element.addClass("input-error");
|
||||||
|
this.uiSelect.addClass("input-error");
|
||||||
|
if (typeof valid === "string") {
|
||||||
|
let tooltip = this.element.data("tooltip");
|
||||||
|
if (tooltip) {
|
||||||
|
tooltip.setContent(valid);
|
||||||
|
} else {
|
||||||
|
const target = this.typeMap[type]?.options ? this.optionSelectLabel : this.elementDiv;
|
||||||
|
tooltip = RED.popover.tooltip(target, valid);
|
||||||
|
this.element.data("tooltip", tooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = true;
|
this.element.removeClass("input-error");
|
||||||
|
this.uiSelect.removeClass("input-error");
|
||||||
|
const tooltip = this.element.data("tooltip");
|
||||||
|
if (tooltip) {
|
||||||
|
this.element.data("tooltip", null);
|
||||||
|
tooltip.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result) {
|
if (options?.returnErrorMessage === true) {
|
||||||
this.uiSelect.removeClass('input-error');
|
return valid;
|
||||||
} else {
|
|
||||||
this.uiSelect.addClass('input-error');
|
|
||||||
}
|
}
|
||||||
return result;
|
// Must return a boolean for no 3.x validator
|
||||||
|
return (typeof valid === "string") ? false : valid;
|
||||||
},
|
},
|
||||||
show: function() {
|
show: function() {
|
||||||
this.uiSelect.show();
|
this.uiSelect.show();
|
||||||
|
|||||||
@@ -30,23 +30,46 @@ RED.contextMenu = (function () {
|
|||||||
const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
|
const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
|
||||||
const canEdit = !RED.workspaces.isLocked()
|
const canEdit = !RED.workspaces.isLocked()
|
||||||
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
|
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
|
||||||
const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
|
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
|
||||||
const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
|
if (hasSelection) {
|
||||||
const offset = $("#red-ui-workspace-chart").offset()
|
const nodes = selection.nodes.slice();
|
||||||
|
while (nodes.length) {
|
||||||
|
const n = nodes.shift();
|
||||||
|
if (n.type === 'group') {
|
||||||
|
hasGroup = true;
|
||||||
|
nodes.push(...n.nodes);
|
||||||
|
} else {
|
||||||
|
isAllGroups = false;
|
||||||
|
if (n.d) {
|
||||||
|
hasDisabledNode = true;
|
||||||
|
} else {
|
||||||
|
hasEnabledNode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n.l === undefined || n.l) {
|
||||||
|
hasLabeledNode = true;
|
||||||
|
} else {
|
||||||
|
hasUnlabeledNode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
|
const scale = RED.view.scale()
|
||||||
let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
|
const offset = $("#red-ui-workspace-chart").offset()
|
||||||
|
let addX = (options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / scale
|
||||||
|
let addY = (options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()) / scale
|
||||||
|
|
||||||
if (RED.view.snapGrid) {
|
if (RED.view.snapGrid) {
|
||||||
const gridSize = RED.view.gridSize()
|
const gridSize = RED.view.gridSize()
|
||||||
addX = gridSize * Math.floor(addX / gridSize)
|
addX = gridSize * Math.round(addX / gridSize)
|
||||||
addY = gridSize * Math.floor(addY / gridSize)
|
addY = gridSize * Math.round(addY / gridSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems.push(
|
if (RED.settings.theme("menu.menu-item-action-list", true)) {
|
||||||
{ onselect: 'core:show-action-list', onpostselect: function () { } }
|
menuItems.push(
|
||||||
)
|
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
|
||||||
|
)
|
||||||
|
}
|
||||||
const insertOptions = []
|
const insertOptions = []
|
||||||
menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
|
menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
|
||||||
insertOptions.push(
|
insertOptions.push(
|
||||||
@@ -55,7 +78,7 @@ RED.contextMenu = (function () {
|
|||||||
onselect: function () {
|
onselect: function () {
|
||||||
RED.view.showQuickAddDialog({
|
RED.view.showQuickAddDialog({
|
||||||
position: [addX, addY],
|
position: [addX, addY],
|
||||||
touchTrigger: true,
|
touchTrigger: 'ontouchstart' in window,
|
||||||
splice: isSingleLink ? selection.links[0] : undefined,
|
splice: isSingleLink ? selection.links[0] : undefined,
|
||||||
// spliceMultiple: isMultipleLinks
|
// spliceMultiple: isMultipleLinks
|
||||||
})
|
})
|
||||||
@@ -64,7 +87,9 @@ RED.contextMenu = (function () {
|
|||||||
},
|
},
|
||||||
(hasLinks) ? { // has least 1 wire selected
|
(hasLinks) ? { // has least 1 wire selected
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
onselect: 'core:split-wires-with-junctions',
|
onselect: function () {
|
||||||
|
RED.actions.invoke('core:split-wires-with-junctions', { x: addX, y: addY })
|
||||||
|
},
|
||||||
disabled: !canEdit || !hasLinks
|
disabled: !canEdit || !hasLinks
|
||||||
} : {
|
} : {
|
||||||
label: RED._("contextMenu.junction"),
|
label: RED._("contextMenu.junction"),
|
||||||
@@ -79,17 +104,18 @@ RED.contextMenu = (function () {
|
|||||||
w: 0, h: 0,
|
w: 0, h: 0,
|
||||||
outputs: 1,
|
outputs: 1,
|
||||||
inputs: 1,
|
inputs: 1,
|
||||||
dirty: true
|
dirty: true,
|
||||||
|
moved: true
|
||||||
}
|
}
|
||||||
|
const junction = RED.nodes.addJunction(nn);
|
||||||
const historyEvent = {
|
const historyEvent = {
|
||||||
dirty: RED.nodes.dirty(),
|
dirty: RED.nodes.dirty(),
|
||||||
t: 'add',
|
t: 'add',
|
||||||
junctions: [nn]
|
junctions: [junction]
|
||||||
}
|
}
|
||||||
RED.nodes.addJunction(nn);
|
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
RED.view.select({nodes: [nn] });
|
RED.view.select({nodes: [junction] });
|
||||||
RED.view.redraw(true)
|
RED.view.redraw(true)
|
||||||
},
|
},
|
||||||
disabled: !canEdit
|
disabled: !canEdit
|
||||||
@@ -99,24 +125,30 @@ RED.contextMenu = (function () {
|
|||||||
onselect: 'core:split-wire-with-link-nodes',
|
onselect: 'core:split-wire-with-link-nodes',
|
||||||
disabled: !canEdit || !hasLinks
|
disabled: !canEdit || !hasLinks
|
||||||
},
|
},
|
||||||
null,
|
null
|
||||||
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
|
|
||||||
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
|
|
||||||
)
|
)
|
||||||
|
if (RED.settings.theme("menu.menu-item-import-library", true)) {
|
||||||
|
insertOptions.push(
|
||||||
|
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
|
||||||
|
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (hasSelection && canEdit) {
|
if (hasSelection && canEdit) {
|
||||||
const nodeOptions = []
|
const nodeOptions = []
|
||||||
if (!hasMultipleSelection && !isGroup) {
|
if (!hasMultipleSelection && !isGroup) {
|
||||||
nodeOptions.push(
|
nodeOptions.push(
|
||||||
{ onselect: 'core:show-node-help' },
|
{ onselect: 'core:show-node-help', label: RED._('menu.label.showNodeHelp') },
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
nodeOptions.push(
|
nodeOptions.push(
|
||||||
{ onselect: 'core:enable-selected-nodes' },
|
{ onselect: 'core:enable-selected-nodes', label: RED._('menu.label.enableSelectedNodes'), disabled: !hasDisabledNode },
|
||||||
{ onselect: 'core:disable-selected-nodes' },
|
{ onselect: 'core:disable-selected-nodes', label: RED._('menu.label.disableSelectedNodes'), disabled: !hasEnabledNode },
|
||||||
null,
|
null,
|
||||||
{ onselect: 'core:show-selected-node-labels' },
|
{ onselect: 'core:show-selected-node-labels', label: RED._('menu.label.showSelectedNodeLabels'), disabled: !hasUnlabeledNode },
|
||||||
{ onselect: 'core:hide-selected-node-labels' }
|
{ onselect: 'core:hide-selected-node-labels', label: RED._('menu.label.hideSelectedNodeLabels'), disabled: !hasLabeledNode }
|
||||||
)
|
)
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
label: RED._('sidebar.info.node'),
|
label: RED._('sidebar.info.node'),
|
||||||
@@ -125,19 +157,26 @@ RED.contextMenu = (function () {
|
|||||||
menuItems.push({
|
menuItems.push({
|
||||||
label: RED._('sidebar.info.group'),
|
label: RED._('sidebar.info.group'),
|
||||||
options: [
|
options: [
|
||||||
{ onselect: 'core:group-selection' },
|
{ onselect: 'core:group-selection', label: RED._("menu.label.groupSelection") },
|
||||||
{ onselect: 'core:ungroup-selection', disabled: !hasGroup },
|
{ onselect: 'core:ungroup-selection', label: RED._("menu.label.ungroupSelection"), disabled: !hasGroup },
|
||||||
null,
|
|
||||||
{ onselect: 'core:copy-group-style', disabled: !hasGroup },
|
|
||||||
{ onselect: 'core:paste-group-style', disabled: !hasGroup}
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
if (hasGroup) {
|
||||||
|
menuItems[menuItems.length - 1].options.push(
|
||||||
|
{ onselect: 'core:merge-selection-to-group', label: RED._("menu.label.groupMergeSelection") }
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
if (canRemoveFromGroup) {
|
if (canRemoveFromGroup) {
|
||||||
menuItems[menuItems.length - 1].options.push(
|
menuItems[menuItems.length - 1].options.push(
|
||||||
null,
|
|
||||||
{ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
|
{ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
menuItems[menuItems.length - 1].options.push(
|
||||||
|
null,
|
||||||
|
{ onselect: 'core:copy-group-style', label: RED._("keyboard.copyGroupStyle"), disabled: !hasGroup },
|
||||||
|
{ onselect: 'core:paste-group-style', label: RED._("keyboard.pasteGroupStyle"), disabled: !hasGroup}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (canEdit && hasMultipleSelection) {
|
if (canEdit && hasMultipleSelection) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
@@ -160,16 +199,22 @@ RED.contextMenu = (function () {
|
|||||||
|
|
||||||
menuItems.push(
|
menuItems.push(
|
||||||
null,
|
null,
|
||||||
{ onselect: 'core:undo', disabled: RED.history.list().length === 0 },
|
{ onselect: 'core:undo', label: RED._("keyboard.undoChange"), disabled: RED.history.list().length === 0 },
|
||||||
{ onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
|
{ onselect: 'core:redo', label: RED._("keyboard.redoChange"), disabled: RED.history.listRedo().length === 0 },
|
||||||
null,
|
null,
|
||||||
{ onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
|
{ onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
|
||||||
{ onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
|
{ onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
|
||||||
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
|
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
|
||||||
{ onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
|
{ onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
|
||||||
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
|
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
|
||||||
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
|
)
|
||||||
{ onselect: 'core:select-all-nodes' },
|
if (RED.settings.theme("menu.menu-item-export-library", true)) {
|
||||||
|
menuItems.push(
|
||||||
|
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
menuItems.push(
|
||||||
|
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
var currentDiff = null;
|
var currentDiff = null;
|
||||||
|
|
||||||
|
var activeBackgroundDeployNotification;
|
||||||
|
|
||||||
function changeDeploymentType(type) {
|
function changeDeploymentType(type) {
|
||||||
deploymentType = type;
|
deploymentType = type;
|
||||||
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
|
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
|
||||||
@@ -42,6 +44,7 @@ RED.deploy = (function() {
|
|||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
* type: "default" - Button with drop-down options - no further customisation available
|
* type: "default" - Button with drop-down options - no further customisation available
|
||||||
|
* label: the text to display - default: "Deploy"
|
||||||
* type: "simple" - Button without dropdown. Customisations:
|
* type: "simple" - Button without dropdown. Customisations:
|
||||||
* label: the text to display - default: "Deploy"
|
* label: the text to display - default: "Deploy"
|
||||||
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.svg"
|
||||||
@@ -49,19 +52,20 @@ RED.deploy = (function() {
|
|||||||
function init(options) {
|
function init(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var type = options.type || "default";
|
var type = options.type || "default";
|
||||||
|
var label = options.label || RED._("deploy.deploy");
|
||||||
|
|
||||||
if (type == "default") {
|
if (type == "default") {
|
||||||
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
$('<li><span class="red-ui-deploy-button-group button-group">'+
|
||||||
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
'<a id="red-ui-header-button-deploy" class="red-ui-deploy-button disabled" href="#">'+
|
||||||
'<span class="red-ui-deploy-button-content">'+
|
'<span class="red-ui-deploy-button-content">'+
|
||||||
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
'<img id="red-ui-header-button-deploy-icon" src="red/images/deploy-full-o.svg"> '+
|
||||||
'<span>'+RED._("deploy.deploy")+'</span>'+
|
'<span>'+label+'</span>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'<span class="red-ui-deploy-button-spinner hide">'+
|
'<span class="red-ui-deploy-button-spinner hide">'+
|
||||||
'<img src="red/images/spin.svg"/>'+
|
'<img src="red/images/spin.svg"/>'+
|
||||||
'</span>'+
|
'</span>'+
|
||||||
'</a>'+
|
'</a>'+
|
||||||
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
|
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i><i class="fa fa-lock"></i></a>'+
|
||||||
'</span></li>').prependTo(".red-ui-header-toolbar");
|
'</span></li>').prependTo(".red-ui-header-toolbar");
|
||||||
const mainMenuItems = [
|
const mainMenuItems = [
|
||||||
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||||
@@ -76,7 +80,6 @@ RED.deploy = (function() {
|
|||||||
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
||||||
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
||||||
} else if (type == "simple") {
|
} else if (type == "simple") {
|
||||||
var label = options.label || RED._("deploy.deploy");
|
|
||||||
var icon = 'red/images/deploy-full-o.svg';
|
var icon = 'red/images/deploy-full-o.svg';
|
||||||
if (options.hasOwnProperty('icon')) {
|
if (options.hasOwnProperty('icon')) {
|
||||||
icon = options.icon;
|
icon = options.icon;
|
||||||
@@ -112,53 +115,80 @@ RED.deploy = (function() {
|
|||||||
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function (event) {
|
||||||
|
if (RED.nodes.dirty()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
event.returnValue = RED._("deploy.confirm.undeployedChanges");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
RED.events.on('workspace:dirty',function(state) {
|
RED.events.on('workspace:dirty',function(state) {
|
||||||
|
if (RED.settings.user?.permissions === 'read') {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (state.dirty) {
|
if (state.dirty) {
|
||||||
window.onbeforeunload = function() {
|
// window.onbeforeunload = function() {
|
||||||
return RED._("deploy.confirm.undeployedChanges");
|
// return
|
||||||
}
|
// }
|
||||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
} else {
|
} else {
|
||||||
window.onbeforeunload = null;
|
// window.onbeforeunload = null;
|
||||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var activeNotifyMessage;
|
|
||||||
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
|
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
|
||||||
if (!activeNotifyMessage) {
|
var currentRev = RED.nodes.version();
|
||||||
var currentRev = RED.nodes.version();
|
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
||||||
if (currentRev === null || deployInflight || currentRev === msg.revision) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
|
||||||
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
|
activeBackgroundDeployNotification.showNotification()
|
||||||
activeNotifyMessage = RED.notify(message,{
|
return
|
||||||
modal: true,
|
}
|
||||||
fixed: true,
|
const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
|
||||||
buttons: [
|
const options = {
|
||||||
{
|
id: 'background-update',
|
||||||
text: RED._('deploy.confirm.button.ignore'),
|
type: 'compact',
|
||||||
click: function() {
|
modal: false,
|
||||||
activeNotifyMessage.close();
|
fixed: true,
|
||||||
activeNotifyMessage = null;
|
timeout: 10000,
|
||||||
}
|
buttons: [
|
||||||
},
|
{
|
||||||
{
|
text: RED._('deploy.confirm.button.review'),
|
||||||
text: RED._('deploy.confirm.button.review'),
|
class: "primary",
|
||||||
class: "primary",
|
click: function() {
|
||||||
click: function() {
|
activeBackgroundDeployNotification.hideNotification();
|
||||||
activeNotifyMessage.close();
|
var nns = RED.nodes.createCompleteNodeSet();
|
||||||
var nns = RED.nodes.createCompleteNodeSet();
|
resolveConflict(nns,false);
|
||||||
resolveConflict(nns,false);
|
|
||||||
activeNotifyMessage = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
});
|
]
|
||||||
|
}
|
||||||
|
if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
|
||||||
|
activeBackgroundDeployNotification = RED.notify(message, options)
|
||||||
|
} else {
|
||||||
|
activeBackgroundDeployNotification.update(message, options)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
updateLockedState()
|
||||||
|
RED.events.on('login', updateLockedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLockedState() {
|
||||||
|
if (RED.settings.user?.permissions === 'read') {
|
||||||
|
$(".red-ui-deploy-button-group").addClass("readOnly");
|
||||||
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
|
} else {
|
||||||
|
$(".red-ui-deploy-button-group").removeClass("readOnly");
|
||||||
|
if (RED.nodes.dirty()) {
|
||||||
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeInfo(node) {
|
function getNodeInfo(node) {
|
||||||
@@ -213,7 +243,11 @@ RED.deploy = (function() {
|
|||||||
class: "primary disabled",
|
class: "primary disabled",
|
||||||
click: function() {
|
click: function() {
|
||||||
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
|
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
|
||||||
RED.diff.showRemoteDiff();
|
RED.diff.showRemoteDiff(null, {
|
||||||
|
onmerge: function () {
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
|
}
|
||||||
|
});
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,6 +260,7 @@ RED.deploy = (function() {
|
|||||||
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
||||||
RED.diff.mergeDiff(currentDiff);
|
RED.diff.mergeDiff(currentDiff);
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,6 +273,7 @@ RED.deploy = (function() {
|
|||||||
click: function() {
|
click: function() {
|
||||||
save(true,activeDeploy);
|
save(true,activeDeploy);
|
||||||
conflictNotification.close();
|
conflictNotification.close();
|
||||||
|
activeBackgroundDeployNotification.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -248,21 +284,17 @@ RED.deploy = (function() {
|
|||||||
buttons: buttons
|
buttons: buttons
|
||||||
});
|
});
|
||||||
|
|
||||||
var now = Date.now();
|
|
||||||
RED.diff.getRemoteDiff(function(diff) {
|
RED.diff.getRemoteDiff(function(diff) {
|
||||||
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
|
|
||||||
currentDiff = diff;
|
currentDiff = diff;
|
||||||
setTimeout(function() {
|
conflictCheck.hide();
|
||||||
conflictCheck.hide();
|
var d = Object.keys(diff.conflicts);
|
||||||
var d = Object.keys(diff.conflicts);
|
if (d.length === 0) {
|
||||||
if (d.length === 0) {
|
conflictAutoMerge.show();
|
||||||
conflictAutoMerge.show();
|
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
|
||||||
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
|
} else {
|
||||||
} else {
|
conflictManualMerge.show();
|
||||||
conflictManualMerge.show();
|
}
|
||||||
}
|
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
|
||||||
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
|
|
||||||
},ellapsed);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function cropList(list) {
|
function cropList(list) {
|
||||||
@@ -393,11 +425,15 @@ RED.deploy = (function() {
|
|||||||
const unknownNodes = [];
|
const unknownNodes = [];
|
||||||
const invalidNodes = [];
|
const invalidNodes = [];
|
||||||
|
|
||||||
|
const isDisabled = function (node) {
|
||||||
|
return (node.d || RED.nodes.workspace(node.z)?.disabled);
|
||||||
|
};
|
||||||
|
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if (node.valid === undefined) {
|
if (node.valid === undefined) {
|
||||||
RED.editor.validateNode(node);
|
RED.editor.validateNode(node);
|
||||||
}
|
}
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@@ -407,7 +443,7 @@ RED.deploy = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.nodes.eachNode(function (node) {
|
RED.nodes.eachNode(function (node) {
|
||||||
if (!node.valid && !node.d) {
|
if (!node.valid && !isDisabled(node)) {
|
||||||
invalidNodes.push(getNodeInfo(node));
|
invalidNodes.push(getNodeInfo(node));
|
||||||
}
|
}
|
||||||
if (node.type === "unknown") {
|
if (node.type === "unknown") {
|
||||||
@@ -421,7 +457,7 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
const unusedConfigNodes = [];
|
const unusedConfigNodes = [];
|
||||||
RED.nodes.eachConfig(function (node) {
|
RED.nodes.eachConfig(function (node) {
|
||||||
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
|
if ((node._def.hasUsers !== false) && (node.users.length === 0) && !isDisabled(node)) {
|
||||||
unusedConfigNodes.push(getNodeInfo(node));
|
unusedConfigNodes.push(getNodeInfo(node));
|
||||||
hasUnusedConfig = true;
|
hasUnusedConfig = true;
|
||||||
}
|
}
|
||||||
@@ -557,12 +593,19 @@ RED.deploy = (function() {
|
|||||||
} else {
|
} else {
|
||||||
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
|
||||||
}
|
}
|
||||||
RED.nodes.eachNode(function (node) {
|
const flowsToLock = new Set()
|
||||||
const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null);
|
// Node's properties cannot be modified if its workspace is locked.
|
||||||
|
function ensureUnlocked(id) {
|
||||||
|
// TODO: `RED.nodes.subflow` is useless
|
||||||
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
const isLocked = flow ? flow.locked : false;
|
const isLocked = flow ? flow.locked : false;
|
||||||
if (flow && isLocked) {
|
if (flow && isLocked) {
|
||||||
flow.locked = false;
|
flow.locked = false;
|
||||||
|
flowsToLock.add(flow)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.eachNode(function (node) {
|
||||||
|
ensureUnlocked(node.z)
|
||||||
if (node.changed) {
|
if (node.changed) {
|
||||||
node.dirty = true;
|
node.dirty = true;
|
||||||
node.changed = false;
|
node.changed = false;
|
||||||
@@ -574,25 +617,66 @@ RED.deploy = (function() {
|
|||||||
if (node.credentials) {
|
if (node.credentials) {
|
||||||
delete node.credentials;
|
delete node.credentials;
|
||||||
}
|
}
|
||||||
if (flow && isLocked) {
|
|
||||||
flow.locked = isLocked;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
RED.nodes.eachGroup(function (node) {
|
||||||
|
ensureUnlocked(node.z)
|
||||||
|
if (node.changed) {
|
||||||
|
node.dirty = true;
|
||||||
|
node.changed = false;
|
||||||
|
}
|
||||||
|
if (node.moved) {
|
||||||
|
node.dirty = true;
|
||||||
|
node.moved = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
RED.nodes.eachJunction(function (node) {
|
||||||
|
ensureUnlocked(node.z)
|
||||||
|
if (node.changed) {
|
||||||
|
node.dirty = true;
|
||||||
|
node.changed = false;
|
||||||
|
}
|
||||||
|
if (node.moved) {
|
||||||
|
node.dirty = true;
|
||||||
|
node.moved = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
RED.nodes.eachConfig(function (confNode) {
|
RED.nodes.eachConfig(function (confNode) {
|
||||||
|
if (confNode.z) {
|
||||||
|
ensureUnlocked(confNode.z)
|
||||||
|
}
|
||||||
confNode.changed = false;
|
confNode.changed = false;
|
||||||
if (confNode.credentials) {
|
if (confNode.credentials) {
|
||||||
delete confNode.credentials;
|
delete confNode.credentials;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Subflow cannot be locked
|
||||||
RED.nodes.eachSubflow(function (subflow) {
|
RED.nodes.eachSubflow(function (subflow) {
|
||||||
subflow.changed = false;
|
if (subflow.changed) {
|
||||||
|
subflow.changed = false;
|
||||||
|
RED.events.emit("subflows:change", subflow);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
RED.nodes.eachWorkspace(function (ws) {
|
RED.nodes.eachWorkspace(function (ws) {
|
||||||
ws.changed = false;
|
if (ws.changed || ws.added) {
|
||||||
|
// Ensure the Workspace is unlocked to modify its properties.
|
||||||
|
ensureUnlocked(ws.id);
|
||||||
|
ws.changed = false;
|
||||||
|
delete ws.added
|
||||||
|
if (flowsToLock.has(ws)) {
|
||||||
|
ws.locked = true;
|
||||||
|
flowsToLock.delete(ws);
|
||||||
|
}
|
||||||
|
RED.events.emit("flows:change", ws)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
// Ensures all workspaces to be locked have been locked.
|
||||||
|
flowsToLock.forEach(flow => {
|
||||||
|
flow.locked = true
|
||||||
|
})
|
||||||
// Once deployed, cannot undo back to a clean state
|
// Once deployed, cannot undo back to a clean state
|
||||||
RED.history.markAllDirty();
|
RED.history.markAllDirty();
|
||||||
RED.view.redraw();
|
RED.view.redraw();
|
||||||
|
RED.sidebar.config.refresh();
|
||||||
RED.events.emit("deploy");
|
RED.events.emit("deploy");
|
||||||
}).fail(function (xhr, textStatus, err) {
|
}).fail(function (xhr, textStatus, err) {
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
RED.diff = (function() {
|
RED.diff = (function() {
|
||||||
|
|
||||||
var currentDiff = {};
|
var currentDiff = {};
|
||||||
var diffVisible = false;
|
var diffVisible = false;
|
||||||
var diffList;
|
var diffList;
|
||||||
@@ -62,12 +61,14 @@ RED.diff = (function() {
|
|||||||
addedCount:0,
|
addedCount:0,
|
||||||
deletedCount:0,
|
deletedCount:0,
|
||||||
changedCount:0,
|
changedCount:0,
|
||||||
|
movedCount:0,
|
||||||
unchangedCount: 0
|
unchangedCount: 0
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
addedCount:0,
|
addedCount:0,
|
||||||
deletedCount:0,
|
deletedCount:0,
|
||||||
changedCount:0,
|
changedCount:0,
|
||||||
|
movedCount:0,
|
||||||
unchangedCount: 0
|
unchangedCount: 0
|
||||||
},
|
},
|
||||||
conflicts: 0
|
conflicts: 0
|
||||||
@@ -138,7 +139,7 @@ RED.diff = (function() {
|
|||||||
$(this).parent().toggleClass('collapsed');
|
$(this).parent().toggleClass('collapsed');
|
||||||
});
|
});
|
||||||
|
|
||||||
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
|
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
|
||||||
selectState = "";
|
selectState = "";
|
||||||
if (conflicts[tab.id]) {
|
if (conflicts[tab.id]) {
|
||||||
flowStats.conflicts++;
|
flowStats.conflicts++;
|
||||||
@@ -208,19 +209,26 @@ RED.diff = (function() {
|
|||||||
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
|
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
|
||||||
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
|
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
|
||||||
|
|
||||||
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
|
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
|
||||||
if (flowStats.conflicts > 0) {
|
if (flowStats.conflicts > 0) {
|
||||||
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
|
||||||
}
|
}
|
||||||
if (flowStats.local.addedCount > 0) {
|
if (flowStats.local.addedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.added'))
|
||||||
}
|
}
|
||||||
if (flowStats.local.changedCount > 0) {
|
if (flowStats.local.changedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.changed'))
|
||||||
|
}
|
||||||
|
if (flowStats.local.movedCount > 0) {
|
||||||
|
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.moved'))
|
||||||
}
|
}
|
||||||
if (flowStats.local.deletedCount > 0) {
|
if (flowStats.local.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
|
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
|
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
|
||||||
}
|
}
|
||||||
@@ -246,19 +254,26 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
|
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
|
||||||
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
|
||||||
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
|
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
|
||||||
if (flowStats.conflicts > 0) {
|
if (flowStats.conflicts > 0) {
|
||||||
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
|
||||||
}
|
}
|
||||||
if (flowStats.remote.addedCount > 0) {
|
if (flowStats.remote.addedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.added'))
|
||||||
}
|
}
|
||||||
if (flowStats.remote.changedCount > 0) {
|
if (flowStats.remote.changedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.changed'))
|
||||||
|
}
|
||||||
|
if (flowStats.remote.movedCount > 0) {
|
||||||
|
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.moved'))
|
||||||
}
|
}
|
||||||
if (flowStats.remote.deletedCount > 0) {
|
if (flowStats.remote.deletedCount > 0) {
|
||||||
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
|
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
|
||||||
|
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
|
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
|
||||||
}
|
}
|
||||||
@@ -293,7 +308,7 @@ RED.diff = (function() {
|
|||||||
if (options.mode === "merge") {
|
if (options.mode === "merge") {
|
||||||
diffPanel.addClass("red-ui-diff-panel-merge");
|
diffPanel.addClass("red-ui-diff-panel-merge");
|
||||||
}
|
}
|
||||||
var diffList = createDiffTable(diffPanel, diff);
|
var diffList = createDiffTable(diffPanel, diff, options);
|
||||||
|
|
||||||
var localDiff = diff.localDiff;
|
var localDiff = diff.localDiff;
|
||||||
var remoteDiff = diff.remoteDiff;
|
var remoteDiff = diff.remoteDiff;
|
||||||
@@ -482,7 +497,7 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (c === 0) {
|
if (c === 0) {
|
||||||
result.text("none");
|
result.text(RED._("diff.type.none"));
|
||||||
} else {
|
} else {
|
||||||
list.appendTo(result);
|
list.appendTo(result);
|
||||||
}
|
}
|
||||||
@@ -516,7 +531,6 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
var hasChanges = false; // exists in original and local/remote but with changes
|
var hasChanges = false; // exists in original and local/remote but with changes
|
||||||
var unChanged = true; // existing in original,local,remote unchanged
|
var unChanged = true; // existing in original,local,remote unchanged
|
||||||
var localChanged = false;
|
|
||||||
|
|
||||||
if (localDiff.added[node.id]) {
|
if (localDiff.added[node.id]) {
|
||||||
stats.local.addedCount++;
|
stats.local.addedCount++;
|
||||||
@@ -535,12 +549,20 @@ RED.diff = (function() {
|
|||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
if (localDiff.changed[node.id]) {
|
if (localDiff.changed[node.id]) {
|
||||||
stats.local.changedCount++;
|
if (localDiff.positionChanged[node.id]) {
|
||||||
|
stats.local.movedCount++
|
||||||
|
} else {
|
||||||
|
stats.local.changedCount++;
|
||||||
|
}
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
if (remoteDiff && remoteDiff.changed[node.id]) {
|
if (remoteDiff && remoteDiff.changed[node.id]) {
|
||||||
stats.remote.changedCount++;
|
if (remoteDiff.positionChanged[node.id]) {
|
||||||
|
stats.remote.movedCount++
|
||||||
|
} else {
|
||||||
|
stats.remote.changedCount++;
|
||||||
|
}
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
unChanged = false;
|
unChanged = false;
|
||||||
}
|
}
|
||||||
@@ -605,27 +627,32 @@ RED.diff = (function() {
|
|||||||
localNodeDiv.addClass("red-ui-diff-status-moved");
|
localNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
var localMovedMessage = "";
|
var localMovedMessage = "";
|
||||||
if (node.z === localN.z) {
|
if (node.z === localN.z) {
|
||||||
localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
|
const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
|
||||||
|
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
|
||||||
|
localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
|
||||||
} else {
|
} else {
|
||||||
localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
|
const movedToNodeTab = localDiff.newConfig.all[localN.z]
|
||||||
|
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
|
||||||
|
localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
|
||||||
}
|
}
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.deleted[node.z]) {
|
} else if (localDiff.deleted[node.z]) {
|
||||||
localNodeDiv.addClass("red-ui-diff-empty");
|
localNodeDiv.addClass("red-ui-diff-empty");
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.deleted[node.id]) {
|
} else if (localDiff.deleted[node.id]) {
|
||||||
localNodeDiv.addClass("red-ui-diff-status-deleted");
|
localNodeDiv.addClass("red-ui-diff-status-deleted");
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
|
||||||
localChanged = true;
|
|
||||||
} else if (localDiff.changed[node.id]) {
|
} else if (localDiff.changed[node.id]) {
|
||||||
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
localNodeDiv.addClass("red-ui-diff-empty");
|
localNodeDiv.addClass("red-ui-diff-empty");
|
||||||
} else {
|
} else {
|
||||||
localNodeDiv.addClass("red-ui-diff-status-changed");
|
if (localDiff.positionChanged[node.id]) {
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
localNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
localChanged = true;
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
|
||||||
|
} else {
|
||||||
|
localNodeDiv.addClass("red-ui-diff-status-changed");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
@@ -646,9 +673,13 @@ RED.diff = (function() {
|
|||||||
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
var remoteMovedMessage = "";
|
var remoteMovedMessage = "";
|
||||||
if (node.z === remoteN.z) {
|
if (node.z === remoteN.z) {
|
||||||
remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
|
const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
|
||||||
|
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
|
||||||
|
remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
|
||||||
} else {
|
} else {
|
||||||
remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
|
const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
|
||||||
|
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
|
||||||
|
remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
|
||||||
}
|
}
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
|
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
|
||||||
}
|
}
|
||||||
@@ -661,8 +692,13 @@ RED.diff = (function() {
|
|||||||
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
remoteNodeDiv.addClass("red-ui-diff-empty");
|
remoteNodeDiv.addClass("red-ui-diff-empty");
|
||||||
} else {
|
} else {
|
||||||
remoteNodeDiv.addClass("red-ui-diff-status-changed");
|
if (remoteDiff.positionChanged[node.id]) {
|
||||||
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
remoteNodeDiv.addClass("red-ui-diff-status-moved");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
|
||||||
|
} else {
|
||||||
|
remoteNodeDiv.addClass("red-ui-diff-status-changed");
|
||||||
|
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
||||||
@@ -785,10 +821,10 @@ RED.diff = (function() {
|
|||||||
conflict = true;
|
conflict = true;
|
||||||
}
|
}
|
||||||
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
||||||
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
|
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.position")).appendTo(row);
|
||||||
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
||||||
if (localNode) {
|
if (localNode) {
|
||||||
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
|
localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
|
||||||
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
||||||
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
|
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
|
||||||
var localPosition = {x:localNode.x,y:localNode.y};
|
var localPosition = {x:localNode.x,y:localNode.y};
|
||||||
@@ -813,7 +849,7 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
if (remoteNode !== undefined) {
|
if (remoteNode !== undefined) {
|
||||||
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
|
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
|
||||||
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
|
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
|
||||||
if (remoteNode) {
|
if (remoteNode) {
|
||||||
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
||||||
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
|
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
|
||||||
@@ -863,7 +899,7 @@ RED.diff = (function() {
|
|||||||
conflict = true;
|
conflict = true;
|
||||||
}
|
}
|
||||||
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
||||||
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("wires").appendTo(row);
|
$("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.wires")).appendTo(row);
|
||||||
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
|
||||||
if (localNode) {
|
if (localNode) {
|
||||||
if (!conflict) {
|
if (!conflict) {
|
||||||
@@ -989,9 +1025,10 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
if (localNode && remoteNode && typeof localNode[d] === "string") {
|
if (localNode && remoteNode && typeof localNode[d] === "string") {
|
||||||
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
|
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
|
||||||
$('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
var textDiff = $('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
||||||
showTextDiff(localNode[d],remoteNode[d]);
|
showTextDiff(localNode[d],remoteNode[d]);
|
||||||
}).appendTo(propertyNameCell);
|
}).appendTo(propertyNameCell);
|
||||||
|
RED.popover.tooltip(textDiff, RED._("diff.compareChanges"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,11 +1135,11 @@ RED.diff = (function() {
|
|||||||
// var diff = generateDiff(originalFlow,nns);
|
// var diff = generateDiff(originalFlow,nns);
|
||||||
// showDiff(diff);
|
// showDiff(diff);
|
||||||
// }
|
// }
|
||||||
function showRemoteDiff(diff) {
|
function showRemoteDiff(diff, options = {}) {
|
||||||
if (diff === undefined) {
|
if (!diff) {
|
||||||
getRemoteDiff(showRemoteDiff);
|
getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
|
||||||
} else {
|
} else {
|
||||||
showDiff(diff,{mode:'merge'});
|
showDiff(diff,{...options, mode:'merge'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function parseNodes(nodeList) {
|
function parseNodes(nodeList) {
|
||||||
@@ -1143,23 +1180,53 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function generateDiff(currentNodes,newNodes) {
|
function generateDiff(currentNodes,newNodes) {
|
||||||
var currentConfig = parseNodes(currentNodes);
|
const currentConfig = parseNodes(currentNodes);
|
||||||
var newConfig = parseNodes(newNodes);
|
const newConfig = parseNodes(newNodes);
|
||||||
var added = {};
|
const added = {};
|
||||||
var deleted = {};
|
const deleted = {};
|
||||||
var changed = {};
|
const changed = {};
|
||||||
var moved = {};
|
const positionChanged = {};
|
||||||
|
const moved = {};
|
||||||
|
|
||||||
Object.keys(currentConfig.all).forEach(function(id) {
|
Object.keys(currentConfig.all).forEach(function(id) {
|
||||||
var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
|
const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
|
||||||
if (!newConfig.all.hasOwnProperty(id)) {
|
if (!newConfig.all.hasOwnProperty(id)) {
|
||||||
deleted[id] = true;
|
deleted[id] = true;
|
||||||
} else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
|
return
|
||||||
|
}
|
||||||
|
const currentConfigJSON = JSON.stringify(currentConfig.all[id])
|
||||||
|
const newConfigJSON = JSON.stringify(newConfig.all[id])
|
||||||
|
|
||||||
|
if (currentConfigJSON !== newConfigJSON) {
|
||||||
changed[id] = true;
|
changed[id] = true;
|
||||||
|
|
||||||
if (currentConfig.all[id].z !== newConfig.all[id].z) {
|
if (currentConfig.all[id].z !== newConfig.all[id].z) {
|
||||||
moved[id] = true;
|
moved[id] = true;
|
||||||
|
} else if (
|
||||||
|
currentConfig.all[id].x !== newConfig.all[id].x ||
|
||||||
|
currentConfig.all[id].y !== newConfig.all[id].y ||
|
||||||
|
currentConfig.all[id].w !== newConfig.all[id].w ||
|
||||||
|
currentConfig.all[id].h !== newConfig.all[id].h
|
||||||
|
) {
|
||||||
|
// This node's position on its parent has changed. We want to
|
||||||
|
// check if this is the *only* change for this given node
|
||||||
|
const currentNodeClone = JSON.parse(currentConfigJSON)
|
||||||
|
const newNodeClone = JSON.parse(newConfigJSON)
|
||||||
|
|
||||||
|
delete currentNodeClone.x
|
||||||
|
delete currentNodeClone.y
|
||||||
|
delete currentNodeClone.w
|
||||||
|
delete currentNodeClone.h
|
||||||
|
delete newNodeClone.x
|
||||||
|
delete newNodeClone.y
|
||||||
|
delete newNodeClone.w
|
||||||
|
delete newNodeClone.h
|
||||||
|
|
||||||
|
if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
|
||||||
|
// Only the position has changed - everything else is the same
|
||||||
|
positionChanged[id] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Object.keys(newConfig.all).forEach(function(id) {
|
Object.keys(newConfig.all).forEach(function(id) {
|
||||||
@@ -1168,13 +1235,14 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var diff = {
|
const diff = {
|
||||||
currentConfig: currentConfig,
|
currentConfig,
|
||||||
newConfig: newConfig,
|
newConfig,
|
||||||
added: added,
|
added,
|
||||||
deleted: deleted,
|
deleted,
|
||||||
changed: changed,
|
changed,
|
||||||
moved: moved
|
positionChanged,
|
||||||
|
moved
|
||||||
};
|
};
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
@@ -1239,12 +1307,14 @@ RED.diff = (function() {
|
|||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDiff(diff,options) {
|
function showDiff(diff, options) {
|
||||||
if (diffVisible) {
|
if (diffVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var mode = options.mode || 'merge';
|
var mode = options.mode || 'merge';
|
||||||
|
|
||||||
|
options.hidePositionChanges = true
|
||||||
|
|
||||||
var localDiff = diff.localDiff;
|
var localDiff = diff.localDiff;
|
||||||
var remoteDiff = diff.remoteDiff;
|
var remoteDiff = diff.remoteDiff;
|
||||||
@@ -1314,6 +1384,9 @@ RED.diff = (function() {
|
|||||||
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
|
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
|
||||||
refreshConflictHeader(diff);
|
refreshConflictHeader(diff);
|
||||||
mergeDiff(diff);
|
mergeDiff(diff);
|
||||||
|
if (options.onmerge) {
|
||||||
|
options.onmerge()
|
||||||
|
}
|
||||||
RED.tray.close();
|
RED.tray.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1344,6 +1417,7 @@ RED.diff = (function() {
|
|||||||
var newConfig = [];
|
var newConfig = [];
|
||||||
var node;
|
var node;
|
||||||
var nodeChangedStates = {};
|
var nodeChangedStates = {};
|
||||||
|
var nodeMovedStates = {};
|
||||||
var localChangedStates = {};
|
var localChangedStates = {};
|
||||||
for (id in localDiff.newConfig.all) {
|
for (id in localDiff.newConfig.all) {
|
||||||
if (localDiff.newConfig.all.hasOwnProperty(id)) {
|
if (localDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
@@ -1351,12 +1425,14 @@ RED.diff = (function() {
|
|||||||
if (resolutions[id] === 'local') {
|
if (resolutions[id] === 'local') {
|
||||||
if (node) {
|
if (node) {
|
||||||
nodeChangedStates[id] = node.changed;
|
nodeChangedStates[id] = node.changed;
|
||||||
|
nodeMovedStates[id] = node.moved;
|
||||||
}
|
}
|
||||||
newConfig.push(localDiff.newConfig.all[id]);
|
newConfig.push(localDiff.newConfig.all[id]);
|
||||||
} else if (resolutions[id] === 'remote') {
|
} else if (resolutions[id] === 'remote') {
|
||||||
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
if (node) {
|
if (node) {
|
||||||
nodeChangedStates[id] = node.changed;
|
nodeChangedStates[id] = node.changed;
|
||||||
|
nodeMovedStates[id] = node.moved;
|
||||||
}
|
}
|
||||||
localChangedStates[id] = 1;
|
localChangedStates[id] = 1;
|
||||||
newConfig.push(remoteDiff.newConfig.all[id]);
|
newConfig.push(remoteDiff.newConfig.all[id]);
|
||||||
@@ -1380,8 +1456,9 @@ RED.diff = (function() {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
config: newConfig,
|
config: newConfig,
|
||||||
nodeChangedStates: nodeChangedStates,
|
nodeChangedStates,
|
||||||
localChangedStates: localChangedStates
|
nodeMovedStates,
|
||||||
|
localChangedStates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1392,6 +1469,7 @@ RED.diff = (function() {
|
|||||||
|
|
||||||
var newConfig = appliedDiff.config;
|
var newConfig = appliedDiff.config;
|
||||||
var nodeChangedStates = appliedDiff.nodeChangedStates;
|
var nodeChangedStates = appliedDiff.nodeChangedStates;
|
||||||
|
var nodeMovedStates = appliedDiff.nodeMovedStates;
|
||||||
var localChangedStates = appliedDiff.localChangedStates;
|
var localChangedStates = appliedDiff.localChangedStates;
|
||||||
|
|
||||||
var isDirty = RED.nodes.dirty();
|
var isDirty = RED.nodes.dirty();
|
||||||
@@ -1400,33 +1478,56 @@ RED.diff = (function() {
|
|||||||
t:"replace",
|
t:"replace",
|
||||||
config: RED.nodes.createCompleteNodeSet(),
|
config: RED.nodes.createCompleteNodeSet(),
|
||||||
changed: nodeChangedStates,
|
changed: nodeChangedStates,
|
||||||
|
moved: nodeMovedStates,
|
||||||
|
complete: true,
|
||||||
dirty: isDirty,
|
dirty: isDirty,
|
||||||
rev: RED.nodes.version()
|
rev: RED.nodes.version()
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
|
|
||||||
var originalFlow = RED.nodes.originalFlow();
|
// var originalFlow = RED.nodes.originalFlow();
|
||||||
// originalFlow is what the editor things it loaded
|
// // originalFlow is what the editor thinks it loaded
|
||||||
// - add any newly added nodes from remote diff as they are now part of the record
|
// // - add any newly added nodes from remote diff as they are now part of the record
|
||||||
for (var id in diff.remoteDiff.added) {
|
// for (var id in diff.remoteDiff.added) {
|
||||||
if (diff.remoteDiff.added.hasOwnProperty(id)) {
|
// if (diff.remoteDiff.added.hasOwnProperty(id)) {
|
||||||
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
// if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
|
||||||
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
|
// originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
RED.nodes.clear();
|
RED.nodes.clear();
|
||||||
var imported = RED.nodes.import(newConfig);
|
var imported = RED.nodes.import(newConfig);
|
||||||
|
|
||||||
// Restore the original flow so subsequent merge resolutions can properly
|
// // Restore the original flow so subsequent merge resolutions can properly
|
||||||
// identify new-vs-old
|
// // identify new-vs-old
|
||||||
RED.nodes.originalFlow(originalFlow);
|
// RED.nodes.originalFlow(originalFlow);
|
||||||
|
|
||||||
|
// Clear all change flags from the import
|
||||||
|
RED.nodes.dirty(false);
|
||||||
|
|
||||||
|
const flowsToLock = new Set()
|
||||||
|
function ensureUnlocked(id) {
|
||||||
|
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
|
||||||
|
const isLocked = flow ? flow.locked : false;
|
||||||
|
if (flow && isLocked) {
|
||||||
|
flow.locked = false;
|
||||||
|
flowsToLock.add(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
imported.nodes.forEach(function(n) {
|
imported.nodes.forEach(function(n) {
|
||||||
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
|
if (nodeChangedStates[n.id]) {
|
||||||
|
ensureUnlocked(n.z)
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
}
|
}
|
||||||
|
if (nodeMovedStates[n.id]) {
|
||||||
|
ensureUnlocked(n.z)
|
||||||
|
n.moved = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
flowsToLock.forEach(flow => {
|
||||||
|
flow.locked = true
|
||||||
})
|
})
|
||||||
|
|
||||||
RED.nodes.version(diff.remoteDiff.rev);
|
RED.nodes.version(diff.remoteDiff.rev);
|
||||||
@@ -1928,15 +2029,14 @@ RED.diff = (function() {
|
|||||||
if (!isSeparator) {
|
if (!isSeparator) {
|
||||||
var isOurs = /^..<<<<<<</.test(lineText);
|
var isOurs = /^..<<<<<<</.test(lineText);
|
||||||
if (isOurs) {
|
if (isOurs) {
|
||||||
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
|
$('<span>').text("<<<<<<< " + RED._("diff.localChanges")).appendTo(line);
|
||||||
hunk.localChangeStart = actualLineNumber;
|
hunk.localChangeStart = actualLineNumber;
|
||||||
} else {
|
} else {
|
||||||
hunk.remoteChangeEnd = actualLineNumber;
|
hunk.remoteChangeEnd = actualLineNumber;
|
||||||
$('<span>').text(">>>>>>> Remote Changes").appendTo(line);
|
$('<span>').text(">>>>>>> " + RED._("diff.remoteChanges")).appendTo(line);
|
||||||
|
|
||||||
}
|
}
|
||||||
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
|
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
|
||||||
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
|
$('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> '+RED._(isOurs?"diff.useLocalChanges":"diff.useRemoteChanges")+'</button>')
|
||||||
.appendTo(line)
|
.appendTo(line)
|
||||||
.on("click", function(evt) {
|
.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@@ -2018,7 +2118,7 @@ RED.diff = (function() {
|
|||||||
$("<h3>").text(commit.title).appendTo(content);
|
$("<h3>").text(commit.title).appendTo(content);
|
||||||
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
|
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
|
||||||
var summary = $('<div class="commit-summary"></div>').appendTo(content);
|
var summary = $('<div class="commit-summary"></div>').appendTo(content);
|
||||||
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
|
$('<div style="float: right">').text(RED._('diff.commit')+" "+commit.sha).appendTo(summary);
|
||||||
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
|
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
|
||||||
|
|
||||||
if (commit.files) {
|
if (commit.files) {
|
||||||
|
|||||||
@@ -45,11 +45,13 @@ RED.editor = (function() {
|
|||||||
var hasChanged;
|
var hasChanged;
|
||||||
if (node.type.indexOf("subflow:")===0) {
|
if (node.type.indexOf("subflow:")===0) {
|
||||||
subflow = RED.nodes.subflow(node.type.substring(8));
|
subflow = RED.nodes.subflow(node.type.substring(8));
|
||||||
isValid = subflow.valid;
|
if (subflow){
|
||||||
hasChanged = subflow.changed;
|
isValid = subflow.valid;
|
||||||
if (isValid === undefined) {
|
|
||||||
isValid = validateNode(subflow);
|
|
||||||
hasChanged = subflow.changed;
|
hasChanged = subflow.changed;
|
||||||
|
if (isValid === undefined) {
|
||||||
|
isValid = validateNode(subflow);
|
||||||
|
hasChanged = subflow.changed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
validationErrors = validateNodeProperties(node, node._def.defaults, node);
|
validationErrors = validateNodeProperties(node, node._def.defaults, node);
|
||||||
node.valid = isValid && validationErrors.length === 0;
|
node.valid = isValid && validationErrors.length === 0;
|
||||||
@@ -113,8 +115,9 @@ RED.editor = (function() {
|
|||||||
var valid = validateNodeProperty(node, definition, prop, properties[prop]);
|
var valid = validateNodeProperty(node, definition, prop, properties[prop]);
|
||||||
if ((typeof valid) === "string") {
|
if ((typeof valid) === "string") {
|
||||||
result.push(valid);
|
result.push(valid);
|
||||||
}
|
} else if (Array.isArray(valid)) {
|
||||||
else if(!valid) {
|
result = result.concat(valid)
|
||||||
|
} else if(!valid) {
|
||||||
result.push(prop);
|
result.push(prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +157,12 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (valid && "validate" in definition[property]) {
|
if (valid && "validate" in definition[property]) {
|
||||||
|
if (definition[property].hasOwnProperty("required") &&
|
||||||
|
definition[property].required === false) {
|
||||||
|
if (value === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
var opt = {};
|
var opt = {};
|
||||||
if (label) {
|
if (label) {
|
||||||
@@ -163,7 +172,7 @@ RED.editor = (function() {
|
|||||||
// If the validator takes two arguments, it is a 3.x validator that
|
// If the validator takes two arguments, it is a 3.x validator that
|
||||||
// can return a String to mean 'invalid' and provide a reason
|
// can return a String to mean 'invalid' and provide a reason
|
||||||
if ((definition[property].validate.length === 2) &&
|
if ((definition[property].validate.length === 2) &&
|
||||||
((typeof valid) === "string")) {
|
((typeof valid) === "string") || Array.isArray(valid)) {
|
||||||
return valid;
|
return valid;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, a 2.x returns a truth-like/false-like value that
|
// Otherwise, a 2.x returns a truth-like/false-like value that
|
||||||
@@ -179,6 +188,25 @@ RED.editor = (function() {
|
|||||||
error: err.message
|
error: err.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (valid) {
|
||||||
|
if (definition[property].hasOwnProperty("required") && definition[property].required === false) {
|
||||||
|
if (value === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the validator is not provided in node property => Check if the input has a validator
|
||||||
|
if ("category" in node._def) {
|
||||||
|
const isConfig = node._def.category === "config";
|
||||||
|
const prefix = isConfig ? "node-config-input" : "node-input";
|
||||||
|
const input = $("#"+prefix+"-"+property);
|
||||||
|
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
|
||||||
|
if (isTypedInput) {
|
||||||
|
valid = input.typedInput("validate", { returnErrorMessage: true });
|
||||||
|
if (typeof valid === "string") {
|
||||||
|
return label ? label + ": " + valid : valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
||||||
if (!value || value == "_ADD_") {
|
if (!value || value == "_ADD_") {
|
||||||
@@ -234,6 +262,8 @@ RED.editor = (function() {
|
|||||||
var value = input.val();
|
var value = input.val();
|
||||||
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
|
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
|
||||||
value = input.text();
|
value = input.text();
|
||||||
|
} else if (input.attr("type") === "checkbox") {
|
||||||
|
value = input.prop("checked");
|
||||||
}
|
}
|
||||||
var valid = validateNodeProperty(node, defaults, property,value);
|
var valid = validateNodeProperty(node, defaults, property,value);
|
||||||
if (((typeof valid) === "string") || !valid) {
|
if (((typeof valid) === "string") || !valid) {
|
||||||
@@ -312,49 +342,101 @@ RED.editor = (function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a config-node select box for this property
|
* Create a config-node select box for this property
|
||||||
* @param node - the node being edited
|
* @param {Object} node - the node being edited
|
||||||
* @param property - the name of the field
|
* @param {String} property - the name of the node property
|
||||||
* @param type - the type of the config-node
|
* @param {String} type - the type of the config-node
|
||||||
|
* @param {"node-config-input"|"node-input"|"node-input-subflow-env"} prefix - the prefix to use in the input element ids
|
||||||
|
* @param {Function} [filter] - a function to filter the list of config nodes
|
||||||
|
* @param {Object} [env] - the environment variable object (only used for subflow env vars)
|
||||||
*/
|
*/
|
||||||
function prepareConfigNodeSelect(node,property,type,prefix,filter) {
|
function prepareConfigNodeSelect(node, property, type, prefix, filter, env) {
|
||||||
var input = $("#"+prefix+"-"+property);
|
let nodeValue
|
||||||
if (input.length === 0 ) {
|
if (prefix === 'node-input-subflow-env') {
|
||||||
|
nodeValue = env?.value
|
||||||
|
} else {
|
||||||
|
nodeValue = node[property]
|
||||||
|
}
|
||||||
|
|
||||||
|
const addBtnId = `${prefix}-btn-${property}-add`;
|
||||||
|
const editBtnId = `${prefix}-btn-${property}-edit`;
|
||||||
|
const selectId = prefix + '-' + property;
|
||||||
|
const input = $(`#${selectId}`);
|
||||||
|
if (input.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var newWidth = input.width();
|
const attrStyle = input.attr('style');
|
||||||
var attrStyle = input.attr('style');
|
let newWidth;
|
||||||
var m;
|
let m;
|
||||||
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
|
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
|
||||||
newWidth = m[2].trim();
|
newWidth = m[2].trim();
|
||||||
} else {
|
} else {
|
||||||
newWidth = "70%";
|
newWidth = "70%";
|
||||||
}
|
}
|
||||||
var outerWrap = $("<div></div>").css({
|
const outerWrap = $("<div></div>").css({
|
||||||
width: newWidth,
|
width: newWidth,
|
||||||
display:'inline-flex'
|
display: 'inline-flex'
|
||||||
});
|
});
|
||||||
var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(outerWrap);
|
const select = $('<select id="' + selectId + '"></select>').appendTo(outerWrap);
|
||||||
input.replaceWith(outerWrap);
|
input.replaceWith(outerWrap);
|
||||||
// set the style attr directly - using width() on FF causes a value of 114%...
|
// set the style attr directly - using width() on FF causes a value of 114%...
|
||||||
select.css({
|
select.css({
|
||||||
'flex-grow': 1
|
'flex-grow': 1
|
||||||
});
|
});
|
||||||
updateConfigNodeSelect(property,type,node[property],prefix,filter);
|
|
||||||
$('<a id="'+prefix+'-lookup-'+property+'" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
|
||||||
.css({"margin-left":"10px"})
|
|
||||||
|
// create the edit button
|
||||||
|
const editButton = $('<a id="' + editBtnId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
||||||
|
.css({ "margin-left": "10px" })
|
||||||
.appendTo(outerWrap);
|
.appendTo(outerWrap);
|
||||||
$('#'+prefix+'-lookup-'+property).on("click", function(e) {
|
|
||||||
showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix,node);
|
RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
|
||||||
|
|
||||||
|
// create the add button
|
||||||
|
const addButton = $('<a id="' + addBtnId + '" class="red-ui-button"><i class="fa fa-plus"></i></a>')
|
||||||
|
.css({ "margin-left": "10px" })
|
||||||
|
.appendTo(outerWrap);
|
||||||
|
RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
|
||||||
|
|
||||||
|
const disableButton = function(button, disabled) {
|
||||||
|
$(button).prop("disabled", !!disabled)
|
||||||
|
$(button).toggleClass("disabled", !!disabled)
|
||||||
|
};
|
||||||
|
|
||||||
|
// add the click handler
|
||||||
|
addButton.on("click", function (e) {
|
||||||
|
if (addButton.prop("disabled")) { return }
|
||||||
|
showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
editButton.on("click", function (e) {
|
||||||
|
const selectedOpt = select.find(":selected")
|
||||||
|
if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
|
||||||
|
if (editButton.prop("disabled")) { return }
|
||||||
|
showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
var label = "";
|
|
||||||
var configNode = RED.nodes.node(node[property]);
|
|
||||||
var node_def = RED.nodes.getType(type);
|
|
||||||
|
|
||||||
if (configNode) {
|
// dont permit the user to click the button if the selected option is an env var
|
||||||
label = RED.utils.getNodeLabel(configNode,configNode.id);
|
select.on("change", function () {
|
||||||
}
|
const selectedOpt = select.find(":selected");
|
||||||
input.val(label);
|
const optionsLength = select.find("option").length;
|
||||||
|
if (selectedOpt?.data('env')) {
|
||||||
|
disableButton(addButton, true);
|
||||||
|
disableButton(editButton, true);
|
||||||
|
// disable the edit button if no options available or 'none' selected
|
||||||
|
} else if (optionsLength === 1 || selectedOpt.val() === "_ADD_") {
|
||||||
|
disableButton(addButton, false);
|
||||||
|
disableButton(editButton, true);
|
||||||
|
} else {
|
||||||
|
disableButton(addButton, false);
|
||||||
|
disableButton(editButton, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the value is "", 'add new...' option if no config node available or 'none' option
|
||||||
|
// Otherwise, it's a config node
|
||||||
|
select.val(nodeValue || '_ADD_');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -718,15 +800,39 @@ RED.editor = (function() {
|
|||||||
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
||||||
oldValues[d] = editing_node[d];
|
oldValues[d] = editing_node[d];
|
||||||
} else {
|
} else {
|
||||||
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
// Dont clone the group node `nodes` array
|
||||||
|
if (editing_node.type !== 'group' || d !== "nodes") {
|
||||||
|
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldCreds = {};
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (editing_node._def.credentials[prop].type === 'password') {
|
||||||
|
oldCreds['has_' + prop] = editing_node.credentials['has_' + prop];
|
||||||
|
}
|
||||||
|
if (prop in editing_node.credentials) {
|
||||||
|
oldCreds[prop] = editing_node.credentials[prop];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var rc = editing_node._def.oneditsave.call(editing_node);
|
const rc = editing_node._def.oneditsave.call(editing_node);
|
||||||
if (rc === true) {
|
if (rc === true) {
|
||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
|
} else if (typeof rc === 'object' && rc !== null ) {
|
||||||
|
if (rc.changed === true) {
|
||||||
|
editState.changed = true
|
||||||
|
}
|
||||||
|
if (Array.isArray(rc.history) && rc.history.length > 0) {
|
||||||
|
editState.history = rc.history
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
|
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
|
||||||
@@ -747,16 +853,32 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editing_node._def.credentials) {
|
||||||
|
for (const prop in editing_node._def.credentials) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(editing_node._def.credentials, prop)) {
|
||||||
|
if (oldCreds[prop] !== editing_node.credentials[prop]) {
|
||||||
|
if (editing_node.credentials[prop] === '__PWRD__') {
|
||||||
|
// The password may not exist in oldCreds
|
||||||
|
// The value '__PWRD__' means the password exists,
|
||||||
|
// so ignore this change
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editState.changes.credentials = editState.changes.credentials || {};
|
||||||
|
editState.changes.credentials['has_' + prop] = oldCreds['has_' + prop];
|
||||||
|
editState.changes.credentials[prop] = oldCreds[prop];
|
||||||
|
editState.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultConfigNodeSort(A,B) {
|
function defaultConfigNodeSort(A,B) {
|
||||||
if (A.__label__ < B.__label__) {
|
// sort case insensitive so that `[env] node-name` items are at the top and
|
||||||
return -1;
|
// not mixed inbetween the the lower and upper case items
|
||||||
} else if (A.__label__ > B.__label__) {
|
return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'})
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConfigNodeSelect(name,type,value,prefix,filter) {
|
function updateConfigNodeSelect(name,type,value,prefix,filter) {
|
||||||
@@ -771,7 +893,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
$("#"+prefix+"-"+name).val(value);
|
$("#"+prefix+"-"+name).val(value);
|
||||||
} else {
|
} else {
|
||||||
|
let inclSubflowEnvvars = false
|
||||||
var select = $("#"+prefix+"-"+name);
|
var select = $("#"+prefix+"-"+name);
|
||||||
var node_def = RED.nodes.getType(type);
|
var node_def = RED.nodes.getType(type);
|
||||||
select.children().remove();
|
select.children().remove();
|
||||||
@@ -779,6 +901,7 @@ RED.editor = (function() {
|
|||||||
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
if (!activeWorkspace) {
|
if (!activeWorkspace) {
|
||||||
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
inclSubflowEnvvars = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var configNodes = [];
|
var configNodes = [];
|
||||||
@@ -794,6 +917,31 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// as includeSubflowEnvvars is true, this is a subflow.
|
||||||
|
// include any 'conf-types' env vars as a list of avaiable configs
|
||||||
|
// in the config dropdown as `[env] node-name`
|
||||||
|
if (inclSubflowEnvvars && activeWorkspace.env) {
|
||||||
|
const parentEnv = activeWorkspace.env.filter(env => env.ui?.type === 'conf-types' && env.type === type)
|
||||||
|
if (parentEnv && parentEnv.length > 0) {
|
||||||
|
const locale = RED.i18n.lang()
|
||||||
|
for (let i = 0; i < parentEnv.length; i++) {
|
||||||
|
const tenv = parentEnv[i]
|
||||||
|
const ui = tenv.ui || {}
|
||||||
|
const labels = ui.label || {}
|
||||||
|
const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
|
||||||
|
const config = {
|
||||||
|
env: tenv,
|
||||||
|
id: '${' + tenv.name + '}',
|
||||||
|
type: type,
|
||||||
|
label: labelText,
|
||||||
|
__label__: `[env] ${labelText}`
|
||||||
|
}
|
||||||
|
configNodes.push(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var configSortFn = defaultConfigNodeSort;
|
var configSortFn = defaultConfigNodeSort;
|
||||||
if (typeof node_def.sort == "function") {
|
if (typeof node_def.sort == "function") {
|
||||||
configSortFn = node_def.sort;
|
configSortFn = node_def.sort;
|
||||||
@@ -805,7 +953,10 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configNodes.forEach(function(cn) {
|
configNodes.forEach(function(cn) {
|
||||||
$('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
const option = $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
||||||
|
if (cn.env) {
|
||||||
|
option.data('env', cn.env) // set a data attribute to indicate this is an env var (to inhibit the edit button)
|
||||||
|
}
|
||||||
delete cn.__label__;
|
delete cn.__label__;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -818,7 +969,14 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
|
if (!configNodes.length) {
|
||||||
|
// Add 'add new...' option
|
||||||
|
select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
|
||||||
|
} else {
|
||||||
|
// Add 'none' option
|
||||||
|
select.append('<option value="_ADD_">' + RED._("editor.inputs.none") + '</option>');
|
||||||
|
}
|
||||||
|
|
||||||
window.setTimeout(function() { select.trigger("change");},50);
|
window.setTimeout(function() { select.trigger("change");},50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -897,6 +1055,17 @@ RED.editor = (function() {
|
|||||||
dirty: startDirty
|
dirty: startDirty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editing_node.g) {
|
||||||
|
const group = RED.nodes.group(editing_node.g);
|
||||||
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
|
// a change event on the node - but we're deleting it
|
||||||
|
const index = group?.nodes.indexOf(editing_node) ?? -1;
|
||||||
|
if (index > -1) {
|
||||||
|
group.nodes.splice(index, 1);
|
||||||
|
RED.group.markDirty(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
RED.view.redraw(true);
|
RED.view.redraw(true);
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
@@ -998,7 +1167,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var historyEvent = {
|
let historyEvent = {
|
||||||
t:'edit',
|
t:'edit',
|
||||||
node:editing_node,
|
node:editing_node,
|
||||||
changes:editState.changes,
|
changes:editState.changes,
|
||||||
@@ -1014,6 +1183,15 @@ RED.editor = (function() {
|
|||||||
instances:subflowInstances
|
instances:subflowInstances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editState.history) {
|
||||||
|
historyEvent = {
|
||||||
|
t: 'multi',
|
||||||
|
events: [ historyEvent, ...editState.history ],
|
||||||
|
dirty: wasDirty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
}
|
}
|
||||||
editing_node.dirty = true;
|
editing_node.dirty = true;
|
||||||
@@ -1214,7 +1392,11 @@ RED.editor = (function() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (node_def.hasUsers !== false) {
|
if (node_def.hasUsers !== false) {
|
||||||
$('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
|
// $('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
|
||||||
|
$('<button type="button" class="red-ui-button"><i class="fa fa-user"></i><span id="red-ui-editor-config-user-count"></span></button>').on('click', function() {
|
||||||
|
RED.sidebar.info.outliner.search('uses:'+editing_config_node.id)
|
||||||
|
RED.sidebar.info.show()
|
||||||
|
}).appendTo(trayFooterLeft);
|
||||||
}
|
}
|
||||||
trayFooter.append('<span class="red-ui-tray-footer-right"><span id="red-ui-editor-config-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="red-ui-editor-config-scope"></select></span>');
|
trayFooter.append('<span class="red-ui-tray-footer-right"><span id="red-ui-editor-config-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="red-ui-editor-config-scope"></select></span>');
|
||||||
|
|
||||||
@@ -1272,7 +1454,8 @@ RED.editor = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (node_def.hasUsers !== false) {
|
if (node_def.hasUsers !== false) {
|
||||||
$("#red-ui-editor-config-user-count").text(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
|
$("#red-ui-editor-config-user-count").text(editing_config_node.users.length).parent().show();
|
||||||
|
RED.popover.tooltip($("#red-ui-editor-config-user-count").parent(), function() { return RED._('editor.nodesUse',{count:editing_config_node.users.length})});
|
||||||
}
|
}
|
||||||
trayBody.i18n();
|
trayBody.i18n();
|
||||||
trayFooter.i18n();
|
trayFooter.i18n();
|
||||||
@@ -1331,139 +1514,193 @@ RED.editor = (function() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "node-config-dialog-ok",
|
id: "node-config-dialog-ok",
|
||||||
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
|
text: adding ? RED._("editor.configAdd") : RED._("editor.configUpdate"),
|
||||||
class: "primary",
|
class: "primary",
|
||||||
click: function() {
|
click: function() {
|
||||||
var editState = {
|
// TODO: Already defined
|
||||||
|
const configProperty = name;
|
||||||
|
const configType = type;
|
||||||
|
const configTypeDef = RED.nodes.getType(configType);
|
||||||
|
|
||||||
|
const wasChanged = editing_config_node.changed;
|
||||||
|
const editState = {
|
||||||
changes: {},
|
changes: {},
|
||||||
changed: false,
|
changed: false,
|
||||||
outputMap: null
|
outputMap: null
|
||||||
};
|
};
|
||||||
var configProperty = name;
|
|
||||||
var configId = editing_config_node.id;
|
// Call `oneditsave` and search for changes
|
||||||
var configType = type;
|
handleEditSave(editing_config_node, editState);
|
||||||
var configAdding = adding;
|
|
||||||
var configTypeDef = RED.nodes.getType(configType);
|
|
||||||
var d;
|
|
||||||
var input;
|
|
||||||
|
|
||||||
if (configTypeDef.oneditsave) {
|
// Search for changes in the edit box (panes)
|
||||||
try {
|
activeEditPanes.forEach(function (pane) {
|
||||||
configTypeDef.oneditsave.call(editing_config_node);
|
|
||||||
} catch(err) {
|
|
||||||
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (d in configTypeDef.defaults) {
|
|
||||||
if (configTypeDef.defaults.hasOwnProperty(d)) {
|
|
||||||
var newValue;
|
|
||||||
input = $("#node-config-input-"+d);
|
|
||||||
if (input.attr('type') === "checkbox") {
|
|
||||||
newValue = input.prop('checked');
|
|
||||||
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
|
||||||
newValue = input.text();
|
|
||||||
} else {
|
|
||||||
newValue = input.val();
|
|
||||||
}
|
|
||||||
if (newValue != null && newValue !== editing_config_node[d]) {
|
|
||||||
if (editing_config_node._def.defaults[d].type) {
|
|
||||||
if (newValue == "_ADD_") {
|
|
||||||
newValue = "";
|
|
||||||
}
|
|
||||||
// Change to a related config node
|
|
||||||
var configNode = RED.nodes.node(editing_config_node[d]);
|
|
||||||
if (configNode) {
|
|
||||||
var users = configNode.users;
|
|
||||||
users.splice(users.indexOf(editing_config_node),1);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
configNode = RED.nodes.node(newValue);
|
|
||||||
if (configNode) {
|
|
||||||
configNode.users.push(editing_config_node);
|
|
||||||
RED.events.emit("nodes:change",configNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editing_config_node[d] = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeEditPanes.forEach(function(pane) {
|
|
||||||
if (pane.apply) {
|
if (pane.apply) {
|
||||||
pane.apply.call(pane, editState);
|
pane.apply.call(pane, editState);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
editing_config_node.label = configTypeDef.label;
|
// TODO: Why?
|
||||||
|
editing_config_node.label = configTypeDef.label
|
||||||
var scope = $("#red-ui-editor-config-scope").val();
|
|
||||||
editing_config_node.z = scope;
|
|
||||||
|
|
||||||
|
// Check if disabled has changed
|
||||||
if ($("#node-config-input-node-disabled").prop('checked')) {
|
if ($("#node-config-input-node-disabled").prop('checked')) {
|
||||||
if (editing_config_node.d !== true) {
|
if (editing_config_node.d !== true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
editing_config_node.d = true;
|
editing_config_node.d = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editing_config_node.d === true) {
|
if (editing_config_node.d === true) {
|
||||||
|
editState.changes.d = editing_config_node.d;
|
||||||
|
editState.changed = true;
|
||||||
delete editing_config_node.d;
|
delete editing_config_node.d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: must be undefined if no scope used
|
||||||
|
const scope = $("#red-ui-editor-config-scope").val() || undefined;
|
||||||
|
|
||||||
|
// Check if the scope has changed
|
||||||
|
if (editing_config_node.z !== scope) {
|
||||||
|
editState.changes.z = editing_config_node.z;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.z = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for nodes that use this config node that are no longer
|
||||||
|
// in scope, so must be removed
|
||||||
|
const historyEvents = [];
|
||||||
if (scope) {
|
if (scope) {
|
||||||
// Search for nodes that use this one that are no longer
|
const newUsers = editing_config_node.users.filter(function (node) {
|
||||||
// in scope, so must be removed
|
let keepNode = false;
|
||||||
editing_config_node.users = editing_config_node.users.filter(function(n) {
|
let nodeModified = null;
|
||||||
var keep = true;
|
|
||||||
for (var d in n._def.defaults) {
|
for (const d in node._def.defaults) {
|
||||||
if (n._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
if (n._def.defaults[d].type === editing_config_node.type &&
|
if (node._def.defaults[d].type === editing_config_node.type) {
|
||||||
n[d] === editing_config_node.id &&
|
if (node[d] === editing_config_node.id) {
|
||||||
n.z !== scope) {
|
if (node.z === editing_config_node.z) {
|
||||||
keep = false;
|
// The node is kept only if at least one property uses
|
||||||
// Remove the reference to this node
|
// this config node in the correct scope.
|
||||||
// and revalidate
|
keepNode = true;
|
||||||
n[d] = null;
|
} else {
|
||||||
n.dirty = true;
|
if (!nodeModified) {
|
||||||
n.changed = true;
|
nodeModified = {
|
||||||
validateNode(n);
|
t: "edit",
|
||||||
|
node: node,
|
||||||
|
changes: { [d]: node[d] },
|
||||||
|
changed: node.changed,
|
||||||
|
dirty: node.dirty
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
nodeModified.changes[d] = node[d];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reference to the config node
|
||||||
|
node[d] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keep;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configAdding) {
|
// Add the node modified to the history
|
||||||
RED.nodes.add(editing_config_node);
|
if (nodeModified) {
|
||||||
}
|
historyEvents.push(nodeModified);
|
||||||
|
|
||||||
validateNode(editing_config_node);
|
|
||||||
var validatedNodes = {};
|
|
||||||
validatedNodes[editing_config_node.id] = true;
|
|
||||||
|
|
||||||
var userStack = editing_config_node.users.slice();
|
|
||||||
while(userStack.length > 0) {
|
|
||||||
var user = userStack.pop();
|
|
||||||
if (!validatedNodes[user.id]) {
|
|
||||||
validatedNodes[user.id] = true;
|
|
||||||
if (user.users) {
|
|
||||||
userStack = userStack.concat(user.users);
|
|
||||||
}
|
}
|
||||||
validateNode(user);
|
|
||||||
|
// Mark as changed and revalidate this node
|
||||||
|
if (!keepNode) {
|
||||||
|
node.changed = true;
|
||||||
|
node.dirty = true;
|
||||||
|
validateNode(node);
|
||||||
|
RED.events.emit("nodes:change", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keepNode;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if users are changed
|
||||||
|
if (editing_config_node.users.length !== newUsers.length) {
|
||||||
|
editState.changes.users = editing_config_node.users;
|
||||||
|
editState.changed = true;
|
||||||
|
editing_config_node.users = newUsers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RED.nodes.dirty(true);
|
|
||||||
RED.view.redraw(true);
|
if (editState.changed) {
|
||||||
if (!configAdding) {
|
// Set the congig node as changed
|
||||||
RED.events.emit("editor:save",editing_config_node);
|
editing_config_node.changed = true;
|
||||||
RED.events.emit("nodes:change",editing_config_node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now, validate the config node
|
||||||
|
validateNode(editing_config_node);
|
||||||
|
|
||||||
|
// And validate nodes using this config node too
|
||||||
|
const validatedNodes = new Set();
|
||||||
|
const userStack = editing_config_node.users.slice();
|
||||||
|
|
||||||
|
validatedNodes.add(editing_config_node.id);
|
||||||
|
while (userStack.length) {
|
||||||
|
const node = userStack.pop();
|
||||||
|
if (!validatedNodes.has(node.id)) {
|
||||||
|
validatedNodes.add(node.id);
|
||||||
|
if (node.users) {
|
||||||
|
userStack.push(...node.users);
|
||||||
|
}
|
||||||
|
validateNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let historyEvent = {
|
||||||
|
t: "edit",
|
||||||
|
node: editing_config_node,
|
||||||
|
changes: editState.changes,
|
||||||
|
changed: wasChanged,
|
||||||
|
dirty: RED.nodes.dirty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (historyEvents.length) {
|
||||||
|
// Need a multi events
|
||||||
|
historyEvent = {
|
||||||
|
t: "multi",
|
||||||
|
events: [historyEvent].concat(historyEvents),
|
||||||
|
dirty: historyEvent.dirty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adding) {
|
||||||
|
// This event is triggered when the edit box is saved,
|
||||||
|
// regardless of whether there are any modifications.
|
||||||
|
RED.events.emit("editor:save", editing_config_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editState.changed) {
|
||||||
|
if (adding) {
|
||||||
|
RED.history.push({ t: "add", nodes: [editing_config_node.id], dirty: RED.nodes.dirty() });
|
||||||
|
// Add the new config node and trigger the `nodes:add` event
|
||||||
|
RED.nodes.add(editing_config_node);
|
||||||
|
} else {
|
||||||
|
RED.history.push(historyEvent);
|
||||||
|
RED.events.emit("nodes:change", editing_config_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
RED.nodes.dirty(true);
|
||||||
|
RED.view.redraw(true);
|
||||||
|
}
|
||||||
|
|
||||||
RED.tray.close(function() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
// when editing a config via subflow edit panel, the `configProperty` will not
|
||||||
filter = function(n) {
|
// necessarily be a property of the editContext._def.defaults object
|
||||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
// Also, when editing via dashboard sidebar, editContext can be null
|
||||||
|
// so we need to guard both scenarios
|
||||||
|
if (editContext?._def) {
|
||||||
|
const isSubflow = (editContext._def.type === 'subflow' || /subflow:.*/.test(editContext._def.type))
|
||||||
|
if (editContext && !isSubflow && typeof editContext._def.defaults?.[configProperty]?.filter === 'function') {
|
||||||
|
filter = function(n) {
|
||||||
|
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
|
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
|
||||||
@@ -1524,7 +1761,7 @@ RED.editor = (function() {
|
|||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
RED.tray.close(function() {
|
RED.tray.close(function() {
|
||||||
var filter = null;
|
var filter = null;
|
||||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') {
|
||||||
filter = function(n) {
|
filter = function(n) {
|
||||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||||
}
|
}
|
||||||
@@ -1599,20 +1836,31 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let envToRemove = new Set()
|
||||||
if (!isSameObj(old_env, new_env)) {
|
if (!isSameObj(old_env, new_env)) {
|
||||||
editing_node.env = new_env;
|
// Get a list of env properties that have been removed
|
||||||
|
// by comparing old_env and new_env
|
||||||
|
if (old_env) {
|
||||||
|
old_env.forEach(env => { envToRemove.add(env.name) })
|
||||||
|
}
|
||||||
|
if (new_env) {
|
||||||
|
new_env.forEach(env => {
|
||||||
|
envToRemove.delete(env.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
editState.changes.env = editing_node.env;
|
editState.changes.env = editing_node.env;
|
||||||
|
editing_node.env = new_env;
|
||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (editState.changed) {
|
if (editState.changed) {
|
||||||
var wasChanged = editing_node.changed;
|
let wasChanged = editing_node.changed;
|
||||||
editing_node.changed = true;
|
editing_node.changed = true;
|
||||||
validateNode(editing_node);
|
validateNode(editing_node);
|
||||||
var subflowInstances = [];
|
let subflowInstances = [];
|
||||||
|
let instanceHistoryEvents = []
|
||||||
RED.nodes.eachNode(function(n) {
|
RED.nodes.eachNode(function(n) {
|
||||||
if (n.type == "subflow:"+editing_node.id) {
|
if (n.type == "subflow:"+editing_node.id) {
|
||||||
subflowInstances.push({
|
subflowInstances.push({
|
||||||
@@ -1622,13 +1870,35 @@ RED.editor = (function() {
|
|||||||
n._def.color = editing_node.color;
|
n._def.color = editing_node.color;
|
||||||
n.changed = true;
|
n.changed = true;
|
||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
|
if (n.env) {
|
||||||
|
const oldEnv = n.env
|
||||||
|
const newEnv = []
|
||||||
|
let envChanged = false
|
||||||
|
n.env.forEach((env, index) => {
|
||||||
|
if (envToRemove.has(env.name)) {
|
||||||
|
envChanged = true
|
||||||
|
} else {
|
||||||
|
newEnv.push(env)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (envChanged) {
|
||||||
|
instanceHistoryEvents.push({
|
||||||
|
t: 'edit',
|
||||||
|
node: n,
|
||||||
|
changes: { env: oldEnv },
|
||||||
|
dirty: n.dirty,
|
||||||
|
changed: n.changed
|
||||||
|
})
|
||||||
|
n.env = newEnv
|
||||||
|
}
|
||||||
|
}
|
||||||
updateNodeProperties(n);
|
updateNodeProperties(n);
|
||||||
validateNode(n);
|
validateNode(n);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.events.emit("subflows:change",editing_node);
|
RED.events.emit("subflows:change",editing_node);
|
||||||
RED.nodes.dirty(true);
|
RED.nodes.dirty(true);
|
||||||
var historyEvent = {
|
let historyEvent = {
|
||||||
t:'edit',
|
t:'edit',
|
||||||
node:editing_node,
|
node:editing_node,
|
||||||
changes:editState.changes,
|
changes:editState.changes,
|
||||||
@@ -1638,7 +1908,13 @@ RED.editor = (function() {
|
|||||||
instances:subflowInstances
|
instances:subflowInstances
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (instanceHistoryEvents.length > 0) {
|
||||||
|
historyEvent = {
|
||||||
|
t: 'multi',
|
||||||
|
events: [ historyEvent, ...instanceHistoryEvents ],
|
||||||
|
dirty: wasDirty
|
||||||
|
}
|
||||||
|
}
|
||||||
RED.history.push(historyEvent);
|
RED.history.push(historyEvent);
|
||||||
}
|
}
|
||||||
editing_node.dirty = true;
|
editing_node.dirty = true;
|
||||||
@@ -2065,6 +2341,7 @@ RED.editor = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
||||||
|
getEditStack: function () { return [...editStack] },
|
||||||
buildEditForm: buildEditForm,
|
buildEditForm: buildEditForm,
|
||||||
validateNode: validateNode,
|
validateNode: validateNode,
|
||||||
updateNodeProperties: updateNodeProperties,
|
updateNodeProperties: updateNodeProperties,
|
||||||
@@ -2109,6 +2386,7 @@ RED.editor = (function() {
|
|||||||
filteredEditPanes[type] = filter
|
filteredEditPanes[type] = filter
|
||||||
}
|
}
|
||||||
editPanes[type] = definition;
|
editPanes[type] = definition;
|
||||||
}
|
},
|
||||||
|
prepareConfigNodeSelect: prepareConfigNodeSelect,
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
var i=0,l=bufferBinValue.length;
|
var i=0,l=bufferBinValue.length;
|
||||||
var c = 0;
|
var c = 0;
|
||||||
for(i=0;i<l;i++) {
|
for(i=0;i<l;i++) {
|
||||||
var d = parseInt(bufferBinValue[i]);
|
var d = parseInt(Number(bufferBinValue[i]));
|
||||||
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
|
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
|
||||||
valid = false;
|
valid = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
initialised = selectedCodeEditor.init();
|
initialised = selectedCodeEditor.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('<div id="red-ui-image-drop-target"><div data-i18n="[append]workspace.dropImageHere"><i class="fa fa-download"></i><br></div></div>').appendTo('#red-ui-editor');
|
$('<div id="red-ui-drop-target-markdown-editor"><div><i class="fa fa-download"></i><br></div></div>').appendTo('#red-ui-editor');
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(options) {
|
function create(options) {
|
||||||
|
|||||||
@@ -59,18 +59,21 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
//TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some).
|
//TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some).
|
||||||
const knownModules = {
|
const knownModules = {
|
||||||
"assert": {package: "node", module: "assert", path: "node/assert.d.ts" },
|
"assert": {package: "node", module: "assert", path: "node/assert.d.ts" },
|
||||||
|
"assert/strict": {package: "node", module: "assert/strict", path: "node/assert/strict.d.ts" },
|
||||||
"async_hooks": {package: "node", module: "async_hooks", path: "node/async_hooks.d.ts" },
|
"async_hooks": {package: "node", module: "async_hooks", path: "node/async_hooks.d.ts" },
|
||||||
"buffer": {package: "node", module: "buffer", path: "node/buffer.d.ts" },
|
"buffer": {package: "node", module: "buffer", path: "node/buffer.d.ts" },
|
||||||
"child_process": {package: "node", module: "child_process", path: "node/child_process.d.ts" },
|
"child_process": {package: "node", module: "child_process", path: "node/child_process.d.ts" },
|
||||||
"cluster": {package: "node", module: "cluster", path: "node/cluster.d.ts" },
|
"cluster": {package: "node", module: "cluster", path: "node/cluster.d.ts" },
|
||||||
"console": {package: "node", module: "console", path: "node/console.d.ts" },
|
"console": {package: "node", module: "console", path: "node/console.d.ts" },
|
||||||
"constants": {package: "node", module: "constants", path: "node/constants.d.ts" },
|
|
||||||
"crypto": {package: "node", module: "crypto", path: "node/crypto.d.ts" },
|
"crypto": {package: "node", module: "crypto", path: "node/crypto.d.ts" },
|
||||||
"dgram": {package: "node", module: "dgram", path: "node/dgram.d.ts" },
|
"dgram": {package: "node", module: "dgram", path: "node/dgram.d.ts" },
|
||||||
|
"diagnostics_channel.d": {package: "node", module: "diagnostics_channel", path: "node/diagnostics_channel.d.ts" },
|
||||||
"dns": {package: "node", module: "dns", path: "node/dns.d.ts" },
|
"dns": {package: "node", module: "dns", path: "node/dns.d.ts" },
|
||||||
|
"dns/promises": {package: "node", module: "dns/promises", path: "node/dns/promises.d.ts" },
|
||||||
"domain": {package: "node", module: "domain", path: "node/domain.d.ts" },
|
"domain": {package: "node", module: "domain", path: "node/domain.d.ts" },
|
||||||
"events": {package: "node", module: "events", path: "node/events.d.ts" },
|
"events": {package: "node", module: "events", path: "node/events.d.ts" },
|
||||||
"fs": {package: "node", module: "fs", path: "node/fs.d.ts" },
|
"fs": {package: "node", module: "fs", path: "node/fs.d.ts" },
|
||||||
|
"fs/promises": {package: "node", module: "fs/promises", path: "node/fs/promises.d.ts" },
|
||||||
"globals": {package: "node", module: "globals", path: "node/globals.d.ts" },
|
"globals": {package: "node", module: "globals", path: "node/globals.d.ts" },
|
||||||
"http": {package: "node", module: "http", path: "node/http.d.ts" },
|
"http": {package: "node", module: "http", path: "node/http.d.ts" },
|
||||||
"http2": {package: "node", module: "http2", path: "node/http2.d.ts" },
|
"http2": {package: "node", module: "http2", path: "node/http2.d.ts" },
|
||||||
@@ -84,8 +87,13 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
"querystring": {package: "node", module: "querystring", path: "node/querystring.d.ts" },
|
"querystring": {package: "node", module: "querystring", path: "node/querystring.d.ts" },
|
||||||
"readline": {package: "node", module: "readline", path: "node/readline.d.ts" },
|
"readline": {package: "node", module: "readline", path: "node/readline.d.ts" },
|
||||||
"stream": {package: "node", module: "stream", path: "node/stream.d.ts" },
|
"stream": {package: "node", module: "stream", path: "node/stream.d.ts" },
|
||||||
|
"stream/consumers": {package: "node", module: "stream/consumers", path: "node/stream/consumers.d.ts" },
|
||||||
|
"stream/promises": {package: "node", module: "stream/promises", path: "node/stream/promises.d.ts" },
|
||||||
|
"stream/web": {package: "node", module: "stream/web", path: "node/stream/web.d.ts" },
|
||||||
"string_decoder": {package: "node", module: "string_decoder", path: "node/string_decoder.d.ts" },
|
"string_decoder": {package: "node", module: "string_decoder", path: "node/string_decoder.d.ts" },
|
||||||
|
"test": {package: "node", module: "test", path: "node/test.d.ts" },
|
||||||
"timers": {package: "node", module: "timers", path: "node/timers.d.ts" },
|
"timers": {package: "node", module: "timers", path: "node/timers.d.ts" },
|
||||||
|
"timers/promises": {package: "node", module: "timers/promises", path: "node/timers/promises.d.ts" },
|
||||||
"tls": {package: "node", module: "tls", path: "node/tls.d.ts" },
|
"tls": {package: "node", module: "tls", path: "node/tls.d.ts" },
|
||||||
"trace_events": {package: "node", module: "trace_events", path: "node/trace_events.d.ts" },
|
"trace_events": {package: "node", module: "trace_events", path: "node/trace_events.d.ts" },
|
||||||
"tty": {package: "node", module: "tty", path: "node/tty.d.ts" },
|
"tty": {package: "node", module: "tty", path: "node/tty.d.ts" },
|
||||||
@@ -100,7 +108,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
"node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
|
"node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
|
||||||
"node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
|
"node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
|
||||||
}
|
}
|
||||||
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
|
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"], knownModules["timers"] , knownModules["util"] ];
|
||||||
|
|
||||||
const modulesCache = {};
|
const modulesCache = {};
|
||||||
|
|
||||||
@@ -157,7 +165,13 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
//Handles orphaned models
|
//Handles orphaned models
|
||||||
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
|
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
|
||||||
RED.events.on("editor:close",function() {
|
RED.events.on("editor:close",function() {
|
||||||
let models = window.monaco ? monaco.editor.getModels() : null;
|
if (!window.monaco) { return; }
|
||||||
|
const editors = window.monaco.editor.getEditors()
|
||||||
|
const orphanEditors = editors.filter(editor => editor && !document.body.contains(editor.getDomNode()))
|
||||||
|
orphanEditors.forEach(editor => {
|
||||||
|
editor.dispose();
|
||||||
|
});
|
||||||
|
let models = monaco.editor.getModels()
|
||||||
if(models && models.length) {
|
if(models && models.length) {
|
||||||
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
|
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
|
||||||
for (let index = 0; index < models.length; index++) {
|
for (let index = 0; index < models.length; index++) {
|
||||||
@@ -506,7 +520,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
|
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
|
||||||
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
|
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("monaco - Error setting up json options", err)
|
console.warn("monaco - Error setting up json options", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +532,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
|
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
|
||||||
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
|
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("monaco - Error setting up html options", err)
|
console.warn("monaco - Error setting up html options", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +552,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
|
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
|
||||||
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
|
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
|
console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +591,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
|
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
|
||||||
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
|
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
|
||||||
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
|
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
|
||||||
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
|
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME,NR_SUBFLOW_NAME,NR_SUBFLOW_ID,NR_SUBFLOW_PATH|}");','Get env variable value',range),
|
||||||
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
|
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
|
||||||
["```typescript",
|
["```typescript",
|
||||||
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
|
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
|
||||||
@@ -677,6 +691,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
2322, //Type 'unknown' is not assignable to type 'string'
|
2322, //Type 'unknown' is not assignable to type 'string'
|
||||||
2339, //property does not exist on
|
2339, //property does not exist on
|
||||||
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions'
|
||||||
|
2538, //Ignore symbols as index property error.
|
||||||
7043, //i forget what this one is,
|
7043, //i forget what this one is,
|
||||||
80001, //Convert to ES6 module
|
80001, //Convert to ES6 module
|
||||||
80004, //JSDoc types may be moved to TypeScript types.
|
80004, //JSDoc types may be moved to TypeScript types.
|
||||||
@@ -958,12 +973,10 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
|
|
||||||
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
|
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
|
||||||
try {
|
try {
|
||||||
ed._standaloneKeybindingService.addDynamicKeybinding(
|
monaco.editor.addKeybindingRule({keybinding: 0, command: "-editor.action.insertLineAfter"});
|
||||||
'-editor.action.insertLineAfter', // command ID prefixed by '-'
|
} catch (error) {
|
||||||
null, // keybinding
|
console.warn(error)
|
||||||
() => {} // need to pass an empty handler
|
}
|
||||||
);
|
|
||||||
} catch (error) { }
|
|
||||||
|
|
||||||
ed.nodered = {
|
ed.nodered = {
|
||||||
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
|
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
|
||||||
@@ -1118,6 +1131,7 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
|
|
||||||
$(el).remove();
|
$(el).remove();
|
||||||
$(toolbarRow).remove();
|
$(toolbarRow).remove();
|
||||||
|
ed.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
ed.resize = function resize() {
|
ed.resize = function resize() {
|
||||||
@@ -1160,19 +1174,19 @@ RED.editor.codeEditor.monaco = (function() {
|
|||||||
// Warning: 4
|
// Warning: 4
|
||||||
// Error: 8
|
// Error: 8
|
||||||
ed.getAnnotations = function getAnnotations() {
|
ed.getAnnotations = function getAnnotations() {
|
||||||
var aceCompatibleMarkers = [];
|
let aceCompatibleMarkers;
|
||||||
try {
|
try {
|
||||||
var _model = ed.getModel();
|
const _model = ed.getModel();
|
||||||
if (_model !== null) {
|
if (_model !== null) {
|
||||||
var id = _model._languageId; // e.g. javascript
|
const id = _model.getLanguageId(); // e.g. javascript
|
||||||
var ra = _model._associatedResource.authority; //e.g. model
|
const ra = _model.uri.authority; // e.g. model
|
||||||
var rp = _model._associatedResource.path; //e.g. /18
|
const rp = _model.uri.path; // e.g. /18
|
||||||
var rs = _model._associatedResource.scheme; //e.g. inmemory
|
const rs = _model.uri.scheme; // e.g. inmemory
|
||||||
var modelMarkers = monaco.editor.getModelMarkers(_model) || [];
|
const modelMarkers = monaco.editor.getModelMarkers(_model) || [];
|
||||||
var thisEditorsMarkers = modelMarkers.filter(function (marker) {
|
const thisEditorsMarkers = modelMarkers.filter(function (marker) {
|
||||||
var _ra = marker.resource.authority; //e.g. model
|
const _ra = marker.resource.authority; // e.g. model
|
||||||
var _rp = marker.resource.path; //e.g. /18
|
const _rp = marker.resource.path; // e.g. /18
|
||||||
var _rs = marker.resource.scheme; //e.g. inmemory
|
const _rs = marker.resource.scheme; // e.g. inmemory
|
||||||
return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
|
return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
|
||||||
})
|
})
|
||||||
aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
|
aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
RED.editor.envVarList = (function() {
|
RED.editor.envVarList = (function() {
|
||||||
|
|
||||||
var currentLocale = 'en-US';
|
var currentLocale = 'en-US';
|
||||||
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
const DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
||||||
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
const DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES = ['str','num','bool','json','bin','env','conf-types'];
|
||||||
|
const DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create env var edit interface
|
* Create env var edit interface
|
||||||
@@ -10,8 +11,8 @@ RED.editor.envVarList = (function() {
|
|||||||
* @param node - subflow node
|
* @param node - subflow node
|
||||||
*/
|
*/
|
||||||
function buildPropertiesList(envContainer, node) {
|
function buildPropertiesList(envContainer, node) {
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) }
|
||||||
var isTemplateNode = (node.type === "subflow");
|
const isTemplateNode = (node.type === "subflow");
|
||||||
|
|
||||||
envContainer
|
envContainer
|
||||||
.css({
|
.css({
|
||||||
@@ -83,7 +84,14 @@ RED.editor.envVarList = (function() {
|
|||||||
// if `opt.ui` does not exist, then apply defaults. If these
|
// if `opt.ui` does not exist, then apply defaults. If these
|
||||||
// defaults do not change then they will get stripped off
|
// defaults do not change then they will get stripped off
|
||||||
// before saving.
|
// before saving.
|
||||||
if (opt.type === 'cred') {
|
if (opt.type === 'conf-types') {
|
||||||
|
opt.ui = opt.ui || {
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
type: "conf-types",
|
||||||
|
opts: {opts:[]}
|
||||||
|
}
|
||||||
|
opt.ui.type = "conf-types";
|
||||||
|
} else if (opt.type === 'cred') {
|
||||||
opt.ui = opt.ui || {
|
opt.ui = opt.ui || {
|
||||||
icon: "",
|
icon: "",
|
||||||
type: "cred"
|
type: "cred"
|
||||||
@@ -119,11 +127,11 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
|
buildEnvEditRow(uiRow, opt, nameField, valueField);
|
||||||
nameField.trigger('change');
|
nameField.trigger('change');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sortable: ".red-ui-editableList-item-handle",
|
sortable: true,
|
||||||
removable: false
|
removable: false
|
||||||
});
|
});
|
||||||
var parentEnv = {};
|
var parentEnv = {};
|
||||||
@@ -181,21 +189,23 @@ RED.editor.envVarList = (function() {
|
|||||||
* @param nameField - name field of env var
|
* @param nameField - name field of env var
|
||||||
* @param valueField - value field of env var
|
* @param valueField - value field of env var
|
||||||
*/
|
*/
|
||||||
function buildEnvEditRow(container, ui, nameField, valueField) {
|
function buildEnvEditRow(container, opt, nameField, valueField) {
|
||||||
|
const ui = opt.ui
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: buildEnvEditRow', container, ui, nameField, valueField) }
|
||||||
container.addClass("red-ui-editor-subflow-env-ui-row")
|
container.addClass("red-ui-editor-subflow-env-ui-row")
|
||||||
var topRow = $('<div></div>').appendTo(container);
|
var topRow = $('<div></div>').appendTo(container);
|
||||||
$('<div></div>').appendTo(topRow);
|
$('<div></div>').appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
||||||
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
|
$('<div class="red-env-ui-input-type-col">').text(RED._("editor.inputType")).appendTo(topRow);
|
||||||
|
|
||||||
var row = $('<div></div>').appendTo(container);
|
var row = $('<div></div>').appendTo(container);
|
||||||
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
||||||
var typeOptions = {
|
var typeOptions = {
|
||||||
'input': {types:DEFAULT_ENV_TYPE_LIST},
|
'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES},
|
||||||
'select': {opts:[]},
|
'select': {opts:[]},
|
||||||
'spinner': {},
|
'spinner': {},
|
||||||
'cred': {}
|
'cred': {}
|
||||||
};
|
};
|
||||||
if (ui.opts) {
|
if (ui.opts) {
|
||||||
typeOptions[ui.type] = ui.opts;
|
typeOptions[ui.type] = ui.opts;
|
||||||
@@ -260,15 +270,16 @@ RED.editor.envVarList = (function() {
|
|||||||
labelInput.attr("placeholder",$(this).val())
|
labelInput.attr("placeholder",$(this).val())
|
||||||
});
|
});
|
||||||
|
|
||||||
var inputCell = $('<div></div>').appendTo(row);
|
var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row);
|
||||||
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
||||||
if (ui.type === "input") {
|
if (ui.type === "input") {
|
||||||
inputCellInput.val(ui.opts.types.join(","));
|
uiInputTypeInput.val(ui.opts.types.join(","));
|
||||||
}
|
}
|
||||||
var checkbox;
|
var checkbox;
|
||||||
var selectBox;
|
var selectBox;
|
||||||
|
|
||||||
inputCellInput.typedInput({
|
// the options presented in the UI section for an "input" type selection
|
||||||
|
uiInputTypeInput.typedInput({
|
||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
value:"input",
|
value:"input",
|
||||||
@@ -429,7 +440,7 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.opts.opts = vals;
|
ui.opts.opts = vals;
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,12 +507,13 @@ RED.editor.envVarList = (function() {
|
|||||||
} else {
|
} else {
|
||||||
delete ui.opts.max;
|
delete ui.opts.max;
|
||||||
}
|
}
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'conf-types',
|
||||||
{
|
{
|
||||||
value:"none",
|
value:"none",
|
||||||
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
||||||
@@ -519,14 +531,20 @@ RED.editor.envVarList = (function() {
|
|||||||
// In the case of 'input' type, the typedInput uses the multiple-option
|
// In the case of 'input' type, the typedInput uses the multiple-option
|
||||||
// mode. Its value needs to be set to a comma-separately list of the
|
// mode. Its value needs to be set to a comma-separately list of the
|
||||||
// selected options.
|
// selected options.
|
||||||
inputCellInput.typedInput('value',ui.opts.types.join(","))
|
uiInputTypeInput.typedInput('value',ui.opts.types.join(","))
|
||||||
|
} else if (ui.type === 'conf-types') {
|
||||||
|
// In the case of 'conf-types' type, the typedInput will be populated
|
||||||
|
// with a list of all config nodes types installed.
|
||||||
|
// Restore the value to the last selected type
|
||||||
|
uiInputTypeInput.typedInput('value', opt.type)
|
||||||
} else {
|
} else {
|
||||||
// No other type cares about `value`, but doing this will
|
// No other type cares about `value`, but doing this will
|
||||||
// force a refresh of the label now that `ui.opts` has
|
// force a refresh of the label now that `ui.opts` has
|
||||||
// been updated.
|
// been updated.
|
||||||
inputCellInput.typedInput('value',Date.now())
|
uiInputTypeInput.typedInput('value',Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:typedinputtypechange. ui.type = ' + ui.type) }
|
||||||
switch (ui.type) {
|
switch (ui.type) {
|
||||||
case 'input':
|
case 'input':
|
||||||
valueField.typedInput('types',ui.opts.types);
|
valueField.typedInput('types',ui.opts.types);
|
||||||
@@ -544,7 +562,7 @@ RED.editor.envVarList = (function() {
|
|||||||
valueField.typedInput('types',['cred']);
|
valueField.typedInput('types',['cred']);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
|
valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST);
|
||||||
}
|
}
|
||||||
if (ui.type === 'checkbox') {
|
if (ui.type === 'checkbox') {
|
||||||
valueField.typedInput('type','bool');
|
valueField.typedInput('type','bool');
|
||||||
@@ -556,8 +574,46 @@ RED.editor.envVarList = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}).on("change", function(evt,type) {
|
}).on("change", function(evt,type) {
|
||||||
if (ui.type === 'input') {
|
const selectedType = $(this).typedInput('type') // the UI typedInput type
|
||||||
var types = inputCellInput.typedInput('value');
|
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:change. selectedType = ' + selectedType) }
|
||||||
|
if (selectedType === 'conf-types') {
|
||||||
|
const selectedConfigType = $(this).typedInput('value') || opt.type
|
||||||
|
let activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||||
|
if (!activeWorkspace) {
|
||||||
|
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a list of all config nodes matching the selectedValue
|
||||||
|
const configNodes = [];
|
||||||
|
RED.nodes.eachConfig(function(config) {
|
||||||
|
if (config.type == selectedConfigType && (!config.z || config.z === activeWorkspace.id)) {
|
||||||
|
const modulePath = config._def?.set?.id || ''
|
||||||
|
let label = RED.utils.getNodeLabel(config, config.id) || config.id;
|
||||||
|
label += config.d ? ' ['+RED._('workspace.disabled')+']' : '';
|
||||||
|
const _config = {
|
||||||
|
_type: selectedConfigType,
|
||||||
|
value: config.id,
|
||||||
|
label: label,
|
||||||
|
title: modulePath ? modulePath + ' - ' + label : label,
|
||||||
|
enabled: config.d !== true,
|
||||||
|
disabled: config.d === true,
|
||||||
|
}
|
||||||
|
configNodes.push(_config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const tiTypes = {
|
||||||
|
value: selectedConfigType,
|
||||||
|
label: "config",
|
||||||
|
icon: "fa fa-cog",
|
||||||
|
options: configNodes,
|
||||||
|
}
|
||||||
|
valueField.typedInput('types', [tiTypes]);
|
||||||
|
valueField.typedInput('type', selectedConfigType);
|
||||||
|
valueField.typedInput('value', opt.value);
|
||||||
|
|
||||||
|
|
||||||
|
} else if (ui.type === 'input') {
|
||||||
|
var types = uiInputTypeInput.typedInput('value');
|
||||||
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
||||||
valueField.typedInput('types',ui.opts.types);
|
valueField.typedInput('types',ui.opts.types);
|
||||||
}
|
}
|
||||||
@@ -569,7 +625,7 @@ RED.editor.envVarList = (function() {
|
|||||||
})
|
})
|
||||||
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
||||||
// event handler (just above ^^) to update the value if needed
|
// event handler (just above ^^) to update the value if needed
|
||||||
inputCellInput.typedInput('type',ui.type)
|
uiInputTypeInput.typedInput('type',ui.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLocale(l, list) {
|
function setLocale(l, list) {
|
||||||
|
|||||||
@@ -294,32 +294,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
|
expr.evaluate(legacyMode?{msg:parsedData}:parsedData, null, (err, result) => {
|
||||||
if (usesContext) {
|
if (err) {
|
||||||
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
|
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
|
||||||
return;
|
} else {
|
||||||
}
|
if (usesContext) {
|
||||||
if (usesEnv) {
|
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
|
||||||
testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (usesEnv) {
|
||||||
if (usesMoment) {
|
testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1);
|
||||||
testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (usesMoment) {
|
||||||
if (usesClone) {
|
testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1);
|
||||||
testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (usesClone) {
|
||||||
|
testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1);
|
||||||
var formattedResult;
|
return;
|
||||||
if (result !== undefined) {
|
}
|
||||||
formattedResult = JSON.stringify(result,null,4);
|
|
||||||
} else {
|
var formattedResult;
|
||||||
formattedResult = RED._("expressionEditor.noMatch");
|
if (result !== undefined) {
|
||||||
}
|
formattedResult = JSON.stringify(result,null,4);
|
||||||
testResultEditor.setValue(formattedResult,-1);
|
} else {
|
||||||
} catch(err) {
|
formattedResult = RED._("expressionEditor.noMatch");
|
||||||
|
}
|
||||||
|
testResultEditor.setValue(formattedResult,-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
|
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function file2Text(file,cb) {
|
||||||
|
file.arrayBuffer().then(d => {
|
||||||
|
cb( new TextDecoder().decode(d) )
|
||||||
|
}).catch(ex => { cb(`error: ${ex}`) })
|
||||||
|
}
|
||||||
|
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var currentEditor = null;
|
var currentEditor = null;
|
||||||
/**
|
/**
|
||||||
@@ -35,16 +41,22 @@
|
|||||||
function initImageDrag(elem, editor) {
|
function initImageDrag(elem, editor) {
|
||||||
$(elem).on("dragenter", function (ev) {
|
$(elem).on("dragenter", function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
$("#red-ui-image-drop-target").css({display:'table'}).focus();
|
$("#red-ui-drop-target-markdown-editor").css({
|
||||||
|
display:'table',
|
||||||
|
top: $(elem).offset().top,
|
||||||
|
left: $(elem).offset().left,
|
||||||
|
width: $(elem).width(),
|
||||||
|
height: $(elem).height()
|
||||||
|
}).focus();
|
||||||
currentEditor = editor;
|
currentEditor = editor;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
$("#red-ui-image-drop-target").on("dragover", function (ev) {
|
$("#red-ui-drop-target-markdown-editor").on("dragover", function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}).on("dragleave", function (ev) {
|
}).on("dragleave", function (ev) {
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
}).on("drop", function (ev) {
|
}).on("drop", function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if ($.inArray("Files",ev.originalEvent.dataTransfer.types) != -1) {
|
if ($.inArray("Files",ev.originalEvent.dataTransfer.types) != -1) {
|
||||||
@@ -52,20 +64,43 @@
|
|||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
var file = files[0];
|
var file = files[0];
|
||||||
var name = file.name.toLowerCase();
|
var name = file.name.toLowerCase();
|
||||||
|
var fileType = file.type.toLowerCase();
|
||||||
|
|
||||||
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
|
||||||
file2base64Image(file, function (image) {
|
file2base64Image(file, function (image) {
|
||||||
var session = currentEditor.getSession();
|
var session = currentEditor.getSession();
|
||||||
var img = `<img src="${image}"/>\n`;
|
var img = `<img src="${image}"/>\n`;
|
||||||
var pos = session.getCursorPosition();
|
var pos = session.getCursorPosition();
|
||||||
session.insert(pos, img);
|
session.insert(pos, img);
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( fileType.startsWith("text/") ) {
|
||||||
|
file2Text(file, function (txt) {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if ($.inArray("text/plain", ev.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
let item = Object.values(ev.originalEvent.dataTransfer.items).filter(d => d.type == "text/plain")[0]
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.getAsString(txt => {
|
||||||
|
var session = currentEditor.getSession();
|
||||||
|
var pos = session.getCursorPosition();
|
||||||
|
session.insert(pos, txt);
|
||||||
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$("#red-ui-image-drop-target").hide();
|
$("#red-ui-drop-target-markdown-editor").hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +204,7 @@
|
|||||||
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
|
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
|
||||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||||
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
|
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
|
||||||
mermaid.init();
|
RED.editor.mermaid.render()
|
||||||
},200);
|
},200);
|
||||||
})
|
})
|
||||||
if (options.header) {
|
if (options.header) {
|
||||||
@@ -178,7 +213,7 @@
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||||
mermaid.init();
|
RED.editor.mermaid.render()
|
||||||
}
|
}
|
||||||
panels = RED.panels.create({
|
panels = RED.panels.create({
|
||||||
id:"red-ui-editor-type-markdown-panels",
|
id:"red-ui-editor-type-markdown-panels",
|
||||||
|
|||||||
67
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
67
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
RED.editor.mermaid = (function () {
|
||||||
|
let initializing = false
|
||||||
|
let loaded = false
|
||||||
|
let pendingEvals = []
|
||||||
|
let diagramIds = 0
|
||||||
|
|
||||||
|
function render(selector = '.mermaid') {
|
||||||
|
// $(selector).hide()
|
||||||
|
if (!loaded) {
|
||||||
|
pendingEvals.push(selector)
|
||||||
|
|
||||||
|
if (!initializing) {
|
||||||
|
initializing = true
|
||||||
|
// Find the cache-buster:
|
||||||
|
let cacheBuster
|
||||||
|
$('script').each(function (i, el) {
|
||||||
|
if (!cacheBuster) {
|
||||||
|
const src = el.getAttribute('src')
|
||||||
|
const m = /\?v=(.+)$/.exec(src)
|
||||||
|
if (m) {
|
||||||
|
cacheBuster = m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$.ajax({
|
||||||
|
url: `vendor/mermaid/mermaid.min.js?v=${cacheBuster}`,
|
||||||
|
dataType: "script",
|
||||||
|
cache: true,
|
||||||
|
success: function (data, stat, jqxhr) {
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
theme: RED.settings.get('mermaid', {}).theme
|
||||||
|
})
|
||||||
|
loaded = true
|
||||||
|
while(pendingEvals.length > 0) {
|
||||||
|
const pending = pendingEvals.shift()
|
||||||
|
render(pending)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nodes = document.querySelectorAll(selector)
|
||||||
|
|
||||||
|
nodes.forEach(async node => {
|
||||||
|
if (!node.getAttribute('mermaid-processed')) {
|
||||||
|
const mermaidContent = node.innerText
|
||||||
|
node.setAttribute('mermaid-processed', true)
|
||||||
|
try {
|
||||||
|
const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
|
||||||
|
node.innerHTML = svg
|
||||||
|
} catch (err) {
|
||||||
|
$('<div>').css({
|
||||||
|
fontSize: '0.8em',
|
||||||
|
border: '1px solid var(--red-ui-border-color-error)',
|
||||||
|
padding: '5px',
|
||||||
|
marginBottom: '10px',
|
||||||
|
}).text(err.toString()).prependTo(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
render: render,
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('<div class="form-row">'+
|
$('<div class="form-row">'+
|
||||||
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
|
'<label for="node-input-show-label" data-i18n="editor.label"></label>'+
|
||||||
'<span style="margin-right: 2px;"/>'+
|
'<span style="margin-right: 2px;"/>'+
|
||||||
'<input type="checkbox" id="node-input-show-label"/>'+
|
'<input type="checkbox" id="node-input-show-label"/>'+
|
||||||
'</div>').appendTo(dialogForm);
|
'</div>').appendTo(dialogForm);
|
||||||
|
|||||||
@@ -20,10 +20,31 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var old_env = node.env;
|
var old_env = node.env;
|
||||||
var new_env = [];
|
var new_env = [];
|
||||||
|
|
||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
|
// Get the list of environment variables from the node properties
|
||||||
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (old_env && old_env.length) {
|
||||||
|
old_env.forEach(function (prop) {
|
||||||
|
if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const stillInUse = new_env?.some((p) => p.type === "conf-type" && p.name === prop.name && p.value === prop.value);
|
||||||
|
if (!stillInUse) {
|
||||||
|
// Remove the node from the config node users
|
||||||
|
// Only for empty value or modified
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) !== -1) {
|
||||||
|
configNode.users.splice(configNode.users.indexOf(node), 1);
|
||||||
|
RED.events.emit('nodes:change', configNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get the values from the Properties table tab
|
// Get the values from the Properties table tab
|
||||||
var items = this.list.editableList('items');
|
var items = this.list.editableList('items');
|
||||||
items.each(function (i,el) {
|
items.each(function (i,el) {
|
||||||
@@ -41,7 +62,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (new_env && new_env.length > 0) {
|
if (new_env && new_env.length > 0) {
|
||||||
new_env.forEach(function(prop) {
|
new_env.forEach(function(prop) {
|
||||||
if (prop.type === "cred") {
|
if (prop.type === "cred") {
|
||||||
@@ -52,6 +72,15 @@
|
|||||||
editState.changed = true;
|
editState.changed = true;
|
||||||
}
|
}
|
||||||
delete prop.value;
|
delete prop.value;
|
||||||
|
} else if (prop.type === "conf-type" && prop.value) {
|
||||||
|
const configNode = RED.nodes.node(prop.value);
|
||||||
|
if (configNode) {
|
||||||
|
if (configNode.users.indexOf(node) === -1) {
|
||||||
|
// Add the node to the config node users
|
||||||
|
configNode.users.push(node);
|
||||||
|
RED.events.emit('nodes:change', configNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
apply: function(editState) {
|
apply: function(editState) {
|
||||||
var newValue;
|
var newValue;
|
||||||
var d;
|
var d;
|
||||||
|
// If the node is a subflow, the node's properties (exepts name) are saved by `envProperties`
|
||||||
if (node._def.defaults) {
|
if (node._def.defaults) {
|
||||||
for (d in node._def.defaults) {
|
for (d in node._def.defaults) {
|
||||||
if (node._def.defaults.hasOwnProperty(d)) {
|
if (node._def.defaults.hasOwnProperty(d)) {
|
||||||
@@ -131,9 +132,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node._def.credentials) {
|
if (node._def.credentials) {
|
||||||
var credDefinition = node._def.credentials;
|
const credDefinition = node._def.credentials;
|
||||||
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
|
const credChanges = updateNodeCredentials(node, credDefinition, this.inputClass);
|
||||||
editState.changed = editState.changed || credsChanged;
|
|
||||||
|
if (Object.keys(credChanges).length) {
|
||||||
|
editState.changed = true;
|
||||||
|
editState.changes.credentials = {
|
||||||
|
...(editState.changes.credentials || {}),
|
||||||
|
...credChanges
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,10 +169,11 @@
|
|||||||
* @param node - the node containing the credentials
|
* @param node - the node containing the credentials
|
||||||
* @param credDefinition - definition of the credentials
|
* @param credDefinition - definition of the credentials
|
||||||
* @param prefix - prefix of the input fields
|
* @param prefix - prefix of the input fields
|
||||||
* @return {boolean} whether anything has changed
|
* @return {object} an object containing the modified properties
|
||||||
*/
|
*/
|
||||||
function updateNodeCredentials(node, credDefinition, prefix) {
|
function updateNodeCredentials(node, credDefinition, prefix) {
|
||||||
var changed = false;
|
const changes = {};
|
||||||
|
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
node.credentials = {_:{}};
|
node.credentials = {_:{}};
|
||||||
} else if (!node.credentials._) {
|
} else if (!node.credentials._) {
|
||||||
@@ -177,22 +186,33 @@
|
|||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
var value = input.val();
|
var value = input.val();
|
||||||
if (credDefinition[cred].type == 'password') {
|
if (credDefinition[cred].type == 'password') {
|
||||||
node.credentials['has_' + cred] = (value !== "");
|
if (value === '__PWRD__') {
|
||||||
if (value == '__PWRD__') {
|
// A cred value exists - no changes
|
||||||
continue;
|
} else if (value === '' && node.credentials['has_' + cred] === false) {
|
||||||
|
// Empty cred value exists - no changes
|
||||||
|
} else if (value === node.credentials[cred]) {
|
||||||
|
// A cred value exists locally in the editor - no changes
|
||||||
|
// Like the user sets a value, saves the config,
|
||||||
|
// reopens the config and save the config again
|
||||||
|
} else {
|
||||||
|
changes['has_' + cred] = node.credentials['has_' + cred];
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
|
|
||||||
}
|
node.credentials['has_' + cred] = (value !== '');
|
||||||
node.credentials[cred] = value;
|
} else {
|
||||||
if (value != node.credentials._[cred]) {
|
// Since these creds are loaded by the editor,
|
||||||
changed = true;
|
// values can be directly compared
|
||||||
|
if (value !== node.credentials[cred]) {
|
||||||
|
changes[cred] = node.credentials[cred];
|
||||||
|
node.credentials[cred] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
|
||||||
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ RED.envVar = (function() {
|
|||||||
var new_env = [];
|
var new_env = [];
|
||||||
var items = list.editableList('items');
|
var items = list.editableList('items');
|
||||||
var credentials = gconf ? gconf.credentials : null;
|
var credentials = gconf ? gconf.credentials : null;
|
||||||
|
if (!gconf && list.editableList('length') === 0) {
|
||||||
|
// No existing global-config node and nothing in the list,
|
||||||
|
// so no need to do anything more
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
credentials = {
|
credentials = {
|
||||||
_ : {},
|
_ : {},
|
||||||
@@ -67,7 +71,7 @@ RED.envVar = (function() {
|
|||||||
};
|
};
|
||||||
if (item.name.trim() !== "") {
|
if (item.name.trim() !== "") {
|
||||||
new_env.push(item);
|
new_env.push(item);
|
||||||
if ((item.type === "cred") && (item.value !== "__PWRD__")) {
|
if (item.type === "cred") {
|
||||||
credentials.map[item.name] = item.value;
|
credentials.map[item.name] = item.value;
|
||||||
credentials.map["has_"+item.name] = (item.value !== "");
|
credentials.map["has_"+item.name] = (item.value !== "");
|
||||||
item.value = "__PWRD__";
|
item.value = "__PWRD__";
|
||||||
@@ -78,6 +82,12 @@ RED.envVar = (function() {
|
|||||||
if (gconf === null) {
|
if (gconf === null) {
|
||||||
gconf = getGlobalConf(true);
|
gconf = getGlobalConf(true);
|
||||||
}
|
}
|
||||||
|
if (!gconf.credentials) {
|
||||||
|
gconf.credentials = {
|
||||||
|
_ : {},
|
||||||
|
map: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
|
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
|
||||||
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
|
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
|
||||||
gconf.env = new_env;
|
gconf.env = new_env;
|
||||||
@@ -143,10 +153,6 @@ RED.envVar = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init(done) {
|
function init(done) {
|
||||||
if (!RED.user.hasPermission("settings.write")) {
|
|
||||||
RED.notify(RED._("user.errors.settings"),"error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RED.userSettings.add({
|
RED.userSettings.add({
|
||||||
id:'envvar',
|
id:'envvar',
|
||||||
title: RED._("env-var.environment"),
|
title: RED._("env-var.environment"),
|
||||||
@@ -166,6 +172,10 @@ RED.envVar = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
RED.actions.add("core:show-global-env", function() {
|
||||||
|
RED.userSettings.show('envvar');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ RED.group = (function() {
|
|||||||
var selection = RED.view.selection();
|
var selection = RED.view.selection();
|
||||||
if (selection.nodes) {
|
if (selection.nodes) {
|
||||||
var newSelection = [];
|
var newSelection = [];
|
||||||
groups = selection.nodes.filter(function(n) { return n.type === "group" });
|
let groups = selection.nodes.filter(function(n) { return n.type === "group" });
|
||||||
|
|
||||||
var historyEvent = {
|
var historyEvent = {
|
||||||
t:"ungroup",
|
t:"ungroup",
|
||||||
@@ -401,7 +401,7 @@ RED.group = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var existingGroup;
|
var existingGroup;
|
||||||
|
var mergedEnv = {}
|
||||||
// Second pass, ungroup any groups in the selection and add their contents
|
// Second pass, ungroup any groups in the selection and add their contents
|
||||||
// to the selection
|
// to the selection
|
||||||
for (var i=0; i<selection.nodes.length; i++) {
|
for (var i=0; i<selection.nodes.length; i++) {
|
||||||
@@ -410,6 +410,11 @@ RED.group = (function() {
|
|||||||
if (!existingGroup) {
|
if (!existingGroup) {
|
||||||
existingGroup = n;
|
existingGroup = n;
|
||||||
}
|
}
|
||||||
|
if (n.env && n.env.length > 0) {
|
||||||
|
n.env.forEach(env => {
|
||||||
|
mergedEnv[env.name] = env
|
||||||
|
})
|
||||||
|
}
|
||||||
ungroupHistoryEvent.groups.push(n);
|
ungroupHistoryEvent.groups.push(n);
|
||||||
nodes = nodes.concat(ungroup(n));
|
nodes = nodes.concat(ungroup(n));
|
||||||
} else {
|
} else {
|
||||||
@@ -427,6 +432,7 @@ RED.group = (function() {
|
|||||||
group.style = existingGroup.style;
|
group.style = existingGroup.style;
|
||||||
group.name = existingGroup.name;
|
group.name = existingGroup.name;
|
||||||
}
|
}
|
||||||
|
group.env = Object.values(mergedEnv)
|
||||||
RED.view.select({nodes:[group]})
|
RED.view.select({nodes:[group]})
|
||||||
}
|
}
|
||||||
historyEvent.events.push({
|
historyEvent.events.push({
|
||||||
@@ -473,9 +479,17 @@ RED.group = (function() {
|
|||||||
if (nodes.length === 0) {
|
if (nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
|
const existingGroup = nodes[0].g
|
||||||
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
return;
|
const n = nodes[i]
|
||||||
|
if (n.type === 'subflow') {
|
||||||
|
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n.g !== existingGroup) {
|
||||||
|
console.warn("Cannot add nooes with different z properties")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// nodes is an array
|
// nodes is an array
|
||||||
// each node must be on the same tab (z)
|
// each node must be on the same tab (z)
|
||||||
@@ -488,12 +502,17 @@ RED.group = (function() {
|
|||||||
y: Number.POSITIVE_INFINITY,
|
y: Number.POSITIVE_INFINITY,
|
||||||
w: 0,
|
w: 0,
|
||||||
h: 0,
|
h: 0,
|
||||||
_def: RED.group.def
|
_def: RED.group.def,
|
||||||
|
changed: true
|
||||||
}
|
}
|
||||||
|
|
||||||
group.z = nodes[0].z;
|
group.z = nodes[0].z;
|
||||||
group = RED.nodes.addGroup(group);
|
group = RED.nodes.addGroup(group);
|
||||||
|
|
||||||
|
if (existingGroup) {
|
||||||
|
addToGroup(RED.nodes.group(existingGroup), group)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
addToGroup(group,nodes);
|
addToGroup(group,nodes);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@@ -517,7 +536,7 @@ RED.group = (function() {
|
|||||||
if (!z) {
|
if (!z) {
|
||||||
z = n.z;
|
z = n.z;
|
||||||
} else if (z !== n.z) {
|
} else if (z !== n.z) {
|
||||||
throw new Error("Cannot add nooes with different z properties")
|
throw new Error("Cannot add nodes with different z properties")
|
||||||
}
|
}
|
||||||
if (n.g) {
|
if (n.g) {
|
||||||
// This is already in a group.
|
// This is already in a group.
|
||||||
@@ -534,14 +553,10 @@ RED.group = (function() {
|
|||||||
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
|
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The nodes are already in a group. The assumption is they should be
|
// The nodes are already in a group - so we need to remove them first
|
||||||
// wrapped in the newly provided group, and that group added to in their
|
|
||||||
// place to the existing containing group.
|
|
||||||
if (g) {
|
if (g) {
|
||||||
g = RED.nodes.group(g);
|
g = RED.nodes.group(g);
|
||||||
g.nodes.push(group);
|
|
||||||
g.dirty = true;
|
g.dirty = true;
|
||||||
group.g = g.id;
|
|
||||||
}
|
}
|
||||||
// Second pass - add them to the group
|
// Second pass - add them to the group
|
||||||
for (i=0;i<nodes.length;i++) {
|
for (i=0;i<nodes.length;i++) {
|
||||||
@@ -593,7 +608,7 @@ RED.group = (function() {
|
|||||||
n.dirty = true;
|
n.dirty = true;
|
||||||
var index = group.nodes.indexOf(n);
|
var index = group.nodes.indexOf(n);
|
||||||
group.nodes.splice(index,1);
|
group.nodes.splice(index,1);
|
||||||
if (reparent && group.g) {
|
if (reparent && parentGroup) {
|
||||||
n.g = group.g
|
n.g = group.g
|
||||||
parentGroup.nodes.push(n);
|
parentGroup.nodes.push(n);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -249,7 +249,10 @@ RED.keyboard = (function() {
|
|||||||
// One exception is shortcuts that include both Cmd and Ctrl. We don't
|
// 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
|
// support them - but we need to make sure we don't block browser-specific
|
||||||
// shortcuts (such as Cmd-Ctrl-F for fullscreen).
|
// shortcuts (such as Cmd-Ctrl-F for fullscreen).
|
||||||
if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) {
|
if (evt.ctrlKey && evt.metaKey) {
|
||||||
|
return null; // dont handle both cmd+ctrl - let browser handle this
|
||||||
|
}
|
||||||
|
if (evt.ctrlKey || evt.metaKey) {
|
||||||
slot = slot.ctrl;
|
slot = slot.ctrl;
|
||||||
}
|
}
|
||||||
if (slot && evt.shiftKey) {
|
if (slot && evt.shiftKey) {
|
||||||
@@ -491,7 +494,11 @@ RED.keyboard = (function() {
|
|||||||
okButton.attr("disabled",!valid);
|
okButton.attr("disabled",!valid);
|
||||||
});
|
});
|
||||||
|
|
||||||
var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
|
var scopeSelect = $('<select>'+
|
||||||
|
'<option value="*" data-i18n="keyboard.global"></option>'+
|
||||||
|
'<option value="red-ui-workspace" data-i18n="keyboard.workspace"></option>'+
|
||||||
|
'<option value="red-ui-editor-stack" data-i18n="keyboard.editor"></option>'+
|
||||||
|
'</select>').appendTo(scope);
|
||||||
scopeSelect.i18n();
|
scopeSelect.i18n();
|
||||||
if (object.scope === "workspace") {
|
if (object.scope === "workspace") {
|
||||||
object.scope = "red-ui-workspace";
|
object.scope = "red-ui-workspace";
|
||||||
|
|||||||
@@ -245,10 +245,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@@ -303,10 +308,15 @@ RED.library = (function() {
|
|||||||
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
if (lib.types && lib.types.indexOf(options.url) === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let icon = 'fa fa-hdd-o';
|
||||||
|
if (lib.icon) {
|
||||||
|
const fullIcon = RED.utils.separateIconPath(lib.icon);
|
||||||
|
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
|
||||||
|
}
|
||||||
listing.push({
|
listing.push({
|
||||||
library: lib.id,
|
library: lib.id,
|
||||||
type: options.url,
|
type: options.url,
|
||||||
icon: lib.icon || 'fa fa-hdd-o',
|
icon,
|
||||||
label: RED._(lib.label||lib.id),
|
label: RED._(lib.label||lib.id),
|
||||||
path: "",
|
path: "",
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@@ -839,10 +849,10 @@ RED.library = (function() {
|
|||||||
if (file && file.label && !file.children) {
|
if (file && file.label && !file.children) {
|
||||||
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
$.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
|
||||||
//TODO: nls + sanitize
|
//TODO: nls + sanitize
|
||||||
var propRow = $('<tr class="red-ui-help-info-row"><td>Type</td><td></td></tr>').appendTo(table);
|
var propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.type")+'</td><td></td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(activeLibrary.type);
|
$(propRow.children()[1]).text(activeLibrary.type);
|
||||||
if (file.props.hasOwnProperty('name')) {
|
if (file.props.hasOwnProperty('name')) {
|
||||||
propRow = $('<tr class="red-ui-help-info-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.name")+'</td><td>'+file.props.name+'</td></tr>').appendTo(table);
|
||||||
$(propRow.children()[1]).text(file.props.name);
|
$(propRow.children()[1]).text(file.props.name);
|
||||||
}
|
}
|
||||||
for (var p in file.props) {
|
for (var p in file.props) {
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
// Mermaid diagram stub library for on-demand dynamic loading
|
|
||||||
// Will be overwritten after script loading by $.getScript
|
|
||||||
var mermaid = (function () {
|
|
||||||
var enabled /* = undefined */;
|
|
||||||
|
|
||||||
var initializing = false;
|
|
||||||
var initCalled = false;
|
|
||||||
|
|
||||||
function initialize(opt) {
|
|
||||||
if (enabled === undefined) {
|
|
||||||
if (RED.settings.markdownEditor &&
|
|
||||||
RED.settings.markdownEditor.mermaid) {
|
|
||||||
enabled = RED.settings.markdownEditor.mermaid.enabled;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (enabled) {
|
|
||||||
initializing = true;
|
|
||||||
$.getScript("vendor/mermaid/mermaid.min.js",
|
|
||||||
function (data, stat, jqxhr) {
|
|
||||||
$(".mermaid").show();
|
|
||||||
// invoke loaded mermaid API
|
|
||||||
initializing = false;
|
|
||||||
mermaid.initialize(opt);
|
|
||||||
if (initCalled) {
|
|
||||||
mermaid.init();
|
|
||||||
initCalled = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
if (initializing) {
|
|
||||||
$(".mermaid").hide();
|
|
||||||
initCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
initialize: initialize,
|
|
||||||
init: init,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -221,12 +221,12 @@ RED.notifications = (function() {
|
|||||||
if (newType) {
|
if (newType) {
|
||||||
n.className = "red-ui-notification red-ui-notification-"+newType;
|
n.className = "red-ui-notification red-ui-notification-"+newType;
|
||||||
}
|
}
|
||||||
|
newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
|
||||||
if (!fixed || newOptions.fixed === false) {
|
if (!fixed || newOptions.fixed === false) {
|
||||||
newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
|
newTimeout = newTimeout || 5000
|
||||||
}
|
}
|
||||||
if (newOptions.buttons) {
|
if (newOptions.buttons) {
|
||||||
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
|
var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
|
||||||
newOptions.buttons.forEach(function(buttonDef) {
|
newOptions.buttons.forEach(function(buttonDef) {
|
||||||
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
|
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
|
||||||
if (buttonDef.id) {
|
if (buttonDef.id) {
|
||||||
@@ -272,6 +272,15 @@ RED.notifications = (function() {
|
|||||||
};
|
};
|
||||||
})());
|
})());
|
||||||
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
n.timeoutid = window.setTimeout(n.close,timeout||5000);
|
||||||
|
} else if (timeout) {
|
||||||
|
$(n).on("click.red-ui-notification-close", (function() {
|
||||||
|
var nn = n;
|
||||||
|
return function() {
|
||||||
|
nn.hideNotification();
|
||||||
|
window.clearTimeout(nn.timeoutid);
|
||||||
|
};
|
||||||
|
})());
|
||||||
|
n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
|
||||||
}
|
}
|
||||||
currentNotifications.push(n);
|
currentNotifications.push(n);
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
|
|||||||
@@ -16,15 +16,17 @@
|
|||||||
RED.palette.editor = (function() {
|
RED.palette.editor = (function() {
|
||||||
|
|
||||||
var disabled = false;
|
var disabled = false;
|
||||||
|
let catalogues = []
|
||||||
|
const loadedCatalogs = []
|
||||||
var editorTabs;
|
var editorTabs;
|
||||||
var filterInput;
|
let filterInput;
|
||||||
var searchInput;
|
let searchInput;
|
||||||
var nodeList;
|
let nodeList;
|
||||||
var packageList;
|
let packageList;
|
||||||
var loadedList = [];
|
let fullList = []
|
||||||
var filteredList = [];
|
let loadedList = [];
|
||||||
var loadedIndex = {};
|
let filteredList = [];
|
||||||
|
let loadedIndex = {};
|
||||||
|
|
||||||
var typesInUse = {};
|
var typesInUse = {};
|
||||||
var nodeEntries = {};
|
var nodeEntries = {};
|
||||||
@@ -131,7 +133,7 @@ RED.palette.editor = (function() {
|
|||||||
}).done(function(data,textStatus,xhr) {
|
}).done(function(data,textStatus,xhr) {
|
||||||
callback();
|
callback();
|
||||||
}).fail(function(xhr,textStatus,err) {
|
}).fail(function(xhr,textStatus,err) {
|
||||||
callback(xhr);
|
callback(xhr,textStatus,err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function removeNodeModule(id,callback) {
|
function removeNodeModule(id,callback) {
|
||||||
@@ -162,7 +164,6 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getContrastingBorder(rgbColor){
|
function getContrastingBorder(rgbColor){
|
||||||
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
|
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
|
||||||
if (parts) {
|
if (parts) {
|
||||||
@@ -247,86 +248,106 @@ RED.palette.editor = (function() {
|
|||||||
var moduleInfo = nodeEntries[module].info;
|
var moduleInfo = nodeEntries[module].info;
|
||||||
var nodeEntry = nodeEntries[module].elements;
|
var nodeEntry = nodeEntries[module].elements;
|
||||||
if (nodeEntry) {
|
if (nodeEntry) {
|
||||||
var activeTypeCount = 0;
|
if (moduleInfo.plugin) {
|
||||||
var typeCount = 0;
|
nodeEntry.enableButton.hide();
|
||||||
var errorCount = 0;
|
nodeEntry.removeButton.show();
|
||||||
nodeEntry.errorList.empty();
|
|
||||||
nodeEntries[module].totalUseCount = 0;
|
|
||||||
nodeEntries[module].setUseCount = {};
|
|
||||||
|
|
||||||
for (var setName in moduleInfo.sets) {
|
let pluginCount = 0;
|
||||||
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
for (let setName in moduleInfo.sets) {
|
||||||
var inUseCount = 0;
|
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
||||||
var set = moduleInfo.sets[setName];
|
let set = moduleInfo.sets[setName];
|
||||||
var setElements = nodeEntry.sets[setName];
|
if (set.plugins) {
|
||||||
if (set.err) {
|
pluginCount += set.plugins.length;
|
||||||
errorCount++;
|
|
||||||
var errMessage = set.err;
|
|
||||||
if (set.err.message) {
|
|
||||||
errMessage = set.err.message;
|
|
||||||
} else if (set.err.code) {
|
|
||||||
errMessage = set.err.code;
|
|
||||||
}
|
}
|
||||||
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
|
|
||||||
}
|
}
|
||||||
if (set.enabled) {
|
}
|
||||||
activeTypeCount += set.types.length;
|
|
||||||
}
|
nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
|
||||||
typeCount += set.types.length;
|
|
||||||
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
|
} else {
|
||||||
var t = moduleInfo.sets[setName].types[i];
|
var activeTypeCount = 0;
|
||||||
inUseCount += (typesInUse[t]||0);
|
var typeCount = 0;
|
||||||
var swatch = setElements.swatches[t];
|
var errorCount = 0;
|
||||||
|
nodeEntry.errorList.empty();
|
||||||
|
nodeEntries[module].totalUseCount = 0;
|
||||||
|
nodeEntries[module].setUseCount = {};
|
||||||
|
|
||||||
|
for (var setName in moduleInfo.sets) {
|
||||||
|
if (moduleInfo.sets.hasOwnProperty(setName)) {
|
||||||
|
var inUseCount = 0;
|
||||||
|
const set = moduleInfo.sets[setName];
|
||||||
|
const setElements = nodeEntry.sets[setName]
|
||||||
|
|
||||||
|
if (set.err) {
|
||||||
|
errorCount++;
|
||||||
|
var errMessage = set.err;
|
||||||
|
if (set.err.message) {
|
||||||
|
errMessage = set.err.message;
|
||||||
|
} else if (set.err.code) {
|
||||||
|
errMessage = set.err.code;
|
||||||
|
}
|
||||||
|
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
|
||||||
|
}
|
||||||
if (set.enabled) {
|
if (set.enabled) {
|
||||||
var def = RED.nodes.getType(t);
|
activeTypeCount += set.types.length;
|
||||||
if (def && def.color) {
|
}
|
||||||
swatch.css({background:RED.utils.getNodeColor(t,def)});
|
typeCount += set.types.length;
|
||||||
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
|
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
|
||||||
|
var t = moduleInfo.sets[setName].types[i];
|
||||||
|
inUseCount += (typesInUse[t]||0);
|
||||||
|
if (setElements && set.enabled) {
|
||||||
|
var def = RED.nodes.getType(t);
|
||||||
|
if (def && def.color) {
|
||||||
|
setElements.swatches[t].css({background:RED.utils.getNodeColor(t,def)});
|
||||||
|
setElements.swatches[t].css({border: "1px solid "+getContrastingBorder(setElements.swatches[t].css('backgroundColor'))})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
nodeEntries[module].setUseCount[setName] = inUseCount;
|
||||||
nodeEntries[module].setUseCount[setName] = inUseCount;
|
nodeEntries[module].totalUseCount += inUseCount;
|
||||||
nodeEntries[module].totalUseCount += inUseCount;
|
|
||||||
|
|
||||||
if (inUseCount > 0) {
|
if (setElements) {
|
||||||
setElements.enableButton.text(RED._('palette.editor.inuse'));
|
if (inUseCount > 0) {
|
||||||
setElements.enableButton.addClass('disabled');
|
setElements.enableButton.text(RED._('palette.editor.inuse'));
|
||||||
} else {
|
setElements.enableButton.addClass('disabled');
|
||||||
setElements.enableButton.removeClass('disabled');
|
} else {
|
||||||
if (set.enabled) {
|
setElements.enableButton.removeClass('disabled');
|
||||||
setElements.enableButton.text(RED._('palette.editor.disable'));
|
if (set.enabled) {
|
||||||
} else {
|
setElements.enableButton.text(RED._('palette.editor.disable'));
|
||||||
setElements.enableButton.text(RED._('palette.editor.enable'));
|
} else {
|
||||||
|
setElements.enableButton.text(RED._('palette.editor.enable'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (errorCount === 0) {
|
if (errorCount === 0) {
|
||||||
nodeEntry.errorRow.hide()
|
nodeEntry.errorRow.hide()
|
||||||
} else {
|
|
||||||
nodeEntry.errorRow.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
|
|
||||||
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
|
|
||||||
|
|
||||||
if (nodeEntries[module].totalUseCount > 0) {
|
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
|
|
||||||
nodeEntry.enableButton.addClass('disabled');
|
|
||||||
nodeEntry.removeButton.hide();
|
|
||||||
} else {
|
|
||||||
nodeEntry.enableButton.removeClass('disabled');
|
|
||||||
if (moduleInfo.local) {
|
|
||||||
nodeEntry.removeButton.css('display', 'inline-block');
|
|
||||||
}
|
|
||||||
if (activeTypeCount === 0) {
|
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
|
|
||||||
} else {
|
} else {
|
||||||
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
|
nodeEntry.errorRow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
|
||||||
|
nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
|
||||||
|
|
||||||
|
if (nodeEntries[module].totalUseCount > 0) {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
|
||||||
|
nodeEntry.enableButton.addClass('disabled');
|
||||||
|
nodeEntry.removeButton.hide();
|
||||||
|
} else {
|
||||||
|
nodeEntry.enableButton.removeClass('disabled');
|
||||||
|
if (moduleInfo.local) {
|
||||||
|
nodeEntry.removeButton.css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
if (activeTypeCount === 0) {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
|
||||||
|
} else {
|
||||||
|
nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
|
||||||
|
}
|
||||||
|
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
|
||||||
}
|
}
|
||||||
nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (moduleInfo.pending_version) {
|
if (moduleInfo.pending_version) {
|
||||||
@@ -369,10 +390,10 @@ RED.palette.editor = (function() {
|
|||||||
var activeSort = sortModulesRelevance;
|
var activeSort = sortModulesRelevance;
|
||||||
|
|
||||||
function handleCatalogResponse(err,catalog,index,v) {
|
function handleCatalogResponse(err,catalog,index,v) {
|
||||||
|
const url = catalog.url
|
||||||
catalogueLoadStatus.push(err||v);
|
catalogueLoadStatus.push(err||v);
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (v.modules) {
|
if (v.modules) {
|
||||||
var a = false;
|
|
||||||
v.modules = v.modules.filter(function(m) {
|
v.modules = v.modules.filter(function(m) {
|
||||||
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
|
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
|
||||||
loadedIndex[m.id] = m;
|
loadedIndex[m.id] = m;
|
||||||
@@ -389,13 +410,14 @@ RED.palette.editor = (function() {
|
|||||||
m.timestamp = 0;
|
m.timestamp = 0;
|
||||||
}
|
}
|
||||||
m.index = m.index.join(",").toLowerCase();
|
m.index = m.index.join(",").toLowerCase();
|
||||||
|
m.catalog = catalog;
|
||||||
|
m.catalogIndex = index;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
loadedList = loadedList.concat(v.modules);
|
loadedList = loadedList.concat(v.modules);
|
||||||
}
|
}
|
||||||
searchInput.searchBox('count',loadedList.length);
|
|
||||||
} else {
|
} else {
|
||||||
catalogueLoadErrors = true;
|
catalogueLoadErrors = true;
|
||||||
}
|
}
|
||||||
@@ -404,7 +426,7 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
if (catalogueLoadStatus.length === catalogueCount) {
|
if (catalogueLoadStatus.length === catalogueCount) {
|
||||||
if (catalogueLoadErrors) {
|
if (catalogueLoadErrors) {
|
||||||
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000);
|
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
|
||||||
}
|
}
|
||||||
var delta = 250-(Date.now() - catalogueLoadStart);
|
var delta = 250-(Date.now() - catalogueLoadStart);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
@@ -416,12 +438,13 @@ RED.palette.editor = (function() {
|
|||||||
|
|
||||||
function initInstallTab() {
|
function initInstallTab() {
|
||||||
if (loadedList.length === 0) {
|
if (loadedList.length === 0) {
|
||||||
|
fullList = [];
|
||||||
loadedList = [];
|
loadedList = [];
|
||||||
loadedIndex = {};
|
loadedIndex = {};
|
||||||
packageList.editableList('empty');
|
packageList.editableList('empty');
|
||||||
|
|
||||||
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
|
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
|
||||||
var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
|
|
||||||
catalogueLoadStatus = [];
|
catalogueLoadStatus = [];
|
||||||
catalogueLoadErrors = false;
|
catalogueLoadErrors = false;
|
||||||
catalogueCount = catalogues.length;
|
catalogueCount = catalogues.length;
|
||||||
@@ -431,27 +454,97 @@ RED.palette.editor = (function() {
|
|||||||
$("#red-ui-palette-module-install-shade").show();
|
$("#red-ui-palette-module-install-shade").show();
|
||||||
catalogueLoadStart = Date.now();
|
catalogueLoadStart = Date.now();
|
||||||
var handled = 0;
|
var handled = 0;
|
||||||
catalogues.forEach(function(catalog,index) {
|
loadedCatalogs.length = 0; // clear the loadedCatalogs array
|
||||||
$.getJSON(catalog, {_: new Date().getTime()},function(v) {
|
for (let index = 0; index < catalogues.length; index++) {
|
||||||
handleCatalogResponse(null,catalog,index,v);
|
const url = catalogues[index];
|
||||||
|
$.getJSON(url, {_: new Date().getTime()},function(v) {
|
||||||
|
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
|
||||||
|
handleCatalogResponse(null,{ url: url, name: v.name},index,v);
|
||||||
refreshNodeModuleList();
|
refreshNodeModuleList();
|
||||||
}).fail(function(jqxhr, textStatus, error) {
|
}).fail(function(jqxhr, textStatus, error) {
|
||||||
console.warn("Error loading catalog",catalog,":",error);
|
console.warn("Error loading catalog",url,":",error);
|
||||||
handleCatalogResponse(jqxhr,catalog,index);
|
handleCatalogResponse(jqxhr,url,index);
|
||||||
}).always(function() {
|
}).always(function() {
|
||||||
handled++;
|
handled++;
|
||||||
if (handled === catalogueCount) {
|
if (handled === catalogueCount) {
|
||||||
searchInput.searchBox('change');
|
//sort loadedCatalogs by e.index ascending
|
||||||
|
loadedCatalogs.sort((a, b) => a.index - b.index)
|
||||||
|
updateCatalogFilter(loadedCatalogs)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the catalog filter dropdown and updates local variables
|
||||||
|
* @param {[{url:String, name:String, updated_at:String, modules_count:Number}]} catalogEntries
|
||||||
|
*/
|
||||||
|
function updateCatalogFilter(catalogEntries, maxRetry = 3) {
|
||||||
|
// clean up existing filters
|
||||||
|
const catalogSelection = $('#red-catalogue-filter-select')
|
||||||
|
if (catalogSelection.length === 0) {
|
||||||
|
// sidebar not yet loaded (red-catalogue-filter-select is not in dom)
|
||||||
|
if (maxRetry > 0) {
|
||||||
|
// console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
|
||||||
|
// try again in 100ms
|
||||||
|
setTimeout(() => {
|
||||||
|
updateCatalogFilter(catalogEntries, maxRetry - 1)
|
||||||
|
}, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return; // give up
|
||||||
|
}
|
||||||
|
catalogSelection.off("change") // remove any existing event handlers
|
||||||
|
catalogSelection.attr('disabled', 'disabled')
|
||||||
|
catalogSelection.empty()
|
||||||
|
catalogSelection.append($('<option>', { value: "loading", text: RED._('palette.editor.loading'), disabled: true, selected: true }));
|
||||||
|
|
||||||
|
fullList = loadedList.slice()
|
||||||
|
catalogSelection.empty() // clear the select list
|
||||||
|
|
||||||
|
// loop through catalogTypes, and an option entry per catalog
|
||||||
|
for (let index = 0; index < catalogEntries.length; index++) {
|
||||||
|
const catalog = catalogEntries[index];
|
||||||
|
catalogSelection.append(`<option value="${catalog.name}">${catalog.name}</option>`)
|
||||||
|
}
|
||||||
|
// select the 1st option in the select list
|
||||||
|
catalogSelection.val(catalogSelection.find('option:first').val())
|
||||||
|
|
||||||
|
// if there is only 1 catalog, hide the select
|
||||||
|
if (catalogEntries.length > 1) {
|
||||||
|
catalogSelection.prepend(`<option value="all">${RED._('palette.editor.allCatalogs')}</option>`)
|
||||||
|
catalogSelection.val('all')
|
||||||
|
catalogSelection.removeAttr('disabled') // permit the user to select a catalog
|
||||||
|
}
|
||||||
|
// refresh the searchInput counter and trigger a change
|
||||||
|
filterByCatalog(catalogSelection.val())
|
||||||
|
searchInput.searchBox('change');
|
||||||
|
|
||||||
|
// hook up the change event handler
|
||||||
|
catalogSelection.on("change", function() {
|
||||||
|
const selectedCatalog = $(this).val();
|
||||||
|
filterByCatalog(selectedCatalog);
|
||||||
|
searchInput.searchBox('change');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByCatalog(selectedCatalog) {
|
||||||
|
if (loadedCatalogs.length <= 1 || selectedCatalog === "all") {
|
||||||
|
loadedList = fullList.slice();
|
||||||
|
} else {
|
||||||
|
loadedList = fullList.filter(function(m) {
|
||||||
|
return (m.catalog.name === selectedCatalog);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refreshFilteredItems();
|
||||||
|
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
|
||||||
|
}
|
||||||
|
|
||||||
function refreshFilteredItems() {
|
function refreshFilteredItems() {
|
||||||
packageList.editableList('empty');
|
packageList.editableList('empty');
|
||||||
var currentFilter = searchInput.searchBox('value').trim();
|
var currentFilter = searchInput.searchBox('value').trim();
|
||||||
if (currentFilter === ""){
|
if (currentFilter === "" && loadedList.length > 20){
|
||||||
packageList.editableList('addItem',{count:loadedList.length})
|
packageList.editableList('addItem',{count:loadedList.length})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -462,7 +555,6 @@ RED.palette.editor = (function() {
|
|||||||
if (filteredList.length === 0) {
|
if (filteredList.length === 0) {
|
||||||
packageList.editableList('addItem',{});
|
packageList.editableList('addItem',{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredList.length > 10) {
|
if (filteredList.length > 10) {
|
||||||
packageList.editableList('addItem',{start:10,more:filteredList.length-10})
|
packageList.editableList('addItem',{start:10,more:filteredList.length-10})
|
||||||
}
|
}
|
||||||
@@ -492,6 +584,7 @@ RED.palette.editor = (function() {
|
|||||||
var updateDenyList = [];
|
var updateDenyList = [];
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
|
||||||
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -605,6 +698,33 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
RED.events.on("registry:plugin-module-added", function(module) {
|
||||||
|
|
||||||
|
if (!nodeEntries.hasOwnProperty(module)) {
|
||||||
|
nodeEntries[module] = {info:RED.plugins.getModule(module)};
|
||||||
|
var index = [module];
|
||||||
|
for (var s in nodeEntries[module].info.sets) {
|
||||||
|
if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
|
||||||
|
index.push(s);
|
||||||
|
index = index.concat(nodeEntries[module].info.sets[s].types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodeEntries[module].index = index.join(",").toLowerCase();
|
||||||
|
nodeList.editableList('addItem', nodeEntries[module]);
|
||||||
|
} else {
|
||||||
|
_refreshNodeModule(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0;i<filteredList.length;i++) {
|
||||||
|
if (filteredList[i].info.id === module) {
|
||||||
|
var installButton = filteredList[i].elements.installButton;
|
||||||
|
installButton.addClass('disabled');
|
||||||
|
installButton.text(RED._('palette.editor.installed'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingsPane;
|
var settingsPane;
|
||||||
@@ -669,7 +789,8 @@ RED.palette.editor = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
nodeList = $('<ol>',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({
|
nodeList = $('<ol>',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({
|
||||||
|
class: "scrollable",
|
||||||
addButton: false,
|
addButton: false,
|
||||||
scrollOnAdd: false,
|
scrollOnAdd: false,
|
||||||
sort: function(A,B) {
|
sort: function(A,B) {
|
||||||
@@ -730,6 +851,7 @@ RED.palette.editor = (function() {
|
|||||||
errorRow: errorRow,
|
errorRow: errorRow,
|
||||||
errorList: errorList,
|
errorList: errorList,
|
||||||
setCount: setCount,
|
setCount: setCount,
|
||||||
|
setButton: setButton,
|
||||||
container: container,
|
container: container,
|
||||||
shade: shade,
|
shade: shade,
|
||||||
versionSpan: versionSpan,
|
versionSpan: versionSpan,
|
||||||
@@ -740,49 +862,88 @@ RED.palette.editor = (function() {
|
|||||||
if (container.hasClass('expanded')) {
|
if (container.hasClass('expanded')) {
|
||||||
container.removeClass('expanded');
|
container.removeClass('expanded');
|
||||||
contentRow.slideUp();
|
contentRow.slideUp();
|
||||||
|
setTimeout(() => {
|
||||||
|
contentRow.empty()
|
||||||
|
}, 200)
|
||||||
|
object.elements.sets = {}
|
||||||
} else {
|
} else {
|
||||||
container.addClass('expanded');
|
container.addClass('expanded');
|
||||||
|
populateSetList()
|
||||||
contentRow.slideDown();
|
contentRow.slideDown();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const populateSetList = function () {
|
||||||
var setList = Object.keys(entry.sets)
|
var setList = Object.keys(entry.sets)
|
||||||
setList.sort(function(A,B) {
|
setList.sort(function(A,B) {
|
||||||
return A.toLowerCase().localeCompare(B.toLowerCase());
|
return A.toLowerCase().localeCompare(B.toLowerCase());
|
||||||
});
|
});
|
||||||
setList.forEach(function(setName) {
|
setList.forEach(function(setName) {
|
||||||
var set = entry.sets[setName];
|
var set = entry.sets[setName];
|
||||||
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
|
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
|
||||||
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
|
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
|
||||||
var typeSwatches = {};
|
var typeSwatches = {};
|
||||||
set.types.forEach(function(t) {
|
let enableButton;
|
||||||
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
if (set.types) {
|
||||||
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
set.types.forEach(function(t) {
|
||||||
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
|
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
||||||
})
|
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
|
if (set.enabled) {
|
||||||
enableButton.on("click", function(evt) {
|
var def = RED.nodes.getType(t);
|
||||||
evt.preventDefault();
|
if (def && def.color) {
|
||||||
if (object.setUseCount[setName] === 0) {
|
typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)});
|
||||||
var currentSet = RED.nodes.registry.getNodeSet(set.id);
|
typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))})
|
||||||
shade.show();
|
|
||||||
var newState = !currentSet.enabled
|
|
||||||
changeNodeState(set.id,newState,shade,function(xhr){
|
|
||||||
if (xhr) {
|
|
||||||
if (xhr.responseJSON) {
|
|
||||||
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
|
||||||
}
|
})
|
||||||
})
|
enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
|
||||||
|
enableButton.on("click", function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (object.setUseCount[setName] === 0) {
|
||||||
|
var currentSet = RED.nodes.registry.getNodeSet(set.id);
|
||||||
|
shade.show();
|
||||||
|
var newState = !currentSet.enabled
|
||||||
|
changeNodeState(set.id,newState,shade,function(xhr){
|
||||||
|
if (xhr) {
|
||||||
|
if (xhr.responseJSON) {
|
||||||
|
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
object.elements.sets[set.name] = {
|
if (object.setUseCount[setName] > 0) {
|
||||||
setRow: setRow,
|
enableButton.text(RED._('palette.editor.inuse'));
|
||||||
enableButton: enableButton,
|
enableButton.addClass('disabled');
|
||||||
swatches: typeSwatches
|
} else {
|
||||||
};
|
enableButton.removeClass('disabled');
|
||||||
});
|
if (set.enabled) {
|
||||||
|
enableButton.text(RED._('palette.editor.disable'));
|
||||||
|
} else {
|
||||||
|
enableButton.text(RED._('palette.editor.enable'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
if (set.plugins) {
|
||||||
|
set.plugins.forEach(function(p) {
|
||||||
|
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
|
||||||
|
// typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
|
$('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
|
||||||
|
$('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
object.elements.sets[set.name] = {
|
||||||
|
setRow: setRow,
|
||||||
|
enableButton: enableButton,
|
||||||
|
swatches: typeSwatches
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
enableButton.on("click", function(evt) {
|
enableButton.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if (object.totalUseCount === 0) {
|
if (object.totalUseCount === 0) {
|
||||||
@@ -800,28 +961,27 @@ RED.palette.editor = (function() {
|
|||||||
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
|
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInstallTab(content) {
|
function createInstallTab(content) {
|
||||||
var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content);
|
const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content);
|
||||||
|
|
||||||
editorTabs.addTab({
|
editorTabs.addTab({
|
||||||
id: 'install',
|
id: 'install',
|
||||||
label: RED._('palette.editor.tab-install'),
|
label: RED._('palette.editor.tab-install'),
|
||||||
content: installTab
|
content: installTab
|
||||||
})
|
})
|
||||||
|
|
||||||
var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
|
const toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
|
||||||
|
|
||||||
var searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
|
const searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
|
||||||
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
|
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
|
||||||
.appendTo(searchDiv)
|
.appendTo(searchDiv)
|
||||||
.searchBox({
|
.searchBox({
|
||||||
delay: 300,
|
delay: 300,
|
||||||
change: function() {
|
change: function() {
|
||||||
var searchTerm = $(this).val().trim().toLowerCase();
|
var searchTerm = $(this).val().trim().toLowerCase();
|
||||||
if (searchTerm.length > 0) {
|
if (searchTerm.length > 0 || loadedList.length < 20) {
|
||||||
filteredList = loadedList.filter(function(m) {
|
filteredList = loadedList.filter(function(m) {
|
||||||
return (m.index.indexOf(searchTerm) > -1);
|
return (m.index.indexOf(searchTerm) > -1);
|
||||||
}).map(function(f) { return {info:f}});
|
}).map(function(f) { return {info:f}});
|
||||||
@@ -831,19 +991,26 @@ RED.palette.editor = (function() {
|
|||||||
searchInput.searchBox('count',loadedList.length);
|
searchInput.searchBox('count',loadedList.length);
|
||||||
packageList.editableList('empty');
|
packageList.editableList('empty');
|
||||||
packageList.editableList('addItem',{count:loadedList.length});
|
packageList.editableList('addItem',{count:loadedList.length});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar);
|
const catalogSelection = $('<select id="red-catalogue-filter-select">').appendTo(toolBar);
|
||||||
var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
|
catalogSelection.addClass('red-ui-palette-editor-catalogue-filter');
|
||||||
var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
|
|
||||||
var sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup);
|
const toolBarActions = $('<div>',{class:"red-ui-palette-editor-toolbar-actions"}).appendTo(toolBar);
|
||||||
var sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
|
|
||||||
|
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBarActions);
|
||||||
|
const sortGroup = $('<span class="button-group"></span>').appendTo(toolBarActions);
|
||||||
|
const sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
|
||||||
|
const sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-sort-alpha-asc"></i></a>').appendTo(sortGroup);
|
||||||
|
const sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-calendar"></i></a>').appendTo(sortGroup);
|
||||||
|
RED.popover.tooltip(sortRelevance,RED._("palette.editor.sortRelevance"));
|
||||||
|
RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ"));
|
||||||
|
RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent"));
|
||||||
|
|
||||||
|
|
||||||
var sortOpts = [
|
const sortOpts = [
|
||||||
{button: sortRelevance, func: sortModulesRelevance},
|
{button: sortRelevance, func: sortModulesRelevance},
|
||||||
{button: sortAZ, func: sortModulesAZ},
|
{button: sortAZ, func: sortModulesAZ},
|
||||||
{button: sortRecent, func: sortModulesRecent}
|
{button: sortRecent, func: sortModulesRecent}
|
||||||
@@ -861,7 +1028,7 @@ RED.palette.editor = (function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var refreshSpan = $('<span>').appendTo(toolBar);
|
var refreshSpan = $('<span>').appendTo(toolBarActions);
|
||||||
var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
|
var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
|
||||||
refreshButton.on("click", function(e) {
|
refreshButton.on("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -871,7 +1038,8 @@ RED.palette.editor = (function() {
|
|||||||
})
|
})
|
||||||
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
|
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
|
||||||
|
|
||||||
packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
|
packageList = $('<ol>').appendTo(installTab).editableList({
|
||||||
|
class: "scrollable",
|
||||||
addButton: false,
|
addButton: false,
|
||||||
scrollOnAdd: false,
|
scrollOnAdd: false,
|
||||||
addItem: function(container,i,object) {
|
addItem: function(container,i,object) {
|
||||||
@@ -906,6 +1074,9 @@ RED.palette.editor = (function() {
|
|||||||
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
||||||
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
|
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
|
||||||
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
|
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
|
||||||
|
if (loadedCatalogs.length > 1) {
|
||||||
|
$('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
|
||||||
|
}
|
||||||
|
|
||||||
var duplicateType = false;
|
var duplicateType = false;
|
||||||
if (entry.types && entry.types.length > 0) {
|
if (entry.types && entry.types.length > 0) {
|
||||||
@@ -952,9 +1123,10 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
|
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
|
||||||
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
|
var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions);
|
||||||
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
|
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
|
||||||
|
|
||||||
var uploadInput = uploadButton.find('input[type="file"]');
|
var uploadInput = uploadButton.find('input[type="file"]');
|
||||||
@@ -1141,7 +1313,55 @@ RED.palette.editor = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}); }
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// dedicated list management for plugins
|
||||||
|
if (entry.plugin) {
|
||||||
|
|
||||||
|
let e = nodeEntries[entry.name];
|
||||||
|
if (e) {
|
||||||
|
nodeList.editableList('removeItem', e);
|
||||||
|
delete nodeEntries[entry.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume that a plugin that implements onremove
|
||||||
|
// cleans the editor accordingly of its left-overs.
|
||||||
|
let found_onremove = true;
|
||||||
|
|
||||||
|
let keys = Object.keys(entry.sets);
|
||||||
|
keys.forEach((key) => {
|
||||||
|
let set = entry.sets[key];
|
||||||
|
for (let i=0; i<set.plugins?.length; i++) {
|
||||||
|
let plgn = RED.plugins.getPlugin(set.plugins[i].id);
|
||||||
|
if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
|
||||||
|
plgn.onremove();
|
||||||
|
} else {
|
||||||
|
if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
|
||||||
|
// if there's no 'onadd', there shouldn't be any left-overs
|
||||||
|
found_onremove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found_onremove) {
|
||||||
|
let removeNotify = RED.notify(RED._("palette.editor.confirm.removePlugin.body",{module:entry.name}),{
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
type: 'warning',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("palette.editor.confirm.button.understood"),
|
||||||
|
class:"primary",
|
||||||
|
click: function(e) {
|
||||||
|
removeNotify.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
notification.close();
|
notification.close();
|
||||||
@@ -1185,9 +1405,28 @@ RED.palette.editor = (function() {
|
|||||||
RED.actions.invoke("core:show-event-log");
|
RED.actions.invoke("core:show-event-log");
|
||||||
});
|
});
|
||||||
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
|
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
|
||||||
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
|
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
|
||||||
spinner.remove();
|
spinner.remove();
|
||||||
if (xhr) {
|
if (err && xhr.status === 504) {
|
||||||
|
var notification = RED.notify(RED._("palette.editor.errors.installTimeout"), {
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.close"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
text: RED._("eventLog.view"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
RED.actions.invoke("core:show-event-log");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
} else if (xhr) {
|
||||||
if (xhr.responseJSON) {
|
if (xhr.responseJSON) {
|
||||||
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
|
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ RED.palette = (function() {
|
|||||||
var categoryContainers = {};
|
var categoryContainers = {};
|
||||||
var sidebarControls;
|
var sidebarControls;
|
||||||
|
|
||||||
|
let paletteState = { filter: "", collapsed: [] };
|
||||||
|
|
||||||
|
let filterRefreshTimeout
|
||||||
|
|
||||||
function createCategory(originalCategory,rootCategory,category,ns) {
|
function createCategory(originalCategory,rootCategory,category,ns) {
|
||||||
if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
|
if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
|
||||||
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
|
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
|
||||||
@@ -60,20 +64,57 @@ RED.palette = (function() {
|
|||||||
catDiv.data('label',label);
|
catDiv.data('label',label);
|
||||||
categoryContainers[category] = {
|
categoryContainers[category] = {
|
||||||
container: catDiv,
|
container: catDiv,
|
||||||
close: function() {
|
hide: function (instant) {
|
||||||
|
if (instant) {
|
||||||
|
catDiv.hide()
|
||||||
|
} else {
|
||||||
|
catDiv.slideUp()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: function () {
|
||||||
|
catDiv.show()
|
||||||
|
},
|
||||||
|
isOpen: function () {
|
||||||
|
return !!catDiv.hasClass("red-ui-palette-open")
|
||||||
|
},
|
||||||
|
getNodeCount: function (visibleOnly) {
|
||||||
|
const nodes = catDiv.find(".red-ui-palette-node")
|
||||||
|
if (visibleOnly) {
|
||||||
|
return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
|
||||||
|
} else {
|
||||||
|
return nodes.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function(instant, skipSaveState) {
|
||||||
catDiv.removeClass("red-ui-palette-open");
|
catDiv.removeClass("red-ui-palette-open");
|
||||||
catDiv.addClass("red-ui-palette-closed");
|
catDiv.addClass("red-ui-palette-closed");
|
||||||
$("#red-ui-palette-base-category-"+category).slideUp();
|
if (instant) {
|
||||||
|
$("#red-ui-palette-base-category-"+category).hide();
|
||||||
|
} else {
|
||||||
|
$("#red-ui-palette-base-category-"+category).slideUp();
|
||||||
|
}
|
||||||
$("#red-ui-palette-header-"+category+" i").removeClass("expanded");
|
$("#red-ui-palette-header-"+category+" i").removeClass("expanded");
|
||||||
|
if (!skipSaveState) {
|
||||||
|
if (!paletteState.collapsed.includes(category)) {
|
||||||
|
paletteState.collapsed.push(category);
|
||||||
|
savePaletteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
open: function() {
|
open: function(skipSaveState) {
|
||||||
catDiv.addClass("red-ui-palette-open");
|
catDiv.addClass("red-ui-palette-open");
|
||||||
catDiv.removeClass("red-ui-palette-closed");
|
catDiv.removeClass("red-ui-palette-closed");
|
||||||
$("#red-ui-palette-base-category-"+category).slideDown();
|
$("#red-ui-palette-base-category-"+category).slideDown();
|
||||||
$("#red-ui-palette-header-"+category+" i").addClass("expanded");
|
$("#red-ui-palette-header-"+category+" i").addClass("expanded");
|
||||||
|
if (!skipSaveState) {
|
||||||
|
if (paletteState.collapsed.includes(category)) {
|
||||||
|
paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1);
|
||||||
|
savePaletteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggle: function() {
|
toggle: function() {
|
||||||
if (catDiv.hasClass("red-ui-palette-open")) {
|
if (categoryContainers[category].isOpen()) {
|
||||||
categoryContainers[category].close();
|
categoryContainers[category].close();
|
||||||
} else {
|
} else {
|
||||||
categoryContainers[category].open();
|
categoryContainers[category].open();
|
||||||
@@ -171,23 +212,15 @@ RED.palette = (function() {
|
|||||||
}
|
}
|
||||||
metaData += type;
|
metaData += type;
|
||||||
|
|
||||||
|
const safeType = type.replace(/'/g,"\\'");
|
||||||
|
const searchType = type.indexOf(' ') > -1 ? '"' + type + '"' : type
|
||||||
|
|
||||||
if (/^subflow:/.test(type)) {
|
if (/^subflow:/.test(type)) {
|
||||||
$('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
|
$('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeType = type.replace(/'/g,"\\'");
|
$('<button type="button" onclick="RED.search.show(\'type:'+searchType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
|
||||||
const wrapStr = function (str) {
|
|
||||||
if(str.indexOf(' ') >= 0) {
|
|
||||||
return '"' + str + '"'
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
$('<button type="button"; return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>')
|
|
||||||
.appendTo(popOverContent)
|
|
||||||
.on('click', function() {
|
|
||||||
RED.search.show('type:' + wrapStr(safeType))
|
|
||||||
})
|
|
||||||
$('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
|
$('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
|
||||||
|
|
||||||
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
|
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
|
||||||
@@ -423,8 +456,16 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
var categoryNode = $("#red-ui-palette-container-"+rootCategory);
|
var categoryNode = $("#red-ui-palette-container-"+rootCategory);
|
||||||
if (categoryNode.find(".red-ui-palette-node").length === 1) {
|
if (categoryNode.find(".red-ui-palette-node").length === 1) {
|
||||||
categoryContainers[rootCategory].open();
|
if (!paletteState?.collapsed?.includes(rootCategory)) {
|
||||||
|
categoryContainers[rootCategory].open();
|
||||||
|
} else {
|
||||||
|
categoryContainers[rootCategory].close(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
clearTimeout(filterRefreshTimeout)
|
||||||
|
filterRefreshTimeout = setTimeout(() => {
|
||||||
|
refreshFilter()
|
||||||
|
}, 200)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,7 +533,7 @@ RED.palette = (function() {
|
|||||||
var currentLabel = paletteNode.attr("data-palette-label");
|
var currentLabel = paletteNode.attr("data-palette-label");
|
||||||
var currentInfo = paletteNode.attr("data-palette-info");
|
var currentInfo = paletteNode.attr("data-palette-info");
|
||||||
|
|
||||||
if (currentLabel !== sf.name || currentInfo !== sf.info) {
|
if (currentLabel !== sf.name || currentInfo !== sf.info || sf.in.length > 0 || sf.out.length > 0) {
|
||||||
paletteNode.attr("data-palette-info",sf.info);
|
paletteNode.attr("data-palette-info",sf.info);
|
||||||
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
|
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
|
||||||
}
|
}
|
||||||
@@ -524,7 +565,8 @@ RED.palette = (function() {
|
|||||||
paletteNode.css("backgroundColor", sf.color);
|
paletteNode.css("backgroundColor", sf.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterChange(val) {
|
function refreshFilter() {
|
||||||
|
const val = $("#red-ui-palette-search input").val()
|
||||||
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
|
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
|
||||||
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
|
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
|
||||||
var currentLabel = $(el).attr("data-palette-label");
|
var currentLabel = $(el).attr("data-palette-label");
|
||||||
@@ -536,16 +578,26 @@ RED.palette = (function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var category in categoryContainers) {
|
for (let category in categoryContainers) {
|
||||||
if (categoryContainers.hasOwnProperty(category)) {
|
if (categoryContainers.hasOwnProperty(category)) {
|
||||||
if (categoryContainers[category].container
|
const categorySection = categoryContainers[category]
|
||||||
.find(".red-ui-palette-node")
|
if (categorySection.getNodeCount(true) === 0) {
|
||||||
.filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
|
categorySection.hide()
|
||||||
categoryContainers[category].close();
|
|
||||||
categoryContainers[category].container.slideUp();
|
|
||||||
} else {
|
} else {
|
||||||
categoryContainers[category].open();
|
categorySection.show()
|
||||||
categoryContainers[category].container.show();
|
if (val) {
|
||||||
|
// There is a filter being applied and it has matched
|
||||||
|
// something in this category - show the contents
|
||||||
|
categorySection.open(true)
|
||||||
|
} else {
|
||||||
|
// No filter. Only show the category if it isn't in lastState
|
||||||
|
if (!paletteState.collapsed.includes(category)) {
|
||||||
|
categorySection.open(true)
|
||||||
|
} else if (categorySection.isOpen()) {
|
||||||
|
// This section should be collapsed but isn't - so make it so
|
||||||
|
categorySection.close(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,6 +613,9 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
||||||
|
|
||||||
|
RED.events.on('logout', function () {
|
||||||
|
RED.settings.removeLocal('palette-state')
|
||||||
|
})
|
||||||
|
|
||||||
RED.events.on('registry:node-type-added', function(nodeType) {
|
RED.events.on('registry:node-type-added', function(nodeType) {
|
||||||
var def = RED.nodes.getType(nodeType);
|
var def = RED.nodes.getType(nodeType);
|
||||||
@@ -604,14 +659,14 @@ RED.palette = (function() {
|
|||||||
|
|
||||||
RED.events.on("subflows:change",refreshSubflow);
|
RED.events.on("subflows:change",refreshSubflow);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$("#red-ui-palette-search input").searchBox({
|
$("#red-ui-palette-search input").searchBox({
|
||||||
delay: 100,
|
delay: 100,
|
||||||
change: function() {
|
change: function() {
|
||||||
filterChange($(this).val());
|
refreshFilter();
|
||||||
|
paletteState.filter = $(this).val();
|
||||||
|
savePaletteState();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
||||||
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
||||||
@@ -677,7 +732,23 @@ RED.palette = (function() {
|
|||||||
togglePalette(state);
|
togglePalette(state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
|
||||||
|
if (paletteState.filter) {
|
||||||
|
// Apply the category filter
|
||||||
|
$("#red-ui-palette-search input").searchBox("value", paletteState.filter);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unexpected error loading palette state from localStorage: ", error);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
// Lazily tidy up any categories that haven't been reloaded
|
||||||
|
paletteState.collapsed = paletteState.collapsed.filter(category => !!categoryContainers[category])
|
||||||
|
savePaletteState()
|
||||||
|
}, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePalette(state) {
|
function togglePalette(state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
||||||
@@ -697,6 +768,15 @@ RED.palette = (function() {
|
|||||||
})
|
})
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function savePaletteState() {
|
||||||
|
try {
|
||||||
|
RED.settings.setLocal("palette-state", JSON.stringify(paletteState));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unexpected error saving palette state to localStorage: ", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
add:addNodeType,
|
add:addNodeType,
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ RED.projects.settings = (function() {
|
|||||||
var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
|
var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
|
||||||
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
|
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
mermaid.init();
|
RED.editor.mermaid.render()
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ RED.projects.settings = (function() {
|
|||||||
var notInstalledCount = 0;
|
var notInstalledCount = 0;
|
||||||
|
|
||||||
for (var m in modulesInUse) {
|
for (var m in modulesInUse) {
|
||||||
if (modulesInUse.hasOwnProperty(m)) {
|
if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) {
|
||||||
depsList.editableList('addItem',{
|
depsList.editableList('addItem',{
|
||||||
id: modulesInUse[m].module,
|
id: modulesInUse[m].module,
|
||||||
version: modulesInUse[m].version,
|
version: modulesInUse[m].version,
|
||||||
@@ -307,8 +307,8 @@ RED.projects.settings = (function() {
|
|||||||
|
|
||||||
if (activeProject.dependencies) {
|
if (activeProject.dependencies) {
|
||||||
for (var m in activeProject.dependencies) {
|
for (var m in activeProject.dependencies) {
|
||||||
if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) {
|
if (activeProject.dependencies.hasOwnProperty(m)) {
|
||||||
var installed = !!RED.nodes.registry.getModule(m);
|
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m]?.version;
|
||||||
depsList.editableList('addItem',{
|
depsList.editableList('addItem',{
|
||||||
id: m,
|
id: m,
|
||||||
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
|
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
|
||||||
@@ -1256,7 +1256,7 @@ RED.projects.settings = (function() {
|
|||||||
notification.close();
|
notification.close();
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
text: 'Delete branch',
|
text: RED._("sidebar.project.projectSettings.deleteBranch"),
|
||||||
click: function() {
|
click: function() {
|
||||||
notification.close();
|
notification.close();
|
||||||
var url = "projects/"+activeProject.name+"/branches/"+entry.name;
|
var url = "projects/"+activeProject.name+"/branches/"+entry.name;
|
||||||
|
|||||||
@@ -747,14 +747,14 @@ RED.projects = (function() {
|
|||||||
var row = $('<div class="form-row"></div>').appendTo(body);
|
var row = $('<div class="form-row"></div>').appendTo(body);
|
||||||
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
|
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
|
||||||
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
||||||
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
|
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow) || "flows.json";
|
||||||
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
|
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
|
||||||
.on("change keyup paste",validateForm)
|
.on("change keyup paste",validateForm)
|
||||||
.appendTo(subrow);
|
.appendTo(subrow);
|
||||||
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
||||||
$('<label class="red-ui-projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
|
$('<label class="red-ui-projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
|
||||||
|
|
||||||
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
|
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials) || "flows_cred.json";
|
||||||
row = $('<div class="form-row"></div>').appendTo(body);
|
row = $('<div class="form-row"></div>').appendTo(body);
|
||||||
$('<label for="red-ui-projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
|
$('<label for="red-ui-projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
|
||||||
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
||||||
@@ -1257,7 +1257,7 @@ RED.projects = (function() {
|
|||||||
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty"></div>').appendTo(container);
|
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty"></div>').appendTo(container);
|
||||||
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
|
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
|
||||||
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
subrow = $('<div style="position:relative;"></div>').appendTo(row);
|
||||||
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flow.json")
|
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flows.json")
|
||||||
.on("change keyup paste",validateForm)
|
.on("change keyup paste",validateForm)
|
||||||
.appendTo(subrow);
|
.appendTo(subrow);
|
||||||
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
|
||||||
|
|||||||
@@ -647,9 +647,9 @@ RED.sidebar.versionControl = (function() {
|
|||||||
$.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
|
$.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
|
||||||
result.project = activeProject;
|
result.project = activeProject;
|
||||||
result.parents = entry.parents;
|
result.parents = entry.parents;
|
||||||
result.oldRev = entry.sha+"~1";
|
result.oldRev = entry.parents[0].length !== 0 ? entry.sha+"~1" : entry.sha;
|
||||||
result.newRev = entry.sha;
|
result.newRev = entry.sha;
|
||||||
result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1";
|
result.oldRevTitle = entry.parents[0].length !== 0 ? RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1" : " ";
|
||||||
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
|
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
|
||||||
result.date = humanizeSinceDate(parseInt(entry.date));
|
result.date = humanizeSinceDate(parseInt(entry.date));
|
||||||
RED.diff.showCommitDiff(result);
|
RED.diff.showCommitDiff(result);
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ RED.subflow = (function() {
|
|||||||
'</script>';
|
'</script>';
|
||||||
|
|
||||||
function findAvailableSubflowIOPosition(subflow,isInput) {
|
function findAvailableSubflowIOPosition(subflow,isInput) {
|
||||||
var pos = {x:50,y:30};
|
const scrollPos = RED.view.scroll()
|
||||||
|
const scaleFactor = RED.view.scale()
|
||||||
|
var pos = { x: (scrollPos[0]/scaleFactor)+50, y: (scrollPos[1]/scaleFactor)+30 };
|
||||||
if (!isInput) {
|
if (!isInput) {
|
||||||
pos.x += 110;
|
pos.x += 110;
|
||||||
}
|
}
|
||||||
@@ -665,7 +667,7 @@ RED.subflow = (function() {
|
|||||||
for (i=0; i<nodeList.length;i++) {
|
for (i=0; i<nodeList.length;i++) {
|
||||||
if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
|
if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
|
||||||
if (containingGroup !== nodeList[i].g) {
|
if (containingGroup !== nodeList[i].g) {
|
||||||
RED.notify("Cannot create subflow across multiple groups","error");
|
RED.notify(RED._("subflow.errors.acrossMultipleGroups"), "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,24 +683,23 @@ RED.subflow = (function() {
|
|||||||
var candidateOutputs = [];
|
var candidateOutputs = [];
|
||||||
var candidateInputNodes = {};
|
var candidateInputNodes = {};
|
||||||
|
|
||||||
var boundingBox = [nodeList[0].x,
|
var boundingBox = [nodeList[0].x-(nodeList[0].w/2),
|
||||||
nodeList[0].y,
|
nodeList[0].y-(nodeList[0].h/2),
|
||||||
nodeList[0].x,
|
nodeList[0].x+(nodeList[0].w/2),
|
||||||
nodeList[0].y];
|
nodeList[0].y+(nodeList[0].h/2)];
|
||||||
|
|
||||||
for (i=0;i<nodeList.length;i++) {
|
for (i=0;i<nodeList.length;i++) {
|
||||||
n = nodeList[i];
|
n = nodeList[i];
|
||||||
nodes[n.id] = {n:n,outputs:{}};
|
nodes[n.id] = {n:n,outputs:{}};
|
||||||
boundingBox = [
|
boundingBox = [
|
||||||
Math.min(boundingBox[0],n.x),
|
Math.min(boundingBox[0],n.x-(n.w/2)),
|
||||||
Math.min(boundingBox[1],n.y),
|
Math.min(boundingBox[1],n.y-(n.h/2)),
|
||||||
Math.max(boundingBox[2],n.x),
|
Math.max(boundingBox[2],n.x+(n.w/2)),
|
||||||
Math.max(boundingBox[3],n.y)
|
Math.max(boundingBox[3],n.y+(n.h/2))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
var offsetX = snapToGrid(boundingBox[0] - 200);
|
var offsetX = snapToGrid(boundingBox[0] - 140);
|
||||||
var offsetY = snapToGrid(boundingBox[1] - 80);
|
var offsetY = snapToGrid(boundingBox[1] - 60);
|
||||||
|
|
||||||
|
|
||||||
var center = [
|
var center = [
|
||||||
snapToGrid((boundingBox[2]+boundingBox[0]) / 2),
|
snapToGrid((boundingBox[2]+boundingBox[0]) / 2),
|
||||||
@@ -908,17 +909,19 @@ RED.subflow = (function() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create interface for controlling env var UI definition
|
* Build the edit dialog for a subflow template (creating/modifying a subflow template)
|
||||||
|
* @param {Object} uiContainer - the jQuery container for the environment variable list
|
||||||
|
* @param {Object} node - the subflow template node
|
||||||
*/
|
*/
|
||||||
function buildEnvControl(envList,node) {
|
function buildEnvControl(uiContainer,node) {
|
||||||
var tabs = RED.tabs.create({
|
var tabs = RED.tabs.create({
|
||||||
id: "subflow-env-tabs",
|
id: "subflow-env-tabs",
|
||||||
onchange: function(tab) {
|
onchange: function(tab) {
|
||||||
if (tab.id === "subflow-env-tab-preview") {
|
if (tab.id === "subflow-env-tab-preview") {
|
||||||
var inputContainer = $("#subflow-input-ui");
|
var inputContainer = $("#subflow-input-ui");
|
||||||
var list = envList.editableList("items");
|
var list = uiContainer.editableList("items");
|
||||||
var exportedEnv = exportEnvList(list, true);
|
var exportedEnv = exportEnvList(list, true);
|
||||||
buildEnvUI(inputContainer, exportedEnv,node);
|
buildEnvUI(inputContainer, exportedEnv, node);
|
||||||
}
|
}
|
||||||
$("#subflow-env-tabs-content").children().hide();
|
$("#subflow-env-tabs-content").children().hide();
|
||||||
$("#" + tab.id).show();
|
$("#" + tab.id).show();
|
||||||
@@ -956,12 +959,33 @@ RED.subflow = (function() {
|
|||||||
RED.editor.envVarList.setLocale(locale);
|
RED.editor.envVarList.setLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
function buildEnvUIRow(row, tenv, ui, node) {
|
* Build a UI row for a subflow instance environment variable
|
||||||
|
* Also used to build the UI row for subflow template preview
|
||||||
|
* @param {JQuery} row - A form row element
|
||||||
|
* @param {Object} tenv - A template environment variable
|
||||||
|
* @param {String} tenv.name - The name of the environment variable
|
||||||
|
* @param {String} tenv.type - The type of the environment variable
|
||||||
|
* @param {String} tenv.value - The value set for this environment variable
|
||||||
|
* @param {Object} tenv.parent - The parent environment variable
|
||||||
|
* @param {String} tenv.parent.value - The value set for the parent environment variable
|
||||||
|
* @param {String} tenv.parent.type - The type of the parent environment variable
|
||||||
|
* @param {Object} tenv.ui - The UI configuration for the environment variable
|
||||||
|
* @param {String} tenv.ui.icon - The icon for the environment variable
|
||||||
|
* @param {Object} tenv.ui.label - The label for the environment variable
|
||||||
|
* @param {String} tenv.ui.type - The type of the UI control for the environment variable
|
||||||
|
* @param {Object} node - The subflow instance node
|
||||||
|
*/
|
||||||
|
function buildEnvUIRow(row, tenv, node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEnvUIRow", tenv) }
|
||||||
|
const ui = tenv.ui || {}
|
||||||
ui.label = ui.label||{};
|
ui.label = ui.label||{};
|
||||||
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
|
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
|
||||||
ui.type = "cred";
|
ui.type = "cred";
|
||||||
ui.opts = {};
|
ui.opts = {};
|
||||||
|
} else if (tenv.type === "conf-types") {
|
||||||
|
ui.type = "conf-types"
|
||||||
|
ui.opts = { types: ['conf-types'] }
|
||||||
} else if (!ui.type) {
|
} else if (!ui.type) {
|
||||||
ui.type = "input";
|
ui.type = "input";
|
||||||
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
|
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
|
||||||
@@ -1005,9 +1029,10 @@ RED.subflow = (function() {
|
|||||||
if (tenv.hasOwnProperty('type')) {
|
if (tenv.hasOwnProperty('type')) {
|
||||||
val.type = tenv.type;
|
val.type = tenv.type;
|
||||||
}
|
}
|
||||||
|
const elId = getSubflowEnvPropertyName(tenv.name)
|
||||||
switch(ui.type) {
|
switch(ui.type) {
|
||||||
case "input":
|
case "input":
|
||||||
input = $('<input type="text">').css('width','70%').appendTo(row);
|
input = $('<input type="text">').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (ui.opts.types && ui.opts.types.length > 0) {
|
if (ui.opts.types && ui.opts.types.length > 0) {
|
||||||
var inputType = val.type;
|
var inputType = val.type;
|
||||||
if (ui.opts.types.indexOf(inputType) === -1) {
|
if (ui.opts.types.indexOf(inputType) === -1) {
|
||||||
@@ -1034,7 +1059,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "select":
|
case "select":
|
||||||
input = $('<select>').css('width','70%').appendTo(row);
|
input = $('<select>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (ui.opts.opts) {
|
if (ui.opts.opts) {
|
||||||
ui.opts.opts.forEach(function(o) {
|
ui.opts.opts.forEach(function(o) {
|
||||||
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
|
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
|
||||||
@@ -1045,7 +1070,7 @@ RED.subflow = (function() {
|
|||||||
case "checkbox":
|
case "checkbox":
|
||||||
label.css("cursor","default");
|
label.css("cursor","default");
|
||||||
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
||||||
input = $('<input type="checkbox">').css({
|
input = $('<input type="checkbox">').attr('id', elId).css({
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: '34px'
|
height: '34px'
|
||||||
@@ -1063,7 +1088,7 @@ RED.subflow = (function() {
|
|||||||
input.prop("checked",boolVal);
|
input.prop("checked",boolVal);
|
||||||
break;
|
break;
|
||||||
case "spinner":
|
case "spinner":
|
||||||
input = $('<input>').css('width','70%').appendTo(row);
|
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
var spinnerOpts = {};
|
var spinnerOpts = {};
|
||||||
if (ui.opts.hasOwnProperty('min')) {
|
if (ui.opts.hasOwnProperty('min')) {
|
||||||
spinnerOpts.min = ui.opts.min;
|
spinnerOpts.min = ui.opts.min;
|
||||||
@@ -1075,7 +1100,7 @@ RED.subflow = (function() {
|
|||||||
input.val(val.value);
|
input.val(val.value);
|
||||||
break;
|
break;
|
||||||
case "cred":
|
case "cred":
|
||||||
input = $('<input type="password">').css('width','70%').appendTo(row);
|
input = $('<input type="password">').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
if (node.credentials) {
|
if (node.credentials) {
|
||||||
if (node.credentials[tenv.name]) {
|
if (node.credentials[tenv.name]) {
|
||||||
input.val(node.credentials[tenv.name]);
|
input.val(node.credentials[tenv.name]);
|
||||||
@@ -1092,18 +1117,25 @@ RED.subflow = (function() {
|
|||||||
default: 'cred'
|
default: 'cred'
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
}
|
case "conf-types":
|
||||||
if (input) {
|
// let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000);
|
||||||
input.attr('id',getSubflowEnvPropertyName(tenv.name))
|
// clsId = clsId.replace(/\W/g, '-');
|
||||||
|
// input = $('<input>').css('width','70%').addClass(clsId).attr('id', elId).appendTo(row);
|
||||||
|
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||||
|
const _type = tenv.parent?.type || tenv.type;
|
||||||
|
RED.editor.prepareConfigNodeSelect(node, tenv.name, _type, 'node-input-subflow-env', null, tenv);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create environment variable input UI
|
* Build the edit form for a subflow instance
|
||||||
|
* Also used to build the preview form in the subflow template edit dialog
|
||||||
* @param uiContainer - container for UI
|
* @param uiContainer - container for UI
|
||||||
* @param envList - env var definitions of template
|
* @param envList - env var definitions of template
|
||||||
*/
|
*/
|
||||||
function buildEnvUI(uiContainer, envList, node) {
|
function buildEnvUI(uiContainer, envList, node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEnvUI",envList) }
|
||||||
uiContainer.empty();
|
uiContainer.empty();
|
||||||
for (var i = 0; i < envList.length; i++) {
|
for (var i = 0; i < envList.length; i++) {
|
||||||
var tenv = envList[i];
|
var tenv = envList[i];
|
||||||
@@ -1111,7 +1143,7 @@ RED.subflow = (function() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
||||||
buildEnvUIRow(row,tenv, tenv.ui || {}, node);
|
buildEnvUIRow(row, tenv, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// buildEnvUI
|
// buildEnvUI
|
||||||
@@ -1184,6 +1216,9 @@ RED.subflow = (function() {
|
|||||||
delete ui.opts
|
delete ui.opts
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "conf-types":
|
||||||
|
delete ui.opts;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
delete ui.opts;
|
delete ui.opts;
|
||||||
}
|
}
|
||||||
@@ -1206,8 +1241,9 @@ RED.subflow = (function() {
|
|||||||
if (/^subflow:/.test(node.type)) {
|
if (/^subflow:/.test(node.type)) {
|
||||||
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
||||||
if (subflowDef.env) {
|
if (subflowDef.env) {
|
||||||
subflowDef.env.forEach(function(env) {
|
subflowDef.env.forEach(function(env, i) {
|
||||||
var item = {
|
var item = {
|
||||||
|
index: i,
|
||||||
name:env.name,
|
name:env.name,
|
||||||
parent: {
|
parent: {
|
||||||
type: env.type,
|
type: env.type,
|
||||||
@@ -1244,14 +1280,20 @@ RED.subflow = (function() {
|
|||||||
var nodePropValue = nodeProp;
|
var nodePropValue = nodeProp;
|
||||||
if (prop.ui && prop.ui.type === "cred") {
|
if (prop.ui && prop.ui.type === "cred") {
|
||||||
nodePropType = "cred";
|
nodePropType = "cred";
|
||||||
|
} else if (prop.ui && prop.ui.type === "conf-types") {
|
||||||
|
nodePropType = prop.value.type
|
||||||
} else {
|
} else {
|
||||||
switch(typeof nodeProp) {
|
switch(typeof nodeProp) {
|
||||||
case "string": nodePropType = "str"; break;
|
case "string": nodePropType = "str"; break;
|
||||||
case "number": nodePropType = "num"; break;
|
case "number": nodePropType = "num"; break;
|
||||||
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
|
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
|
||||||
default:
|
default:
|
||||||
nodePropType = nodeProp.type;
|
if (nodeProp) {
|
||||||
nodePropValue = nodeProp.value;
|
nodePropType = nodeProp.type;
|
||||||
|
nodePropValue = nodeProp.value;
|
||||||
|
} else {
|
||||||
|
nodePropType = 'str'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var item = {
|
var item = {
|
||||||
@@ -1272,6 +1314,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportSubflowInstanceEnv(node) {
|
function exportSubflowInstanceEnv(node) {
|
||||||
|
if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) }
|
||||||
var env = [];
|
var env = [];
|
||||||
// First, get the values for the SubflowTemplate defined properties
|
// First, get the values for the SubflowTemplate defined properties
|
||||||
// - these are the ones with custom UI elements
|
// - these are the ones with custom UI elements
|
||||||
@@ -1303,7 +1346,7 @@ RED.subflow = (function() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "cred":
|
case "cred":
|
||||||
item.value = input.val();
|
item.value = input.typedInput('value');
|
||||||
item.type = 'cred';
|
item.type = 'cred';
|
||||||
break;
|
break;
|
||||||
case "spinner":
|
case "spinner":
|
||||||
@@ -1318,6 +1361,9 @@ RED.subflow = (function() {
|
|||||||
item.type = 'bool';
|
item.type = 'bool';
|
||||||
item.value = ""+input.prop("checked");
|
item.value = ""+input.prop("checked");
|
||||||
break;
|
break;
|
||||||
|
case "conf-types":
|
||||||
|
item.value = input.val() === "_ADD_" ? "" : input.val();
|
||||||
|
item.type = "conf-type"
|
||||||
}
|
}
|
||||||
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
||||||
env.push(item);
|
env.push(item);
|
||||||
@@ -1331,8 +1377,15 @@ RED.subflow = (function() {
|
|||||||
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by subflow.oneditprepare for both instances and templates
|
|
||||||
|
/**
|
||||||
|
* Build the subflow edit form
|
||||||
|
* Called by subflow.oneditprepare for both instances and templates
|
||||||
|
* @param {"subflow"|"subflow-template"} type - the type of subflow being edited
|
||||||
|
* @param {Object} node - the node being edited
|
||||||
|
*/
|
||||||
function buildEditForm(type,node) {
|
function buildEditForm(type,node) {
|
||||||
|
if(RED.subflow.debug) { console.log("buildEditForm",type,node) }
|
||||||
if (type === "subflow-template") {
|
if (type === "subflow-template") {
|
||||||
// This is the tabbed UI that offers the env list - with UI options
|
// This is the tabbed UI that offers the env list - with UI options
|
||||||
// plus the preview tab
|
// plus the preview tab
|
||||||
|
|||||||
@@ -145,17 +145,20 @@ RED.sidebar.config = (function() {
|
|||||||
} else {
|
} else {
|
||||||
var currentType = "";
|
var currentType = "";
|
||||||
nodes.forEach(function(node) {
|
nodes.forEach(function(node) {
|
||||||
var label = RED.utils.getNodeLabel(node,node.id);
|
var labelText = RED.utils.getNodeLabel(node,node.id);
|
||||||
if (node.type != currentType) {
|
if (node.type != currentType) {
|
||||||
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
|
$('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
|
||||||
currentType = node.type;
|
currentType = node.type;
|
||||||
}
|
}
|
||||||
|
if (node.changed) {
|
||||||
|
labelText += "!!"
|
||||||
|
}
|
||||||
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
|
var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
|
||||||
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
|
var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
|
||||||
entry.data('node',node.id);
|
entry.data('node',node.id);
|
||||||
nodeDiv.data('node',node.id);
|
nodeDiv.data('node',node.id);
|
||||||
var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv);
|
var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
|
||||||
|
|
||||||
if (node.d) {
|
if (node.d) {
|
||||||
nodeDiv.addClass("red-ui-palette-node-config-disabled");
|
nodeDiv.addClass("red-ui-palette-node-config-disabled");
|
||||||
$('<i class="fa fa-ban"></i>').prependTo(label);
|
$('<i class="fa fa-ban"></i>').prependTo(label);
|
||||||
@@ -177,6 +180,20 @@ RED.sidebar.config = (function() {
|
|||||||
nodeDiv.addClass("red-ui-palette-node-config-unused");
|
nodeDiv.addClass("red-ui-palette-node-config-unused");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!node.valid) {
|
||||||
|
nodeDiv.addClass("red-ui-palette-node-config-invalid")
|
||||||
|
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
|
||||||
|
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||||
|
errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
|
||||||
|
nodeDivAnnotations.append($(errorBadge))
|
||||||
|
RED.popover.tooltip(nodeDivAnnotations, function () {
|
||||||
|
if (node.validationErrors && node.validationErrors.length > 0) {
|
||||||
|
return RED._("editor.errors.invalidProperties")+"<br> - "+node.validationErrors.join("<br> - ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nodeDiv.on('click',function(e) {
|
nodeDiv.on('click',function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
RED.view.select(false);
|
RED.view.select(false);
|
||||||
@@ -365,9 +382,11 @@ RED.sidebar.config = (function() {
|
|||||||
refreshConfigNodeList();
|
refreshConfigNodeList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
|
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
|
||||||
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
|
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
|
||||||
|
RED.popover.tooltip($('#red-ui-sidebar-config-collapse-all'), RED._("palette.actions.collapse-all"));
|
||||||
|
RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function flashConfigNode(el) {
|
function flashConfigNode(el) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user