mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
1243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b855438af6 | ||
|
|
2ffea143e7 | ||
|
|
61d85b49e6 | ||
|
|
35f617e96c | ||
|
|
6b6ad47c35 | ||
|
|
e57183ed0e | ||
|
|
ecfd61a822 | ||
|
|
153f87704b | ||
|
|
836f7d2163 | ||
|
|
d4d6f71cf4 | ||
|
|
42a9da006e | ||
|
|
2bd5c4f527 | ||
|
|
6a49b5c106 | ||
|
|
23e14d1b72 | ||
|
|
f4f11c8884 | ||
|
|
2b220abdb7 | ||
|
|
c1d947ebe3 | ||
|
|
d695cf392e | ||
|
|
21304a695c | ||
|
|
fa51b06c46 | ||
|
|
7560bb8d7b | ||
|
|
fc9d65abcc | ||
|
|
a7413cccd0 | ||
|
|
7610353f07 | ||
|
|
d3f978c90c | ||
|
|
8d79deffb5 | ||
|
|
8158487c3e | ||
|
|
0cc061196d | ||
|
|
d0ec055222 | ||
|
|
ae12ddd32b | ||
|
|
31da3adaa9 | ||
|
|
9fd5213f13 | ||
|
|
de882f5849 | ||
|
|
fded1e0021 | ||
|
|
6cb06c146d | ||
|
|
b8f1386ad0 | ||
|
|
2b38b5ea50 | ||
|
|
2f707a6b16 | ||
|
|
d4c2fcd559 | ||
|
|
082970cdb7 | ||
|
|
fe97c78977 | ||
|
|
79394aa69f | ||
|
|
21fd6e3c21 | ||
|
|
de4944cd83 | ||
|
|
3fde5c27ed | ||
|
|
e1d492813e | ||
|
|
48d0ee3b6d | ||
|
|
eebb64901c | ||
|
|
60e0ed2af6 | ||
|
|
f030694ef4 | ||
|
|
e9ed13459a | ||
|
|
af1e38fdf7 | ||
|
|
b12900e680 | ||
|
|
44aa1f4a5e | ||
|
|
9425548a85 | ||
|
|
bfd4fc81fe | ||
|
|
439af2a325 | ||
|
|
3204b04455 | ||
|
|
bed1be14ba | ||
|
|
7cd92faf0d | ||
|
|
be7e28af5d | ||
|
|
8eaa762ec5 | ||
|
|
953a9f7cd4 | ||
|
|
c8fd5090bd | ||
|
|
155e1be494 | ||
|
|
cf5e125cb3 | ||
|
|
764fc8477d | ||
|
|
d35e62f8cf | ||
|
|
904babdd13 | ||
|
|
154d3842a8 | ||
|
|
edb8a120bd | ||
|
|
cdfeba0b82 | ||
|
|
8ce1465e9f | ||
|
|
a296b1c9c8 | ||
|
|
bb8d7058a4 | ||
|
|
816cfa1c7e | ||
|
|
53938200fc | ||
|
|
3885bb039d | ||
|
|
4adad6e424 | ||
|
|
5fb9531338 | ||
|
|
273d9c76a7 | ||
|
|
79a1d6c561 | ||
|
|
4b0eb8475d | ||
|
|
3dc874b517 | ||
|
|
8a3da1ce8d | ||
|
|
42d90542b5 | ||
|
|
690a93d82d | ||
|
|
8042fe4e2b | ||
|
|
a27ce375db | ||
|
|
db3688799d | ||
|
|
a88be35292 | ||
|
|
e2d7fcbfc2 | ||
|
|
421d155586 | ||
|
|
7f9e318214 | ||
|
|
57386edb7c | ||
|
|
94d5ba4550 | ||
|
|
2a0b4ea828 | ||
|
|
893ef227d4 | ||
|
|
1fe6e5a00d | ||
|
|
6c96cde73c | ||
|
|
2b12834d53 | ||
|
|
8a2e74b3b8 | ||
|
|
aa1721ab3d | ||
|
|
c0a256306b | ||
|
|
f0b03b4ada | ||
|
|
5503f53af2 | ||
|
|
a89d294b27 | ||
|
|
012e1cbcc5 | ||
|
|
3759e0f778 | ||
|
|
1c18641699 | ||
|
|
e50e2201b1 | ||
|
|
1419729458 | ||
|
|
c14177b0e8 | ||
|
|
126df969b3 | ||
|
|
f8ee92ba06 | ||
|
|
81a278dd8c | ||
|
|
a98013806c | ||
|
|
0171ffac6a | ||
|
|
7544241316 | ||
|
|
061afb3a94 | ||
|
|
8a5eda9c1f | ||
|
|
f62040f0ec | ||
|
|
f2e51779e4 | ||
|
|
da114fa3a5 | ||
|
|
3775a1657b | ||
|
|
2bd7c4bc81 | ||
|
|
253c489a33 | ||
|
|
ac84b6fe3f | ||
|
|
8761e61439 | ||
|
|
29e903e1c8 | ||
|
|
ec27e19e3f | ||
|
|
5df0dae11a | ||
|
|
7fffc1a36d | ||
|
|
f3d0179834 | ||
|
|
8bf69c598a | ||
|
|
aa5fad6628 | ||
|
|
b0f1fad4e2 | ||
|
|
3b6d0995b4 | ||
|
|
0cbf4ac37d | ||
|
|
9ccffee82c | ||
|
|
1b38e2eedf | ||
|
|
ce87abe96e | ||
|
|
becbda8483 | ||
|
|
da210e2ae4 | ||
|
|
d4fc6feeba | ||
|
|
f1cbca8d76 | ||
|
|
dfd9364061 | ||
|
|
1931395fdb | ||
|
|
b01fd24e15 | ||
|
|
c9d1329fc2 | ||
|
|
ab2d3bfd80 | ||
|
|
36bb172f29 | ||
|
|
01e64be39d | ||
|
|
4ebe160f6c | ||
|
|
b427eca21f | ||
|
|
3eb438c8d2 | ||
|
|
068f425833 | ||
|
|
b3c84242dc | ||
|
|
24672d91d8 | ||
|
|
4422af26ec | ||
|
|
5329e803e2 | ||
|
|
ad542b91fa | ||
|
|
e9e03c945b | ||
|
|
e20cfb3dae | ||
|
|
48baac916c | ||
|
|
adadf38b08 | ||
|
|
d4e1469450 | ||
|
|
2c456f044f | ||
|
|
228c15ace3 | ||
|
|
a0d15e6e7b | ||
|
|
2fe78cf971 | ||
|
|
5ec3544340 | ||
|
|
5443a17775 | ||
|
|
40d60e4eb3 | ||
|
|
f3312a6403 | ||
|
|
e638b55b30 | ||
|
|
5caa76a8b3 | ||
|
|
901a5ce9d2 | ||
|
|
6ab74951f4 | ||
|
|
c2625d696d | ||
|
|
77fb5ef2ab | ||
|
|
c20ca3399e | ||
|
|
5825da9c76 | ||
|
|
e3853ae402 | ||
|
|
bd142a9710 | ||
|
|
4f23847546 | ||
|
|
85820c571d | ||
|
|
d9bed03025 | ||
|
|
d6e05962c9 | ||
|
|
d32636ed6b | ||
|
|
bbf066f030 | ||
|
|
a3d2f6592e | ||
|
|
87b6327c5e | ||
|
|
abaebb329d | ||
|
|
8970fe412d | ||
|
|
9dc5ae21c4 | ||
|
|
4132fb79a6 | ||
|
|
9a4dc30604 | ||
|
|
192b542fe4 | ||
|
|
490547cd3d | ||
|
|
17f9829498 | ||
|
|
234e77fd06 | ||
|
|
87ac831c8a | ||
|
|
4e92492165 | ||
|
|
c3d0b1114f | ||
|
|
4463a7d4ba | ||
|
|
741fe3dd90 | ||
|
|
e910f3915d | ||
|
|
39aafc5007 | ||
|
|
9b83afae42 | ||
|
|
bdf54f6cff | ||
|
|
f2a9887a12 | ||
|
|
4f4d78bfab | ||
|
|
3b460fb8fa | ||
|
|
ee15e9acc5 | ||
|
|
48fce35fb3 | ||
|
|
78899378c2 | ||
|
|
67404a327d | ||
|
|
702dfa4b79 | ||
|
|
2144407e41 | ||
|
|
b36dd62c50 | ||
|
|
7026df7d96 | ||
|
|
ed8e7afdf6 | ||
|
|
d78e5932f9 | ||
|
|
26d83bb9ea | ||
|
|
8e89b1bdf2 | ||
|
|
c880cc0987 | ||
|
|
0874ba7a03 | ||
|
|
7962278475 | ||
|
|
c8949f5eeb | ||
|
|
8108b93c5f | ||
|
|
9dbe531bf7 | ||
|
|
46e2ff1001 | ||
|
|
d2cdc67ec7 | ||
|
|
56121203bf | ||
|
|
e13133fd2b | ||
|
|
f20565fd16 | ||
|
|
cf2d5841f5 | ||
|
|
630d2ca926 | ||
|
|
34cb93794c | ||
|
|
122b5ba468 | ||
|
|
401466d6c0 | ||
|
|
7f2627dbc8 | ||
|
|
6aecc3915c | ||
|
|
ef1b3aa7f5 | ||
|
|
1aaab2a814 | ||
|
|
e93734b209 | ||
|
|
9e5218f6b4 | ||
|
|
711ec39327 | ||
|
|
08049252f2 | ||
|
|
f1e7ec0c6b | ||
|
|
23765d9139 | ||
|
|
43febe269c | ||
|
|
40233c7702 | ||
|
|
27ed81614b | ||
|
|
889d23e9bd | ||
|
|
f8571023f6 | ||
|
|
6364e00202 | ||
|
|
a76c6f86c6 | ||
|
|
555e815402 | ||
|
|
8a1d81989b | ||
|
|
ee9234b2c6 | ||
|
|
735b9c5844 | ||
|
|
064f3eb3bc | ||
|
|
f1775d4fd1 | ||
|
|
a9bc111c4f | ||
|
|
c100612473 | ||
|
|
26087f8dc7 | ||
|
|
36e75cb728 | ||
|
|
d7a2fc2be4 | ||
|
|
142176f194 | ||
|
|
c5892fc17e | ||
|
|
6e69cfbca4 | ||
|
|
775181f761 | ||
|
|
36e83d628e | ||
|
|
5f6fcb2bc0 | ||
|
|
7b106e5650 | ||
|
|
79d9c83a2d | ||
|
|
269669ba28 | ||
|
|
4ef7240598 | ||
|
|
efdf689c31 | ||
|
|
f7606e92ca | ||
|
|
6750be3ec9 | ||
|
|
68fb5089f8 | ||
|
|
a8d093bacd | ||
|
|
233a1995b3 | ||
|
|
8ef3baaffb | ||
|
|
c9597b9447 | ||
|
|
b2dc1d8b23 | ||
|
|
859c0c7f6c | ||
|
|
aaf18e2416 | ||
|
|
fd679ef117 | ||
|
|
6cc611b3f1 | ||
|
|
77ee726f66 | ||
|
|
dc603b76a4 | ||
|
|
bcb3371acc | ||
|
|
d14ce7e476 | ||
|
|
4d26b806dd | ||
|
|
a2b95dbb39 | ||
|
|
47f7b43bcc | ||
|
|
77fd8c120c | ||
|
|
a1a6f40158 | ||
|
|
ed09cd7489 | ||
|
|
eb3330d145 | ||
|
|
5ba0588c7b | ||
|
|
d4a199f0e1 | ||
|
|
32dd186f4d | ||
|
|
d820f55358 | ||
|
|
81f0fb3c74 | ||
|
|
972c83cd52 | ||
|
|
66a704af55 | ||
|
|
31c5d6e1c1 | ||
|
|
bf0ab95c09 | ||
|
|
c1d85f760d | ||
|
|
88ad2f4c18 | ||
|
|
be9f9e7b0c | ||
|
|
2cc1973f62 | ||
|
|
eb4625a0b9 | ||
|
|
5bfb01254b | ||
|
|
bb80fa4a2d | ||
|
|
ddb715d88d | ||
|
|
395b499856 | ||
|
|
cce6a47f11 | ||
|
|
7fd17b4ec0 | ||
|
|
e16ab2a0fd | ||
|
|
15f5364c30 | ||
|
|
65081767bf | ||
|
|
c7c595e5fa | ||
|
|
5b24e8b69c | ||
|
|
e6a845e606 | ||
|
|
ec8b8a7b87 | ||
|
|
51a9205105 | ||
|
|
ed5567fc73 | ||
|
|
4b3f5d74a0 | ||
|
|
b01c5a05e7 | ||
|
|
36eddabc1c | ||
|
|
ea11aa7a0d | ||
|
|
e7efa76e6d | ||
|
|
41c8ca8ab4 | ||
|
|
4624079be7 | ||
|
|
c6f6042271 | ||
|
|
e9e3b9b7c6 | ||
|
|
becbb09a29 | ||
|
|
6f6ab50995 | ||
|
|
d8ee766860 | ||
|
|
108c26d8af | ||
|
|
ed8d3088ca | ||
|
|
46c4e2d212 | ||
|
|
94891d45f9 | ||
|
|
7448ad109e | ||
|
|
6211dfe024 | ||
|
|
9b85200954 | ||
|
|
94ee739d91 | ||
|
|
e81a6db9a3 | ||
|
|
b2f5a259ab | ||
|
|
c8a0d3c10d | ||
|
|
97df964051 | ||
|
|
66dd05f8bc | ||
|
|
19589d9117 | ||
|
|
8147b2e0b1 | ||
|
|
be22f8cd14 | ||
|
|
868be9b7ff | ||
|
|
5011281104 | ||
|
|
42992c64ec | ||
|
|
2baff243ed | ||
|
|
83440a6b0f | ||
|
|
87c9a1c06c | ||
|
|
b848fe249f | ||
|
|
1e804d97ce | ||
|
|
218d3c144b | ||
|
|
05a4905490 | ||
|
|
75103da378 | ||
|
|
9db9b53c81 | ||
|
|
0e4787f3e8 | ||
|
|
f8d8d4b186 | ||
|
|
45e0a1ffea | ||
|
|
75c58093f1 | ||
|
|
cc708e9fb4 | ||
|
|
2ce0e38827 | ||
|
|
5b980e8c13 | ||
|
|
21b602650c | ||
|
|
fa4b7a1a69 | ||
|
|
977dfe700b | ||
|
|
48ac50e1c9 | ||
|
|
1a817947eb | ||
|
|
be64603097 | ||
|
|
f6b90c8271 | ||
|
|
26e4be87c7 | ||
|
|
cddbb8d80d | ||
|
|
58023b4bf0 | ||
|
|
4f18a5f1c3 | ||
|
|
56df8d8bd3 | ||
|
|
211ec104c2 | ||
|
|
3fb573247d | ||
|
|
6aac44db14 | ||
|
|
3255e11cfc | ||
|
|
844bf29de1 | ||
|
|
04d91d1422 | ||
|
|
db90e1f801 | ||
|
|
7f30748a41 | ||
|
|
a4e0abb48f | ||
|
|
3f27dc89d8 | ||
|
|
d6f6efc189 | ||
|
|
2cda49fc38 | ||
|
|
04f4a76b41 | ||
|
|
0a8f7085f3 | ||
|
|
7ae48d7390 | ||
|
|
c908502644 | ||
|
|
2f0631809d | ||
|
|
91ab3bd972 | ||
|
|
672636313c | ||
|
|
79875ef50d | ||
|
|
aea5445495 | ||
|
|
754a36fbc9 | ||
|
|
85dafc0b3c | ||
|
|
b516ab9b4f | ||
|
|
1a27e60e55 | ||
|
|
2c710736e8 | ||
|
|
69b9ff69be | ||
|
|
a3a4fc0cc2 | ||
|
|
ae686bb15d | ||
|
|
68a5325849 | ||
|
|
75e3bddfa9 | ||
|
|
bd3a8db438 | ||
|
|
102868bf74 | ||
|
|
1a73a27102 | ||
|
|
a9cf34ab56 | ||
|
|
46d17c3314 | ||
|
|
40f816c311 | ||
|
|
13f1c12912 | ||
|
|
93c25f5d1b | ||
|
|
aa6ec60c34 | ||
|
|
ac159bb52e | ||
|
|
919aee64f9 | ||
|
|
553bec1a1f | ||
|
|
bcb6d1cf93 | ||
|
|
7d24e5b279 | ||
|
|
12253e23b5 | ||
|
|
4acb66fb7a | ||
|
|
68ef85b64b | ||
|
|
b73efe6bb4 | ||
|
|
89c84522d2 | ||
|
|
98172764ac | ||
|
|
448e881104 | ||
|
|
f16134ab1f | ||
|
|
f5dc1564a4 | ||
|
|
133df75bd4 | ||
|
|
440be0653a | ||
|
|
d721a40ca5 | ||
|
|
a9b252b8fa | ||
|
|
8a5b3ddee7 | ||
|
|
d83e543a98 | ||
|
|
bcd6e8fd63 | ||
|
|
d5c5738aab | ||
|
|
9e4dfe081f | ||
|
|
090852b72b | ||
|
|
ff5e038c49 | ||
|
|
5cc2e5f6e1 | ||
|
|
4e8c0573c4 | ||
|
|
ce905ba2c4 | ||
|
|
3104c17fb3 | ||
|
|
7651941722 | ||
|
|
7bf938901a | ||
|
|
f8b61d2926 | ||
|
|
4edea59ab1 | ||
|
|
c8bcd2818d | ||
|
|
9b46dbaff1 | ||
|
|
17a139f27f | ||
|
|
bd00c728d1 | ||
|
|
9d510b514c | ||
|
|
00dcc5ecda | ||
|
|
dbbdd3f799 | ||
|
|
3541b4b968 | ||
|
|
5b1bf35a23 | ||
|
|
591b61945f | ||
|
|
bd1943626b | ||
|
|
f152cdef51 | ||
|
|
33f8c9747d | ||
|
|
714a5e26b3 | ||
|
|
7f2c6e40d3 | ||
|
|
db676ec223 | ||
|
|
ffb3e511a7 | ||
|
|
e9e64f6a44 | ||
|
|
a7b8adb0e1 | ||
|
|
4140ff03d7 | ||
|
|
e042ef05a4 | ||
|
|
7c02e4d66a | ||
|
|
711794cfe1 | ||
|
|
c0e4cf2358 | ||
|
|
a92f8f36c1 | ||
|
|
12698dc347 | ||
|
|
3e6a55f78e | ||
|
|
7585f14b89 | ||
|
|
01b5fc4d49 | ||
|
|
0fb7d3bfc8 | ||
|
|
2cd74d355c | ||
|
|
3d405f8c63 | ||
|
|
a92f0c4c6e | ||
|
|
de142ac9d6 | ||
|
|
468ef7ecff | ||
|
|
4d768fd236 | ||
|
|
bfc1f95190 | ||
|
|
bc17ebd90e | ||
|
|
bb1b3727cb | ||
|
|
4dbebefb45 | ||
|
|
e1c5764fbf | ||
|
|
70f975e4f0 | ||
|
|
845567d1ba | ||
|
|
f570447000 | ||
|
|
9d7b8f1f2f | ||
|
|
bae6bfc32d | ||
|
|
8a63390464 | ||
|
|
0b52cd8b31 | ||
|
|
f97569dd34 | ||
|
|
a9164e63ab | ||
|
|
8c95067ec4 | ||
|
|
4f77bbeb2b | ||
|
|
8bbed2c831 | ||
|
|
6b43a23c4b | ||
|
|
be9521f659 | ||
|
|
90761fd840 | ||
|
|
d49d9a783c | ||
|
|
d7dc7c4eda | ||
|
|
fe64c6a841 | ||
|
|
2bbdc85a29 | ||
|
|
74628b7034 | ||
|
|
15aa249f64 | ||
|
|
fdf58e1225 | ||
|
|
866f305686 | ||
|
|
1550e5343c | ||
|
|
add3dd1077 | ||
|
|
79a142fb19 | ||
|
|
1a30fe4a1a | ||
|
|
4ff991764e | ||
|
|
001f066769 | ||
|
|
c47b553a8e | ||
|
|
319af51f84 | ||
|
|
5dbaaae68e | ||
|
|
8c1a749a5a | ||
|
|
fc8643f238 | ||
|
|
c8653f19bf | ||
|
|
b01100d818 | ||
|
|
d4096a9026 | ||
|
|
b9e780cdcd | ||
|
|
b77cd56a01 | ||
|
|
9cdec156dc | ||
|
|
6aa5968863 | ||
|
|
8f7686cd7b | ||
|
|
d8d384a979 | ||
|
|
ade318bb78 | ||
|
|
ed3aa8189f | ||
|
|
3e43597617 | ||
|
|
4c8e895ac7 | ||
|
|
f6a3671366 | ||
|
|
e641b0a965 | ||
|
|
eddddc6c9b | ||
|
|
f249d6306f | ||
|
|
5c31bd54e4 | ||
|
|
71ba73b38f | ||
|
|
db0ff74857 | ||
|
|
1acb073737 | ||
|
|
251dda3652 | ||
|
|
22db24509d | ||
|
|
54c9d27fd8 | ||
|
|
01888ff078 | ||
|
|
ffbd140a97 | ||
|
|
dedf5c52d9 | ||
|
|
10465c5d68 | ||
|
|
1f4f64a7c0 | ||
|
|
a6f116b57b | ||
|
|
0a80186a92 | ||
|
|
635bdf15cb | ||
|
|
a72bdfdacc | ||
|
|
dc3e04456c | ||
|
|
b0e4fb7602 | ||
|
|
df7aa3339b | ||
|
|
c475536388 | ||
|
|
cc7def89af | ||
|
|
58da87898e | ||
|
|
bded5490d2 | ||
|
|
c3715a2a3d | ||
|
|
abf084f6c2 | ||
|
|
37ba409dc3 | ||
|
|
f29488b24f | ||
|
|
71bdade7b9 | ||
|
|
60d97c887d | ||
|
|
5bba50f01f | ||
|
|
1f7884dc70 | ||
|
|
69dafd6c68 | ||
|
|
64b79cd5ac | ||
|
|
1af21735a9 | ||
|
|
9886af3cec | ||
|
|
1eb8f9ad97 | ||
|
|
08e73d9d7d | ||
|
|
b0e349b215 | ||
|
|
caa98b08da | ||
|
|
00caa13a12 | ||
|
|
cfc0135e86 | ||
|
|
9ee8c1c791 | ||
|
|
cd3aba2b89 | ||
|
|
a150d8e289 | ||
|
|
6da8e92f20 | ||
|
|
1d4dd4be96 | ||
|
|
7df1a03b4b | ||
|
|
ad316ffd37 | ||
|
|
91f5542a57 | ||
|
|
d47a8aa562 | ||
|
|
676f790933 | ||
|
|
70433f3d05 | ||
|
|
9f2a2b9869 | ||
|
|
a0c09fc617 | ||
|
|
19d391fa05 | ||
|
|
d1aa1fd4d8 | ||
|
|
0e02d03d9a | ||
|
|
4133f9c56f | ||
|
|
53055064e1 | ||
|
|
06090d8de1 | ||
|
|
d6ccae38f8 | ||
|
|
f7210effec | ||
|
|
8e7efd98b2 | ||
|
|
ea50ba16f9 | ||
|
|
b62e4f6662 | ||
|
|
f5a1c8bc49 | ||
|
|
4cb8e99430 | ||
|
|
bbac49ff38 | ||
|
|
1d12017f11 | ||
|
|
46af2e37a7 | ||
|
|
a480919ec3 | ||
|
|
f8abf9fce1 | ||
|
|
62f2a552ea | ||
|
|
b053e02174 | ||
|
|
3798167908 | ||
|
|
56fe2014e1 | ||
|
|
be2e64433f | ||
|
|
8732e89e55 | ||
|
|
fdd0a93bad | ||
|
|
dd12572b1d | ||
|
|
e23f20227a | ||
|
|
5cc791690b | ||
|
|
9f1deb0c36 | ||
|
|
4cebbf8d22 | ||
|
|
93971537b4 | ||
|
|
87e816a7f5 | ||
|
|
0f45b1da48 | ||
|
|
a20049c82a | ||
|
|
e57ebdb583 | ||
|
|
372122037f | ||
|
|
23a5cb1917 | ||
|
|
f8d5fef3c4 | ||
|
|
250005ad16 | ||
|
|
719aea2a58 | ||
|
|
0e06da6c63 | ||
|
|
68fef169f3 | ||
|
|
c668201df4 | ||
|
|
1d68c8cc87 | ||
|
|
b9ac8b42ea | ||
|
|
b4a03a56b4 | ||
|
|
9eb668ab30 | ||
|
|
233a74c146 | ||
|
|
e0c7269b8e | ||
|
|
73063df11b | ||
|
|
ff00afb5d7 | ||
|
|
3f43dc1855 | ||
|
|
4a4e7fc7cb | ||
|
|
0253dc9623 | ||
|
|
f8855b83fa | ||
|
|
0d0459d83d | ||
|
|
374ef3902c | ||
|
|
235690064f | ||
|
|
0167c25e08 | ||
|
|
d2432716ea | ||
|
|
52ef85cba3 | ||
|
|
8140057bea | ||
|
|
22df59e229 | ||
|
|
ed351eee54 | ||
|
|
c021b4c368 | ||
|
|
04a3c4bb22 | ||
|
|
b5fda5642f | ||
|
|
b0955705be | ||
|
|
a4a624d537 | ||
|
|
6a8cf1b768 | ||
|
|
aac2a8f830 | ||
|
|
8269490dd1 | ||
|
|
39274b0c5d | ||
|
|
55c2430671 | ||
|
|
023486e175 | ||
|
|
8227643741 | ||
|
|
e44131f97a | ||
|
|
5028377d45 | ||
|
|
51aaf1b150 | ||
|
|
13406e76de | ||
|
|
4672d98e8a | ||
|
|
858b3d640a | ||
|
|
6087002562 | ||
|
|
82ced56bed | ||
|
|
982b8ea51d | ||
|
|
877c463494 | ||
|
|
9882582903 | ||
|
|
ba566657f1 | ||
|
|
cb1a178fbf | ||
|
|
ad788fbed1 | ||
|
|
749533b0b4 | ||
|
|
142a5f7ca1 | ||
|
|
28bfa8e418 | ||
|
|
7b8ed487e9 | ||
|
|
0059f9475e | ||
|
|
9429ea7c64 | ||
|
|
a157580b22 | ||
|
|
41a0147938 | ||
|
|
16e021e94f | ||
|
|
449d76a6c7 | ||
|
|
70172db693 | ||
|
|
ff93a38354 | ||
|
|
e3b70b10d1 | ||
|
|
400141b093 | ||
|
|
ca5e45a46d | ||
|
|
74b547b93c | ||
|
|
a688305572 | ||
|
|
f0f2eefb59 | ||
|
|
bdb548ffdc | ||
|
|
fe5d4abec1 | ||
|
|
70632706f9 | ||
|
|
8f424c063e | ||
|
|
9955c3dd5d | ||
|
|
d555fcf7bd | ||
|
|
8da00c0872 | ||
|
|
393290df2c | ||
|
|
f8a7835341 | ||
|
|
082bac8c3a | ||
|
|
4cafe42cf4 | ||
|
|
89485971fa | ||
|
|
cb72d5100e | ||
|
|
f103533852 | ||
|
|
55f1e7ece1 | ||
|
|
c0a765c998 | ||
|
|
ed44fb461c | ||
|
|
8543613563 | ||
|
|
734adc6445 | ||
|
|
827f8d4d51 | ||
|
|
5bbd3d6273 | ||
|
|
df90e3414d | ||
|
|
16b9abbe92 | ||
|
|
2de43b719e | ||
|
|
3b84f27f36 | ||
|
|
f7a6a333e1 | ||
|
|
c37ea90206 | ||
|
|
0b39ef68d9 | ||
|
|
40ea759e2c | ||
|
|
3671a70e3b | ||
|
|
2fa50e458f | ||
|
|
9c7db1381c | ||
|
|
2d4f5b8603 | ||
|
|
5181890433 | ||
|
|
99a9e3a91b | ||
|
|
101378c625 | ||
|
|
aa5e47b462 | ||
|
|
15715a2968 | ||
|
|
b5751e5746 | ||
|
|
7e40cb5331 | ||
|
|
4b1a86ee02 | ||
|
|
6c66ca8acf | ||
|
|
d58a091bb7 | ||
|
|
0566a2d9b1 | ||
|
|
3d23d1de4f | ||
|
|
c9c5f7f088 | ||
|
|
8e65408b1c | ||
|
|
c3adc956d7 | ||
|
|
f69d6b4eb1 | ||
|
|
916d377aaa | ||
|
|
39532a9d65 | ||
|
|
3dc696b2a9 | ||
|
|
7be7dec19a | ||
|
|
fc709ba266 | ||
|
|
080e2f2589 | ||
|
|
0dc4440a99 | ||
|
|
f770786b89 | ||
|
|
46bc331428 | ||
|
|
3af77b6a31 | ||
|
|
8c4461c4f8 | ||
|
|
bccfd21cf4 | ||
|
|
e6f1394a74 | ||
|
|
de24831fb9 | ||
|
|
e5716162ad | ||
|
|
5809a3af0d | ||
|
|
11a385550a | ||
|
|
255b8f2005 | ||
|
|
3d398cfd53 | ||
|
|
5f8804c25c | ||
|
|
02d1369d5b | ||
|
|
0fef2ab509 | ||
|
|
16088b8a08 | ||
|
|
69befe8f0e | ||
|
|
ae7a3981c0 | ||
|
|
8b4aa3f5af | ||
|
|
60c8a2c598 | ||
|
|
fbb7dd4c3f | ||
|
|
833ecfb1af | ||
|
|
c20bab2436 | ||
|
|
afb17af571 | ||
|
|
5012568464 | ||
|
|
02dd141095 | ||
|
|
0be82d964e | ||
|
|
b41c7962c2 | ||
|
|
6f9e06e78d | ||
|
|
c2347076f4 | ||
|
|
c744af161d | ||
|
|
74ea382cf2 | ||
|
|
ab4a9e72d4 | ||
|
|
3f9a29730f | ||
|
|
8a076c01ab | ||
|
|
ca75efcbaf | ||
|
|
f96ce2fd83 | ||
|
|
d5f4f987f2 | ||
|
|
11475b0c38 | ||
|
|
137fa98903 | ||
|
|
ea62c1806e | ||
|
|
45afd06047 | ||
|
|
e6ec59092c | ||
|
|
35f788693d | ||
|
|
d5314d2a85 | ||
|
|
efd8c3d6d2 | ||
|
|
7d04353843 | ||
|
|
644da0b77b | ||
|
|
785c349adc | ||
|
|
1608196cd2 | ||
|
|
9d34abf603 | ||
|
|
05beb6ca79 | ||
|
|
12c7238c72 | ||
|
|
ed359ca10c | ||
|
|
b66468c4ea | ||
|
|
d2c9ccbfdd | ||
|
|
85e05b787f | ||
|
|
e5471b44e0 | ||
|
|
e0f0a76ae4 | ||
|
|
6336ab121e | ||
|
|
e899d2d5b8 | ||
|
|
a94c19a6cf | ||
|
|
9c09ee3b71 | ||
|
|
d8e68a75b9 | ||
|
|
302c5cfe09 | ||
|
|
1be337fbc5 | ||
|
|
3ec37e2c66 | ||
|
|
3740c21bee | ||
|
|
5a6568e7c2 | ||
|
|
4cd9b7b050 | ||
|
|
dd780945e1 | ||
|
|
e86f6a841a | ||
|
|
fad8dcd304 | ||
|
|
1633a2ff70 | ||
|
|
a2878fa066 | ||
|
|
735de2908a | ||
|
|
818021e0b7 | ||
|
|
3cc6c4433f | ||
|
|
8306ddd40f | ||
|
|
024e71cdf5 | ||
|
|
4313cbaa5c | ||
|
|
f5da2eb633 | ||
|
|
23f0cd3a26 | ||
|
|
74db3e17d0 | ||
|
|
7bde7f0cfd | ||
|
|
ac68e9c6b5 | ||
|
|
32692dce07 | ||
|
|
64d3b8e104 | ||
|
|
a03edf3d58 | ||
|
|
e5b7ccb612 | ||
|
|
9c1ce5543d | ||
|
|
ba387a8e4e | ||
|
|
7fa25c1ff4 | ||
|
|
3bd1bfc769 | ||
|
|
dad47ade38 | ||
|
|
fa6e0c8964 | ||
|
|
bb4b252401 | ||
|
|
a50404b141 | ||
|
|
61690ecf4a | ||
|
|
f4c87af5c1 | ||
|
|
83d12f7d39 | ||
|
|
2e73b229d7 | ||
|
|
3a0074d96e | ||
|
|
7068c175f2 | ||
|
|
9b5ed8407f | ||
|
|
7d08de9c99 | ||
|
|
575d07e41a | ||
|
|
24da3608c4 | ||
|
|
37935bf388 | ||
|
|
9eb7fad621 | ||
|
|
438d51d26e | ||
|
|
34ef055d7b | ||
|
|
4a1d66f210 | ||
|
|
1f6328bf4e | ||
|
|
8e7a230dbc | ||
|
|
6e718ca772 | ||
|
|
70554e24b1 | ||
|
|
a0f736bb88 | ||
|
|
79473c243d | ||
|
|
441eb3bb29 | ||
|
|
ca44af0625 | ||
|
|
9e179170ee | ||
|
|
9f71dbb006 | ||
|
|
7531314e3f | ||
|
|
a006b52052 | ||
|
|
bebebaa3dd | ||
|
|
55ff035fc9 | ||
|
|
d8c8d7bc57 | ||
|
|
b0acb58442 | ||
|
|
2b28ae3402 | ||
|
|
aa47bae2ad | ||
|
|
ccfde84769 | ||
|
|
b1df6d5149 | ||
|
|
d51aefa156 | ||
|
|
c40412d7c6 | ||
|
|
b0bc7ecacb | ||
|
|
5489bd37c9 | ||
|
|
ea2e3f25d8 | ||
|
|
09b37cf538 | ||
|
|
e30a01310e | ||
|
|
d5cc5b2574 | ||
|
|
160ca6add4 | ||
|
|
da96c85d32 | ||
|
|
de15a1c36f | ||
|
|
814fc8bc69 | ||
|
|
0c9fd25d3e | ||
|
|
9a660f3fe9 | ||
|
|
6e1466e411 | ||
|
|
7913b3cbc2 | ||
|
|
87c9ed6356 | ||
|
|
06ceb056f3 | ||
|
|
65b4ef6c3d | ||
|
|
0284ef401e | ||
|
|
8a87f93741 | ||
|
|
af19536222 | ||
|
|
abe77ab96f | ||
|
|
ea720bb4a5 | ||
|
|
6ee2e2b570 | ||
|
|
3885107e6e | ||
|
|
30a68fefec | ||
|
|
fa84c4e461 | ||
|
|
5743a5f91d | ||
|
|
9d2d060dec | ||
|
|
b36e7e172e | ||
|
|
aacb92a7ae | ||
|
|
4943bde3d4 | ||
|
|
fc459be531 | ||
|
|
3151502a3f | ||
|
|
79b10ed18a | ||
|
|
34b27f2e68 | ||
|
|
c433f736a5 | ||
|
|
55e6c6e01a | ||
|
|
496b5a092f | ||
|
|
02510efda1 | ||
|
|
be828af3e2 | ||
|
|
9b1c114c3f | ||
|
|
4df27d4b0b | ||
|
|
e3445dae46 | ||
|
|
f5fcf23678 | ||
|
|
0a6c08e2c3 | ||
|
|
b80a7459cf | ||
|
|
f6480e6e0c | ||
|
|
41d12c433e | ||
|
|
169a2484f2 | ||
|
|
be52ec1390 | ||
|
|
00db43198d | ||
|
|
6bac207611 | ||
|
|
df1eb631e1 | ||
|
|
6150ae787d | ||
|
|
fc7967d455 | ||
|
|
fca21ac126 | ||
|
|
6fb96fa3c1 | ||
|
|
a1f565f756 | ||
|
|
5992ed1fab | ||
|
|
beccdac717 | ||
|
|
9f3e9786a8 | ||
|
|
b72ea63100 | ||
|
|
27550f2d4b | ||
|
|
6917919f35 | ||
|
|
48d6fe5918 | ||
|
|
c9bc530df0 | ||
|
|
0b569a4120 | ||
|
|
950fd7d2cf | ||
|
|
50dd0354d1 | ||
|
|
78f1cb8a66 | ||
|
|
4bfe9a9ae9 | ||
|
|
c5d38d8962 | ||
|
|
bbe3ee701f | ||
|
|
2f86bb1ca5 | ||
|
|
3999690062 | ||
|
|
b1c0d6b452 | ||
|
|
d57edaa4c1 | ||
|
|
3033d2086e | ||
|
|
0f7d185a61 | ||
|
|
81f200641b | ||
|
|
c6129b44a1 | ||
|
|
088419b38e | ||
|
|
8ebcee32c2 | ||
|
|
2b801a756a | ||
|
|
98b639540b | ||
|
|
e0b797fc7e | ||
|
|
795416a84d | ||
|
|
545dda166f | ||
|
|
f19ec5d9b6 | ||
|
|
6ea978d83d | ||
|
|
42f3b70a22 | ||
|
|
1cd10f074b | ||
|
|
bed1d31bc8 | ||
|
|
99478897c5 | ||
|
|
d79cd463a0 | ||
|
|
2a8290a4b7 | ||
|
|
ccf4e73701 | ||
|
|
01b67c692b | ||
|
|
4023ab3f28 | ||
|
|
70f3b7450f | ||
|
|
ca4960e097 | ||
|
|
ebe604e1af | ||
|
|
32b04cd32f | ||
|
|
e149174696 | ||
|
|
f878ffc01b | ||
|
|
15b49f4db8 | ||
|
|
b1cc7b3296 | ||
|
|
65d90a6dff | ||
|
|
a58f4c2ec2 | ||
|
|
f038069fe2 | ||
|
|
407cb3e7d5 | ||
|
|
b8bc62ee11 | ||
|
|
ccbd179f23 | ||
|
|
dac7830bd4 | ||
|
|
75d7ac2d8a | ||
|
|
468cfeffb6 | ||
|
|
bc82ca2106 | ||
|
|
725c962236 | ||
|
|
6720c1aa46 | ||
|
|
280203e64e | ||
|
|
281d8b7cec | ||
|
|
2c6cda1f27 | ||
|
|
d7dfeaf0c1 | ||
|
|
fa532da8c7 | ||
|
|
cbf84647de | ||
|
|
dbfbd54e1f | ||
|
|
c38a490a6f | ||
|
|
9d7a450821 | ||
|
|
8007bea7db | ||
|
|
dc1ab7e331 | ||
|
|
a0d197d0c0 | ||
|
|
a776ba248e | ||
|
|
0ecd9673b8 | ||
|
|
97aa1230ef | ||
|
|
ff0be73b1f | ||
|
|
8049e44dec | ||
|
|
dc26022fb4 | ||
|
|
e8e44f9a32 | ||
|
|
12d56b8b03 | ||
|
|
e62fd7ed15 | ||
|
|
978eb95acd | ||
|
|
e34f4acb22 | ||
|
|
195aeb5caf | ||
|
|
15a600c763 | ||
|
|
82ad5839fa | ||
|
|
9af883231d | ||
|
|
9bfe8ac007 | ||
|
|
f46367d77b | ||
|
|
38649de85f | ||
|
|
eb2e1c0c45 | ||
|
|
33bb86cbcf | ||
|
|
baffc2d6ca | ||
|
|
96ab508c91 | ||
|
|
57e42659e3 | ||
|
|
f059e97697 | ||
|
|
516e6430eb | ||
|
|
f194a8ecf4 | ||
|
|
13f046f310 | ||
|
|
1edf5acb87 | ||
|
|
af636870d4 | ||
|
|
379b8ada61 | ||
|
|
5e63471983 | ||
|
|
086f0f8450 | ||
|
|
97a4b3dc2a | ||
|
|
4eb8d681c1 | ||
|
|
2066584164 | ||
|
|
a954c198fb | ||
|
|
bb1f8cd5e8 | ||
|
|
101e96dcb3 | ||
|
|
59adf82895 | ||
|
|
3b68f56b15 | ||
|
|
2962c4372c | ||
|
|
517e376582 | ||
|
|
7a90fe5aec | ||
|
|
ea45dde63a | ||
|
|
22a301b55e | ||
|
|
605177dcf0 | ||
|
|
460e1f5563 | ||
|
|
6f25337b99 | ||
|
|
08148a07b2 | ||
|
|
27c0e45940 | ||
|
|
bdd736315a | ||
|
|
d57ec0cd53 | ||
|
|
952c9d8bdb | ||
|
|
cf84ec78fa | ||
|
|
b595e84c30 | ||
|
|
6e5f115bd5 | ||
|
|
a1ab00c93e | ||
|
|
6e5c4e832e | ||
|
|
48ea487974 | ||
|
|
54dc98a90b | ||
|
|
c5bdd3d056 | ||
|
|
64d6e1f8e1 | ||
|
|
69d60ffb24 | ||
|
|
e6ffa3d143 | ||
|
|
bb4330e486 | ||
|
|
1a4d720978 | ||
|
|
91c2f479bb | ||
|
|
b61701fa17 | ||
|
|
cb9f910642 | ||
|
|
4b8d07f301 | ||
|
|
2db3a4f1ef | ||
|
|
ead1f65887 | ||
|
|
aae9866866 | ||
|
|
085ff84bc9 | ||
|
|
e12975cf0b | ||
|
|
a33cf6b532 | ||
|
|
5be25d9538 | ||
|
|
2b29eeb795 | ||
|
|
785561a0cc | ||
|
|
b46dc88346 | ||
|
|
96d81ef72b | ||
|
|
81dc3de26a | ||
|
|
4d0c572c2e | ||
|
|
fb2da0ee9e | ||
|
|
b8b0247717 | ||
|
|
103e212aee | ||
|
|
2f33575907 | ||
|
|
576c528573 | ||
|
|
3c444d3fb3 | ||
|
|
7cb499cde9 | ||
|
|
5a174ba014 | ||
|
|
041feb4e86 | ||
|
|
19726cf428 | ||
|
|
aaf134b1c5 | ||
|
|
9d5f5ee94b | ||
|
|
a48f0827ae | ||
|
|
5686158245 | ||
|
|
3824cdde68 | ||
|
|
e619b9bf7b | ||
|
|
b7243c2226 | ||
|
|
70b6674f44 | ||
|
|
ef67b8481e | ||
|
|
baffe4861c | ||
|
|
5cf489a270 | ||
|
|
44b1819926 | ||
|
|
d84c2b780b | ||
|
|
dc8991a1da | ||
|
|
7bd0ca2212 | ||
|
|
4dd619b8c6 | ||
|
|
3a86ab186c | ||
|
|
2f2a6367c2 | ||
|
|
be880c25f9 | ||
|
|
17812f0d77 | ||
|
|
0c5eae2349 | ||
|
|
3ad1803057 | ||
|
|
02c20e97b7 | ||
|
|
716dc781e4 | ||
|
|
d9900d8e4c | ||
|
|
3b9065b057 | ||
|
|
e73b748b95 | ||
|
|
b309161f00 | ||
|
|
b21667834e | ||
|
|
183fa59c83 | ||
|
|
0c5586ddfb | ||
|
|
33855bcb8b | ||
|
|
dc81b7a699 | ||
|
|
b0b2c32654 | ||
|
|
6f1ed76b4c | ||
|
|
f81cee0be2 | ||
|
|
bcd85b11a1 | ||
|
|
ec368ae3fd | ||
|
|
d28c264422 | ||
|
|
fba505bc90 | ||
|
|
c50ed2c328 | ||
|
|
7e11ff2b20 | ||
|
|
3fb83c46e2 | ||
|
|
763f2bd5c5 | ||
|
|
85edee288f | ||
|
|
1aa494a97a | ||
|
|
a8e7627184 | ||
|
|
d590bbdd2c | ||
|
|
80d65b5acb | ||
|
|
1d250f7491 | ||
|
|
dd741ec6d8 | ||
|
|
e741af6d55 | ||
|
|
e691b1b7c3 | ||
|
|
29142128f2 | ||
|
|
758f44e25f | ||
|
|
16c26d8098 | ||
|
|
a004c61afc | ||
|
|
a9d1a64c32 | ||
|
|
889224715b | ||
|
|
442b9d23f1 | ||
|
|
e4dd895709 | ||
|
|
82677c304e | ||
|
|
73d8dfe381 | ||
|
|
1177aa8aca | ||
|
|
0eda0a4935 | ||
|
|
a19dab0dc9 | ||
|
|
d8eb80b72e | ||
|
|
4f3a6821d1 | ||
|
|
77bd7541ca | ||
|
|
ca46bc5366 | ||
|
|
2e19bc07df | ||
|
|
3f4de43b67 | ||
|
|
0d0bf62fc4 | ||
|
|
3c8654fa25 | ||
|
|
756a6ec5aa | ||
|
|
98c7364924 | ||
|
|
62c01b59b2 | ||
|
|
43db1824be | ||
|
|
7f671c9f3f | ||
|
|
410009dd61 | ||
|
|
580cc00967 | ||
|
|
612c565cfd | ||
|
|
979c5351a8 | ||
|
|
97b7479081 | ||
|
|
1df2f5e96a | ||
|
|
0601833387 | ||
|
|
8b36279e52 | ||
|
|
32163d5f21 | ||
|
|
1c337f6817 | ||
|
|
6df26f2400 | ||
|
|
a9431a5aee | ||
|
|
5a3c832a98 | ||
|
|
c4b5bb22db | ||
|
|
2b5a976f35 | ||
|
|
5d7a625883 | ||
|
|
ae1ca85924 | ||
|
|
c9acfdb1d7 | ||
|
|
11ac8fbf13 | ||
|
|
dc541444ba | ||
|
|
8ea25bcd1c | ||
|
|
f5e46a663a | ||
|
|
64ec415a54 | ||
|
|
57154b2853 | ||
|
|
0243a902b2 | ||
|
|
6c04402a98 | ||
|
|
ef7c9b5c2a | ||
|
|
73448a6039 | ||
|
|
176a0ff99b | ||
|
|
b96d562700 |
35
.github/ISSUE_TEMPLATE.md
vendored
35
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,35 +0,0 @@
|
||||
<!--
|
||||
## Before you hit that Submit button....
|
||||
|
||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
||||
|
||||
If your issue is:
|
||||
- a general 'how-to' type question,
|
||||
- a feature request or suggestion for a change,
|
||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
||||
|
||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
## So you have a real issue to raise...
|
||||
|
||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
||||
-->
|
||||
|
||||
### What are the steps to reproduce?
|
||||
|
||||
### What happens?
|
||||
|
||||
### What do you expect to happen?
|
||||
|
||||
### Please tell us about your environment:
|
||||
|
||||
- [ ] Node-RED version:
|
||||
- [ ] Node.js version:
|
||||
- [ ] npm version:
|
||||
- [ ] Platform/OS:
|
||||
- [ ] Browser:
|
||||
- [ ] running in Docker:
|
||||
39
.github/ISSUE_TEMPLATE/--bug_report.md
vendored
39
.github/ISSUE_TEMPLATE/--bug_report.md
vendored
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Reproducible software issues in the core of Node-RED
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
||||
|
||||
If your issue is:
|
||||
- a general 'how-to' type question,
|
||||
- a feature request or suggestion for a change,
|
||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
||||
|
||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
||||
-->
|
||||
|
||||
### What are the steps to reproduce?
|
||||
|
||||
### What happens?
|
||||
|
||||
### What do you expect to happen?
|
||||
|
||||
### Please tell us about your environment:
|
||||
|
||||
- [ ] Node-RED version:
|
||||
- [ ] Node.js version:
|
||||
- [ ] npm version:
|
||||
- [ ] Platform/OS:
|
||||
- [ ] Browser:
|
||||
17
.github/ISSUE_TEMPLATE/-anything-else.md
vendored
17
.github/ISSUE_TEMPLATE/-anything-else.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Anything Else
|
||||
about: Something that is not a bug report
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please DO NOT raise an issue.
|
||||
|
||||
We DO NOT use the issue tracker for general support or feature requests. Only bug reports should be raised here using the 'Bug report' template.
|
||||
|
||||
For general support, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack). You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
For feature requests, please use the Node-RED Forum](https://discourse.nodered.org). Many ideas have already been discussed there and you should search that for your request before starting a new discussion.
|
||||
61
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: 🐞 Report a bug
|
||||
description: File a bug/issue on the core of Node-RED
|
||||
labels: [needs-triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
|
||||
|
||||
If your issue is:
|
||||
- a general 'how-to' type question,
|
||||
- a feature request or suggestion for a change,
|
||||
- or problems with 3rd party (`node-red-contrib-`) nodes
|
||||
|
||||
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
|
||||
|
||||
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
|
||||
|
||||
That way the whole Node-RED user community can help, rather than rely on the core development team.
|
||||
|
||||
To help us understand the issue, please fill-in as much of the following information as you can:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A clear & concise description of what you're experiencing.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear & concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Example flow
|
||||
description: If you have a minimal example flow that demonstrates the issue, share it here.
|
||||
value: |
|
||||
```
|
||||
paste your flow here
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Please tell us about your environment. Include any relevant information on how you are running Node-RED.
|
||||
value: |
|
||||
- Node-RED version:
|
||||
- Node.js version:
|
||||
- npm version:
|
||||
- Platform/OS:
|
||||
- Browser:
|
||||
validations:
|
||||
required: false
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: ❓ Questions
|
||||
url: https://discourse.nodered.org
|
||||
about: Ask your question on the Node-RED forum
|
||||
- name: ⭐️ Feature Request
|
||||
url: https://discourse.nodered.org/c/development/feature-requests
|
||||
about: Discuss your request with the community
|
||||
- name: 🗂 Documentation
|
||||
url: https://nodered.org/docs
|
||||
about: Go straight to the documentation
|
||||
- name: 💬 Slack
|
||||
url: https://nodered.org/slack
|
||||
about: Chat about the project on our slack team
|
||||
29
.github/scripts/update-node-red-docker.js
vendored
Normal file
29
.github/scripts/update-node-red-docker.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const newVersion = require("../../package.json").version;
|
||||
|
||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const currentVersion = require("../../../node-red-docker/package.json").version;
|
||||
|
||||
console.log(`Update from ${currentVersion} to ${newVersion}`)
|
||||
|
||||
updateFile(__dirname+"/../../../node-red-docker/package.json", currentVersion, newVersion);
|
||||
updateFile(__dirname+"/../../../node-red-docker/docker-custom/package.json", currentVersion, newVersion);
|
||||
updateFile(__dirname+"/../../../node-red-docker/README.md", currentVersion, newVersion);
|
||||
|
||||
console.log(`::set-env name=newVersion::${newVersion}`);
|
||||
|
||||
function updateFile(path,from,to) {
|
||||
let contents = fs.readFileSync(path,"utf8");
|
||||
contents = contents.replace(new RegExp(from.replace(/\./g,"\\."),"g"), to);
|
||||
fs.writeFileSync(path, contents);
|
||||
}
|
||||
18
.github/scripts/update-node-red-website.js
vendored
Normal file
18
.github/scripts/update-node-red-website.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const newVersion = require("../../package.json").version;
|
||||
|
||||
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
|
||||
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
|
||||
console.log(`Not updating for a non-stable release - ${newVersion}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const path = __dirname+"/../../../node-red.github.io/index.html";
|
||||
let contents = fs.readFileSync(path, "utf8");
|
||||
contents = contents.replace(/<span class="node-red-latest-version">v\d+\.\d+\.\d+<\/span>/, `<span class="node-red-latest-version">v${newVersion}<\/span>` );
|
||||
fs.writeFileSync(path, contents);
|
||||
59
.github/workflows/release.yml
vendored
Normal file
59
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Publish Release
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
name: 'Update node-red-docker image'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out node-red repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'node-red'
|
||||
- name: Check out node-red-docker repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'node-red/node-red-docker'
|
||||
path: 'node-red-docker'
|
||||
- name: Check out node-red.github.io repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'node-red/node-red.github.io'
|
||||
path: 'node-red.github.io'
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12'
|
||||
- run: node ./node-red/.github/scripts/update-node-red-docker.js
|
||||
- name: Create Docker Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
path: 'node-red-docker'
|
||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
||||
body: |
|
||||
Updates the Node-RED Docker repo for the ${{ env.newVersion }} release.
|
||||
|
||||
Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`.
|
||||
|
||||
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
|
||||
- name: Create Website Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.NR_REPO_TOKEN }}
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
path: 'node-red.github.io'
|
||||
commit-message: 'Bump to ${{ env.newVersion }}'
|
||||
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
|
||||
body: |
|
||||
Updates the Node-RED Website repo for the ${{ env.newVersion }} release.
|
||||
|
||||
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
|
||||
30
.github/workflows/tests.yml
vendored
Normal file
30
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm run test
|
||||
- name: Publish to coveralls.io
|
||||
if: ${{ matrix.node-version == 14 }}
|
||||
uses: coverallsapp/github-action@v1.1.2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -7,7 +7,9 @@
|
||||
.sessions.json
|
||||
.settings
|
||||
.tern-project
|
||||
.i18n-editor-metadata
|
||||
*.backup
|
||||
*.bak
|
||||
*_cred*
|
||||
coverage
|
||||
credentials.json
|
||||
@@ -22,4 +24,6 @@ packages/node_modules/@node-red/editor-client/public
|
||||
!test/**/node_modules
|
||||
docs
|
||||
!packages/node_modules/**/docs
|
||||
.vscode
|
||||
.vscode
|
||||
.nyc_output
|
||||
sync.ffs_db
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,15 +0,0 @@
|
||||
sudo: false
|
||||
addons:
|
||||
chrome: stable
|
||||
language: node_js
|
||||
matrix:
|
||||
include:
|
||||
- node_js: "14"
|
||||
- node_js: "12"
|
||||
- node_js: "10"
|
||||
script:
|
||||
- ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
|
||||
- scripts/install-ui-test-dependencies.sh && grunt test-ui
|
||||
before_script:
|
||||
- npm install -g istanbul coveralls
|
||||
- node_js: "8"
|
||||
16
API.md
16
API.md
@@ -1,8 +1,12 @@
|
||||
Node-RED Modules
|
||||
---
|
||||
Node-RED consists of 6 node modules under the `@node-red` scope, which are pulled together
|
||||
by the top-level `node-red` module. The typical scenario is where you are embedding Node-RED into your
|
||||
own application, in which case you would use the `node-red` module rather than any of the
|
||||
internal modules directly.
|
||||
|
||||
```javascript
|
||||
let RED = require("node-red");
|
||||
```
|
||||
|
||||
Node-RED provides a set of node modules that implement different parts of the
|
||||
application.
|
||||
|
||||
Module | Description
|
||||
-------|-------
|
||||
@@ -10,6 +14,6 @@ Module | Description
|
||||
[@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API
|
||||
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
|
||||
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
|
||||
@node-red/registry | the internal node registry
|
||||
@node-red/nodes | the default set of core nodes
|
||||
[@node-red/registry](@node-red_registry.html) | the internal node registry
|
||||
@node-red/nodes | the default set of core nodes. This module only contains the Node-RED nodes - it does not expose any APIs.
|
||||
@node-red/editor-client | the client-side resources of the Node-RED editor application
|
||||
|
||||
861
CHANGELOG.md
861
CHANGELOG.md
@@ -1,3 +1,864 @@
|
||||
#### 2.1.2: Maintenance Release
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- node-red-pi: Remove bash dependency (#3216) @a16bitsysop
|
||||
|
||||
Editor
|
||||
|
||||
- Improved regex for markdown renderer (#3213) @GerwinvBeek
|
||||
- Fix TypedInput initialisation (#3220) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl
|
||||
|
||||
#### 2.1.1: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary
|
||||
- Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary
|
||||
- Add cache-busting query params to index.mst (#3211) @knolleary
|
||||
- Fix TypedInput validation of type without options (#3207) @knolleary
|
||||
|
||||
#### 2.1.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Position popover properly on a scrolled page
|
||||
- Fixes from 2.1.0-beta.2 (#3202) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Link Out: Fix saving link out node links (#3201) @knolleary
|
||||
- Switch: Refix #3170 - copy switch rule type when adding new rule
|
||||
- TCP Request: Add string option to TCP request node output (#3204) @dceejay
|
||||
|
||||
#### 2.1.0-beta.2: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix switching projects (#3199) @knolleary
|
||||
- Use locale setting when installing/enabling node (#3198) @knolleary
|
||||
- Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary
|
||||
- Fix converting selection to subflow (#3196) @knolleary
|
||||
- Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary
|
||||
- Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary
|
||||
- Ensure tab state updates properly when toggling enable state (#3175) @knolleary
|
||||
- Improve handling of long labels in TreeList (#3176) @knolleary
|
||||
- Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary
|
||||
|
||||
Runtime
|
||||
|
||||
- Update package dependencies
|
||||
- Update to latest node-red-admin
|
||||
|
||||
Nodes
|
||||
|
||||
- Dynamic MQTT connections (#3189)
|
||||
- Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187
|
||||
- Link: Fix link call label (#3200) @knolleary
|
||||
- Debug: Redesign debug filter options and make them persistant (#3183) @knolleary
|
||||
- Inject: Widen Inject interval box for >1 digit (#3184) @knolleary
|
||||
- Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary
|
||||
|
||||
#### 2.1.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add Tour Guide component (#3136) @knolleary
|
||||
- Allow tabs to be hidden (#3120) @knolleary
|
||||
- Add align actions to editor (#3110) @knolleary
|
||||
- Add support of environment variable for tab & group (#3112) @HiroyasuNishiyama
|
||||
- Add autoComplete widget and add to TypedInput for msg. props (#3171) @knolleary
|
||||
- Render node documentation to node-red style guide when written in markdown. (#3169) @Steve-Mcl
|
||||
- Allow colouring of tab icon svg (#3140) @harmonic7
|
||||
- Restore tab selection after merging conflicts (#3151) @GerwinvBeek
|
||||
- Fix serving of theme files on Windows (#3154) @knolleary
|
||||
- Ensure config-node select inherits width properly from input (#3155) @knolleary
|
||||
- Do better remembering TypedInput values whilst switching types (#3159) @knolleary
|
||||
- Update monaco to 0.28.1 (#3153) @knolleary
|
||||
- Improve themeing of tourGuide (#3161) @knolleary
|
||||
- Allow a node to specify a filter for the config nodes it can pick from (#3160) @knolleary
|
||||
- Allow RED.notify.update to modify any notification setting (#3163) @knolleary
|
||||
- Fix typo in ko editor.json Fixes #3119
|
||||
- Improve RED.actions api to ensure actions cannot be overridden
|
||||
- Ensure treeList row has suitable min-height when no content Fixes #3109
|
||||
- Refactor edit dialogs to use separate edit panes
|
||||
- Ensure type select button is not focussable when TypedInput only has one type
|
||||
- Place close tab link in front of fade
|
||||
|
||||
Runtime
|
||||
|
||||
- Improve error reporting with oauth login strategies (#3148) @knolleary
|
||||
- Add allowUpdate feature to externalModules.palette (#3143) @knolleary
|
||||
- Improve node install error reporting (#3158) @knolleary
|
||||
- Improve unit test coverage (#3168) @knolleary
|
||||
- Allow coreNodesDir to be set to false (#3149) @hardillb
|
||||
- Update package dependencies
|
||||
- uncaughtException debug improvements (#3146) @renatojuniorrs
|
||||
|
||||
Nodes
|
||||
|
||||
- Change: Add option to deep-clone properties in Change node (#3156) @knolleary
|
||||
- Delay: Add push to front of rate limit queue. (#3069) @dceejay
|
||||
- File: Add paletteLabel to file nodes to make read/write more obvious (#3157) @knolleary
|
||||
- HTTP Request: Extend HTTP request node to log detailed timing information (#3116) @k-toumura
|
||||
- HTTP Response: Fix sizing of HTTP Response header fields (#3164) @knolleary
|
||||
- Join: Support for msg.restartTimeout (#3121) @magma1447
|
||||
- Link Call: Add Link Call node (#3152) @knolleary
|
||||
- Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary
|
||||
- Delay node: add option to send intermediate messages on separate output (#3166) @knolleary
|
||||
- Typo in http request set method translation (#3173) @mailsvb
|
||||
|
||||
#### 2.0.6: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix typo in ko editor.json Fixes #3119
|
||||
- Change fade color when hovering an inactive tab (#3106) @bonanitech
|
||||
- Ensure treeList row has suitable min-height when no content Fixes #3109
|
||||
|
||||
Runtime
|
||||
|
||||
- Update tar to latest (#3128) @aksswami
|
||||
- Give passport verify callback the same arity as the original callback (#3117) @dschmidt
|
||||
- Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers
|
||||
|
||||
#### 2.0.5: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Remove default ctrl-enter keybinding from monaco editor Fixes #3093
|
||||
|
||||
Runtime
|
||||
|
||||
- Update tar dependency
|
||||
- Add support for maintenance streams in generate-publish-script
|
||||
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix regression in Join node when manual joining array with msg.parts present Fixes #3096
|
||||
|
||||
#### 2.0.4: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix tab fade CSS for when using themes (#3085) @bonanitech
|
||||
- Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090
|
||||
|
||||
Nodes
|
||||
|
||||
- Filter: Fix RBE node handling of default topi property Fixes #3087
|
||||
- HTTP Request: Handle partially encoded url query strings in request node
|
||||
- HTTP Request: Fix support for supplied CA certs (#3089) @hardillb
|
||||
- HTTP Request: Ensure TLS Cert is used (#3092) @hardillb
|
||||
- Inject: Fix inject now button unable to send empty props
|
||||
- Inject: Inject now button success notification should use label with updated props
|
||||
|
||||
#### 2.0.3: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- HTML: Fix HTML parsing when body is included in the select tag Fixes #3079
|
||||
- HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081
|
||||
- HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083
|
||||
- HTTP Request: Add unit tests for HTTP Request encodeURI and error response
|
||||
- HTTP Request: Do not throw HTTP errors in request node Fixes #3082
|
||||
- HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080
|
||||
|
||||
#### 2.0.2: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Use file:// url with dynamic import
|
||||
- Detect if agent-base has patched https.request and undo it Fixes #3072
|
||||
|
||||
Editor
|
||||
|
||||
- Fix tab fade css because Safari Fixes #3073
|
||||
- Fix error closing library dialog with monaco
|
||||
- Handle other error types in Manage Palette view
|
||||
|
||||
|
||||
Nodes
|
||||
|
||||
- HTTP Request node - ignore invalid cookies rather than fail request Fixes #3075
|
||||
- Fix msg.reset handling in Delay node Fixes #3074
|
||||
|
||||
#### 2.0.1: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- Function: Ensure default module export is exposed in Function node
|
||||
|
||||
#### 2.0.0: Milestone Release
|
||||
|
||||
**Migration from 1.x**
|
||||
|
||||
- Node-RED now requires Node.js 12.x or later.
|
||||
|
||||
- The following nodes have had significant dependency updates. Unless stated,
|
||||
they should be fully backward compatible.
|
||||
|
||||
- RBE: Relabelled as 'filter' to make it more discoverable and made part of
|
||||
the core palette, rather than as a separate module.
|
||||
- Tail: This node has been removed from the default palette. You can reinstall it
|
||||
from node-red-node-tail
|
||||
- HTTP Request: Reimplemented with a different underlying module. We have
|
||||
tried to maintain 100% functional compatibility, but it is possible
|
||||
some edge cases remain.
|
||||
- JSON: The schema validation option no longer supports JSON-Schema draft-04
|
||||
- HTML: Its underlying module has had a major version update. Should be fully
|
||||
backward compatible.
|
||||
|
||||
- `functionExternalModules` is now enabled by default for new installs.
|
||||
If you have an existing settings file that contains this setting, you will
|
||||
need to set it to `true` yourself.
|
||||
|
||||
The external modules will now get installed in your Node-RED user directory,
|
||||
(`~/.node-red`) rather than in a subdirectory. This means all dependencies will
|
||||
be listed in your top-level `package.json`. If you have existing external modules,
|
||||
they will get reinstalled to the new location when you first run Node-RED 2.0.
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- Fix missing dependencies (#3052, #2057) @kazuhitoyokoi
|
||||
- Ensure node.types is defined if node html file missing
|
||||
- Fix reporting of type_already_registered error
|
||||
- Move install location of external modules (#3064) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Update translations (#3063) @kazuhitoyokoi
|
||||
- Add a slight fade to tab labels that overflow
|
||||
- Show config node details when selected in outliner
|
||||
- Fix layout of info outliner for subflow entries
|
||||
|
||||
Nodes
|
||||
|
||||
- Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay
|
||||
- Function: external modules is now enabled by default (#3065) @knolleary
|
||||
- Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary
|
||||
- WebSocket: add option for client node to send automatic pings (#3056) @knolleary
|
||||
|
||||
|
||||
##### 2.0.0-beta.2: Beta Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Add `node-red admin init` (via `node-red-admin@2.1.0`)
|
||||
- Move to GH Actions rather than Travis for build (#3042) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- Include hasUser=false config nodes when exporting whole flow (#3048)
|
||||
- Emit nodes:change for any updated config node when node deleted/added
|
||||
- Fix padding of compact notification Closes #3045
|
||||
- Ensure any html in changelog is escaped before displaying
|
||||
- Add support for Map/Set property types on Debug (#3040) @knolleary
|
||||
- Add 'theme' to default settings file
|
||||
- Add RED.view.annotations api (#3032) @knolleary
|
||||
- Update monaco editor to V0.25.2 (#3031) @Steve-Mcl
|
||||
- Lower tray zIndex when overlay tray being opened Fixes #3019
|
||||
- Reduce z-Index of Function expand buttons to prevent overlap Part of #3019
|
||||
- Ensure RED.clipboard.import displays the right library Fixes #3021
|
||||
- Batch messages sent over comms to prevent flooding (#3025) @knolleary
|
||||
- Allow RED.popover.panel to specify a closeButton to ignore click events on
|
||||
- Use browser default language for initial page load
|
||||
- Add css var for node font color
|
||||
- Fix label padding of toggleButton
|
||||
- Give sidebar open tab a bit more room for its label
|
||||
- Various Monaco updates (#3015) @Steve-Mcl
|
||||
- Log readOnly on startup (#3024) @sammachin
|
||||
- Translation updates (#3020 #3022) @HiroyasuNishiyama @kazuhitoyokoi
|
||||
|
||||
Nodes
|
||||
|
||||
- HTTP Request: Fix proxy handling (#3044) @hardillb
|
||||
- HTTP Request: Handle basic auth with @ in username (#3017) @hardillb
|
||||
- Add Japanese translation for file-in node (#3037 #3039) @kazuhitoyokoi
|
||||
- File In: Add option for file-in node to include all properties (default off) (#3035) @dceejay
|
||||
- Exec: add windowsHide option to hide windows under Windows (#3026) @natcl
|
||||
- Support loading external module sub path Fixes #3023
|
||||
|
||||
##### 2.0.0-beta.1: Beta Release
|
||||
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- [MAJOR] Set minimum node version to 12.
|
||||
- [MAJOR] Fix flowfile name to flows.json in settings (#2951) @dceejay
|
||||
- [MAJOR] Update to latest i18n in editor and runtime (#2940) @knolleary
|
||||
- [MAJOR] Deprecate usage of httpRoot (#2953) @knolleary
|
||||
- Add pre/postInstall hooks to npm install handling (#2936) @knolleary
|
||||
- Add engine-strict flag to npm install args (#2965) @nileio
|
||||
- Restructure default settings.js to be more organised (#3012) @knolleary
|
||||
- Ensure httpServerOptions gets applied to ALL the express apps
|
||||
- Allow RED.settings.set to replace string property with object property
|
||||
- Update debug tests to handle compact comms format
|
||||
- Updates to encode/decode message when passed over debug comms link
|
||||
- Remove all input event listeners on a node once it is closed
|
||||
- Move hooks to util package
|
||||
- Rework hooks structure to be a linkedlist
|
||||
- Update dependencies (#2922) @knolleary
|
||||
|
||||
Editor
|
||||
|
||||
- [MAJOR] Change node id generation to give fixed length values without '.' (#2987) @knolleary
|
||||
- [MAJOR] Add Monaco code editor (#2971) @Steve-Mcl
|
||||
- Update to latest Monaco (#3007) @Steve-Mcl
|
||||
- Update Node-RED Function typings in Monaco (#3008) @Steve-Mcl
|
||||
- Add css named variables for certain key colours (#2994) @knolleary
|
||||
- Improve contrast of export dialog JSON font color
|
||||
- Switch editableList buttons from <a> to <button> elements
|
||||
- Add option to RED.nodes.createCompleteNodeSet to include node dimensions
|
||||
- Fix css of node help table of contents elements
|
||||
- Improve red-ui-node-icon css and add red-ui-node-icon-small modifier class
|
||||
- Add RED.hooks to editor
|
||||
- Add viewAddPort viewRemovePort viewAddNode viewRemoveNode hooks to view
|
||||
- Use paletteLabel if set in help sidebar
|
||||
- Add missing args from JSONata $now signature
|
||||
|
||||
Nodes
|
||||
|
||||
- [MAJOR] Relabel RBE node as 'filter' and move into core. Also remove tail (#2944) @dceejay
|
||||
- [MAJOR] HTTP Request: migrate to 'got' module (#2952) @knolleary
|
||||
- [MAJOR] Move Inject node to CronosJS module (#2959) @knolleary
|
||||
- [MAJOR] JSON: Update ajv to 8.2.0 - drop support for JSON-Schema draft-04 (#2969) @knolleary
|
||||
- [MAJOR] HTML node: cheerio update to 1.x (#3011) @knolleary
|
||||
- Join: change default manual mode to object (#2931) @knolleary
|
||||
- File node: Add fileWorkingDirectory (#2932) @knolleary
|
||||
- Delay node enhancements (#2294) @kazuhitoyokoi (#2949) @dceejay
|
||||
- Add Japanese translations for delay node enhancements (#2958) @kazuhitoyokoi
|
||||
- Inject node: reorder TypedInput options (#2961) @dceejay
|
||||
- HTTP Request: update to work with proxies (#2983) @hardillb (#3009) @hardillb
|
||||
- HTTP Request: fix msg.responseUrl (#2986) @hardillb
|
||||
- TLS: Add ALPN support to TLS node (#2988) @hardillb
|
||||
- Inject: add "Inject now" button to edit dialog (#2990) @Steve-Mcl
|
||||
|
||||
### 1.3.5 Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Open subflow tab next to active tab rather than at the end
|
||||
- Shrink default notification box
|
||||
- Support mousewheel scroll in tab bar
|
||||
- Revert some of #2967 to fix treeList gutter width calculation
|
||||
- Prevent unknown node from breaking editor
|
||||
- Stop module with missing types from preventing editor load
|
||||
- Handle sidebar tab that no longer exists when setting first active
|
||||
- Fix plugin loading when browser sends unrecognised lang
|
||||
- Prevent error whilst drag/drop importing from leaving dropTarget visible Fixes #2982
|
||||
- Fix scaling issues when dragging nodes into scaled workspace
|
||||
- Fix incorrect shortcut keys in info tips (#2980) @kazuhitoyokoi
|
||||
- Reduce code duplication around node/label generation
|
||||
- Fix theme handling when no editorTheme.page setting
|
||||
- Fix jshint error in treeList
|
||||
|
||||
Runtime
|
||||
|
||||
- Fix error handling in runtime/lib/api/nodes
|
||||
- Add Node 16 with sass fixed
|
||||
- Migrate from node-sass to sass (#2984)
|
||||
- Fix "installRetry" was declared a constant and changed (#2974) @aheissenberger
|
||||
|
||||
Nodes
|
||||
|
||||
- Function: Fix 'SyntaxError' in Function node when last line of on-stop is a comment
|
||||
- Function: Fix Function tab label names in the node help text Closes #2978
|
||||
- Function: Update Japanese info text of function node (#2985) @HiroyasuNishiyama
|
||||
|
||||
### 1.3.4 Maintenance Release
|
||||
|
||||
Editor
|
||||
- Allow nodes to access resolved theme files Fixes #2968
|
||||
- Fix importing node to currently flow rather than match its old z value
|
||||
- Don't let 'escape' whilst moving nodes interrupt things Fixes #2960
|
||||
- Sort context stores in TypedInput and ensure default first Fixes #2954
|
||||
- Fix margin between nodes on palette (#2947) @kazuhitoyokoi
|
||||
- Ensure typedInput option is selected in dropdown menu Part of #2945
|
||||
- Ensure typedInput without value has focus class removed Closes #2945
|
||||
- TreeList: Fix remove item when depth=0 and wrong gutter calc (#2967) @hanc2006
|
||||
|
||||
Runtime
|
||||
- Handle subflow modules that contain subflows
|
||||
- Timeout http upgrade requests that are not otherwise handled Fixes #2956
|
||||
- Fix error on auto commit for no flow change (#2957) @HiroyasuNishiyama
|
||||
|
||||
Nodes
|
||||
|
||||
- CSV: Fix CSV handling of special chars as separators
|
||||
- Delay: Give delay node random mina nd max more space so you can see complete value
|
||||
- Exec: fix grunt fail on exec node test (#2964) @HiroyasuNishiyama
|
||||
- Function: Ensure function expand button is above vertical scrollbar Fixes #2955
|
||||
- Inject: Fix inject node output tooltip extra property count
|
||||
|
||||
|
||||
### 1.3.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix package semver comparison to allow >1 version increment
|
||||
- Prevent TypedInput label overflowing element Fixes #2941
|
||||
- Remove TypedInput from tab focus when only one type available
|
||||
- Make typedInput.disable more consistent in behaviour Fixes #2942
|
||||
- Fix project credential secret reset handling Part of #2868
|
||||
|
||||
Runtime
|
||||
|
||||
- Export package version in Grunt file so docs template can access
|
||||
|
||||
Nodes
|
||||
|
||||
- CSV: ensure CSV node can send false as string
|
||||
- HTTPIn: handle application/x-protobuf as Buffer type (#2935 #2938) @hardillb
|
||||
- MQTT: Ensure mqtt-close message is published when closing mqtt nodes
|
||||
|
||||
|
||||
### 1.3.2: Maintenance Release
|
||||
|
||||
Runtime
|
||||
- Handle package.json without dependencies section
|
||||
|
||||
Editor
|
||||
|
||||
- Fix variable reference error in editableList Fixes #2933
|
||||
- Fix handling of user-provided keymap Fixes #2926
|
||||
- Ensure theme login image is passed through to api response Fixes #2929
|
||||
- Add Japanese translations for Node-RED v1.3.1 (#2930) @kazuhitoyokoi
|
||||
|
||||
Nodes
|
||||
|
||||
- CSV: Fix CSV parsing with other than , separator
|
||||
- File out: Fix timing of msg.send to be after close
|
||||
- Function: describe `node.outputCount` in help text
|
||||
- MQTT: Fix MQTT Broker TLS config row layout Fixes #2927
|
||||
- Split: add comment to info re $N being number of messages arriving
|
||||
|
||||
### 1.3.1: Maintenance Release
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix change node form validation
|
||||
|
||||
### 1.3.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Remember TypedInput selected sub option when switching types Fixes #2896
|
||||
- Show context store name on TypedInput flow/global types Fixes #2793
|
||||
- Add core:go-to-selected-subflow action
|
||||
- Ctrl-dbclick on subflow node opens subflow tab
|
||||
- Add go-to-previous/next-location actions
|
||||
- Fix copy-to-clipboard action in FireFox
|
||||
- Fix select up/down stream when zoomed in or out
|
||||
- Use cursor keys to change selection in workspace
|
||||
- Prevent accidental text selection of subflow toolbar text
|
||||
- Update node-sass to 5.x Fixes #2907
|
||||
- Allow module to provide resources and automatically expose them (#2903) @knolleary
|
||||
|
||||
Runtime
|
||||
|
||||
- DE language updates (#2806 #2901 #2913) @heikokue
|
||||
- Remove Node 8 from travis due to node-sass breakage
|
||||
- Allow Flow.getNode to return subflowInstance nodes Related to #2898
|
||||
- Fix credential lookup for nested subflows Fixes #2910
|
||||
- Add externalModules config to settings.js
|
||||
- Add Japanese translations for Node-RED v1.3.0 (#2900)
|
||||
- Fix handling encrypted creds on /flows api
|
||||
- Properly handle credentials passed to /flows api
|
||||
- Fix line-number reporting in errors on node load (#2894) @HiroyasuNishiyama
|
||||
|
||||
Nodes
|
||||
|
||||
- Change: Add property validation to Change node rule set Closes #2911
|
||||
- Exec: Allow any property to be appended to command (#2908) @kazuhitoyokoi
|
||||
- HTTP Request: set followAllRedirects to work with POSTs Fixes #2017
|
||||
- Inject: Flag validation errors in Inject node props config Fixes #2914
|
||||
- Function: add node.outputCount to sandbox (#2918) @kristianheljas
|
||||
- Switch: Fix Switch node handling of hasKey rule when property is undefined
|
||||
- Switch: Handle invalid regex set dynamically in Switch node Fixes #2905
|
||||
|
||||
|
||||
### 1.3.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add config node to refer to when exporting subflow
|
||||
- Add confirm dialog when deleting subflow with instances in use (#2845) @knolleary
|
||||
- Add easier ways to find subflow instances
|
||||
- Add enable/disable toggle button for groups in info-outliner (#2844) @knolleary
|
||||
- Add IE11 polyfill to support URI download scheme (#2871) @HiroyasuNishiyama
|
||||
- Add Japanese translations for Node-RED v1.3.0 (#2874) @kazuhitoyokoi
|
||||
- Add preview of exported nodes to Export dialog (#2820) @knolleary
|
||||
- Add RED.plugins module to editor
|
||||
- Add select-connected action (#2877) @knolleary
|
||||
- Add select-up/downstream-nodes action to editor (#2877) @knolleary
|
||||
- Add subflow edit button to palette tooltip
|
||||
- Add subflow meta data edit pane
|
||||
- Add support for library source plugins (#2785) @knolleary
|
||||
- Adds shift-click support for selecting up/down stream nodes
|
||||
- Allow default keymap to be overridden in settings file (#2843) @knolleary
|
||||
- Allow EditableList to have custom buttons (#2881) @bartbutenaers
|
||||
- Allow filtering of debug node output within subflow (#2870) @HiroyasuNishiyama
|
||||
- Ensure the first sidebar tab is shown when editor loads (#2846) @knolleary
|
||||
- Ensure TypedInput Change event is passed type/value properties Fixes #2883
|
||||
- Escape all user input
|
||||
- Filter palette manager nodes based on allow/deny list
|
||||
- Fix check for existing config nodes in subflow export set
|
||||
- Fix handling of + in shortcuts
|
||||
- fix jshint failure (#2850) @HiroyasuNishiyama
|
||||
- Fix keymap entries with multiple keys for same action
|
||||
- fix line break of exporting nodes to clipboard (#2849) @HiroyasuNishiyama
|
||||
- Fix line break of subflow label on palette (#2828)
|
||||
- Fix loading individual module catalog
|
||||
- Fix removing links when deleting node
|
||||
- Fix semver comparison for IE11 (#2888) @knolleary
|
||||
- fixed #2790 swapped description of encodeUrl/encodeUrlComponent and d… (#2791)
|
||||
- Handle timeouts when trying to load node credentials in editor Fixes #2840 (#2841) @knolleary
|
||||
- Hide projects dialog when opening proj with invalid encrypt key
|
||||
- hide unused input field (#2823)
|
||||
- Implement node property typing (#2812) @knolleary
|
||||
- Improve SemVer comparison in Palette Manager (#2821 #2879) @HaKr
|
||||
- Library: properly handle symlinked folders (#2768) @natcl
|
||||
- make flow download code separate utility instead of polyfill
|
||||
- Prevent duplicate keyboard shortcut from being assigned
|
||||
- Prevent rogue mouseup on tab from triggering tab change
|
||||
- Rename paletteEditorEnabled to installerEnabled
|
||||
- Tidy some subflow env props css
|
||||
- Tidy up typedInput syntax
|
||||
- Use subflow.info for help text and meta.type for node type
|
||||
|
||||
Runtime
|
||||
|
||||
- Deprecate autoInstallModules for externalModules.autoInstall
|
||||
- Deprecate editorTheme.palette.editable for externalModules.palette.allowInstall
|
||||
- Initial plugin runtime api implementation (#2779) @knolleary
|
||||
- Add initial support for ThemePlugins (#2836) @knolleary
|
||||
- Support npm subflow modules (#2690) @knolleary
|
||||
- Ability to add projects path to the settings file (#2816) @tfmf
|
||||
- Add i18n function to editor plugins when they are registered
|
||||
- Add optional 'lang' to settings file (#2796) @fellinga
|
||||
- Add SubflowModule class for running subflow modules
|
||||
- Add support for settings.externalModules (#2797) @knolleary
|
||||
- Allow default project workflow to be set via settings (#2763) @knolleary
|
||||
- Allow for adding an array of middleware functions (#2788) @kevinGodell
|
||||
- Better logging when deprecated editorTheme.palette.* settings used
|
||||
- Detect externalModule dependencies inside subflow modules
|
||||
- Fix global leak in lib/flows/index.js
|
||||
- Fix numeric status not displaying by ensuring it's a string (#2859) @knolleary
|
||||
- Fully remove when.js dependency (#2772) @knolleary
|
||||
- make nodes with only group change not deployed by nodes deploy mode
|
||||
- Move exec and events components to util module
|
||||
- Nodes log via parent flow to allow flow-info to be added
|
||||
- Restart node only if node's group changes (#2872) @HiroyasuNishiyama
|
||||
- Stop config nodes after flow nodes Fixes #2876 (#2880) @knolleary
|
||||
- Update marked dependency
|
||||
- Use more async funcs in runtime/lib/api to reduce Promise creation
|
||||
- Use npm info to check pending install version
|
||||
|
||||
Nodes
|
||||
|
||||
- Allow nested msg properties in msg/flow/global expressions (#2822)
|
||||
- Batch: Messaging API support in Batch node (#2738) @k-toumura
|
||||
- CSV: Handle commas in msg.columns if quoted.
|
||||
- CSV: Fix csv node template reset when array complete (#2854) @dceejay
|
||||
- CSV: Messaging API support in CSV node (#2734) @k-toumura
|
||||
- Debug: Sanitize Debug node name when display enable/disable message
|
||||
- Delay: Add support for Messaging API to delay node (#2733)
|
||||
- Exec: Add settings.execMaxBufferSize to control buffer size of exec node (#2819)
|
||||
- Exec: Don't append msg.payload to command by default (#2818)
|
||||
- Function: Add 'node' object to close scope
|
||||
- Function: allow to load external modules (#2873) @knolleary
|
||||
- Function: Add functionExternalModules to settings and default to false
|
||||
- Join: Fix join node in array mode with repeated messages, and allow reset all (#2869) @dceejay
|
||||
- MQTT: Add MQTT v5 support (#2778 #2886) @Steve-Mcl
|
||||
- Sort: Messaging API support in Sort node (#2744) @k-toumura
|
||||
- Split/Join: Messaging API support in Split/Join nodes (#2750) @k-toumura
|
||||
- Trigger: Messaging API support in Trigger node (#2751) @k-toumura
|
||||
- Add example flows for storage nodes (#2784) @HiroyasuNishiyama
|
||||
- Add example flows for network nodes (#2855) @HiroyasuNishiyama
|
||||
- Add example flows for parser nodes (#2749) @HiroyasuNishiyama
|
||||
|
||||
|
||||
|
||||
### 1.2.9: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Sanitize node type names when displaying in notifications
|
||||
- Sanitize branch name before displaying in notification message
|
||||
|
||||
Runtime
|
||||
|
||||
- Handle more valid language codes when validating lang params Fixes #2856
|
||||
|
||||
### 1.2.8: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Ensure subflow help is picked up for palette tooltip Fixes #2834
|
||||
- Improve Ru locale (#2826) @alexk111
|
||||
- Fix scrollbars (#2825) @alexk111
|
||||
|
||||
Runtime
|
||||
|
||||
- Restrict project file access to inside the project directory
|
||||
- Validate user-provided language parameter before passing to i18n
|
||||
- Fix grunt release mkdir issue on Node.js 14 (#2827) @alexk111
|
||||
- Prevent crash when coreNodesDir is empty (#2831) @hardillb
|
||||
|
||||
Nodes
|
||||
|
||||
- Batch node: Fixing minor typo in node's documentation (#2848) @matthiasradde
|
||||
- Split node: Handle out of order messages as long as one of the messages has msg.parts.count set to the proper value (#2748) @s4ke
|
||||
|
||||
### 1.2.7: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Ensure subflow-scoped config nodes do not get moved on import Fixes #2789
|
||||
- Allow TypedInput to be disabled (#2752) @bartbutenaers
|
||||
- Allow userMenu to be explicitly enabled (#2805) @tfmf
|
||||
- Improvements to DE translation (#2192) @ketzu
|
||||
|
||||
|
||||
Runtime
|
||||
|
||||
- Handle `undefined` error passed to node.error (#2781) @johnwang71
|
||||
- Disable nyc coverage reporting on older node versions
|
||||
- Improve Editor API unit test coverage (#2777) @aaronmyatt
|
||||
|
||||
|
||||
Nodes
|
||||
|
||||
- Trigger: ensure timestamp option sends .now() at point of sending
|
||||
|
||||
|
||||
### 1.2.6: Maintenance Release
|
||||
|
||||
|
||||
Editor
|
||||
|
||||
- Update Japanese translations for 1.2.5 (#2764) @kazuhitoyokoi
|
||||
- Library: properly handle symlinked folders (#2768) @natcl
|
||||
|
||||
Runtime
|
||||
|
||||
- Support Windows paths when installing tarball by path name Fixes #2769
|
||||
- Fix unsecure command usage in GH Action
|
||||
|
||||
Nodes
|
||||
|
||||
- Update MQTT to latest to fix Node 8 URL breakage
|
||||
|
||||
|
||||
|
||||
|
||||
### 1.2.5: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix import of config nodes with unknown z property
|
||||
|
||||
Runtime
|
||||
|
||||
- Set ACTIONS_ALLOW_UNSECURE_COMMANDS in GH Action
|
||||
|
||||
### 1.2.4: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Support bigint types in Debug sidebar
|
||||
- Clear retained status of deleted nodes
|
||||
- Prevent needless retention of node status messages
|
||||
- Update projects dialogs to use TypedInput-cred input
|
||||
- Restore cursor position in TypedInput cred-mode
|
||||
- Ensure config nodes with invalid z are imported somewhere
|
||||
- Ensure user keyboard shortcuts override defaults Fixes #2753
|
||||
|
||||
Runtime
|
||||
|
||||
- Disable projects when flowFile passed into grunt dev
|
||||
- Add Russian Locale (#2761) (#2531) (@alexk111)
|
||||
- Add Japanese translation for http-in node (#2758) (@kazuhitoyokoi)
|
||||
|
||||
Nodes
|
||||
|
||||
- CSV: Fix CSV node repeating array output
|
||||
|
||||
### 1.2.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Disable 'use strict' checking in Function node Fixes #2743
|
||||
- Add gray/grey alternate options for status
|
||||
- Handle import errors on initial load and report to user
|
||||
- Only apply recovery tab on initial load Fixes #2731
|
||||
- Reinstate coveralls reporting to travis build
|
||||
- Update Japanese message catalogue for 1.2.3 release #2747 (@HiroyasuNishiyama)
|
||||
|
||||
Runtime
|
||||
|
||||
- Modify default settings comment (#2739)
|
||||
- Add mutex lock to saveSettings storage call Fixes #2736 (#2737)
|
||||
- Migrate to nyc instead of istanbul for code coverage
|
||||
- Move mosca to ui-test-dependencies
|
||||
- Remove " from npm install prefix option
|
||||
|
||||
### 1.2.2: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Prevent node z property getting set to 0 or ""
|
||||
- Only apply z-recovery logic to flow nodes
|
||||
- Fix api call to reload flows Fixes #2726
|
||||
- Remove bad z property from import config nodes
|
||||
|
||||
### 1.2.1: Maintenance Release
|
||||
|
||||
Runtime
|
||||
|
||||
- Fix race condition in .config file migration Fixes #2724
|
||||
|
||||
|
||||
### 1.2.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix selection of link node not existing within active workspace #2722 (@HiroyasuNishiyama)
|
||||
- Fix import of merged flow
|
||||
- Fix width of upload button in Safari #2718 (@HiroyasuNishiyama)
|
||||
- Update Chinese translations #2719 (@JiyeYu)
|
||||
- Update Japanese translations needed for 1.2 #2710 (@kazuhitoyokoi)
|
||||
- Fix unexpected line break of sidebar tab name popover #2716 (@HiroyasuNishiyama)
|
||||
- i18n module refresh tooltip #2717 (@HiroyasuNishiyama)
|
||||
- Add better error message if context file gets corrupted
|
||||
- Update info text of function node #2714 (@HiroyasuNishiyama)
|
||||
- Use markdown editor if editText called with md mode
|
||||
- Prevent group actions when in non-default mouse mode
|
||||
|
||||
### 1.2.0-beta.1: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Detect importing duplicate nodes and help user resolve #2698
|
||||
- Allow sidebar tabs to be reordered #2655
|
||||
- Add tgz upload button to palette manager #2682
|
||||
- Add 'automatic' git workflow for projects #2035
|
||||
- Allow project version string to be edited
|
||||
- Sanitize unknown node type when displaying
|
||||
- Handle nodes with invalid z property Closes #2170
|
||||
- Outline: Ensure sf instance nodes update in outliner when import-replace sf
|
||||
- Outline: Ensure recovered nodes tab is added to outliner properly
|
||||
- Groups: Only recalculate group label offsets when needed
|
||||
- Groups: Reuse first group name/style when merging elements Fixes #2680
|
||||
- Groups: Fix copy/paste of node into active group Fixes #2686
|
||||
- ACE: Update ACE to 1.4.12-src-min-noconflict Fixes #1988
|
||||
- ACE: Add comment highlighting to JSONata and fix regex handling Closes #2701
|
||||
- ACE: Ensure errors in ACE NRJavaScript mode are on valid lines
|
||||
- Prevent Enter on search box from reloading page Fixes #2678
|
||||
- Allow toggleButton icons to be optional
|
||||
- Allow treeList to have a header component
|
||||
- Disable selection of FA icons when dbl clicking node
|
||||
|
||||
Runtime
|
||||
|
||||
- Add RED.hooks API for pluggable routing #2665
|
||||
- Add flows:* events and deprecate nodes-* events
|
||||
- Split .config.json into separate files #2794
|
||||
- Add support for file upload in /nodes api #2682
|
||||
- Add 'done' metric log for message tracing #2685 (@k-toumura)
|
||||
- Add mutex locking around /flow apis #2679
|
||||
- Default flowFilePretty to true if projects enabled
|
||||
- Replace Math.random with crypto.getBytes for session tokens
|
||||
- Fix `this` context when calling multiple event listeners Fixes #2692. #2693 (@mgroenhoff)
|
||||
- Add --userDir=/tmp/foo support to grunt dev
|
||||
- Skip loading node html if disableEditor set #2684
|
||||
- Update util.writeFile to write to tmp file before rename #2683
|
||||
- Fix getModuleFiles function to include path property #2705 (@t-kawamorita)
|
||||
- Update nodemon to latest so grunt dev task behaves
|
||||
- Improve jsdoc of util.getObjectProperty to clarify thrown error See #2703
|
||||
|
||||
Nodes
|
||||
|
||||
- Trigger: allow msg.delay to be used to set delay/loop interval #2707
|
||||
- Function: allow to send & log in its initialize code #2644 (@cinhcet)
|
||||
- MQTT: Update to MQTT 4.2.1 Closes #2694
|
||||
- Debug: Handle undefined value in Debug view of Array and Object Fixes #2696
|
||||
- Switch: Clarify empty rules in switch node documentation #2649 (@natcl) #2669 (@kazuhitoyokoi)
|
||||
- Updated core nodes to use Done callback #2653 (@k-toumura)
|
||||
- yaml, xml, json, html, http, template, range, link, status, catch, complete, inject
|
||||
|
||||
### 1.1.3: Maintenance Release
|
||||
|
||||
Editor
|
||||
- Fix vertical align of fa node icons Fixes #2670
|
||||
- Allow lasso selection to be restricted to active group
|
||||
- Make ctrl-click on nested group more intuitive
|
||||
- Fix copy/paste of nested groups
|
||||
- Add Set(iterable) polyfill for IE11
|
||||
- Support select-all inside active group
|
||||
- Improve performance of moving groups
|
||||
- Add additional check for git auth failure response Fixes #2656
|
||||
- german translation, wording (#2660) (#2666)
|
||||
- Remove filtering of duplicate fa icons
|
||||
- Show node help when switching node edit dialogs Fixes #2652
|
||||
- Ensure group theme picks up theme defaults properly Fixes #2651
|
||||
|
||||
Nodes
|
||||
- Clarify Switch node isEmpty help
|
||||
- HTTP In: handle application/cbor as binary
|
||||
|
||||
Runtime
|
||||
- Move runtime settings back to adminApi from editorApi Fixes #2662
|
||||
- Update Chinese message for debug node
|
||||
|
||||
### 1.1.2: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Fix all the touch screen issues Fixes #2647
|
||||
- Add RED.view.redrawStatus to avoid full redraw on update
|
||||
- Ensure node/group xrefs are consistent on import
|
||||
- Disable keyboard handler when dialogs are open
|
||||
- Ensure unknown nodes removed from outliner when node registers Fixes #2646
|
||||
|
||||
Runtime
|
||||
|
||||
- Allow Comms websocket auth to be done via token header Fixes #2642
|
||||
|
||||
### 1.1.1: Maintenance Release
|
||||
|
||||
Editor
|
||||
|
||||
- Set apiRootUrl for debug pop-out to load locales properly Fixes #2629, #2630
|
||||
- Update build-custom-theme to handle keyframes properly Fixes #2636
|
||||
- Remove hardcoded css and allow group to default from theme Fixes #2633
|
||||
- Add RED.view.DEBUG_SYNC_REDRAW to disable requestAnimationFrame References #2631
|
||||
- Fix up subflow port wiring
|
||||
- Ensure groups are removed when deleting subflows
|
||||
- Get group order right in history events to ensure proper handling
|
||||
- Prevent wiring to node with no corresponding port Fixes #2641
|
||||
- Avoid copying duplicate nodes to internal clipboard
|
||||
- Fix connecting wires to subflow status or io ports on touchscreen Fixes #2637
|
||||
|
||||
Runtime
|
||||
|
||||
- Authenticate websocket comms using user-provided token if present Fixes #2642
|
||||
|
||||
Nodes
|
||||
|
||||
- Delay: add words about independence of messages being delayed.
|
||||
- Debug: fix debug status to not loop, make migration more seamless, detect status type objects #2638
|
||||
- Debug: Update Japanese message for debug node #2645 (@kazuhitoyokoi)
|
||||
|
||||
### 1.1.0: Milestone Release
|
||||
|
||||
Editor
|
||||
|
||||
@@ -46,6 +46,14 @@ If you raise a pull-request without having signed the CLA, you will be prompted
|
||||
to do so automatically.
|
||||
|
||||
|
||||
### Code Branches
|
||||
|
||||
When raising a PR for a fix or a new feature, it is important to target the right branch.
|
||||
|
||||
- `master` - this is the main branch for the latest stable release of Node-RED. All bug fixes for that release should target this branch.
|
||||
- `v1.x` - this is the maintenance branch for the 1.x stream. If a fix *only* applies to 1.x, then it should target this branch. If it applies to the current stable release as well, target `master` first. We will then decide if it needs to be back ported to the 1.x stream.
|
||||
- `dev` - this is the branch for new feature development targeting the next milestone release.
|
||||
|
||||
### Coding standards
|
||||
|
||||
Please ensure you follow the coding standards used through-out the existing
|
||||
|
||||
131
Gruntfile.js
131
Gruntfile.js
@@ -16,14 +16,20 @@
|
||||
|
||||
var path = require("path");
|
||||
var fs = require("fs-extra");
|
||||
var sass = require("node-sass");
|
||||
var sass = require("sass");
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
var nodemonArgs = ["-v"];
|
||||
var nodemonArgs = ["-V"];
|
||||
var flowFile = grunt.option('flowFile');
|
||||
if (flowFile) {
|
||||
nodemonArgs.push(flowFile);
|
||||
process.env.NODE_RED_ENABLE_PROJECTS=false;
|
||||
}
|
||||
var userDir = grunt.option('userDir');
|
||||
if (userDir) {
|
||||
nodemonArgs.push("-u");
|
||||
nodemonArgs.push(userDir);
|
||||
}
|
||||
|
||||
var browserstack = grunt.option('browserstack');
|
||||
@@ -34,8 +40,10 @@ module.exports = function(grunt) {
|
||||
if (nonHeadless) {
|
||||
process.env.NODE_RED_NON_HEADLESS = true;
|
||||
}
|
||||
const pkg = grunt.file.readJSON('package.json');
|
||||
process.env.NODE_RED_PACKAGE_VERSION = pkg.version;
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
pkg: pkg,
|
||||
paths: {
|
||||
dist: ".dist"
|
||||
},
|
||||
@@ -47,8 +55,8 @@ module.exports = function(grunt) {
|
||||
ui: 'bdd',
|
||||
reporter: 'spec'
|
||||
},
|
||||
all: { src: ['test/**/*_spec.js'] },
|
||||
core: { src: ["test/_spec.js","test/unit/**/*_spec.js"]},
|
||||
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
|
||||
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
},
|
||||
webdriver: {
|
||||
@@ -56,19 +64,19 @@ module.exports = function(grunt) {
|
||||
configFile: 'test/editor/wdio.conf.js'
|
||||
}
|
||||
},
|
||||
mocha_istanbul: {
|
||||
nyc: {
|
||||
options: {
|
||||
globals: ['expect'],
|
||||
timeout: 3000,
|
||||
ignoreLeaks: false,
|
||||
ui: 'bdd',
|
||||
reportFormats: ['lcov','html'],
|
||||
print: 'both',
|
||||
istanbulOptions: ['--no-default-excludes', '-i','**/packages/node_modules/**']
|
||||
cwd: '.',
|
||||
include: ['packages/node_modules/**'],
|
||||
excludeNodeModules: false,
|
||||
exclude: ['packages/node_modules/@node-red/editor-client/**'],
|
||||
reporter: ['lcov', 'html','text-summary'],
|
||||
reportDir: 'coverage',
|
||||
all: true
|
||||
},
|
||||
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
|
||||
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
|
||||
nodes: { src: ["test/nodes/**/*_spec.js"]}
|
||||
all: { cmd: false, args: ['grunt', 'simplemocha:all'] },
|
||||
core: { options: { exclude:['packages/node_modules/@node-red/editor-client/**', 'packages/node_modules/@node-red/nodes/**']},cmd: false, args: ['grunt', 'simplemocha:core'] },
|
||||
nodes: { cmd: false, args: ['grunt', 'simplemocha:nodes'] }
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
@@ -129,6 +137,7 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/red.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/events.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/hooks.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/i18n.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||
@@ -136,6 +145,7 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/plugins.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/history.js",
|
||||
@@ -152,7 +162,7 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
||||
@@ -160,6 +170,7 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-annotations.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
|
||||
@@ -171,7 +182,9 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
|
||||
@@ -187,7 +200,8 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js"
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/tour/*.js"
|
||||
],
|
||||
dest: "packages/node_modules/@node-red/editor-client/public/red/red.js"
|
||||
},
|
||||
@@ -201,7 +215,9 @@ module.exports = function(grunt) {
|
||||
"node_modules/marked/marked.min.js",
|
||||
"node_modules/dompurify/dist/purify.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js",
|
||||
"node_modules/i18next/i18next.min.js",
|
||||
"node_modules/i18next-http-backend/i18nextHttpBackend.min.js",
|
||||
"node_modules/jquery-i18next/jquery-i18next.min.js",
|
||||
"node_modules/jsonata/jsonata-es5.min.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
|
||||
@@ -274,7 +290,9 @@ module.exports = function(grunt) {
|
||||
"packages/node_modules/@node-red/editor-client/public/index.html",
|
||||
"packages/node_modules/@node-red/editor-client/public/favicon.ico",
|
||||
"packages/node_modules/@node-red/editor-client/public/icons",
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor"
|
||||
"packages/node_modules/@node-red/editor-client/public/vendor",
|
||||
"packages/node_modules/@node-red/editor-client/public/types/node",
|
||||
"packages/node_modules/@node-red/editor-client/public/types/node-red",
|
||||
]
|
||||
},
|
||||
release: {
|
||||
@@ -310,6 +328,12 @@ module.exports = function(grunt) {
|
||||
],
|
||||
tasks: ['jsonlint:keymaps','copy:build']
|
||||
},
|
||||
tours: {
|
||||
files: [
|
||||
'packages/node_modules/@node-red/editor-client/src/tours/**/*.js'
|
||||
],
|
||||
tasks: ['copy:build']
|
||||
},
|
||||
misc: {
|
||||
files: [
|
||||
'CHANGELOG.md'
|
||||
@@ -364,11 +388,24 @@ module.exports = function(grunt) {
|
||||
src: [
|
||||
'ace/**',
|
||||
'jquery/css/base/**',
|
||||
'font-awesome/**'
|
||||
'font-awesome/**',
|
||||
'monaco/dist/**',
|
||||
'monaco/types/extraLibs.js',
|
||||
'monaco/style.css',
|
||||
'monaco/monaco-bootstrap.js'
|
||||
],
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src',
|
||||
src: [
|
||||
'types/node/*.ts',
|
||||
'types/node-red/*.ts',
|
||||
],
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/icons',
|
||||
src: '**',
|
||||
@@ -394,6 +431,12 @@ module.exports = function(grunt) {
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/'
|
||||
},
|
||||
{
|
||||
cwd: 'packages/node_modules/@node-red/editor-client/src/tours',
|
||||
src: '**',
|
||||
expand: true,
|
||||
dest: 'packages/node_modules/@node-red/editor-client/public/red/tours/'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -452,13 +495,16 @@ module.exports = function(grunt) {
|
||||
'packages/node_modules/@node-red/runtime/lib/index.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/api/*.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/events.js',
|
||||
'packages/node_modules/@node-red/runtime/lib/hooks.js',
|
||||
'packages/node_modules/@node-red/util/**/*.js',
|
||||
'packages/node_modules/@node-red/editor-api/lib/index.js',
|
||||
'packages/node_modules/@node-red/editor-api/lib/auth/index.js'
|
||||
'packages/node_modules/@node-red/editor-api/lib/auth/index.js',
|
||||
'packages/node_modules/@node-red/registry/lib/index.js'
|
||||
],
|
||||
options: {
|
||||
destination: 'docs',
|
||||
configure: './jsdoc.json'
|
||||
configure: './jsdoc.json',
|
||||
fred: "hi there"
|
||||
}
|
||||
},
|
||||
_editor: {
|
||||
@@ -502,12 +548,10 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-sass');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-contrib-compress');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-chmod');
|
||||
grunt.loadNpmTasks('grunt-jsonlint');
|
||||
grunt.loadNpmTasks('grunt-mocha-istanbul');
|
||||
if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
|
||||
grunt.loadNpmTasks('grunt-webdriver');
|
||||
}
|
||||
@@ -515,11 +559,31 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
|
||||
grunt.loadNpmTasks('grunt-npm-command');
|
||||
grunt.loadNpmTasks('grunt-mkdir');
|
||||
grunt.loadNpmTasks('grunt-simple-nyc');
|
||||
|
||||
grunt.registerMultiTask('nodemon', 'Runs a nodemon monitor of your node.js server.', function () {
|
||||
const nodemon = require('nodemon');
|
||||
this.async();
|
||||
const options = this.options();
|
||||
options.script = this.data.script;
|
||||
let callback;
|
||||
if (options.callback) {
|
||||
callback = options.callback;
|
||||
delete options.callback;
|
||||
} else {
|
||||
callback = function(nodemonApp) {
|
||||
nodemonApp.on('log', function (event) {
|
||||
console.log(event.colour);
|
||||
});
|
||||
};
|
||||
}
|
||||
callback(nodemon(options));
|
||||
});
|
||||
|
||||
grunt.registerMultiTask('attachCopyright', function() {
|
||||
var files = this.data.src;
|
||||
var copyright = "/**\n"+
|
||||
" * Copyright JS Foundation and other contributors, http://js.foundation\n"+
|
||||
" * Copyright OpenJS Foundation and other contributors, https://openjsf.org/\n"+
|
||||
" *\n"+
|
||||
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
|
||||
" * you may not use this file except in compliance with the License.\n"+
|
||||
@@ -596,11 +660,16 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('default',
|
||||
'Builds editor content then runs code style checks and unit tests on all components',
|
||||
['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:all']);
|
||||
['build','verifyPackageDependencies','jshint:editor','nyc:all']);
|
||||
|
||||
grunt.registerTask('no-coverage',
|
||||
'Builds editor content then runs code style checks and unit tests on all components without code coverage',
|
||||
['build','verifyPackageDependencies','jshint:editor','simplemocha:all']);
|
||||
|
||||
|
||||
grunt.registerTask('test-core',
|
||||
'Runs code style check and unit tests on core runtime code',
|
||||
['build','mocha_istanbul:core']);
|
||||
['build','nyc:core']);
|
||||
|
||||
grunt.registerTask('test-editor',
|
||||
'Runs code style check on editor code',
|
||||
@@ -618,12 +687,12 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('test-nodes',
|
||||
'Runs unit tests on core nodes',
|
||||
['build','mocha_istanbul:nodes']);
|
||||
['build','nyc:nodes']);
|
||||
|
||||
grunt.registerTask('build',
|
||||
'Builds editor content',
|
||||
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
|
||||
|
||||
|
||||
grunt.registerTask('build-dev',
|
||||
'Developer mode: build dev version',
|
||||
['clean:build','concat:build','concat:vendor','copy:build','sass:build','setDevEnv']);
|
||||
@@ -643,7 +712,7 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('coverage',
|
||||
'Run Istanbul code test coverage task',
|
||||
['build','mocha_istanbul:all']);
|
||||
['build','nyc:all']);
|
||||
|
||||
grunt.registerTask('docs',
|
||||
'Generates API documentation',
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
||||
@@ -67,4 +67,4 @@ It was created by [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-t
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright JS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.0.0 | :white_check_mark: |
|
||||
| 0.20.x | :white_check_mark: |
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.
|
||||
|
||||
110
package.json
110
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "1.1.0",
|
||||
"version": "2.1.2",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -26,96 +26,100 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"ajv": "6.12.2",
|
||||
"acorn": "8.5.0",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.6.3",
|
||||
"async-mutex": "0.3.2",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.0",
|
||||
"cheerio": "0.22.0",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"clone": "2.1.2",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.1",
|
||||
"cookie-parser": "1.4.5",
|
||||
"cors": "2.8.5",
|
||||
"cron": "1.7.2",
|
||||
"denque": "1.4.1",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "2.0.1",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"express-session": "1.17.2",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"fs.notify": "0.0.4",
|
||||
"got": "11.8.2",
|
||||
"hash-sum": "2.0.0",
|
||||
"hpagent": "0.1.2",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"i18next": "15.1.2",
|
||||
"iconv-lite": "0.5.1",
|
||||
"i18next": "21.3.1",
|
||||
"iconv-lite": "0.6.3",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.14.0",
|
||||
"js-yaml": "3.14.1",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.8.3",
|
||||
"jsonata": "1.8.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"media-typer": "1.1.0",
|
||||
"memorystore": "1.6.2",
|
||||
"mime": "2.4.6",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"mqtt": "2.18.8",
|
||||
"multer": "1.4.2",
|
||||
"mustache": "4.0.1",
|
||||
"node-red-admin": "^0.2.6",
|
||||
"node-red-node-rbe": "^0.2.9",
|
||||
"node-red-node-sentiment": "^0.1.6",
|
||||
"node-red-node-tail": "^0.1.0",
|
||||
"nopt": "4.0.3",
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"moment-timezone": "0.5.33",
|
||||
"mqtt": "4.2.8",
|
||||
"multer": "1.4.3",
|
||||
"mustache": "4.2.0",
|
||||
"node-red-admin": "^2.2.1",
|
||||
"nopt": "5.0.0",
|
||||
"oauth2orize": "1.11.0",
|
||||
"on-headers": "1.0.2",
|
||||
"passport": "0.4.1",
|
||||
"passport": "0.5.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.4.1",
|
||||
"request": "2.88.0",
|
||||
"semver": "6.3.0",
|
||||
"uglify-js": "3.9.4",
|
||||
"when": "3.7.8",
|
||||
"ws": "6.2.1",
|
||||
"semver": "7.3.5",
|
||||
"tar": "6.1.11",
|
||||
"tough-cookie": "4.0.0",
|
||||
"uglify-js": "3.14.2",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.1",
|
||||
"xml2js": "0.4.23"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "3.0.8"
|
||||
"bcrypt": "5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"marked": "0.8.2",
|
||||
"dompurify": "2.0.11",
|
||||
"grunt": "~1.0.4",
|
||||
"dompurify": "2.3.3",
|
||||
"grunt": "1.4.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.3.2",
|
||||
"grunt-concurrent": "~2.3.1",
|
||||
"grunt-cli": "~1.4.3",
|
||||
"grunt-concurrent": "3.0.0",
|
||||
"grunt-contrib-clean": "~2.0.0",
|
||||
"grunt-contrib-compress": "~1.5.0",
|
||||
"grunt-contrib-compress": "2.0.0",
|
||||
"grunt-contrib-concat": "~1.0.1",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-jshint": "~2.1.0",
|
||||
"grunt-contrib-uglify": "~4.0.1",
|
||||
"grunt-contrib-jshint": "3.1.1",
|
||||
"grunt-contrib-uglify": "5.0.1",
|
||||
"grunt-contrib-watch": "~1.1.0",
|
||||
"grunt-jsdoc": "^2.2.1",
|
||||
"grunt-jsdoc-to-markdown": "^4.0.0",
|
||||
"grunt-jsonlint": "~2.0.0",
|
||||
"grunt-mkdir": "~1.0.0",
|
||||
"grunt-mocha-istanbul": "5.0.2",
|
||||
"grunt-nodemon": "~0.4.2",
|
||||
"grunt-jsdoc": "2.4.1",
|
||||
"grunt-jsdoc-to-markdown": "6.0.0",
|
||||
"grunt-jsonlint": "2.1.3",
|
||||
"grunt-mkdir": "~1.1.0",
|
||||
"grunt-npm-command": "~0.1.2",
|
||||
"grunt-sass": "~3.1.0",
|
||||
"grunt-simple-mocha": "~0.4.1",
|
||||
"http-proxy": "1.18.1",
|
||||
"istanbul": "0.4.5",
|
||||
"grunt-simple-nyc": "^3.0.1",
|
||||
"i18next-http-backend": "1.3.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||
"marked": "3.0.7",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "^5.2.0",
|
||||
"mosca": "^2.8.3",
|
||||
"node-red-node-test-helper": "^0.2.5",
|
||||
"node-sass": "^4.14.1",
|
||||
"should": "^8.4.0",
|
||||
"sinon": "1.17.7",
|
||||
"mocha": "9.1.2",
|
||||
"node-red-node-test-helper": "^0.2.7",
|
||||
"nodemon": "2.0.13",
|
||||
"proxy": "^1.0.2",
|
||||
"sass": "1.43.2",
|
||||
"should": "13.2.3",
|
||||
"sinon": "11.1.2",
|
||||
"stoppable": "^1.1.0",
|
||||
"supertest": "3.4.2"
|
||||
"supertest": "6.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,33 @@ var flows = require("./flows");
|
||||
var flow = require("./flow");
|
||||
var context = require("./context");
|
||||
var auth = require("../auth");
|
||||
var info = require("./settings");
|
||||
var plugins = require("./plugins");
|
||||
|
||||
var apiUtil = require("../util");
|
||||
|
||||
module.exports = {
|
||||
init: function(runtimeAPI) {
|
||||
init: function(settings,runtimeAPI) {
|
||||
flows.init(runtimeAPI);
|
||||
flow.init(runtimeAPI);
|
||||
nodes.init(runtimeAPI);
|
||||
context.init(runtimeAPI);
|
||||
info.init(settings,runtimeAPI);
|
||||
plugins.init(runtimeAPI);
|
||||
|
||||
var 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]);
|
||||
}
|
||||
|
||||
|
||||
// Flows
|
||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
|
||||
@@ -47,7 +60,16 @@ module.exports = {
|
||||
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||
|
||||
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) {
|
||||
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) {
|
||||
const multer = require('multer');
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
|
||||
} else {
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
|
||||
}
|
||||
}
|
||||
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
|
||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
|
||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
|
||||
@@ -67,6 +89,12 @@ module.exports = {
|
||||
// adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
|
||||
|
||||
adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
|
||||
|
||||
// Plugins
|
||||
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
|
||||
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
|
||||
|
||||
return adminApp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ module.exports = {
|
||||
})
|
||||
} else {
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
|
||||
res.send(configs);
|
||||
})
|
||||
@@ -45,11 +48,22 @@ module.exports = {
|
||||
module: req.body.module,
|
||||
version: req.body.version,
|
||||
url: req.body.url,
|
||||
tarball: undefined,
|
||||
req: apiUtils.getRequestLogObject(req)
|
||||
}
|
||||
if (!runtimeAPI.settings.editorTheme || !runtimeAPI.settings.editorTheme.palette || runtimeAPI.settings.editorTheme.palette.upload !== false) {
|
||||
if (req.file) {
|
||||
opts.tarball = {
|
||||
name: req.file.originalname,
|
||||
size: req.file.size,
|
||||
buffer: req.file.buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
runtimeAPI.nodes.addModule(opts).then(function(info) {
|
||||
res.json(info);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
@@ -81,6 +95,9 @@ module.exports = {
|
||||
})
|
||||
} else {
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
|
||||
return res.send(result);
|
||||
}).catch(function(err) {
|
||||
@@ -150,6 +167,9 @@ module.exports = {
|
||||
lang: req.query.lng,
|
||||
req: apiUtils.getRequestLogObject(req)
|
||||
}
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
@@ -164,6 +184,9 @@ module.exports = {
|
||||
lang: req.query.lng,
|
||||
req: apiUtils.getRequestLogObject(req)
|
||||
}
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
|
||||
44
packages/node_modules/@node-red/editor-api/lib/admin/plugins.js
vendored
Normal file
44
packages/node_modules/@node-red/editor-api/lib/admin/plugins.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
getAll: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
req: apiUtils.getRequestLogObject(req)
|
||||
}
|
||||
if (req.get("accept") == "application/json") {
|
||||
runtimeAPI.plugins.getPluginList(opts).then(function(list) {
|
||||
res.json(list);
|
||||
})
|
||||
} else {
|
||||
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) {
|
||||
res.send(configs);
|
||||
})
|
||||
}
|
||||
},
|
||||
getCatalogs: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
lang: req.query.lng,
|
||||
req: apiUtils.getRequestLogObject(req)
|
||||
}
|
||||
if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
|
||||
opts.lang = "en-US";
|
||||
}
|
||||
runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
};
|
||||
72
packages/node_modules/@node-red/editor-api/lib/admin/settings.js
vendored
Normal file
72
packages/node_modules/@node-red/editor-api/lib/admin/settings.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var apiUtils = require("../util");
|
||||
var runtimeAPI;
|
||||
var settings;
|
||||
var theme = require("../editor/theme");
|
||||
var clone = require("clone");
|
||||
|
||||
var i18n = require("@node-red/util").i18n
|
||||
|
||||
function extend(target, source) {
|
||||
var keys = Object.keys(source);
|
||||
var i = keys.length;
|
||||
while(i--) {
|
||||
var value = source[keys[i]]
|
||||
var type = typeof value;
|
||||
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
|
||||
target[keys[i]] = value;
|
||||
} else if (value === null) {
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
delete target[keys[i]];
|
||||
}
|
||||
} else {
|
||||
// Object
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
target[keys[i]] = extend(target[keys[i]],value);
|
||||
} else {
|
||||
target[keys[i]] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings,_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
settings = _settings;
|
||||
},
|
||||
runtimeSettings: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
|
||||
if (!settings.disableEditor) {
|
||||
result.editorTheme = result.editorTheme||{};
|
||||
var themeSettings = theme.settings();
|
||||
if (themeSettings) {
|
||||
// result.editorTheme may already exist with the palette
|
||||
// disabled. Need to merge that into the receive settings
|
||||
result.editorTheme = extend(clone(themeSettings),result.editorTheme);
|
||||
}
|
||||
result.editorTheme.languages = i18n.availableLanguages("editor");
|
||||
}
|
||||
res.json(result);
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
@@ -90,7 +90,7 @@ function getToken(req,res,next) {
|
||||
return server.token()(req,res,next);
|
||||
}
|
||||
|
||||
function login(req,res) {
|
||||
async function login(req,res) {
|
||||
var response = {};
|
||||
if (settings.adminAuth) {
|
||||
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
|
||||
@@ -116,8 +116,9 @@ function login(req,res) {
|
||||
response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
|
||||
}
|
||||
}
|
||||
if (theme.context().login && theme.context().login.image) {
|
||||
response.image = theme.context().login.image;
|
||||
let themeContext = await theme.context();
|
||||
if (themeContext.login && themeContext.login.image) {
|
||||
response.image = themeContext.login.image;
|
||||
}
|
||||
}
|
||||
res.json(response);
|
||||
@@ -172,31 +173,38 @@ function genericStrategy(adminApp,strategy) {
|
||||
adminApp.use(passport.session());
|
||||
|
||||
var options = strategy.options;
|
||||
var verify = function() {
|
||||
var originalDone = arguments[arguments.length-1];
|
||||
if (options.verify) {
|
||||
var args = Array.from(arguments);
|
||||
args[args.length-1] = function(err,profile) {
|
||||
if (err) {
|
||||
return originalDone(err);
|
||||
} else {
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
};
|
||||
|
||||
passport.use(new strategy.strategy(options,
|
||||
function() {
|
||||
var originalDone = arguments[arguments.length-1];
|
||||
if (options.verify) {
|
||||
var args = Array.from(arguments);
|
||||
args[args.length-1] = function(err,profile) {
|
||||
if (err) {
|
||||
return originalDone(err);
|
||||
} else {
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
};
|
||||
options.verify.apply(null,args);
|
||||
} else {
|
||||
var profile = arguments[arguments.length - 2];
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
|
||||
options.verify.apply(null,args);
|
||||
} else {
|
||||
var profile = arguments[arguments.length - 2];
|
||||
return completeVerify(profile,originalDone);
|
||||
}
|
||||
));
|
||||
};
|
||||
// Give our callback the same arity as the original one from options
|
||||
if (options.verify) {
|
||||
Object.defineProperty(verify, "length", { value: options.verify.length })
|
||||
}
|
||||
|
||||
passport.use(new strategy.strategy(options, verify));
|
||||
|
||||
adminApp.get('/auth/strategy',
|
||||
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
|
||||
completeGenerateStrategyAuth
|
||||
passport.authenticate(strategy.name, {session:false,
|
||||
failureMessage: true,
|
||||
failureRedirect: settings.httpAdminRoot
|
||||
}),
|
||||
completeGenerateStrategyAuth,
|
||||
handleStrategyError
|
||||
);
|
||||
|
||||
var callbackMethodFunc = adminApp.get;
|
||||
@@ -204,8 +212,13 @@ function genericStrategy(adminApp,strategy) {
|
||||
callbackMethodFunc = adminApp.post;
|
||||
}
|
||||
callbackMethodFunc.call(adminApp,'/auth/strategy/callback',
|
||||
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
|
||||
completeGenerateStrategyAuth
|
||||
passport.authenticate(strategy.name, {
|
||||
session:false,
|
||||
failureMessage: true,
|
||||
failureRedirect: settings.httpAdminRoot
|
||||
}),
|
||||
completeGenerateStrategyAuth,
|
||||
handleStrategyError
|
||||
);
|
||||
|
||||
}
|
||||
@@ -215,6 +228,13 @@ function completeGenerateStrategyAuth(req,res) {
|
||||
// Successful authentication, redirect home.
|
||||
res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
|
||||
}
|
||||
function handleStrategyError(err, req, res, next) {
|
||||
if (res.headersSent) {
|
||||
return next(err)
|
||||
}
|
||||
log.audit({event: "auth.login.fail.oauth",error:err.toString()});
|
||||
res.redirect(settings.httpAdminRoot + '?session_message='+err.toString());
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
|
||||
@@ -123,38 +123,57 @@ AnonymousStrategy.prototype.authenticate = function(req) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function authenticateUserToken(req) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
var token = null;
|
||||
var tokenHeader = Users.tokenHeader();
|
||||
if (Users.tokenHeader() === null) {
|
||||
// No custom user token provided. Fail the request
|
||||
reject();
|
||||
return;
|
||||
} else if (Users.tokenHeader() === 'authorization') {
|
||||
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
} else {
|
||||
token = req.headers[Users.tokenHeader()];
|
||||
}
|
||||
if (token) {
|
||||
Users.tokens(token).then(function(user) {
|
||||
if (user) {
|
||||
resolve(user);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function TokensStrategy() {
|
||||
passport.Strategy.call(this);
|
||||
this.name = 'tokens';
|
||||
}
|
||||
util.inherits(TokensStrategy, passport.Strategy);
|
||||
TokensStrategy.prototype.authenticate = function(req) {
|
||||
var self = this;
|
||||
var token = null;
|
||||
if (Users.tokenHeader() === 'authorization') {
|
||||
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
} else {
|
||||
token = req.headers[Users.tokenHeader()];
|
||||
}
|
||||
if (token) {
|
||||
Users.tokens(token).then(function(admin) {
|
||||
if (admin) {
|
||||
self.success(admin,{scope:admin.permissions});
|
||||
} else {
|
||||
self.fail(401);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.fail(401);
|
||||
}
|
||||
authenticateUserToken(req).then(user => {
|
||||
this.success(user,{scope:user.permissions});
|
||||
}).catch(err => {
|
||||
this.fail(401);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
bearerStrategy: bearerStrategy,
|
||||
clientPasswordStrategy: clientPasswordStrategy,
|
||||
passwordTokenExchange: passwordTokenExchange,
|
||||
anonymousStrategy: new AnonymousStrategy(),
|
||||
tokensStrategy: new TokensStrategy()
|
||||
tokensStrategy: new TokensStrategy(),
|
||||
authenticateUserToken: authenticateUserToken
|
||||
}
|
||||
|
||||
@@ -14,15 +14,7 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
function generateToken(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
var storage;
|
||||
var sessionExpiryTime
|
||||
@@ -115,7 +107,7 @@ module.exports = {
|
||||
},
|
||||
create: function(user,client,scope) {
|
||||
return loadSessions().then(function() {
|
||||
var accessToken = generateToken(128);
|
||||
var accessToken = crypto.randomBytes(128).toString('base64');
|
||||
|
||||
var accessTokenExpiresAt = Date.now() + (sessionExpiryTime*1000);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ var api = {
|
||||
authenticate: authenticate,
|
||||
default: getDefaultUser,
|
||||
tokens: getDefaultUser,
|
||||
tokenHeader: "authorization"
|
||||
tokenHeader: null
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
@@ -111,6 +111,8 @@ function init(config) {
|
||||
api.tokens = config.tokens;
|
||||
if (config.tokenHeader && typeof config.tokenHeader === "string") {
|
||||
api.tokenHeader = config.tokenHeader.toLowerCase();
|
||||
} else {
|
||||
api.tokenHeader = "authorization";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
var ws = require("ws");
|
||||
var url = require("url");
|
||||
const crypto = require("crypto");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var Tokens;
|
||||
var Users;
|
||||
var Permissions;
|
||||
var Strategies;
|
||||
|
||||
var server;
|
||||
var settings;
|
||||
@@ -31,8 +33,6 @@ var activeConnections = [];
|
||||
|
||||
var anonymousUser;
|
||||
|
||||
var retained = {};
|
||||
|
||||
var heartbeatTimer;
|
||||
var lastSentTime;
|
||||
|
||||
@@ -44,6 +44,7 @@ function init(_server,_settings,_runtimeAPI) {
|
||||
Tokens.onSessionExpiry(handleSessionExpiry);
|
||||
Users = require("../auth/users");
|
||||
Permissions = require("../auth/permissions");
|
||||
Strategies = require("../auth/strategies");
|
||||
|
||||
}
|
||||
function handleSessionExpiry(session) {
|
||||
@@ -54,26 +55,19 @@ function handleSessionExpiry(session) {
|
||||
}
|
||||
})
|
||||
}
|
||||
function generateSession(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
function CommsConnection(ws) {
|
||||
this.session = generateSession(32);
|
||||
function CommsConnection(ws, user) {
|
||||
this.session = crypto.randomBytes(32).toString('base64');
|
||||
this.ws = ws;
|
||||
this.stack = [];
|
||||
this.user = null;
|
||||
this.user = user;
|
||||
this.lastSentTime = 0;
|
||||
var self = this;
|
||||
|
||||
log.audit({event: "comms.open"});
|
||||
log.trace("comms.open "+self.session);
|
||||
var pendingAuth = (settings.adminAuth != null);
|
||||
var preAuthed = !!user;
|
||||
var pendingAuth = !this.user && (settings.adminAuth != null);
|
||||
|
||||
if (!pendingAuth) {
|
||||
addActiveConnection(self);
|
||||
@@ -130,8 +124,16 @@ function CommsConnection(ws) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
completeConnection(null,null,false);
|
||||
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 {
|
||||
@@ -156,25 +158,31 @@ function CommsConnection(ws) {
|
||||
}
|
||||
|
||||
CommsConnection.prototype.send = function(topic,data) {
|
||||
var self = this;
|
||||
if (topic && data) {
|
||||
this.stack.push({topic:topic,data:data});
|
||||
}
|
||||
this._queueSend();
|
||||
}
|
||||
CommsConnection.prototype._queueSend = function() {
|
||||
var self = this;
|
||||
if (!this._xmitTimer) {
|
||||
this._xmitTimer = setTimeout(function() {
|
||||
try {
|
||||
self.ws.send(JSON.stringify(self.stack));
|
||||
self.ws.send(JSON.stringify(self.stack.splice(0,50)));
|
||||
self.lastSentTime = Date.now();
|
||||
} catch(err) {
|
||||
removeActiveConnection(self);
|
||||
log.warn(log._("comms.error-send",{message:err.toString()}));
|
||||
}
|
||||
delete self._xmitTimer;
|
||||
self.stack = [];
|
||||
if (self.stack.length > 0) {
|
||||
self._queueSend();
|
||||
}
|
||||
},50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CommsConnection.prototype.subscribe = function(topic) {
|
||||
runtimeAPI.comms.subscribe({
|
||||
user: this.user,
|
||||
@@ -191,8 +199,8 @@ function start() {
|
||||
var commsPath = settings.httpAdminRoot || "/";
|
||||
commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms";
|
||||
wsServer = new ws.Server({ noServer: true });
|
||||
wsServer.on('connection',function(ws) {
|
||||
var commsConnection = new CommsConnection(ws);
|
||||
wsServer.on('connection',function(ws, request, user) {
|
||||
var commsConnection = new CommsConnection(ws, user);
|
||||
});
|
||||
wsServer.on('error', function(err) {
|
||||
log.warn(log._("comms.error-server",{message:err.toString()}));
|
||||
@@ -201,8 +209,26 @@ function start() {
|
||||
server.on('upgrade', function upgrade(request, socket, head) {
|
||||
const pathname = url.parse(request.url).pathname;
|
||||
if (pathname === commsPath) {
|
||||
if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) {
|
||||
// The user has provided custom token handling. For the websocket,
|
||||
// the token could be provided in two ways:
|
||||
// - as an http header (only possible with a reverse proxy setup)
|
||||
// - passed over the connected websock in an auth packet
|
||||
// If the header is present, verify the token. If not, use the auth
|
||||
// packet over the connected socket
|
||||
//
|
||||
Strategies.authenticateUserToken(request).then(user => {
|
||||
wsServer.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wsServer.emit('connection', ws, request, user);
|
||||
});
|
||||
}).catch(err => {
|
||||
log.audit({event: "comms.auth.fail"});
|
||||
socket.destroy();
|
||||
})
|
||||
return
|
||||
}
|
||||
wsServer.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wsServer.emit('connection', ws, request);
|
||||
wsServer.emit('connection', ws, request, null);
|
||||
});
|
||||
}
|
||||
// Don't destroy the socket as other listeners may want to handle the
|
||||
|
||||
@@ -64,10 +64,12 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (settings.httpServerOptions) {
|
||||
for (var eOption in settings.httpServerOptions) {
|
||||
editorApp.set(eOption, settings.httpServerOptions[eOption]);
|
||||
}
|
||||
var defaultServerSettings = {
|
||||
"x-powered-by": false
|
||||
}
|
||||
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||
for (var eOption in serverSettings) {
|
||||
editorApp.set(eOption, serverSettings[eOption]);
|
||||
}
|
||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||
|
||||
@@ -75,8 +77,10 @@ module.exports = {
|
||||
editorApp.get("/icons/:module/:icon",ui.icon);
|
||||
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
|
||||
|
||||
editorApp.get(/^\/resources\/((?:@[^\/]+\/)?[^\/]+)\/(.+)$/,ui.moduleResource);
|
||||
|
||||
var theme = require("./theme");
|
||||
theme.init(settings);
|
||||
theme.init(settings, runtimeAPI);
|
||||
editorApp.use("/theme",theme.app());
|
||||
editorApp.use("/",ui.editorResources);
|
||||
|
||||
@@ -93,6 +97,7 @@ module.exports = {
|
||||
// Library
|
||||
var library = require("./library");
|
||||
library.init(runtimeAPI);
|
||||
// editorApp.get("/library/:id",needsPermission("library.read"),library.getLibraryConfig);
|
||||
editorApp.get(/^\/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
|
||||
editorApp.post(/^\/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
|
||||
|
||||
@@ -103,7 +108,7 @@ module.exports = {
|
||||
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
|
||||
|
||||
// Settings
|
||||
editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
|
||||
// Main /settings route is an admin route - see lib/admin/settings.js
|
||||
// User Settings
|
||||
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
|
||||
// User Settings
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
var apiUtils = require("../util");
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
var when = require('when');
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
@@ -25,6 +24,17 @@ module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
// getLibraryConfig: function(req,res) {
|
||||
// var opts = {
|
||||
// user: req.user,
|
||||
// library: req.params.id
|
||||
// }
|
||||
// runtimeAPI.library.getConfig(opts).then(function(result) {
|
||||
// res.json(result);
|
||||
// }).catch(function(err) {
|
||||
// apiUtils.rejectHandler(req,res,err);
|
||||
// });
|
||||
// },
|
||||
getEntry: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
|
||||
@@ -39,9 +39,12 @@ module.exports = {
|
||||
},
|
||||
get: function(req,res) {
|
||||
var namespace = req.params[0];
|
||||
var lngs = req.query.lng;
|
||||
namespace = namespace.replace(/\.json$/,"");
|
||||
var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||
if (/[^0-9a-z=\-\*]/i.test(lang)) {
|
||||
res.json({});
|
||||
return;
|
||||
}
|
||||
var prevLang = i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
i18n.i.changeLanguage(lang, function(){
|
||||
|
||||
@@ -137,6 +137,7 @@ module.exports = {
|
||||
req.body.hasOwnProperty('description') ||
|
||||
req.body.hasOwnProperty('dependencies')||
|
||||
req.body.hasOwnProperty('summary') ||
|
||||
req.body.hasOwnProperty('version') ||
|
||||
req.body.hasOwnProperty('files') ||
|
||||
req.body.hasOwnProperty('git')) {
|
||||
runtimeAPI.projects.updateProject(opts).then(function() {
|
||||
|
||||
@@ -16,56 +16,12 @@
|
||||
var apiUtils = require("../util");
|
||||
var runtimeAPI;
|
||||
var sshkeys = require("./sshkeys");
|
||||
var theme = require("./theme");
|
||||
var clone = require("clone");
|
||||
|
||||
var i18n = require("@node-red/util").i18n
|
||||
|
||||
function extend(target, source) {
|
||||
var keys = Object.keys(source);
|
||||
var i = keys.length;
|
||||
while(i--) {
|
||||
var value = source[keys[i]]
|
||||
var type = typeof value;
|
||||
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
|
||||
target[keys[i]] = value;
|
||||
} else if (value === null) {
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
delete target[keys[i]];
|
||||
}
|
||||
} else {
|
||||
// Object
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
target[keys[i]] = extend(target[keys[i]],value);
|
||||
} else {
|
||||
target[keys[i]] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
sshkeys.init(runtimeAPI);
|
||||
},
|
||||
runtimeSettings: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
}
|
||||
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
|
||||
result.editorTheme = result.editorTheme||{};
|
||||
var themeSettings = theme.settings();
|
||||
if (themeSettings) {
|
||||
// result.editorTheme may already exist with the palette
|
||||
// disabled. Need to merge that into the receive settings
|
||||
result.editorTheme = extend(clone(themeSettings),result.editorTheme);
|
||||
}
|
||||
result.editorTheme.languages = i18n.availableLanguages("editor");
|
||||
res.json(result);
|
||||
});
|
||||
},
|
||||
userSettings: function(req, res) {
|
||||
var opts = {
|
||||
user: req.user
|
||||
|
||||
@@ -18,14 +18,6 @@ var apiUtils = require("../util");
|
||||
var express = require("express");
|
||||
var runtimeAPI;
|
||||
|
||||
function getUsername(userObj) {
|
||||
var username = '__default';
|
||||
if ( userObj && userObj.name ) {
|
||||
username = userObj.name;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
|
||||
@@ -24,16 +24,20 @@ var defaultContext = {
|
||||
page: {
|
||||
title: "Node-RED",
|
||||
favicon: "favicon.ico",
|
||||
tabicon: "red/images/node-red-icon-black.svg"
|
||||
tabicon: {
|
||||
icon: "red/images/node-red-icon-black.svg",
|
||||
colour: "#8f0000"
|
||||
},
|
||||
version: require(path.join(__dirname,"../../package.json")).version
|
||||
},
|
||||
header: {
|
||||
title: "Node-RED",
|
||||
image: "red/images/node-red.svg"
|
||||
},
|
||||
asset: {
|
||||
red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js",
|
||||
main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js",
|
||||
|
||||
red: "red/red.min.js",
|
||||
main: "red/main.min.js",
|
||||
vendorMonaco: ""
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,6 +45,10 @@ var theme = null;
|
||||
var themeContext = clone(defaultContext);
|
||||
var themeSettings = null;
|
||||
|
||||
var activeTheme = null;
|
||||
var activeThemeInitialised = false;
|
||||
|
||||
var runtimeAPI;
|
||||
var themeApp;
|
||||
|
||||
function serveFile(app,baseUrl,file) {
|
||||
@@ -58,7 +66,7 @@ function serveFile(app,baseUrl,file) {
|
||||
}
|
||||
}
|
||||
|
||||
function serveFilesFromTheme(themeValue, themeApp, directory) {
|
||||
function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
||||
var result = [];
|
||||
if (themeValue) {
|
||||
var array = themeValue;
|
||||
@@ -67,7 +75,14 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
|
||||
}
|
||||
|
||||
for (var i=0;i<array.length;i++) {
|
||||
var url = serveFile(themeApp,directory,array[i]);
|
||||
let fullPath = array[i];
|
||||
if (baseDirectory) {
|
||||
fullPath = path.resolve(baseDirectory,array[i]);
|
||||
if (fullPath.indexOf(path.resolve(baseDirectory)) !== 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var url = serveFile(themeApp,directory,fullPath);
|
||||
if (url) {
|
||||
result.push(url);
|
||||
}
|
||||
@@ -77,10 +92,17 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
init: function(settings, _runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
themeContext = clone(defaultContext);
|
||||
if (process.env.NODE_ENV == "development") {
|
||||
themeContext.asset.red = "red/red.js";
|
||||
themeContext.asset.main = "red/main.js";
|
||||
}
|
||||
themeSettings = null;
|
||||
theme = settings.editorTheme || {};
|
||||
themeContext.asset.vendorMonaco = ((theme.codeEditor || {}).lib === "monaco") ? "vendor/monaco/monaco-bootstrap.js" : "";
|
||||
activeTheme = theme.theme;
|
||||
},
|
||||
|
||||
app: function() {
|
||||
@@ -109,13 +131,25 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (theme.page.tabicon) {
|
||||
url = serveFile(themeApp,"/tabicon/",theme.page.tabicon)
|
||||
let icon = theme.page.tabicon.icon || theme.page.tabicon
|
||||
url = serveFile(themeApp,"/tabicon/", icon)
|
||||
if (url) {
|
||||
themeContext.page.tabicon = url;
|
||||
themeContext.page.tabicon.icon = url;
|
||||
}
|
||||
if (theme.page.tabicon.colour) {
|
||||
themeContext.page.tabicon.colour = theme.page.tabicon.colour
|
||||
}
|
||||
}
|
||||
|
||||
themeContext.page.title = theme.page.title || themeContext.page.title;
|
||||
|
||||
// Store the resolved urls to these resources so nodes (such as Debug)
|
||||
// can access them
|
||||
theme.page._ = {
|
||||
css: themeContext.page.css,
|
||||
scripts: themeContext.page.scripts,
|
||||
favicon: themeContext.page.favicon
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.header) {
|
||||
@@ -169,7 +203,9 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
themeApp.get("/", function(req,res) {
|
||||
themeApp.get("/", async function(req,res) {
|
||||
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
|
||||
themeContext.themes = themePluginList.map(theme => theme.id);
|
||||
res.json(themeContext);
|
||||
})
|
||||
|
||||
@@ -185,10 +221,49 @@ module.exports = {
|
||||
themeSettings.projects = theme.projects;
|
||||
}
|
||||
|
||||
if (theme.hasOwnProperty("keymap")) {
|
||||
themeSettings.keymap = theme.keymap;
|
||||
}
|
||||
|
||||
if (theme.theme) {
|
||||
themeSettings.theme = theme.theme;
|
||||
}
|
||||
return themeApp;
|
||||
},
|
||||
context: function() {
|
||||
context: async function() {
|
||||
if (activeTheme && !activeThemeInitialised) {
|
||||
const themePlugin = await runtimeAPI.plugins.getPlugin({
|
||||
id:activeTheme
|
||||
});
|
||||
if (themePlugin) {
|
||||
if (themePlugin.css) {
|
||||
const cssFiles = serveFilesFromTheme(
|
||||
themePlugin.css,
|
||||
themeApp,
|
||||
"/css/",
|
||||
themePlugin.path
|
||||
);
|
||||
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
|
||||
theme.page = theme.page || {_:{}}
|
||||
theme.page._.css = cssFiles.concat(theme.page._.css || [])
|
||||
}
|
||||
if (themePlugin.scripts) {
|
||||
const scriptFiles = serveFilesFromTheme(
|
||||
themePlugin.scripts,
|
||||
themeApp,
|
||||
"/scripts/",
|
||||
themePlugin.path
|
||||
)
|
||||
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
|
||||
theme.page = theme.page || {_:{}}
|
||||
theme.page._.scripts = scriptFiles.concat(theme.page._.scripts || [])
|
||||
}
|
||||
if(theme.codeEditor) {
|
||||
theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
|
||||
}
|
||||
}
|
||||
activeThemeInitialised = true;
|
||||
}
|
||||
return themeContext;
|
||||
},
|
||||
settings: function() {
|
||||
|
||||
@@ -68,8 +68,39 @@ module.exports = {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
editor: function(req,res) {
|
||||
res.send(Mustache.render(editorTemplate,theme.context()));
|
||||
|
||||
moduleResource: function(req, res) {
|
||||
let resourcePath = req.params[1];
|
||||
let opts = {
|
||||
user: req.user,
|
||||
module: req.params[0],
|
||||
path: resourcePath
|
||||
}
|
||||
runtimeAPI.nodes.getModuleResource(opts).then(function(data) {
|
||||
if (data) {
|
||||
var contentType = mime.getType(resourcePath);
|
||||
res.set("Content-Type", contentType);
|
||||
res.send(data);
|
||||
} else {
|
||||
res.status(404).end()
|
||||
}
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
||||
editor: async function(req,res) {
|
||||
|
||||
let sessionMessages;
|
||||
if (req.session && req.session.messages) {
|
||||
sessionMessages = JSON.stringify(req.session.messages);
|
||||
delete req.session.messages
|
||||
}
|
||||
res.send(Mustache.render(editorTemplate,{
|
||||
sessionMessages,
|
||||
...await theme.context()
|
||||
}));
|
||||
},
|
||||
editorResources: express.static(path.join(editorClientDir,'public'))
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ var express = require("express");
|
||||
var bodyParser = require("body-parser");
|
||||
var util = require('util');
|
||||
var passport = require('passport');
|
||||
var when = require('when');
|
||||
var cors = require('cors');
|
||||
|
||||
var auth = require("./auth");
|
||||
@@ -60,11 +59,19 @@ function init(settings,_server,storage,runtimeAPI) {
|
||||
adminApp.use(corsHandler);
|
||||
|
||||
if (settings.httpAdminMiddleware) {
|
||||
if (typeof settings.httpAdminMiddleware === "function") {
|
||||
adminApp.use(settings.httpAdminMiddleware)
|
||||
if (typeof settings.httpAdminMiddleware === "function" || Array.isArray(settings.httpAdminMiddleware)) {
|
||||
adminApp.use(settings.httpAdminMiddleware);
|
||||
}
|
||||
}
|
||||
|
||||
var defaultServerSettings = {
|
||||
"x-powered-by": false
|
||||
}
|
||||
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||
for (var eOption in serverSettings) {
|
||||
adminApp.set(eOption, serverSettings[eOption]);
|
||||
}
|
||||
|
||||
auth.init(settings,storage);
|
||||
|
||||
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
||||
@@ -99,7 +106,7 @@ function init(settings,_server,storage,runtimeAPI) {
|
||||
adminApp.use(corsHandler);
|
||||
}
|
||||
|
||||
var adminApiApp = require("./admin").init(runtimeAPI);
|
||||
var adminApiApp = require("./admin").init(settings, runtimeAPI);
|
||||
adminApp.use(adminApiApp);
|
||||
} else {
|
||||
adminApp = null;
|
||||
@@ -111,11 +118,9 @@ function init(settings,_server,storage,runtimeAPI) {
|
||||
* @return {Promise} resolves when the application is ready to handle requests
|
||||
* @memberof @node-red/editor-api
|
||||
*/
|
||||
function start() {
|
||||
async function start() {
|
||||
if (editor) {
|
||||
return editor.start();
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,11 +129,10 @@ function start() {
|
||||
* @return {Promise} resolves when the application is stopped
|
||||
* @memberof @node-red/editor-api
|
||||
*/
|
||||
function stop() {
|
||||
async function stop() {
|
||||
if (editor) {
|
||||
editor.stop();
|
||||
}
|
||||
return when.resolve();
|
||||
}
|
||||
module.exports = {
|
||||
init: init,
|
||||
|
||||
@@ -43,6 +43,10 @@ module.exports = {
|
||||
rejectHandler: function(req,res,err) {
|
||||
//TODO: why this when errorHandler also?!
|
||||
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req);
|
||||
if (!err.code) {
|
||||
// by definition, an unexpected_error to log
|
||||
log.error(err);
|
||||
}
|
||||
var response = {
|
||||
code: err.code||"unexpected_error",
|
||||
message: err.message||err.toString()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "1.1.0",
|
||||
"version": "2.1.2",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,25 +16,25 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "1.1.0",
|
||||
"@node-red/editor-client": "1.1.0",
|
||||
"@node-red/util": "2.1.2",
|
||||
"@node-red/editor-client": "2.1.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.0",
|
||||
"clone": "2.1.2",
|
||||
"cors": "2.8.5",
|
||||
"express-session": "1.17.1",
|
||||
"express-session": "1.17.2",
|
||||
"express": "4.17.1",
|
||||
"memorystore": "1.6.2",
|
||||
"mime": "2.4.6",
|
||||
"mustache": "4.0.1",
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"multer": "1.4.3",
|
||||
"mustache": "4.2.0",
|
||||
"oauth2orize": "1.11.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"passport": "0.4.1",
|
||||
"when": "3.7.8",
|
||||
"ws": "6.2.1"
|
||||
"passport": "0.5.0",
|
||||
"ws": "7.5.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "3.0.6"
|
||||
"bcrypt": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"info" : {
|
||||
"tip0" : "Sie können die ausgewählten Nodes oder Verbindungen mit {{ core:delete-selection }} entfernen.",
|
||||
"tip1" : "Suche nach Nodes mit {{ core:search }}",
|
||||
"tip2" : "{{ core:toggle-sidebar }} schaltet die Ansicht dieser Seitenleiste ein.",
|
||||
"tip3" : "Sie können Ihre Palette von Nodes mit {{ core:manage-palette }} verwalten.",
|
||||
"tip4" : "Ihre Flow-Konfigurations-Nodes werden in der Seitenleiste angezeigt. Es kann über das Menü oder mit {{ core:show-config-tab }} aufgerufen werden.",
|
||||
"tip5" : "Aktiviert oder inaktiviert diese Tipps von der Option in den Einstellungen",
|
||||
"tip6" : "Verschieben Sie die ausgewählten Nodes mit Hilfe der [left] [up] [down] und [right] Tasten. Halten Sie [Shift] gedrückt, um das Fenster weiter zu schieben",
|
||||
"tip7" : "Wenn Sie einen Node auf eine Verbindung ziehen, wird er in die Verbindung eingefügt.",
|
||||
"tip8" : "Die ausgewählten Nodes exportieren oder die aktuelle Registerkarte mit {{ core:show-export-dialog }}",
|
||||
"tip9" : "Importieren Sie einen Flow, indem Sie sein JSON in den Editor ziehen oder mit {{ core:show-import-dialog }}.",
|
||||
"tip10" : "[Umschalt] [Klicken] und ziehen Sie auf einen Node-Anschluss, um alle angeschlossenen Verbindungen oder nur die ausgewählte zu verschieben.",
|
||||
"tip11" : "Die Registerkarte \"Info\" mit {{ core:show-info-tab }} oder der Registerkarte \"Debug\" mit {{ core:show-debug-tab }} anzeigen",
|
||||
"tip12" : "[ctrl] [Klicken] in den Arbeitsbereich, um den Schnellhinzufügedialog zu öffnen.",
|
||||
"tip13" : "Halten Sie [ctrl] gedrückt, wenn Sie auf einem Node-Anschluss klicken, um eine Schnellverbindung zu aktivieren.",
|
||||
"tip14" : "Halten Sie [Umschalt] gedrückt, wenn Sie auf einen Node klicken, um auch alle verbundenen Nodes auszuwählen.",
|
||||
"tip15" : "Halten Sie [ctrl] gedrückt, wenn Sie auf einen Node klicken, um ihn aus der aktuellen Auswahl hinzuzufügen oder zu entfernen.",
|
||||
"tip16" : "Indexzungen wechseln mit {{ core:show-previous-tab }} und {{ core:show-next-tab }}",
|
||||
"tip17" : "Sie können die Änderungen im Editierrahmen des Nodes mit {{ core:confirm-edit-tray }} bestätigen oder sie mit {{ core:cancel-edit-tray }} abbrechen.",
|
||||
"tip18" : "Durch Drücken von {{ core:edit-selected-node }} wird der erste Node in der aktuellen Auswahl bearbeitet."
|
||||
}
|
||||
}
|
||||
"info": {
|
||||
"tip0": "Sie können die ausgewählten Nodes oder Verbindungen mit {{ core:delete-selection }} entfernen",
|
||||
"tip1": "Sie können nach Nodes mit {{ core:search }} suchen",
|
||||
"tip2": "{{ core:toggle-sidebar }} blendet die Seitenleiste ein/aus",
|
||||
"tip3": "Sie können Ihre Node-Palette mit {{ core:manage-palette }} verwalten",
|
||||
"tip4": "Ihre Flow-Konfigurationsnodes werden in der Seitenleiste angezeigt, die über das Menü oder mit {{ core:show-config-tab }} angezeigt werden kann",
|
||||
"tip5": "Aktiviere oder deaktiviere diese Tipps in den Einstellungen im Tab 'Ansicht'",
|
||||
"tip6": "Sie können die ausgewählten Nodes mit den [left]/[up]/[down]/[right]-Tasten verschieben. Wenn Sie dabei [Shift] gedrückt halten, können Sie den Fensterausschnitt verschieben.",
|
||||
"tip7": "Wenn Sie ein Node auf eine Verbindung ziehen, wird es in die Verbindung eingefügt",
|
||||
"tip8": "Sie können die ausgewählten Nodes oder den aktuellen Flow-Tab mit {{ core:show-export-dialog }} exportieren",
|
||||
"tip9": "Sie können einen Flow importieren, indem Sie sein JSON in den Editor ziehen oder mittels {{ core:show-import-dialog }}",
|
||||
"tip10": "Halten Sie [Shift] beim [Klicken] auf ein Node gedrückt, um auch alle verbundenen Nodes mit zu verschieben",
|
||||
"tip11": "Sie können den Tab 'Info' mit {{ core:show-info-tab }} oder den Tab 'Debug' mit {{ core:show-debug-tab }} anzeigen lassen",
|
||||
"tip12": "Halten Sie [Strg] beim [Klicken] in den Arbeitsbereich gedrückt, um den Schnellhinzufügedialog öffnen",
|
||||
"tip13": "Halten Sie [Strg] beim [Klicken] auf einen Node-Anschluss gedrückt, um eine Verbindung nur durch kurzes [Klicken] (ohne Halten) zu verlegen",
|
||||
"tip14": "Halten Sie [Shift] beim [Klicken] auf ein Node gedrückt, um auch alle verbundenen Nodes mit auszuwählen",
|
||||
"tip15": "Halten Sie [Strg] beim [Klicken] auf ein Node gedrückt, um es zu der aktuellen Auswahl hinzuzufügen oder aus ihr zu entfernen",
|
||||
"tip16": "Sie können die Flow-Tabs mit {{ core:show-previous-tab }} und {{ core:show-next-tab }} wechseln",
|
||||
"tip17": "Sie können die Änderungen im Node-Editor mit {{ core:confirm-edit-tray }} bestätigen oder sie mit {{ core:cancel-edit-tray }} verwerfen",
|
||||
"tip18": "Sie können mit {{ core:edit-selected-node }} den ersten Node in der aktuellen Auswahl bearbeiten"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +1,274 @@
|
||||
{
|
||||
"$string" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Transformiert den Parameter *arg* in eine Zeichenfolge mit den folgenden Transformationsregeln:\n\n -Zeichenfolgen bleiben unverändert\n -Funktionen werden in eine leere Zeichenfolge konvertiert\n -Numerische Unendlichkeit und NaN lösen einen Fehler aus, da sie nicht als JSON-Nummer dargestellt werden können.\n -Alle anderen Werte werden mit Hilfe der Funktion 'JSON.stringify' in eine JSON-Zeichenfolge konvertiert."
|
||||
},
|
||||
"$length" : {
|
||||
"args" : "str",
|
||||
"desc" : "Gibt die Anzahl der Zeichen in der Zeichenfolge `str` zurück. Es wird ein Fehler ausgelöst, wenn `str` keine Zeichenfolge ist."
|
||||
},
|
||||
"$substring" : {
|
||||
"args" : "str, start [, länge]",
|
||||
"desc" : "Gibt eine Zeichenfolge zurück, die die Zeichen im ersten Parameter `str` beginnend bei Position `start` (Null-Offset) enthält. Wenn \"length\" angegeben ist, enthält die Unterzeichenfolge maximal \"Länge\" Zeichen. Wenn `start` negativ ist, gibt es die Anzahl der Zeichen am Ende von `str` an."
|
||||
},
|
||||
"$substringBefore" : {
|
||||
"args" : "str, chars",
|
||||
"desc" : "Gibt die Unterzeichenfolge vor dem ersten Auftreten der Zeichenfolge `chars` in `str` zurück. Falls `str` nicht `chars` enthält, gibt es `str` zurück."
|
||||
},
|
||||
"$substringAfter" : {
|
||||
"args" : "str, chars",
|
||||
"desc" : "Gibt die Unterzeichenfolge nach dem ersten Auftreten der Zeichenfolge `chars` in `str` zurück. Falls `str` nicht `chars` enthält, gibt es `str` zurück."
|
||||
},
|
||||
"$uppercase" : {
|
||||
"args" : "str",
|
||||
"desc" : "Gibt eine Zeichenfolge mit allen Zeichen von `str` zurück, die in Großbuchstaben konvertiert werden."
|
||||
},
|
||||
"$lowercase" : {
|
||||
"args" : "str",
|
||||
"desc" : "Gibt eine Zeichenfolge mit allen Zeichen von `str` in Kleinbuchstaben zurück."
|
||||
},
|
||||
"$trim" : {
|
||||
"args" : "str",
|
||||
"desc" : "Normalisiert und trimmt alle Leerzeichen in `str` durch Anwenden der folgenden Schritte:\n\n -Alle Tabulatorstopps, Wagenrückläufe und Zeilenvorschübe werden durch Leerzeichen ersetzt.\n-Zusammenhängende Folgen von Räumen werden auf einen einzigen Raum reduziert.\n-Trailing und führende Plätze werden entfernt.\n\n Wenn 'str' nicht angegeben ist (d. h. Diese Funktion wird ohne Argumente aufgerufen), dann wird der Kontextwert als Wert von `str` verwendet. Es wird ein Fehler ausgelöst, wenn `str` keine Zeichenfolge ist."
|
||||
},
|
||||
"$contains" : {
|
||||
"args" : "str, Muster",
|
||||
"desc" : "Gibt `true` zurück, wenn `str` durch `Muster` abgeglichen wird, sonst gibt es `false` zurück. Wenn 'str' nicht angegeben ist (d. h. Diese Funktion wird mit einem Argument aufgerufen), dann wird der Kontextwert als Wert von `str` verwendet. Der Parameter 'Muster' kann entweder eine Zeichenfolge oder ein regulärer Ausdruck sein."
|
||||
},
|
||||
"$split" : {
|
||||
"args" : "str [, Trennzeichen] [, Grenzwert]",
|
||||
"desc" : "Teilt den Parameter 'str' in einem Array mit Unterzeichenfolgen. Es ist ein Fehler, wenn `str` keine Zeichenfolge ist. Der optionale Parameter 'Trennzeichen' gibt die Zeichen in der `str` an, um die es entweder als Zeichenfolge oder als regulärer Ausdruck geteilt werden soll. Wenn 'Trennzeichen' nicht angegeben wird, wird die leere Zeichenfolge angenommen, und `str` wird in ein Array aus einzelnen Zeichen aufgeteilt. Es handelt sich um einen Fehler, wenn `Trennzeichen' keine Zeichenfolge ist. Der optionale Parameter 'Grenzwert' ist eine Zahl, die die maximale Anzahl von Unterzeichenfolgen angibt, die in das resultierende Array eingeschlossen werden sollen. Alle zusätzlichen Unterzeichenfolgen werden gelöscht. Wenn 'Grenzwert' nicht angegeben wird, wird ' str ` vollständig geteilt, wobei die Größe des resultierenden Arrays nicht begrenzt ist. Es handelt sich um einen Fehler, wenn `Grenzwert' keine nicht negative Zahl ist."
|
||||
},
|
||||
"$join" : {
|
||||
"args" : "array [, Trennzeichen]",
|
||||
"desc" : "Verkettet ein Array von Komponentenzeichenfolgen in eine einzelne verkettete Zeichenfolge mit jeder Komponentenzeichenfolge, die durch den optionalen Parameter 'separator' getrennt ist. Es ist ein Fehler, wenn die Eingabe `Array` ein Element enthält, das keine Zeichenfolge ist. Wenn 'Trennzeichen' nicht angegeben wird, wird davon ausgegangen, dass es sich um eine leere Zeichenfolge handelt, d. h. Zwischen den Komponentenzeichenfolgen ist kein Trennzeichen vorhanden. Es handelt sich um einen Fehler, wenn `Trennzeichen' keine Zeichenfolge ist."
|
||||
},
|
||||
"$match" : {
|
||||
"args" : "str, Muster [, Grenzwert]",
|
||||
"desc" : "Wendet die Zeichenfolge `str` an den regulären Ausdruck `Muster` an und gibt ein Array von Objekten zurück, wobei jedes Objekt Informationen zu jedem Vorkommen einer Übereinstimmung in `str` enthält."
|
||||
},
|
||||
"$replace" : {
|
||||
"args" : "str, Muster, Ersatz [, Grenzwert]",
|
||||
"desc" : "Findet Vorkommen von `Muster` in `str` und ersetzt sie durch `Ersatz`.\n\nDer optionale Parameter 'Grenzwert' ist die maximale Anzahl an Ersetzungen."
|
||||
},
|
||||
"$now" : {
|
||||
"args" : "",
|
||||
"desc" : "Generiert einen Zeitstempel im ISO-8601-kompatiblen Format und gibt sie als Zeichenfolge zurück."
|
||||
},
|
||||
"$base64encode" : {
|
||||
"args" : "Zeichenfolge",
|
||||
"desc" : "Konvertiert eine ASCII-Zeichenfolge in eine Basis-64-Darstellung. Jedes Zeichen in der Zeichenfolge wird als Byte mit binären Daten behandelt. Dies setzt voraus, dass alle Zeichen in der Zeichenfolge im Bereich von 0x00 bis 0xFF liegen, der alle Zeichen in URI-codierten Zeichenfolgen enthält. Unicode-Zeichen außerhalb dieses Bereichs werden nicht unterstützt."
|
||||
},
|
||||
"$base64decode" : {
|
||||
"args" : "Zeichenfolge",
|
||||
"desc" : "Konvertiert die Basis-64-codierten Byte in eine Zeichenfolge unter Verwendung einer UTF-8-Unicode-Codepage."
|
||||
},
|
||||
"$number" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Der Parameter 'arg' wird unter Verwendung der folgenden Regeln für das Casting in eine Zahl verwendet:\n\n -Zahlen bleiben unverändert\n -Zeichenfolgen, die eine Folge von Zeichen enthalten, die eine rechtliche JSON-Nummer darstellen, werden in diese Zahl konvertiert.\n -Alle anderen Werte bewirken, dass ein Fehler ausgelöst wird."
|
||||
},
|
||||
"$abs" : {
|
||||
"args" : "Anzahl",
|
||||
"desc" : "Gibt den absoluten Wert des Parameters 'Zahl' zurück."
|
||||
},
|
||||
"$floor" : {
|
||||
"args" : "Anzahl",
|
||||
"desc" : "Gibt den Wert von 'Zahl' auf die nächste ganze Zahl zurück, die kleiner oder gleich 'Zahl' ist."
|
||||
},
|
||||
"$ceil" : {
|
||||
"args" : "Anzahl",
|
||||
"desc" : "Gibt den Wert von 'Zahl' auf die nächste ganze Zahl zurück, die größer oder gleich 'Zahl' ist."
|
||||
},
|
||||
"$round" : {
|
||||
"args" : "Zahl [, Genauigkeit]",
|
||||
"desc" : "Gibt den Wert des Parameters `Zahl` zurück, der auf die Anzahl der Dezimalstellen gerundet wird, die durch den optionalen Parameter 'Genauigkeit' angegeben wird."
|
||||
},
|
||||
"$power" : {
|
||||
"args" : "Basis, Exponent",
|
||||
"desc" : "Gibt den Wert von `Basis` potenziert mit `Exponent` zurück."
|
||||
},
|
||||
"$sqrt" : {
|
||||
"args" : "Zahl",
|
||||
"desc" : "Gibt die Quadratwurzel des Werts des Parameters 'Zahl' zurück."
|
||||
},
|
||||
"$random" : {
|
||||
"args" : "",
|
||||
"desc" : "Gibt eine Pseudozufallszahl größer-gleich null und kleiner als eins zurück."
|
||||
},
|
||||
"$millis" : {
|
||||
"args" : "",
|
||||
"desc" : "Gibt die Anzahl der Millisekunden seit der Unix-Epoche (1. Januar 1970 (UTC)) als Zahl zurück. Alle Invocationen von `$millis ()` innerhalb einer Auswertung eines Ausdrucks geben alle denselben Wert zurück."
|
||||
},
|
||||
"$sum" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt die arithmetische Summe eines `Array` von Zahlen zurück. Es ist ein Fehler, wenn die Eingabe `Array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$max" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt die maximale Anzahl in einem `Array` von Zahlen zurück. Es ist ein Fehler, wenn die Eingabe `Array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$min" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt die minimale Zahl in einem `Array` von Zahlen zurück. Es ist ein Fehler, wenn die Eingabe `Array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$average" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt den Mittelwert eines `Array` von Zahlen zurück. Es ist ein Fehler, wenn die Eingabe `Array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$boolean" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Castet das Argument mit den folgenden Regeln in einen Booleschen Wert:\n\n -` Boolean ': nicht geändert\n -` string `: leer: `false`\n -` string `: nicht leer: `true`\n -` Zahl `: ` 0 `: ` falsch `\n -` Zahl `: Nicht-Null: `true`\n -` null `: `false`\n -` array `: leer: `false`\n -` array `: enthält ein Mitglied, das auf `true` setzt: `true`\n -` array `: alle Member werden in `false` umgesetzt: `false`\n -` object `: empty: `false`\n -` object `: non-empty: `true`\n -` Funktion `: ` falsch `"
|
||||
},
|
||||
"$not" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Gibt den Booleschen Wert NOT für das Argument zurück. `arg` wird zuerst in einen Booleschen Wert umgesetzt."
|
||||
},
|
||||
"$exists" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Gibt den Booleschen Wert 'true' zurück, wenn der Ausdruck `arg` als Wert ausgewertet wird, oder 'false', wenn der Ausdruck nicht mit einem anderen Ausdruck übereinstimmt (z. B. ein Pfad zu einer nicht vorhandenen Feldreferenz)."
|
||||
},
|
||||
"$count" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt die Anzahl der Elemente in dem Array zurück."
|
||||
},
|
||||
"$append" : {
|
||||
"args" : "Array, Array",
|
||||
"desc" : "Hängen Sie zwei Arrays an."
|
||||
},
|
||||
"$sort" : {
|
||||
"args" : "array [, Funktion]",
|
||||
"desc" : "Gibt ein Array zurück, das alle Werte im Parameter 'array' enthält, aber in der Reihenfolge sortiert wird.\n\nWenn ein Vergleichsoperator 'function' angegeben wird, muss es sich um eine Funktion handeln, die zwei Parameter benötigt:\n\n` Funktion (links, rechts) `\n\nDiese Funktion wird durch den Sortieralgorithmus aufgerufen, um zwei Werte links und rechts zu vergleichen. Wenn der Wert von links nach dem Wert von rechts in der gewünschten Sortierreihenfolge platziert werden soll, muss die Funktion den Booleschen Wert 'true' zurückgeben, um einen Auslagerungsspeicher anzuzeigen. Andernfalls muss 'false' zurückgegeben werden."
|
||||
},
|
||||
"$reverse" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt ein Array zurück, das alle Werte aus dem Parameter 'array' enthält, aber in umgekehrter Reihenfolge."
|
||||
},
|
||||
"$shuffle" : {
|
||||
"args" : "Array",
|
||||
"desc" : "Gibt ein Array zurück, das alle Werte aus dem Parameter ` array ` enthält, aber in zufälliger Reihenfolge geschattiert ist."
|
||||
},
|
||||
"$zip" : {
|
||||
"args" : "Array, ...",
|
||||
"desc" : "Gibt ein konvolviertes (gezipptes) Array zurück, das gruppierte Arrays von Werten aus den Argumenten ` array1 ` ... ` arrayN ' aus Index 0, 1, 2 ... enthält."
|
||||
},
|
||||
"$keys" : {
|
||||
"args" : "Objekt",
|
||||
"desc" : "Gibt ein Array zurück, das die Schlüssel in dem Objekt enthält. Wenn es sich bei dem Argument um ein Array von Objekten handelt, enthält das zurückgegebene Array eine deduplizierte Liste aller Schlüssel in allen Objekten."
|
||||
},
|
||||
"$lookup" : {
|
||||
"args" : "Objekt, Schlüssel",
|
||||
"desc" : "Gibt den Wert zurück, der dem Schlüssel im Objekt zugeordnet ist. Wenn es sich bei dem ersten Argument um ein Array von Objekten handelt, werden alle Objekte im Array durchsucht, und die Werte, die mit allen Vorkommen des Schlüssels verknüpft sind, werden zurückgegeben."
|
||||
},
|
||||
"$spread" : {
|
||||
"args" : "Objekt",
|
||||
"desc" : "Teilt ein Objekt, das Schlüssel/Wert-Paare enthält, in ein Array von Objekten, von denen jedes ein einzelnes Schlüssel/Wert-Paar aus dem Eingabeobjekt hat. Wenn es sich bei dem Parameter um ein Array von Objekten handelt, enthält die resultierende Feldgruppe ein Objekt für jedes Schlüssel/Wert-Paar in jedem Objekt in der angegebenen Feldgruppe."
|
||||
},
|
||||
"$merge" : {
|
||||
"args" : "array <object>",
|
||||
"desc" : "Mischt ein Array von ` Objekten ` in ein einzelnes ` Objekt `, das alle Schlüssel/Wert-Paare aus jedem der Objekte in dem Eingabe-Array enthält. Wenn eines der Eingabeobjekte denselben Schlüssel enthält, enthält das zurückgegebene Objekt den Wert des letzten Objekts in der Feldgruppe. Es handelt sich um einen Fehler, wenn das Eingabe-Array ein Element enthält, das kein Objekt ist."
|
||||
},
|
||||
"$sift" : {
|
||||
"args" : "Objekt, Funktion",
|
||||
"desc" : "Gibt ein Objekt zurück, das nur die Schlüssel/Wert-Paare aus dem Parameter 'object' enthält, die die Prädikat ` funktion ' erfüllen, die als zweiter Parameter übergeben wird.\n\nDie Funktion ` function `, die als zweiter Parameter angegeben wird, muss die folgende Signatur aufweisen:\n\n` function (value [, key [, object]]) `"
|
||||
},
|
||||
"$each" : {
|
||||
"args" : "Objekt, Funktion",
|
||||
"desc" : "Gibt ein Array zurück, das die Werte enthält, die von der Funktion ` function ` zurückgegeben werden, wenn sie auf jedes Schlüssel/Wert-Paar im ` object ` angewendet werden."
|
||||
},
|
||||
"$map" : {
|
||||
"args" : "Array, Funktion",
|
||||
"desc" : "Gibt ein Array zurück, das die Ergebnisse der Anwendung des Parameters ` function ` auf jeden Wert im Parameter 'array' enthält.\n\nDie Funktion ` function `, die als zweiter Parameter angegeben wird, muss die folgende Signatur aufweisen:\n\n` function (value [, index [, array]]) `"
|
||||
},
|
||||
"$filter" : {
|
||||
"args" : "Array, Funktion",
|
||||
"desc" : "Gibt ein Array zurück, das nur die Werte im Parameter 'array' enthält, die das Prädikat ` funktion ` erfüllen.\n\nDie Funktion ` function `, die als zweiter Parameter angegeben wird, muss die folgende Signatur aufweisen:\n\n` function (value [, index [, array]]) `"
|
||||
},
|
||||
"$reduce" : {
|
||||
"args" : "array, function [, init]",
|
||||
"desc" : "Gibt einen aggregierten Wert zurück, der aus der Anwendung des Parameters ` function 'nacheinander auf jeden Wert in' array ` in Kombination mit dem Ergebnis der vorherigen Anwendung der Funktion angewendet wurde.\n\nDie Funktion muss zwei Argumente akzeptieren und verhält sich wie ein Infix-Operator zwischen jedem Wert innerhalb des ` Array `.\n\nDer optionale Parameter 'init' wird als Anfangswert in der Aggregation verwendet."
|
||||
},
|
||||
"$flowContext" : {
|
||||
"args" : "Zeichenfolge [, Zeichenfolge]",
|
||||
"desc" : "Ruft eine Flusskontexteigenschaft ab.\n\nDies ist eine definierte Funktion vom Typ \"Node-RED\"."
|
||||
},
|
||||
"$globalContext" : {
|
||||
"args" : "Zeichenfolge [, Zeichenfolge]",
|
||||
"desc" : "Ruft eine globale Kontexteigenschaft ab.\n\nDies ist eine definierte Funktion vom Typ \"Node-RED\"."
|
||||
},
|
||||
"$pad" : {
|
||||
"args" : "string, width [, char]",
|
||||
"desc" : "Gibt eine Kopie der ` Zeichenfolge ` mit zusätzlichen Aufenthalten zurück, falls erforderlich, so dass die Gesamtzahl der Zeichen mindestens der absolute Wert des Parameters 'width' ist.\n\nWenn ` width ` eine positive Zahl ist, wird die Zeichenfolge nach rechts aufgefüllt. Wenn sie negativ ist, wird sie nach links geplisften.\n\nDas optionale Argument 'char' gibt die Padding-Zeichen an, die verwendet werden sollen. Wenn keine Angabe gemacht wird, wird standardmäßig der Wert für das Leerzeichen angenommen."
|
||||
},
|
||||
"$fromMillis" : {
|
||||
"args" : "Anzahl",
|
||||
"desc" : "Konvertieren Sie eine Zahl, die Millisekunden seit der Unix-Epoche (1. Januar 1970 (UTC)) enthält in eine Zeitangabe im ISO 8601-Format."
|
||||
},
|
||||
"$formatNumber" : {
|
||||
"args" : "Zahl, Bild [, Optionen]",
|
||||
"desc" : "Transformiere die `Zahl` an eine Zeichenfolge und formatiert sie in eine dezimale Darstellung, wie in der 'Bild' -Zeichenfolge angegeben.\n\n Das 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 Parameter für die Bildzeichenfolge definiert, wie die Zahl formatiert ist und hat die gleiche Syntax wie fn:format-number.\n\nDas optionale dritte Argument ` Optionen ` wird verwendet, um die standardmäßigen länderspezifischen Formatierungszeichen, wie z. B. das Dezimaltrennzeichen, zu überschreiben. Wenn dieses Argument angegeben 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 angegeben sind."
|
||||
},
|
||||
"$formatBase" : {
|
||||
"args" : "Zahl [, Radix]",
|
||||
"desc" : "Transformiere die `Zahl` in eine Zeichenfolge und formatiert sie in eine ganze Zahl, die in der durch das `radix` -Argument angegebenen Zahlenbasis dargestellt wird. Wenn 'radix' nicht angegeben wird, wird standardmäßig die Basis 10 verwendet. 'radix` kann zwischen 2 und 36 liegen, andernfalls wird ein Fehler ausgelöst."
|
||||
},
|
||||
"$toMillis" : {
|
||||
"args" : "timestamp",
|
||||
"desc" : "Konvertieren Sie eine Zeitangabe im ISO 8601-Format in die Anzahl der Millisekunden seit der Unix-Epoche (1. Januar 1970 (UTC)) als Zahl. Es wird ein Fehler ausgelöst, wenn die Zeichenfolge nicht das richtige Format hat."
|
||||
},
|
||||
"$env" : {
|
||||
"args" : "arg",
|
||||
"desc" : "Gibt den Wert einer Umgebungsvariablen zurück.\n\nDies ist eine definierte Funktion vom Typ \"Node-RED\"."
|
||||
}
|
||||
}
|
||||
"$string": {
|
||||
"args": "arg[, prettify]",
|
||||
"desc": "Wandelt `arg` in eine Zeichenfolge um gemäß der folgenden Regeln:\n\n- Zeichenfolgen (string) bleiben unverändert\n- Funktionen werden in eine leere Zeichenfolge konvertiert\n- Numerische Unendlichkeit und NaN lösen einen Fehler aus, da sie nicht als JSON-Zahlenwert dargestellt werden können.\n- Alle anderen Werte werden mit Hilfe der Funktion `JSON.stringify` in eine JSON-Zeichenfolge konvertiert. Wenn `prettify` `true` ist, wird \"prettified\" JSON erzeugt. Z.B. Eine Zeile pro Feld und Zeilen werden eingeschoben basierend auf der Feldtiefe."
|
||||
},
|
||||
"$length": {
|
||||
"args": "str",
|
||||
"desc": "Gibt die Zeichenanzahl von `str` zurück. Es wird ein Fehler ausgelöst, wenn `str` keine Zeichenfolge ist."
|
||||
},
|
||||
"$substring": {
|
||||
"args": "str, start [, length]",
|
||||
"desc": "Gibt eine Teilzeichenfolge zurück, die die Zeichen in `str` beginnend bei Position `start` (Null-Offset) enthält. Wenn `length` vorgegeben ist, enthält die rückgegebene Zeichenfolge maximal die in `length` vorgegebene Zeichenanzahl. Wenn `start` negativ ist, werden die Zeichen vom Ende aus gezählt von `str` zurückgegeben."
|
||||
},
|
||||
"$substringBefore": {
|
||||
"args": "str, chars",
|
||||
"desc": "Gibt die Teilzeichenfolge vor dem ersten Auftreten der Zeichenfolge `chars` in `str` zurück. Falls `str` nicht `chars` enthält, wird `str` zurückgegeben."
|
||||
},
|
||||
"$substringAfter": {
|
||||
"args": "str, chars",
|
||||
"desc": "Gibt die Teilzeichenfolge nach dem ersten Auftreten der Zeichenfolge `chars` in `str` zurück. Falls `str` nicht `chars` enthält, wird `str` zurückgegeben."
|
||||
},
|
||||
"$uppercase": {
|
||||
"args": "str",
|
||||
"desc": "Gibt veränderten `str` zurück, bei dem allen Zeichen in Großbuchstaben umgewandelt wurden."
|
||||
},
|
||||
"$lowercase": {
|
||||
"args": "str",
|
||||
"desc": "Gibt veränderten `str` zurück, bei dem allen Zeichen in Kleinbuchstaben umgewandelt wurden."
|
||||
},
|
||||
"$trim": {
|
||||
"args": "[str]",
|
||||
"desc": "Normalisiert und trimmt alle Leerzeichen in `str` durch Anwenden der folgenden Schritte:\n\n- Alle Tabulatoren, Wagenrückläufe (returns) und Zeilenvorschübe (line feeds) werden durch Leerzeichen ersetzt.\n- Zusammenhängende Folgen von Leerzeichen werden auf ein einzelnes Leerzeichen reduziert.\n- Leerzeichen am Anfang und am Ende werden entfernt.\n\nWenn `str` nicht vorgegeben ist (d.h. diese Funktion wird ohne Parameter aufgerufen), dann wird der Kontextwert als Wert von `str` verwendet. Es wird ein Fehler ausgelöst, wenn `str` keine Zeichenfolge ist."
|
||||
},
|
||||
"$contains": {
|
||||
"args": "str, pattern",
|
||||
"desc": "Gibt `false` zurück, wenn `pattern` als Teilzeichenfolge in `str` enthalten ist, sonst gibt sie `false` zurück. Wenn `str` nicht vorgegeben ist (d. h. Diese Funktion wird mit einem Parameter aufgerufen), dann wird der Kontextwert als Wert von `str` verwendet. `pattern` kann entweder eine Zeichenfolge oder ein regulärer Ausdruck sein."
|
||||
},
|
||||
"$split": {
|
||||
"args": "str [, separator] [, limit]",
|
||||
"desc": "Teilt `str` in einem Array mit Teilzeichenfolgen. Es ergibt einen Fehler, wenn `str` keine Zeichenfolge ist.\n\nDer optionale Parameter `separator` gibt die Zeichen in der `str` an, anhand dem, vorgegeben entweder als Zeichenfolge oder als regulärer Ausdruck, `str` geteilt werden soll. Wenn `separator` nicht vorgegeben wird, wird ein leerer String als `separator` angenommen und `str` wird in ein Array aus einzelnen Zeichen aufgeteilt. Es handelt sich um einen Fehler, wenn `separator` leer ist.\n\nDer optionale Parameter `limit` ist eine Zahl, die die maximale Anzahl von Teilzeichenfolgen angibt, die in dem rückzugebenen Array enthalten sein sollen. Alle zusätzlichen Teilzeichenfolgen werden verworfen. Wenn `limit` nicht vorgegeben wird, wird `str` vollständig geteilt, wobei die Größe des resultierenden Arrays nicht begrenzt ist. Es handelt sich um einen Fehler, wenn `limit` eine negative Zahl ist."
|
||||
},
|
||||
"$join": {
|
||||
"args": "array [, separator]",
|
||||
"desc": "Verkettet ein Array von Zeichenfolgen zu einer einzigen Zeichenfolge, wobei die einzelnen Zeichenfolgen durch den optionalen Trennzeichen-Parameter `separator` getrennt sind. Es ergibt einen Fehler, wenn das `array` ein Element enthält, das keine Zeichenfolge ist. Wenn `separator` nicht vorgegeben wird, wird davon ausgegangen, dass es sich um eine leere Zeichenfolge handelt, d.h. zwischen den einzelnen Zeichenfolgen wird kein Trennzeichen eingefügt. Es handelt sich um einen Fehler, wenn `separator` keine Zeichenfolge ist."
|
||||
},
|
||||
"$match": {
|
||||
"args": "str, pattern [, limit]",
|
||||
"desc": "Wendet den regulären Ausdruck `pattern` auf die Zeichenfolge `str` an und gibt ein Array von Objekten zurück, die Informationen zu jedem Vorkommen von `pattern` in `str` enthält."
|
||||
},
|
||||
"$replace": {
|
||||
"args": "str, pattern, replacement [, limit]",
|
||||
"desc": "Findet Vorkommen von `pattern` in `str` und ersetzt sie durch `replacement`.\n\nDer optionale Parameter `limit` ist die maximale Anzahl an Ersetzungen."
|
||||
},
|
||||
"$now": {
|
||||
"args": "",
|
||||
"desc": "Generiert einen Zeitstempel im ISO-8601-kompatiblen Format und gibt sie als Zeichenfolge zurück."
|
||||
},
|
||||
"$base64encode": {
|
||||
"args": "str",
|
||||
"desc": "Konvertiert eine ASCII-Zeichenfolge `str` in eine Basis-64-Darstellung. Jedes Zeichen in `str` wird als Byte mit binären Daten behandelt. Dies setzt voraus, dass alle Zeichen in der Zeichenfolge im Bereich von 0x00 bis 0xFF liegen, der alle Zeichen in URI-codierten Zeichenfolgen enthält. Unicode-Zeichen außerhalb dieses Bereichs werden nicht unterstützt."
|
||||
},
|
||||
"$base64decode": {
|
||||
"args": "str",
|
||||
"desc": "Konvertiert den Basis-64-codierten `str` in eine Zeichenfolge unter Verwendung einer UTF-8-Unicode-Codepage."
|
||||
},
|
||||
"$number": {
|
||||
"args": "arg",
|
||||
"desc": "Wandelt `arg` unter Verwendung der folgenden Regeln in eine Zahl um:\n\n- Zahlen bleiben unverändert\n- Zeichenfolgen, die eine Folge von Zeichen enthalten, die einen echten JSON-Zahlenwert darstellen, werden in die entsprechende Zahl konvertiert.\n- Alle anderen Werte bewirken, dass ein Fehler ausgelöst wird."
|
||||
},
|
||||
"$abs": {
|
||||
"args": "number",
|
||||
"desc": "Gibt den absoluten Wert von `number` zurück."
|
||||
},
|
||||
"$floor": {
|
||||
"args": "number",
|
||||
"desc": "Gibt `number` abgerundet auf die nächste ganze Zahl zurück, die kleiner oder gleich `number` ist."
|
||||
},
|
||||
"$ceil": {
|
||||
"args": "number",
|
||||
"desc": "Gibt `number` aufgerundet auf die nächste ganze Zahl zurück, die größer oder gleich `number` ist."
|
||||
},
|
||||
"$round": {
|
||||
"args": "number [, precision]",
|
||||
"desc": "Gibt `number` gerundet auf die Anzahl der Nachkommastellen zurück, welche durch den optionalen Parameter `precision` vorgegeben ist."
|
||||
},
|
||||
"$power": {
|
||||
"args": "base, exponent",
|
||||
"desc": "Gibt `base` potenziert mit `exponent` zurück."
|
||||
},
|
||||
"$sqrt": {
|
||||
"args": "number",
|
||||
"desc": "Gibt die Quadratwurzel von `number` zurück."
|
||||
},
|
||||
"$random": {
|
||||
"args": "",
|
||||
"desc": "Gibt eine Pseudozufallszahl größer-gleich null und kleiner als eins zurück."
|
||||
},
|
||||
"$millis": {
|
||||
"args": "",
|
||||
"desc": "Gibt die aktuelle Anzahl der Millisekunden seit Beginn der Unix-Zeitrechnung (1. Januar 1970 UTC) als Zahl zurück. Alle Aufrufe von `$millis()` innerhalb der Auswertung eines Ausdrucks geben alle denselben Wert zurück."
|
||||
},
|
||||
"$sum": {
|
||||
"args": "array",
|
||||
"desc": "Gibt die arithmetische Summe eines `array` von Zahlen zurück. Es ergibt einen Fehler, wenn `array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$max": {
|
||||
"args": "array",
|
||||
"desc": "Gibt die größte Zahl von einem `array` von Zahlen zurück. Es ergibt einen Fehler, wenn `array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$min": {
|
||||
"args": "array",
|
||||
"desc": "Gibt die kleinste Zahl von einem `array` von Zahlen zurück. Es ergibt einen Fehler, wenn `array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$average": {
|
||||
"args": "array",
|
||||
"desc": "Gibt den Mittelwert eines `array` von Zahlen zurück. Es ergibt einen Fehler, wenn `array` ein Element enthält, das keine Zahl ist."
|
||||
},
|
||||
"$boolean": {
|
||||
"args": "arg",
|
||||
"desc": "Wandelt `arg` gemäß folgender Regeln in einen booleschen Wert um:\n\n- `Boolean`: unverändert\n- `string`: leer → `false`, nicht leer → `true`\n- `Zahl`: `0` → `falsch`, Nicht-Null → `true`\n- `null` → `false`\n- `array`: leer → `false`, enthält mindestens ein Element, das `true` ist → `true`, alle Elemente sind `false` → `false`\n- `object`: leer → `false`, nicht leer → `true`\n- `function`: `false`"
|
||||
},
|
||||
"$not": {
|
||||
"args": "arg",
|
||||
"desc": "Gibt den invertierten booleschen Wert von `arg` zurück. `arg` wird zuerst in einen booleschen Wert umgesetzt."
|
||||
},
|
||||
"$exists": {
|
||||
"args": "arg",
|
||||
"desc": "Gibt den booleschen Wert `true` zurück, wenn der Ausdruck `arg` zu einem Wert ausgewertet wird, oder `false`, wenn der Ausdruck nicht mit einem anderen Ausdruck übereinstimmt (z.B. ein Pfad zu einer nicht vorhandenen Feldreferenz)."
|
||||
},
|
||||
"$count": {
|
||||
"args": "array",
|
||||
"desc": "Gibt die Anzahl der Elemente in dem Array `array` zurück."
|
||||
},
|
||||
"$append": {
|
||||
"args": "array, array",
|
||||
"desc": "Verkettet zwei Arrays miteinander."
|
||||
},
|
||||
"$sort": {
|
||||
"args": "array [, function]",
|
||||
"desc": "Gibt ein Array zurück, das alle Elemente vom `array` in sortierter Reihenfolge enthält.\n\nWenn ein Vergleichsoperator `function` vorgegeben wird, muss es sich um eine Funktion handeln, die zwei Parameter benötigt:\n\n`function(left, right)`\n\nDiese Funktion wird durch den Sortieralgorithmus aufgerufen, um zwei Elemente links und rechts zu vergleichen. Wenn das linke Element nach dem rechten in der gewünschten Sortierreihenfolge platziert werden soll, muss die Funktion den booleschen Wert `true` zurückgeben, um eine Vertauschung anzuzeigen. Andernfalls muss `false` zurückgegeben werden."
|
||||
},
|
||||
"$reverse": {
|
||||
"args": "array",
|
||||
"desc": "Gibt ein Array zurück, das alle Elemente vom `array` in umgekehrter Reihenfolge enthält."
|
||||
},
|
||||
"$shuffle": {
|
||||
"args": "array",
|
||||
"desc": "Gibt ein Array zurück, das alle Elemente vom `array` in zufälliger Reihenfolge enthält."
|
||||
},
|
||||
"$zip": {
|
||||
"args": "array, ...",
|
||||
"desc": "Gibt ein gepacktes (geziptes) Array zurück, das gruppierte Arrays der Elemente von `array1` ... `arrayN` aus Index 0, 1, 2 ... enthält."
|
||||
},
|
||||
"$keys": {
|
||||
"args": "object",
|
||||
"desc": "Gibt ein Array zurück, das die Schlüssel in dem Objekt `object` enthält. Wenn es sich bei dem Parameter um ein Array von Objekten handelt, enthält das zurückgegebene Array eine deduplizierte Liste aller Schlüssel in allen Objekten."
|
||||
},
|
||||
"$lookup": {
|
||||
"args": "object, key",
|
||||
"desc": "Gibt den Wert zurück, der dem Schlüssel `key` im Objekt `object` zugeordnet ist. Wenn es sich bei dem ersten Parameter um ein Array von Objekten handelt, werden alle Objekte im Array durchsucht, und die Werte, die mit allen Vorkommen des Schlüssels verknüpft sind, werden zurückgegeben."
|
||||
},
|
||||
"$spread": {
|
||||
"args": "object",
|
||||
"desc": "Teilt ein Objekt `object`, das Schlüssel/Wert-Paare enthält, in ein Array von Objekten, von denen jedes ein einzelnes Schlüssel/Wert-Paar aus dem Eingabeobjekt hat. Wenn es sich bei dem Parameter um ein Array von Objekten handelt, enthält die resultierende Feldgruppe ein Objekt für jedes Schlüssel/Wert-Paar in jedem Objekt in der vorgegebenen Feldgruppe."
|
||||
},
|
||||
"$merge": {
|
||||
"args": "array<object>",
|
||||
"desc": "Fügt ein Array von Objekt-Elementen `object` in ein einzelnes Objekt `object` zusammen, das alle Schlüssel/Wert-Paare aus jedem der Objekte in dem Ausgangs-Array enthält. Wenn eines der Ausgangs-Objekte denselben Schlüssel enthält, enthält das zurückgegebene Objekt den Wert des letzten Objekts des Arrays. Es handelt sich um einen Fehler, wenn das Ausgangs-Array ein Element enthält, das kein Objekt ist."
|
||||
},
|
||||
"$sift": {
|
||||
"args": "object, function",
|
||||
"desc": "Gibt ein Objekt zurück, das nur die Schlüssel/Wert-Paare aus dem Parameter `object` enthält, die die Prädikat `function` erfüllen, die als zweiter Parameter übergeben wird.\n\nDie Funktion `function`, die als zweiter Parameter vorgegeben wird, muss die folgende Signatur aufweisen:\n\n`function(value [, key [, object]])`"
|
||||
},
|
||||
"$each": {
|
||||
"args": "object, function",
|
||||
"desc": "Gibt ein Array zurück, das die Werte enthält, die von der Funktion `function` zurückgegeben werden, wenn sie auf jedes Schlüssel/Wert-Paar im `object` angewendet werden."
|
||||
},
|
||||
"$map": {
|
||||
"args": "array, function",
|
||||
"desc": "Gibt ein Array zurück, das die Ergebnisse von `function`, angewendet auf jedes Element von `array`, enthält.\n\nDie Funktion `function`, die als zweiter Parameter vorgegeben wird, muss die folgende Signatur aufweisen:\n\n`function(value [, index [, array]])`"
|
||||
},
|
||||
"$filter": {
|
||||
"args": "array, function",
|
||||
"desc": "Gibt ein Array zurück, das nur die Elemente von `array` enthält, die das Eigenschaft `function` erfüllen.\n\nDie Funktion `function`, die als zweiter Parameter vorgegeben wird, muss die folgende Signatur aufweisen:\n\n`function(value [, index [, array]])`"
|
||||
},
|
||||
"$reduce": {
|
||||
"args": "array, function [, init]",
|
||||
"desc": "Gibt einen aggregierten Wert zurück, der aus der Anwendung des Parameters `function` nacheinander auf jedes Element in `array` in Kombination mit dem Ergebnis der vorherigen Anwendung der Funktion angewendet wurde.\n\nDie Funktion muss zwei Parameter akzeptieren und verhält sich wie ein Infix-Operator zwischen jedem Element innerhalb des `array`.\n\nDer optionale Parameter `init` wird als Anfangswert in der Aggregation verwendet."
|
||||
},
|
||||
"$flowContext": {
|
||||
"args": "str [, str]",
|
||||
"desc": "Ruft eine Flow-Kontexteigenschaft ab.\n\nDies ist eine definierte Funktion vom Typ `Node-RED`."
|
||||
},
|
||||
"$globalContext": {
|
||||
"args": "str [, str]",
|
||||
"desc": "Ruft eine globale Kontexteigenschaft ab.\n\nDies ist eine definierte Funktion vom Typ `Node-RED`."
|
||||
},
|
||||
"$pad": {
|
||||
"args": "str, width [, char]",
|
||||
"desc": "Gibt eine aufgefüllte Kopie von `str` zurück, so dass (falls erforderlich) die Gesamtzahl der Zeichen mindestens dem absoluten Wert von `width` entspricht.\n\nWenn `width` eine positive Zahl ist, wird die Zeichenfolge nach rechts aufgefüllt. Wenn sie negativ ist, wird sie nach links aufgefüllt.\n\nDer optionale Parameter `char` gibt die Auffüll-Zeichen an, die verwendet werden sollen. Wenn keine Angabe gemacht wird, wird standardmäßig mit Leerzeichen aufgefüllt."
|
||||
},
|
||||
"$fromMillis": {
|
||||
"args": "number",
|
||||
"desc": "Konvertiert `number`, die die Millisekunden seit Beginn der Unix-Zeitrechnung (1. Januar 1970 UTC) enthält, in eine Zeitangabe im ISO 8601-Format."
|
||||
},
|
||||
"$formatNumber": {
|
||||
"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."
|
||||
},
|
||||
"$formatBase": {
|
||||
"args": "number [, radix]",
|
||||
"desc": "Wandelt `number` in eine Zeichenfolge um und formatiert sie in eine ganze Zahl, die in der durch den `radix`-Parameter vorgegebenen Zahlenbasis dargestellt wird. Wenn `radix` nicht vorgegeben wird, wird standardmäßig die Basis 10 verwendet. `radix` kann zwischen 2 und 36 liegen, andernfalls wird ein Fehler ausgelöst."
|
||||
},
|
||||
"$toMillis": {
|
||||
"args": "timestamp",
|
||||
"desc": "Konvertiert eine Zeitangabe `timestamp` im ISO 8601-Format in die Anzahl der Millisekunden seit Beginn der Unix-Zeitrechnung (1. Januar 1970 UTC). Es wird ein Fehler ausgelöst, wenn die Zeichenfolge nicht das richtige Format hat."
|
||||
},
|
||||
"$env": {
|
||||
"args": "arg",
|
||||
"desc": "Gibt den Wert einer Umgebungsvariablen zurück.\n\nDies ist eine definierte Funktion vom Typ `Node-RED`."
|
||||
},
|
||||
"$eval": {
|
||||
"args": "expr [, context]",
|
||||
"desc": "Analysiert (parse) und evaluiert den String `expr`, welcher JSON or a JSONata Ausdrücke enthält, unter Benutzung des aktuellen Kontextes für die Evaluierung."
|
||||
},
|
||||
"$formatInteger": {
|
||||
"args": "number, picture",
|
||||
"desc": "Wandelt `number` in eine Zeichenfolge um und formatiert sie in einer Ganzzahl-Darstellung, spezifiziert durch den `picture`-String-Parameter. Der `picture`-String-Parameter definiert, wie die Zahl `number` formatiert werden soll und hat den selben Syntax wie `fn:format-integer` der XPath F&O 3.1 Spezifikation."
|
||||
},
|
||||
"$parseInteger": {
|
||||
"args": "str, picture",
|
||||
"desc": "Wandelt den Inhalt von `str` in eine Ganzzahl `integer` (als JSON Zahl), spezifiziert durch den `picture`-String-Parameter. Der `picture`-String-Parameter hat das selbe Format wie `$formatInteger`."
|
||||
},
|
||||
"$error": {
|
||||
"args": "[str]",
|
||||
"desc": "Erzeugt eine Fehlermeldung. Der optionale String `str` ersetzt die Standardmeldung `$error() function evaluated`."
|
||||
},
|
||||
"$assert": {
|
||||
"args": "arg, str",
|
||||
"desc": "Wenn `arg` gleich `true` ist, liefert die Function `undefined` zurück. Wenn `arg` gleich `false` ist, wird ein Ausnahmefehler gemeldet mit dem String_Parameter `str` als Meldetext."
|
||||
},
|
||||
"$single": {
|
||||
"args": "array, function",
|
||||
"desc": "Gibt ein einziges Element aus `array` zurück, welches die Bedingung `function` erfüllt (d.h. die Funktion `function` gibt den booleschen Wert `true` zurück, wenn das Element übergeben werden soll). Sie meldet einen Ausnahmefehler, wenn die Anzahl der Elemente mit erfüllter Bedingung (`function` ist `true`) nicht genau eins ist.\n\nDie Funktion `function` sollte in der folgenden Art vorgegeben werden: `function(value [, index [, array]])` wobei `value` für jedes Element des Arrays gilt, `index` ist die Position dieses Elements und das gesamte Array `array` wird als dritter Parameter übergeben."
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Kodiert eine URL-Komponente (Uniform Resource Locator), indem jedes Vorkommen bestimmter Zeichen durch eine, zwei, drei oder vier Escape-Sequenzen ersetzt wird, die die UTF-8-Kodierung des Zeichens darstellen.\n\nBeispiel: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "Kodiert eine URL (Uniform Resource Locator), indem jedes Vorkommen bestimmter Zeichen durch eine, zwei, drei oder vier Escape-Sequenzen ersetzt wird, die die UTF-8-Kodierung des Zeichens darstellen.\n\nBeispiel: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Dekodiert eine URL-Komponente (Uniform Resource Locator) zuvor erzeugt von encodeUrlComponent.\n\nBeispiel: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "Dekodiert eine URL (Uniform Resource Locator) zuvor erzeugt von encodeUrl.\n\nBeispiel: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
|
||||
},
|
||||
"$distinct": {
|
||||
"args": "array",
|
||||
"desc": "Liefert ein `array` zurück, bei dem doppelte Elemente entfernt wurden."
|
||||
},
|
||||
"$type": {
|
||||
"args": "value",
|
||||
"desc": "Liefert den Typ von `value` als String. When `value` undefiniert ist, wird `undefined` zurückgeliefert."
|
||||
},
|
||||
"$moment": {
|
||||
"args": "[str]",
|
||||
"desc": "Liefert ein `date` Objekt unter Benutzung der Moment Library."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"color": "Color",
|
||||
"position": "Position",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable"
|
||||
"disable": "Disable",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"type": {
|
||||
"string": "string",
|
||||
@@ -37,11 +38,14 @@
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"loadPlugins": "Loading Plugins",
|
||||
"loadPalette": "Loading Palette",
|
||||
"loadNodeCatalogs": "Loading Node catalogs",
|
||||
"loadNodes": "Loading Nodes __count__",
|
||||
"loadFlows": "Loading Flows",
|
||||
"importFlows": "Adding Flows to workspace"
|
||||
"importFlows": "Adding Flows to workspace",
|
||||
"importError": "<p>Error adding flows</p><p>__message__</p>",
|
||||
"loadingProject": "Loading project"
|
||||
},
|
||||
"workspace": {
|
||||
"defaultName": "Flow __number__",
|
||||
@@ -49,8 +53,15 @@
|
||||
"confirmDelete": "Confirm delete",
|
||||
"delete": "Are you sure you want to delete '__label__'?",
|
||||
"dropFlowHere": "Drop the flow here",
|
||||
"addFlow": "Add Flow",
|
||||
"listFlows": "List Flows",
|
||||
"addFlow": "Add flow",
|
||||
"addFlowToRight": "Add flow to the right",
|
||||
"hideFlow": "Hide flow",
|
||||
"hideOtherFlows": "Hide other flows",
|
||||
"showAllFlows": "Show all flows",
|
||||
"hideAllFlows": "Hide all flows",
|
||||
"showLastHiddenFlow": "Show last hidden flow",
|
||||
"listFlows": "List flows",
|
||||
"listSubflows": "List subflows",
|
||||
"status": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled":"Disabled",
|
||||
@@ -101,17 +112,32 @@
|
||||
"editPalette":"Manage palette",
|
||||
"other": "Other",
|
||||
"showTips": "Show tips",
|
||||
"showWelcomeTours": "Show guided tours for new versions",
|
||||
"help": "Node-RED website",
|
||||
"projects": "Projects",
|
||||
"projects-new": "New",
|
||||
"projects-open": "Open",
|
||||
"projects-settings": "Project Settings",
|
||||
"showNodeLabelDefault": "Show label of newly added nodes",
|
||||
"codeEditor": "Code Editor",
|
||||
"groups": "Groups",
|
||||
"groupSelection": "Group selection",
|
||||
"ungroupSelection": "Ungroup selection",
|
||||
"groupMergeSelection": "Merge selection",
|
||||
"groupRemoveSelection": "Remove from group"
|
||||
"groupRemoveSelection": "Remove from group",
|
||||
"arrange":"Arrange",
|
||||
"alignLeft":"Align to left",
|
||||
"alignCenter":"Align to center",
|
||||
"alignRight":"Align to right",
|
||||
"alignTop":"Align to top",
|
||||
"alignMiddle":"Align to middle",
|
||||
"alignBottom":"Align to bottom",
|
||||
"distributeHorizontally":"Distribute horizontally",
|
||||
"distributeVertically":"Distribute vertically",
|
||||
"moveToBack":"Move to back",
|
||||
"moveToFront":"Move to front",
|
||||
"moveBackwards":"Move backwards",
|
||||
"moveForwards":"Move forwards"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
@@ -140,6 +166,7 @@
|
||||
"nodeActionDisabled": "node actions disabled",
|
||||
"nodeActionDisabledSubflow": "node actions disabled within subflow",
|
||||
"missing-types": "<p>Flows stopped due to missing node types.</p>",
|
||||
"missing-modules": "<p>Flows stopped due to missing modules.</p>",
|
||||
"safe-mode":"<p>Flows stopped in safe mode.</p><p>You can modify your flows and deploy the changes to restart.</p>",
|
||||
"restartRequired": "Node-RED must be restarted to enable upgraded modules",
|
||||
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
|
||||
@@ -197,6 +224,8 @@
|
||||
"flow_plural": "__count__ flows",
|
||||
"subflow": "__count__ subflow",
|
||||
"subflow_plural": "__count__ subflows",
|
||||
"replacedNodes": "__count__ node replaced",
|
||||
"replacedNodes_plural": "__count__ nodes replaced",
|
||||
"pasteNodes": "Paste flow json or",
|
||||
"selectFile": "select a file to import",
|
||||
"importNodes": "Import nodes",
|
||||
@@ -204,6 +233,8 @@
|
||||
"download": "Download",
|
||||
"importUnrecognised": "Imported unrecognised type:",
|
||||
"importUnrecognised_plural": "Imported unrecognised types:",
|
||||
"importDuplicate": "Imported duplicate node:",
|
||||
"importDuplicate_plural": "Imported duplicate nodes:",
|
||||
"nodesExported": "Nodes exported to clipboard",
|
||||
"nodesImported": "Imported:",
|
||||
"nodeCopied": "__count__ node copied",
|
||||
@@ -212,6 +243,9 @@
|
||||
"groupCopied_plural": "__count__ groups copied",
|
||||
"groupStyleCopied": "Group style copied",
|
||||
"invalidFlow": "Invalid flow: __message__",
|
||||
"recoveredNodes": "Recovered Nodes",
|
||||
"recoveredNodesInfo": "The nodes on this flow were missing a valid flow id when they were imported. They have been added to this flow so you can either restore or delete them.",
|
||||
"recoveredNodesNotification": "<p>Imported nodes without a valid flow id</p><p>They have been added to a new flow called '__flowName__'.</p>",
|
||||
"export": {
|
||||
"selected":"selected nodes",
|
||||
"current":"current flow",
|
||||
@@ -226,13 +260,19 @@
|
||||
},
|
||||
"import": {
|
||||
"import": "Import to",
|
||||
"importSelected": "Import selected",
|
||||
"importCopy": "Import copy",
|
||||
"viewNodes": "View nodes...",
|
||||
"newFlow": "new flow",
|
||||
"replace": "replace",
|
||||
"errors": {
|
||||
"notArray": "Input not a JSON Array",
|
||||
"itemNotObject": "Input not a valid flow - item __index__ not a node object",
|
||||
"missingId": "Input not a valid flow - item __index__ missing 'id' 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.",
|
||||
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them."
|
||||
},
|
||||
"copyMessagePath": "Path copied",
|
||||
"copyMessageValue": "Value copied",
|
||||
@@ -322,8 +362,21 @@
|
||||
"output": "outputs:",
|
||||
"status": "status node",
|
||||
"deleteSubflow": "delete subflow",
|
||||
"confirmDelete": "Are you sure you want to delete this subflow?",
|
||||
"info": "Description",
|
||||
"category": "Category",
|
||||
"module": "Module",
|
||||
"license": "License",
|
||||
"licenseNone": "none",
|
||||
"licenseOther": "Other",
|
||||
"type": "Node Type",
|
||||
"version": "Version",
|
||||
"versionPlaceholder": "x.y.z",
|
||||
"keys": "Keywords",
|
||||
"keysPlaceholder": "Comma-separated keywords",
|
||||
"author": "Author",
|
||||
"authorPlaceholder": "Your Name <email@example.com>",
|
||||
"desc": "Description",
|
||||
"env": {
|
||||
"restore": "Restore to subflow default",
|
||||
"remove": "Remove environment variable"
|
||||
@@ -370,6 +423,7 @@
|
||||
"icon": "Icon",
|
||||
"inputType": "Input type",
|
||||
"selectType": "select types...",
|
||||
"loadCredentials": "Loading node credentials",
|
||||
"inputs" : {
|
||||
"input": "input",
|
||||
"select": "select",
|
||||
@@ -404,7 +458,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it",
|
||||
"invalidProperties": "Invalid properties:"
|
||||
"invalidProperties": "Invalid properties:",
|
||||
"credentialLoadFailed": "Failed to load node credentials"
|
||||
}
|
||||
},
|
||||
"keyboard": {
|
||||
@@ -416,8 +471,9 @@
|
||||
"unassigned": "Unassigned",
|
||||
"global": "global",
|
||||
"workspace": "workspace",
|
||||
"selectAll": "Select all nodes",
|
||||
"selectAllConnected": "Select all connected nodes",
|
||||
"selectAll": "Select all",
|
||||
"selectNone": "Select none",
|
||||
"selectAllConnected": "Select connected",
|
||||
"addRemoveNode": "Add/remove node from selection",
|
||||
"editSelected": "Edit selected node",
|
||||
"deleteSelected": "Delete selected nodes or link",
|
||||
@@ -430,7 +486,10 @@
|
||||
"copyNode": "Copy selected nodes",
|
||||
"cutNode": "Cut selected nodes",
|
||||
"pasteNode": "Paste nodes",
|
||||
"undoChange": "Undo the last change performed",
|
||||
"copyGroupStyle": "Copy group style",
|
||||
"pasteGroupStyle": "Paste group style",
|
||||
"undoChange": "Undo",
|
||||
"redoChange": "Redo",
|
||||
"searchBox": "Open search box",
|
||||
"managePalette": "Manage palette",
|
||||
"actionList":"Action list"
|
||||
@@ -485,7 +544,8 @@
|
||||
"nodeEnabled_plural": "Nodes enabled:",
|
||||
"nodeDisabled": "Node disabled:",
|
||||
"nodeDisabled_plural": "Nodes disabled:",
|
||||
"nodeUpgraded": "Node module __module__ upgraded to version __version__"
|
||||
"nodeUpgraded": "Node module __module__ upgraded to version __version__",
|
||||
"unknownNodeRegistered": "Error loading node: <ul><li>__type__<br>__error__</li></ul>"
|
||||
},
|
||||
"editor": {
|
||||
"title": "Manage palette",
|
||||
@@ -532,6 +592,8 @@
|
||||
"sortAZ": "a-z",
|
||||
"sortRecent": "recent",
|
||||
"more": "+ __count__ more",
|
||||
"upload": "Upload module tgz file",
|
||||
"refresh": "Refresh module list",
|
||||
"errors": {
|
||||
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
|
||||
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
|
||||
@@ -708,6 +770,12 @@
|
||||
"committerTip": "Leave blank to use system default",
|
||||
"userName": "Username",
|
||||
"email": "Email",
|
||||
"workflow": "Workflow",
|
||||
"workfowTip": "Choose your preferred git workflow",
|
||||
"workflowManual": "Manual",
|
||||
"workflowManualTip": "All changes must be manually committed under the 'history' sidebar",
|
||||
"workflowAuto": "Automatic",
|
||||
"workflowAutoTip": "Changes are committed automatically with every deploy",
|
||||
"sshKeys": "SSH Keys",
|
||||
"sshKeysTip": "Allows you to create secure connections to remote git repositories.",
|
||||
"add": "add key",
|
||||
@@ -818,7 +886,8 @@
|
||||
}
|
||||
},
|
||||
"editableList": {
|
||||
"add": "add"
|
||||
"add": "add",
|
||||
"addTitle": "add an item"
|
||||
},
|
||||
"search": {
|
||||
"empty": "No matches found",
|
||||
@@ -843,6 +912,9 @@
|
||||
"eval": "Error evaluating expression:\n __message__"
|
||||
}
|
||||
},
|
||||
"monaco": {
|
||||
"setTheme": "Set theme"
|
||||
},
|
||||
"jsEditor": {
|
||||
"title": "JavaScript editor"
|
||||
},
|
||||
@@ -1056,16 +1128,22 @@
|
||||
"editor-tab": {
|
||||
"properties": "Properties",
|
||||
"envProperties": "Environment Variables",
|
||||
"module": "Module Properties",
|
||||
"description": "Description",
|
||||
"appearance": "Appearance",
|
||||
"preview": "UI Preview",
|
||||
"defaultValue": "Default value"
|
||||
},
|
||||
"tourGuide": {
|
||||
"start": "Start",
|
||||
"next": "Next"
|
||||
},
|
||||
"languages" : {
|
||||
"de": "German",
|
||||
"en-US": "English",
|
||||
"ja": "Japanese",
|
||||
"ko": "Korean",
|
||||
"ru": "Russian",
|
||||
"zh-CN": "Chinese(Simplified)",
|
||||
"zh-TW": "Chinese(Traditional)"
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"desc": "Finds occurrences of `pattern` within `str` and replaces them with `replacement`.\n\nThe optional `limit` parameter is the maximum number of replacements."
|
||||
},
|
||||
"$now": {
|
||||
"args":"",
|
||||
"desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string."
|
||||
"args":"$[picture [, timezone]]",
|
||||
"desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string. If the optional picture and timezone parameters are supplied, then the current timestamp is formatted as described by the `$fromMillis()` function"
|
||||
},
|
||||
"$base64encode": {
|
||||
"args":"string",
|
||||
@@ -200,8 +200,8 @@
|
||||
"desc": "Returns a copy of the `string` with extra padding, if necessary, so that its total number of characters is at least the absolute value of the `width` parameter.\n\nIf `width` is a positive number, then the string is padded to the right; if negative, it is padded to the left.\n\nThe optional `char` argument specifies the padding character(s) to use. If not specified, it defaults to the space character."
|
||||
},
|
||||
"$fromMillis": {
|
||||
"args": "number",
|
||||
"desc": "Convert a number representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a timestamp string in the ISO 8601 format."
|
||||
"args": "number, [, picture [, timezone]]",
|
||||
"desc": "Convert the `number` representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a formatted string representation of the timestamp as specified by the picture string.\n\nIf the optional `picture` parameter is omitted, then the timestamp is formatted in the ISO 8601 format.\n\nIf the optional `picture` string is supplied, then the timestamp is formatted occording to the representation specified in that string. The behaviour of this function is consistent with the two-argument version of the XPath/XQuery function `format-dateTime` as defined in the XPath F&O 3.1 specification. The picture string parameter defines how the timestamp is formatted and has the same syntax as `format-dateTime`.\n\nIf the optional `timezone` string is supplied, then the formatted timestamp will be in that timezone. The `timezone` string should be in the format '±HHMM', where ± is either the plus or minus sign and HHMM is the offset in hours and minutes from UTC. Positive offset for timezones east of UTC, negative offset for timezones west of UTC."
|
||||
},
|
||||
"$formatNumber": {
|
||||
"args": "number, picture [, options]",
|
||||
@@ -243,19 +243,19 @@
|
||||
"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"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Encodes a Uniform Resource Locator (URL) component 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: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"$encodeUrl": {
|
||||
"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\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent. \n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"$decodeUrl": {
|
||||
"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=шеллы\"`"
|
||||
},
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"color": "色",
|
||||
"position": "配置",
|
||||
"enable": "有効",
|
||||
"disable": "無効"
|
||||
"disable": "無効",
|
||||
"upload": "アップロード"
|
||||
},
|
||||
"type": {
|
||||
"string": "文字列",
|
||||
@@ -37,11 +38,14 @@
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"loadPlugins": "プラグインを読み込み中",
|
||||
"loadPalette": "パレットを読み込み中",
|
||||
"loadNodeCatalogs": "ノードカタログを読み込み中",
|
||||
"loadNodes": "ノードを読み込み中 __count__",
|
||||
"loadFlows": "フローを読み込み中",
|
||||
"importFlows": "ワークスペースにフローを追加中"
|
||||
"importFlows": "ワークスペースにフローを追加中",
|
||||
"importError": "<p>フロー追加エラー</p><p>__message__</p>",
|
||||
"loadingProject": "プロジェクトを読み込み中"
|
||||
},
|
||||
"workspace": {
|
||||
"defaultName": "フロー __number__",
|
||||
@@ -83,7 +87,7 @@
|
||||
"userSettings": "ユーザ設定",
|
||||
"nodes": "ノード",
|
||||
"displayStatus": "ノードのステータスを表示",
|
||||
"displayConfig": "ノードの設定",
|
||||
"displayConfig": "設定ノード",
|
||||
"import": "読み込み",
|
||||
"export": "書き出し",
|
||||
"search": "ノードを検索",
|
||||
@@ -107,6 +111,7 @@
|
||||
"projects-open": "開く",
|
||||
"projects-settings": "設定",
|
||||
"showNodeLabelDefault": "追加したノードのラベルを表示",
|
||||
"codeEditor": "コードエディタ",
|
||||
"groups": "グループ",
|
||||
"groupSelection": "選択部分をグループ化",
|
||||
"ungroupSelection": "選択部分をグループ解除",
|
||||
@@ -140,6 +145,7 @@
|
||||
"nodeActionDisabled": "ノードのアクションは無効になっています",
|
||||
"nodeActionDisabledSubflow": "ノードのアクションは、サブフロー内で無効になっています",
|
||||
"missing-types": "<p>不明なノードが存在するため、フローを停止しました。</p>",
|
||||
"missing-modules": "<p>不明なモジュールが存在するため、フローを停止しました。</p>",
|
||||
"safe-mode": "<p>セーフモードでフローを停止しました</p><p>フローを変更し、再起動するために変更をデプロイできます</p>",
|
||||
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります",
|
||||
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
|
||||
@@ -197,13 +203,17 @@
|
||||
"flow_plural": "__count__ 個のフロー",
|
||||
"subflow": "__count__ 個のサブフロー",
|
||||
"subflow_plural": "__count__ 個のサブフロー",
|
||||
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
|
||||
"selectFile": "読み込むファイルを選択してください",
|
||||
"importNodes": "フローをクリップボートから読み込み",
|
||||
"exportNodes": "フローをクリップボードへ書き出し",
|
||||
"replacedNodes": "置換された __count__ 個のノード",
|
||||
"replacedNodes_plural": "置換された __count__ 個のノード",
|
||||
"pasteNodes": "JSON形式のフローデータを貼り付け",
|
||||
"selectFile": "読み込むファイルを選択",
|
||||
"importNodes": "フローを読み込み",
|
||||
"exportNodes": "フローを書き出し",
|
||||
"download": "ダウンロード",
|
||||
"importUnrecognised": "認識できない型が読み込まれました:",
|
||||
"importUnrecognised_plural": "認識できない型が読み込まれました:",
|
||||
"importDuplicate": "重複したノードを読み込みました:",
|
||||
"importDuplicate_plural": "重複したノードを読み込みました:",
|
||||
"nodesExported": "クリップボードへフローを書き出しました",
|
||||
"nodesImported": "読み込みました:",
|
||||
"nodeCopied": "__count__ 個のノードをコピーしました",
|
||||
@@ -212,6 +222,9 @@
|
||||
"groupCopied_plural": "__count__ 個のグループをコピーしました",
|
||||
"groupStyleCopied": "グループの形式をコピーしました",
|
||||
"invalidFlow": "不正なフロー: __message__",
|
||||
"recoveredNodes": "復旧したノード",
|
||||
"recoveredNodesInfo": "このフロー内のノードは読み込み時に、有効なフローIDがありませんでした。これらフローIDは、フローに追加されているため、復元または削除できます。",
|
||||
"recoveredNodesNotification": "<p>有効なフローIDを持たないノードが読み込まれました</p><p>これらノードは '__flowName__' という新しいフローへ追加されました。</p>",
|
||||
"export": {
|
||||
"selected": "選択したフロー",
|
||||
"current": "現在のタブ",
|
||||
@@ -226,13 +239,19 @@
|
||||
},
|
||||
"import": {
|
||||
"import": "読み込み先",
|
||||
"importSelected": "選択したノードを読み込み",
|
||||
"importCopy": "コピーを読み込み",
|
||||
"viewNodes": "ノードを参照...",
|
||||
"newFlow": "新規のタブ",
|
||||
"replace": "置換",
|
||||
"errors": {
|
||||
"notArray": "JSON形式の配列ではありません",
|
||||
"itemNotObject": "不正なフロー - __index__ 番目の要素はノードオブジェクトではありません",
|
||||
"missingId": "不正なフロー - __index__ 番目の要素に'id'プロパティがありません",
|
||||
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
||||
}
|
||||
},
|
||||
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
||||
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
|
||||
},
|
||||
"copyMessagePath": "パスをコピーしました",
|
||||
"copyMessageValue": "値をコピーしました",
|
||||
@@ -251,7 +270,7 @@
|
||||
"successfulDeploy": "デプロイが成功しました",
|
||||
"successfulRestart": "フローの再起動が成功しました",
|
||||
"deployFailed": "デプロイが失敗しました: __message__",
|
||||
"unusedConfigNodes": "使われていない「ノードの設定」があります。",
|
||||
"unusedConfigNodes": "使われていない設定ノードがあります。",
|
||||
"unusedConfigNodesLink": "設定を参照する",
|
||||
"errors": {
|
||||
"noResponse": "サーバの応答がありません"
|
||||
@@ -322,8 +341,21 @@
|
||||
"output": "出力:",
|
||||
"status": "ステータスノード",
|
||||
"deleteSubflow": "サブフローを削除",
|
||||
"confirmDelete": "このサブフローを削除しても良いですか?",
|
||||
"info": "詳細",
|
||||
"category": "カテゴリ",
|
||||
"module": "モジュール",
|
||||
"license": "ライセンス",
|
||||
"licenseNone": "なし",
|
||||
"licenseOther": "その他",
|
||||
"type": "ノードの型",
|
||||
"version": "バージョン",
|
||||
"versionPlaceholder": "x.y.z",
|
||||
"keys": "キーワード",
|
||||
"keysPlaceholder": "カンマ区切りのキーワード",
|
||||
"author": "作者",
|
||||
"authorPlaceholder": "名前 <email@example.com>",
|
||||
"desc": "説明",
|
||||
"env": {
|
||||
"restore": "デフォルト値に戻す",
|
||||
"remove": "環境変数を削除"
|
||||
@@ -347,9 +379,9 @@
|
||||
"configDelete": "削除",
|
||||
"nodesUse": "__count__ 個のノードが、この設定を使用しています",
|
||||
"nodesUse_plural": "__count__ 個のノードが、この設定を使用しています",
|
||||
"addNewConfig": "新規に __type__ ノードの設定を追加",
|
||||
"addNewConfig": "新規に __type__ 設定ノードを追加",
|
||||
"editNode": "__type__ ノードを編集",
|
||||
"editConfig": "__type__ ノードの設定を編集",
|
||||
"editConfig": "__type__ 設定ノードを編集",
|
||||
"addNewType": "新規に __type__ を追加...",
|
||||
"nodeProperties": "プロパティ",
|
||||
"label": "ラベル",
|
||||
@@ -370,6 +402,7 @@
|
||||
"icon": "記号",
|
||||
"inputType": "入力形式",
|
||||
"selectType": "形式選択...",
|
||||
"loadCredentials": "ノードの認証情報を読み込み中",
|
||||
"inputs": {
|
||||
"input": "入力",
|
||||
"select": "メニュー",
|
||||
@@ -404,7 +437,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします",
|
||||
"invalidProperties": "プロパティが不正です:"
|
||||
"invalidProperties": "プロパティが不正です:",
|
||||
"credentialLoadFailed": "ノードの認証情報の読み込みに失敗"
|
||||
}
|
||||
},
|
||||
"keyboard": {
|
||||
@@ -491,8 +525,8 @@
|
||||
"title": "パレットの管理",
|
||||
"palette": "パレット",
|
||||
"times": {
|
||||
"seconds": "秒前",
|
||||
"minutes": "分前",
|
||||
"seconds": "数秒前",
|
||||
"minutes": "数分前",
|
||||
"minutesV": "__count__ 分前",
|
||||
"hoursV": "__count__ 時間前",
|
||||
"hoursV_plural": "__count__ 時間前",
|
||||
@@ -532,6 +566,8 @@
|
||||
"sortAZ": "辞書順",
|
||||
"sortRecent": "日付順",
|
||||
"more": "+ さらに __count__ 個",
|
||||
"upload": "モジュールのtgzファイルをアップロード",
|
||||
"refresh": "モジュールリスト更新",
|
||||
"errors": {
|
||||
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
|
||||
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
|
||||
@@ -620,8 +656,8 @@
|
||||
"noHelp": "ヘルプのトピックが未選択"
|
||||
},
|
||||
"config": {
|
||||
"name": "ノードの設定を表示",
|
||||
"label": "ノードの設定",
|
||||
"name": "設定ノードを表示",
|
||||
"label": "設定ノード",
|
||||
"global": "全てのフロー上",
|
||||
"none": "なし",
|
||||
"subflows": "サブフロー",
|
||||
@@ -708,6 +744,12 @@
|
||||
"committerTip": "システムのデフォルトを使用する場合、空白のままにしてください",
|
||||
"userName": "ユーザ名",
|
||||
"email": "メールアドレス",
|
||||
"workflow": "ワークフロー",
|
||||
"workfowTip": "望ましいgitワークフローを選択してください",
|
||||
"workflowManual": "手動",
|
||||
"workflowManualTip": "全ての変更は「履歴」サイドバー内で手動でコミットする必要があります",
|
||||
"workflowAuto": "自動",
|
||||
"workflowAutoTip": "変更はデプロイの度に自動的にコミットされます",
|
||||
"sshKeys": "SSH キー",
|
||||
"sshKeysTip": "gitリポジトリへのセキュアな接続を作成できます。",
|
||||
"add": "キーを追加",
|
||||
@@ -818,7 +860,8 @@
|
||||
}
|
||||
},
|
||||
"editableList": {
|
||||
"add": "追加"
|
||||
"add": "追加",
|
||||
"addTitle": "要素を追加"
|
||||
},
|
||||
"search": {
|
||||
"empty": "一致したものが見つかりませんでした",
|
||||
@@ -843,6 +886,9 @@
|
||||
"eval": "表現評価エラー:\n __message__"
|
||||
}
|
||||
},
|
||||
"monaco": {
|
||||
"setTheme": "テーマを設定:"
|
||||
},
|
||||
"jsEditor": {
|
||||
"title": "JavaScriptエディタ"
|
||||
},
|
||||
@@ -1056,6 +1102,7 @@
|
||||
"editor-tab": {
|
||||
"properties": "プロパティ",
|
||||
"envProperties": "環境変数",
|
||||
"module": "モジュールプロパティ",
|
||||
"description": "説明",
|
||||
"appearance": "外観",
|
||||
"preview": "UIプレビュー",
|
||||
@@ -1066,6 +1113,7 @@
|
||||
"en-US": "英語",
|
||||
"ja": "日本語",
|
||||
"ko": "韓国語",
|
||||
"ru": "ロシア語",
|
||||
"zh-CN": "中国語(簡体)",
|
||||
"zh-TW": "中国語(繁体)"
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"desc": "文字列 `str` からパターン `pattern` を検索し、置換文字列 `replacement` に置き換えます。\n\n任意の引数 `limit` には、置換回数の上限値を指定します。"
|
||||
},
|
||||
"$now": {
|
||||
"args": "",
|
||||
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。"
|
||||
"args": "$[picture [, timezone]]",
|
||||
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。pictureおよびtimezoneパラメータが指定されている場合、現在時刻を`$fromMillis()`関数の説明に従ってフォーマットします。"
|
||||
},
|
||||
"$base64encode": {
|
||||
"args": "string",
|
||||
@@ -200,8 +200,8 @@
|
||||
"desc": "文字数が引数 `width` の絶対値以上となるよう、必要に応じて追加文字を付け足した `string` のコピーを返します。\n\n`width` が正の値の場合、文字列の右側に追加文字を付け足します。もし負の値の場合、文字列の左側に追加文字を付け足します。\n\n任意の引数 `char` には、本関数で用いる追加文字を指定します。もし追加文字を指定しない場合は、既定値として空白文字を使用します。"
|
||||
},
|
||||
"$fromMillis": {
|
||||
"args": "number",
|
||||
"desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値を、ISO 8601形式のタイムスタンプの文字列に変換します。"
|
||||
"args": "number, [, picture [, timezone]]",
|
||||
"desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値を、`picture`の指定に従ってタイムスタンプの文字列に変換します。\n\n`picture`パラメータが指定されない場合、ISO 8601形式に変換します。\n\n`picture`を指定すると、指定した文字列に従って変換を行います。この変換はXPath F&O 3.1仕様におけるXPath/XQueryの2引数形式`format-dateTime`と同様です。`picture`パラメータはタイムスタンプの変換形式を定義し、その書式は`format-dateTime`と同じです。\n\n`timezone`を指定すると、指定タイムゾーンで変換します。`timezone`は'±HHMM'という形式で指定します。ここで、±は+もしくは-記号を表し、HHMMはUTCからの差分時間と分を表します。正の差分はUTCの東、負の差分は西のタイムゾーンとなります。"
|
||||
},
|
||||
"$formatNumber": {
|
||||
"args": "number, picture [, options]",
|
||||
@@ -243,19 +243,19 @@
|
||||
"args": "array, function",
|
||||
"desc": "`array`の要素のうち、条件判定関数`function`を満たす(`function`に与えた場合に真偽値`true`を返す)要素が1つのみである場合、それを返します。マッチする要素が1つのみでない場合、例外を送出します。\n\n指定する関数は`function(value [, index [, array]])`というシグネチャでなければなりません。ここで、`value`は`array`の要素値、`index`は要素の添字、第三引数には配列全体を渡します。"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"$encodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "encodeUrlComponentで置換したUniform Resource Locator (URL)をデコードします。\n\n例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"$decodeUrl": {
|
||||
"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=шеллы\"`"
|
||||
},
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"displayConfig": "설정노드 보기",
|
||||
"import": "가져오기",
|
||||
"export": "내보내기",
|
||||
"search": "플로우 겅색",
|
||||
"search": "플로우 검색",
|
||||
"searchInput": "플로우 검색",
|
||||
"subflows": "보조 플로우",
|
||||
"createSubflow": "보조 플로우 생성",
|
||||
|
||||
1139
packages/node_modules/@node-red/editor-client/locales/ru/editor.json
vendored
Executable file
1139
packages/node_modules/@node-red/editor-client/locales/ru/editor.json
vendored
Executable file
File diff suppressed because it is too large
Load Diff
23
packages/node_modules/@node-red/editor-client/locales/ru/infotips.json
vendored
Executable file
23
packages/node_modules/@node-red/editor-client/locales/ru/infotips.json
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"info": {
|
||||
"tip0" : "Вы можете удалить выбранные узлы или провода с {{core:delete-selection}}",
|
||||
"tip1" : "Ищите узлы с {{core:search}}",
|
||||
"tip2" : "{{core:toggle-sidebar}} показывает/скрывает эту боковою панель",
|
||||
"tip3" : "Вы можете управлять палитрой узлов с помощью {{core:manage-palette}}",
|
||||
"tip4" : "Узлы конфигурации потока перечисляются на боковой панели. Доступ к списку можно получить из меню или с помощью {{core:show-config-tab}}",
|
||||
"tip5" : "Эти советы можно включить/выключить через настройки",
|
||||
"tip6" : "Перемещайте выбранные узлы клавишами [влево] [вверх] [вниз] и [вправо]. Удерживайте [Shift], чтобы увеличить шаг",
|
||||
"tip7" : "Перетаскивание узла на провод соединит его с обеих сторон",
|
||||
"tip8" : "Экспортируйте выбранные узлы или текущую вкладку с {{core:show-export-dialog}}",
|
||||
"tip9" : "Импортируйте поток, перетаскивая его JSON в редактор или с помощью {{core:show-import-dialog}}",
|
||||
"tip10" : "Нажмите [Shift], [кликните] по порту узла и перетаскивайте подключенные провода на другой узел",
|
||||
"tip11" : "Открывайте вкладку Информация с {{core:show-info-tab}} или вкладку Отладка с {{core:show-debug-tab}}",
|
||||
"tip12" : "Нажмите [ctrl] и [кликните] в рабочей области, чтобы открыть диалог быстрого добавления",
|
||||
"tip13" : "Нажмите [ctrl] и [кликните] по порту узла, чтобы начать быстрое подключение",
|
||||
"tip14" : "Нажмите [Shift] и [кликните] по узлу, чтобы выбрать все соединенные узлы",
|
||||
"tip15" : "Нажмите [ctrl] и [кликните] по узлу, чтобы добавить или убрать его из текущего выбора",
|
||||
"tip16" : "Переключайте вкладки потока с помощью {{core:show-previous-tab}} и {{core:show-next-tab}}",
|
||||
"tip17" : "Вы можете подтвердить изменения в редакторе узла с {{core:confirm-edit-tray}} или отменить их с {{core:cancel-edit-tray}}",
|
||||
"tip18" : "Нажатие {{core:edit-selected-node}} откроет редактор первого узла в текущем выборе"
|
||||
}
|
||||
}
|
||||
274
packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json
vendored
Executable file
274
packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json
vendored
Executable file
@@ -0,0 +1,274 @@
|
||||
{
|
||||
"$string": {
|
||||
"args": "arg[, prettify]",
|
||||
"desc": "Преобразует параметр `arg` в строку, используя следующие правила приведения:\n\n - Строки возвращаются как есть\n - Функции преобразуются в пустую строку\n - Числовая бесконечность и NaN выдают ошибку, поскольку они не могут быть представлены числом в JSON\n - Все остальные значения преобразуются в строку JSON с помощью функции `JSON.stringify`. Если значение `prettify` равно true, тогда будет сгенерирован \"отформатированный\" JSON. То есть каждое поле будет в отдельной строке, а строки будут иметь отступ в зависимости от глубины поля."
|
||||
},
|
||||
"$length": {
|
||||
"args": "str",
|
||||
"desc": "Возвращает количество символов в строке `str`. Выдается ошибка, если `str` не является строкой."
|
||||
},
|
||||
"$substring": {
|
||||
"args": "str, start[, length]",
|
||||
"desc": "Возвращает строку, содержащую символы из первого параметра `str`, начиная с позиции `start` (отсчет с нуля). Если указан `length`, то подстрока будет содержать максимум `length` символов. Если `start` отрицателен, то это означает количество символов с конца `str`."
|
||||
},
|
||||
"$substringBefore": {
|
||||
"args": "str, chars",
|
||||
"desc": "Возвращает подстроку перед первым вхождением последовательности символов `chars` в строке `str`. Если `str` не содержит `chars`, то он возвращает `str`."
|
||||
},
|
||||
"$substringAfter": {
|
||||
"args": "str, chars",
|
||||
"desc": "Возвращает подстроку после первого вхождения последовательности символов `chars` в строке `str`. Если `str` не содержит `chars`, то он возвращает `str`."
|
||||
},
|
||||
"$uppercase": {
|
||||
"args": "str",
|
||||
"desc": "Возвращает строку со всеми символами `str`, преобразованными в верхний регистр."
|
||||
},
|
||||
"$lowercase": {
|
||||
"args": "str",
|
||||
"desc": "Возвращает строку со всеми символами `str`, преобразованными в нижний регистр."
|
||||
},
|
||||
"$trim": {
|
||||
"args": "str",
|
||||
"desc": "Нормализует и обрезает все пробельные символы в строке `str`, выполняя следующие шаги:\n\n - Все символы табуляции, возврата каретки и перевода строки заменяются пробелами.\n- Последовательности пробелов сокращаются до одного пробела.\n- Пробелы в начале и конце `str` удаляются.\n\n Если `str` не указан (то есть эта функция вызывается без аргументов), тогда значение контекста используется в качестве значения `str`. Выдается ошибка, если `str` не является строкой."
|
||||
},
|
||||
"$contains": {
|
||||
"args": "str, pattern",
|
||||
"desc": "Возвращает `true`, если строка `str` соответствует шаблону `pattern`, в противном случае возвращает `false`. Если `str` не указан (то есть эта функция вызывается с одним аргументом), то значение контекста используется как значение `str`. Параметр `pattern` может быть либо строкой, либо регулярным выражением."
|
||||
},
|
||||
"$split": {
|
||||
"args": "str[, separator][, limit]",
|
||||
"desc": "Разбивает строку `str` на массив подстрок. Выдает ошибку, если `str` не является строкой. Необязательный параметр `separator` (строка или регулярное выражение) указывает символы внутри строки `str`, относительно которых она должна быть разделена. Если `separator` не указан, то предполагается пустая строка, и `str` будет разбит на массив из отдельных символов. Выдает ошибку, если `separator` не является строкой. Необязательный параметр `limit` - это число, указывающее максимальное количество подстрок для включения в результирующий массив. Любые дополнительные подстроки отбрасываются. Если `limit` не указан, то весь `str` разделяется без ограничения размера результирующего массива. Выдает ошибку, если `limit` не является положительным числом."
|
||||
},
|
||||
"$join": {
|
||||
"args": "array[, separator]",
|
||||
"desc": "Объединяет массив подстрок в одну объединенную строку, в которой каждая подстрока отделена необязательным параметром `separator`. Выдает ошибку, если входной массив содержит элемент, который не является строкой. Если `separator` не указан, то предполагается, что это пустая строка, то есть нет `separator` между подстроками. Выдает ошибку, если `separator` не является строкой."
|
||||
},
|
||||
"$match": {
|
||||
"args": "str, pattern [, limit]",
|
||||
"desc": "Применяет строку `str` к регулярному выражению `pattern` и возвращает массив объектов, каждый из которых содержит информацию о каждом совпадении внутри `str`."
|
||||
},
|
||||
"$replace": {
|
||||
"args": "str, pattern, replacement [, limit]",
|
||||
"desc": "Находит вхождения шаблона `pattern` в строке `str` и заменяет их на строку `replacement`.\n\nНеобязательный параметр `limit` - это максимальное количество замен."
|
||||
},
|
||||
"$now": {
|
||||
"args":"",
|
||||
"desc":"Создает отметку времени в формате, совместимом с ISO 8601, и возвращает ее как строку."
|
||||
},
|
||||
"$base64encode": {
|
||||
"args":"string",
|
||||
"desc":"Преобразует ASCII-строку в base-64 кодировку. Каждый символ в строке обрабатывается как байт двоичных данных. Для этого необходимо, чтобы все символы в строке находились в диапазоне от 0x00 до 0xFF, который включает все символы строк в URI-кодировке. Символы Юникода за пределами этого диапазона не поддерживаются."
|
||||
},
|
||||
"$base64decode": {
|
||||
"args":"string",
|
||||
"desc":"Преобразует байты в кодировке base-64 в строку, используя кодовую страницу Юникод UTF-8."
|
||||
},
|
||||
"$number": {
|
||||
"args": "arg",
|
||||
"desc": "Преобразует параметр `arg` в число с использованием следующих правил приведения:\n\n - Числа возвращаются как есть\n - Строки, которые содержат последовательность символов, представляющих допустимое в JSON число, преобразуются в это число\n - Все остальные значения вызывают ошибку."
|
||||
},
|
||||
"$abs": {
|
||||
"args":"number",
|
||||
"desc":"Возвращает абсолютное значение числа `number`."
|
||||
},
|
||||
"$floor": {
|
||||
"args":"number",
|
||||
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое меньше или равно `number`."
|
||||
},
|
||||
"$ceil": {
|
||||
"args":"number",
|
||||
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое больше или равно `number`."
|
||||
},
|
||||
"$round": {
|
||||
"args":"number [, precision]",
|
||||
"desc":"Возвращает значение числа `number`, округленное до количества десятичных знаков, указанных необязательным параметром `precision`."
|
||||
},
|
||||
"$power": {
|
||||
"args":"base, exponent",
|
||||
"desc":"Возвращает значение числа `base`, возведенное в степень `exponent`."
|
||||
},
|
||||
"$sqrt": {
|
||||
"args":"number",
|
||||
"desc":"Возвращает квадратный корень из значения числа `number`."
|
||||
},
|
||||
"$random": {
|
||||
"args":"",
|
||||
"desc":"Возвращает псевдослучайное число, которе больше или равно нулю и меньше единицы."
|
||||
},
|
||||
"$millis": {
|
||||
"args":"",
|
||||
"desc":"Возвращает число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу) в виде числа. Все вызовы `$millis()` в пределах выполнения выражения будут возвращать одно и то же значение."
|
||||
},
|
||||
"$sum": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает арифметическую сумму массива чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
|
||||
},
|
||||
"$max": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает максимальное число в массиве чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
|
||||
},
|
||||
"$min": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает минимальное число в массиве чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
|
||||
},
|
||||
"$average": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает среднее значение массива чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
|
||||
},
|
||||
"$boolean": {
|
||||
"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`"
|
||||
},
|
||||
"$not": {
|
||||
"args": "arg",
|
||||
"desc": "Возвращает логическое НЕ для аргумента. `arg` сначала приводится к логическому значению"
|
||||
},
|
||||
"$exists": {
|
||||
"args": "arg",
|
||||
"desc": "Возвращает логическое `true`, если выполнение выражения `arg` возвращает значение, или `false`, если выражение ничему не соответствует (например, путь к несуществующему полю)."
|
||||
},
|
||||
"$count": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает количество элементов в массиве"
|
||||
},
|
||||
"$append": {
|
||||
"args": "array, array",
|
||||
"desc": "Присоединяет один массив к другому"
|
||||
},
|
||||
"$sort": {
|
||||
"args":"array [, function]",
|
||||
"desc":"Возвращает массив, содержащий все значения параметра `array`, но отсортированные по порядку.\n\nЕсли указан компаратор `function`, то это должна быть функция, которая принимает два параметра:\n\n`function(val1, val2)`\n\nЭту функцию вызывает алгоритм сортировки для сравнения двух значений: val1 и val2. Если значение val1 следует поместить после значения val2 в желаемом порядке сортировки, то функция должна возвращать логическое значение `true`, чтобы обозначить замену. В противном случае она должна вернуть `false`."
|
||||
},
|
||||
"$reverse": {
|
||||
"args":"array",
|
||||
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но в обратном порядке."
|
||||
},
|
||||
"$shuffle": {
|
||||
"args":"array",
|
||||
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но перемешанный в случайном порядке."
|
||||
},
|
||||
"$zip": {
|
||||
"args":"array, ...",
|
||||
"desc":"Возвращает свернутый (сжатый) массив, содержащий сгруппированные массивы значений из аргументов `array1` … `arrayN` по индексам 0, 1, 2...."
|
||||
},
|
||||
"$keys": {
|
||||
"args": "object",
|
||||
"desc": "Возвращает массив, содержащий ключи объекта. Если аргумент является массивом объектов, то возвращаемый массив содержит недублированный список всех ключей из всех объектов."
|
||||
},
|
||||
"$lookup": {
|
||||
"args": "object, key",
|
||||
"desc": "Возвращает значение, связанное с ключом в объекте. Если первый аргумент является массивом объектов, то просходит поиск по всем объектам в массиве, и возвращаются значения, связанные со всеми вхождениями ключа."
|
||||
},
|
||||
"$spread": {
|
||||
"args": "object",
|
||||
"desc": "Разбивает объект, содержащий пары ключ / значение, на массив объектов, каждый из которых имеет одну пару ключ / значение из входного объекта. Если параметр является массивом объектов, то результирующий массив содержит объект для каждой пары ключ / значение из каждого объекта предоставленного массива."
|
||||
},
|
||||
"$merge": {
|
||||
"args": "array<object>",
|
||||
"desc": "Объединяет массив объектов в один объект, содержащий все пары ключ / значение каждого из объектов входного массива. Если какой-либо из входных объектов содержит один и тот же ключ, возвращаемый объект будет содержать значение последнего в массиве. Вызывает ошибку, если входной массив содержит элемент, который не является объектом."
|
||||
},
|
||||
"$sift": {
|
||||
"args":"object, function",
|
||||
"desc":"Возвращает объект, который содержит только пары ключ / значение из параметра `object`, которые удовлетворяют предикату `function`, переданному в качестве второго параметра.\n\n`function`, которая передается в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, key [, object]])`"
|
||||
},
|
||||
"$each": {
|
||||
"args":"object, function",
|
||||
"desc":"Возвращает массив, который содержит значения, возвращаемые функцией `function` при применении к каждой паре ключ/значение из объекта `object`."
|
||||
},
|
||||
"$map": {
|
||||
"args":"array, function",
|
||||
"desc":"Возвращает массив, содержащий результаты применения функции `function` к каждому значению массива `array`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
||||
},
|
||||
"$filter": {
|
||||
"args":"array, function",
|
||||
"desc":"Возвращает массив, содержащий только те значения из массива `array`, которые удовлетворяют предикату `function`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
|
||||
},
|
||||
"$reduce": {
|
||||
"args":"array, function [, init]",
|
||||
"desc":"Возвращает агрегированное значение, полученное в результате последовательного применения функции `function` к каждому значению в массиве в сочетании с результатом от предыдущего применения функции.\n\nФункция должна принимать два аргумента и вести себя как инфиксный оператор между каждым значением в массиве `array`. Сигнатура `function` должна иметь форму: `myfunc($accumulator, $value[, $index[, $array]])`\n\nНеобязательный параметр `init` используется в качестве начального значения в агрегации."
|
||||
},
|
||||
"$flowContext": {
|
||||
"args": "string[, string]",
|
||||
"desc": "Извлекает свойство контекста потока.\n\nЭто функция от Node-RED."
|
||||
},
|
||||
"$globalContext": {
|
||||
"args": "string[, string]",
|
||||
"desc": "Извлекает свойство глобального контекста.\n\nЭто функция от Node-RED."
|
||||
},
|
||||
"$pad": {
|
||||
"args": "string, width [, char]",
|
||||
"desc": "Возвращает копию строки `string` с дополнительным заполнением, если необходимо, чтобы общее количество символов как минимум соответствовало абсолютному значению параметра `width`.\n\nЕсли `width` является положительным числом, то строка дополняется справа; если отрицательным, то дополняется слева.\n\nНеобязательный аргумент `char` указывает символ(ы) для заполнения. Если не указано, по умолчанию используется пробел."
|
||||
},
|
||||
"$fromMillis": {
|
||||
"args": "number",
|
||||
"desc": "Преобразует число, представляющее миллисекунды с начала Unix-эпохи (1 января 1970 года по Гринвичу), в строку отметки времени в формате ISO 8601."
|
||||
},
|
||||
"$formatNumber": {
|
||||
"args": "number, picture [, options]",
|
||||
"desc": "Преобразует число `number` в строку и форматирует ее в десятичное представление, как указано в строке `picture`.\n\nПоведение этой функции соответствует XPath/XQuery-функции fn:format-number, как определено в спецификация XPath F&O 3.1. Строка `picture` определяет, как форматируется число и имеет тот же синтаксис, что и fn:format-number.\n\nНеобязательный третий аргумент `options` используется для переопределения символов форматирования, специфичных для локали по умолчанию, таких как десятичный разделитель. Если аргумент указан, то это должен быть объект, содержащий пары имя/значение, указанные в разделе десятичного формата спецификации XPath F&O 3.1."
|
||||
},
|
||||
"$formatBase": {
|
||||
"args": "number [, radix]",
|
||||
"desc": "Преобразует число `number` в строку и форматирует ее в целое число, представленное в системе счисления, указанной аргументом `radix`. Если `radix` не указан, то по умолчанию используется десятичная. Значение 'radix` может быть от 2 до 36, в противном случае выдается ошибка."
|
||||
},
|
||||
"$toMillis": {
|
||||
"args": "timestamp",
|
||||
"desc": "Преобразует строку `timestamp` в формате ISO 8601 в число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу). Вызывает ошибку, если строка в неправильном формате."
|
||||
},
|
||||
"$env": {
|
||||
"args": "arg",
|
||||
"desc": "Возвращает значение переменной среды.\n\nЭто функция от Node-RED."
|
||||
},
|
||||
"$eval": {
|
||||
"args": "expr [, context]",
|
||||
"desc": "Анализирует и исполняет строку `expr`, которая содержит JSON или выражение JSONata, используя текущий контекст в качестве контекста для исполнения."
|
||||
},
|
||||
"$formatInteger": {
|
||||
"args": "number, picture",
|
||||
"desc": "Преобразует число `number` в строку и форматирует ее в целочисленное представление, как указано в строке `picture`. Строка `picture` определяет, как форматируется число и имеет тот же синтаксис, что и `fn:format-integer` из спецификации XPath F&O 3.1."
|
||||
},
|
||||
"$parseInteger": {
|
||||
"args": "string, picture",
|
||||
"desc": "Разбирает содержимое строки `string` в целое число (как число JSON), используя формат, указанный в строке `picture`. Строковый параметр `picture` имеет тот же формат, что и `$formatInteger`."
|
||||
},
|
||||
"$error": {
|
||||
"args": "[str]",
|
||||
"desc": "Вызывает ошибку с сообщением. Необязательная строка `str` заменяет сообщение по умолчанию $error() function evaluated`"
|
||||
},
|
||||
"$assert": {
|
||||
"args": "arg, str",
|
||||
"desc": "Если значение `arg` равно true, функция возвращает значение undefined. Если значение `arg` равно false, генерируется исключение с `str` в качестве сообщения об исключении."
|
||||
},
|
||||
"$single": {
|
||||
"args": "array, function",
|
||||
"desc": "Возвращает одно-единственное значение из массива `array`, которое удовлетворяет предикату `function` (то есть когда `function` возвращает логическое `true` при передаче значения). Выдает исключение, если число подходящих значений не одно.\n\nФункция должна соответствовать следующей сигнатуре: `function(value [, index [, array]])` где value - элемент массива, index - позиция этого значения, а весь массив передается в качестве третьего аргумента"
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Кодирует компонент Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "Кодирует Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrlComponent.\n\nПример: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"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=шеллы\"`"
|
||||
},
|
||||
"$distinct": {
|
||||
"args": "array",
|
||||
"desc": "Возвращает массив содержащий все элементы из массива `array`, с удаленными дупликатами"
|
||||
},
|
||||
"$type": {
|
||||
"args": "value",
|
||||
"desc": "Возвращает тип значения `value` в виде строки. Если `value` не определено, то будет возвращено `undefined`"
|
||||
},
|
||||
"$moment": {
|
||||
"args": "[str]",
|
||||
"desc": "Получает date объект, используя библиотеку Moment."
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@
|
||||
"color": "颜色",
|
||||
"position": "位置",
|
||||
"enable": "启用",
|
||||
"disable": "禁用"
|
||||
"disable": "禁用",
|
||||
"upload": "上传"
|
||||
},
|
||||
"type": {
|
||||
"string": "字符串",
|
||||
@@ -197,6 +198,8 @@
|
||||
"flow_plural": "__count__ 个流程",
|
||||
"subflow": "__count__ 个子流程",
|
||||
"subflow_plural": "__count__ 子流程",
|
||||
"replacedNodes": "__count__ 个节点被置换",
|
||||
"replacedNodes_plural": "__count__ 个节点被置换",
|
||||
"pasteNodes": "在这里粘贴节点",
|
||||
"selectFile": "选择要导入的文件",
|
||||
"importNodes": "导入节点",
|
||||
@@ -212,6 +215,9 @@
|
||||
"groupCopied_plural": "已复制 __count__ 个groups",
|
||||
"groupStyleCopied": "已复制组风格",
|
||||
"invalidFlow": "无效的流程: __message__",
|
||||
"recoveredNodes": "复原的节点",
|
||||
"recoveredNodesInfo": "导入节点时,此流上的节点缺少有效的流ID。 它们已被添加到此流中,您可以复原或删除它们。",
|
||||
"recoveredNodesNotification": "<p>导入的节点缺少有效的流ID</p><p>已将它们添加到名为 '__flowName__'的新流中。</p>",
|
||||
"export": {
|
||||
"selected": "已选择的节点",
|
||||
"current": "现在的节点",
|
||||
@@ -226,13 +232,19 @@
|
||||
},
|
||||
"import": {
|
||||
"import": "导入到",
|
||||
"importSelected": "导入所选项",
|
||||
"importCopy": "导入副本",
|
||||
"viewNodes": "查看节点",
|
||||
"newFlow": "新流程",
|
||||
"replace": "置换",
|
||||
"errors": {
|
||||
"notArray": "输入的不是JSON数组",
|
||||
"itemNotObject": "输入的流无效 - 项目 __index__ 不是节点对象",
|
||||
"missingId": "输入的流无效-项 __index__ 缺少'id'属性",
|
||||
"missingType": "输入的流程无效-项 __index__ 缺少'类型'属性"
|
||||
}
|
||||
},
|
||||
"conflictNotification1": "您要导入的某些节点已经存在于工作空间中。",
|
||||
"conflictNotification2": "选择要导入的节点,并确认要替换现有的节点还是导入它们的副本"
|
||||
},
|
||||
"copyMessagePath": "已复制路径",
|
||||
"copyMessageValue": "已复制数值",
|
||||
@@ -533,6 +545,7 @@
|
||||
"sortAZ": "a-z顺序",
|
||||
"sortRecent": "日期顺序",
|
||||
"more": "增加 __count__ 个",
|
||||
"upload": "上传模块tgz文件",
|
||||
"errors": {
|
||||
"catalogLoadFailed": "无法加载节点目录。<br>查看浏览器控制台了解更多信息",
|
||||
"installFailed": "无法安装: __module__<br>__message__<br>查看日志了解更多信息",
|
||||
@@ -635,7 +648,7 @@
|
||||
},
|
||||
"context": {
|
||||
"name": "上下文数据",
|
||||
"label": "上下午",
|
||||
"label": "上下文",
|
||||
"none": "未选择",
|
||||
"refresh": "刷新以加载",
|
||||
"empty": "空",
|
||||
@@ -709,6 +722,12 @@
|
||||
"committerTip": "保留空白以使用系统默认值",
|
||||
"userName": "用户名",
|
||||
"email": "电子邮件",
|
||||
"workflow": "工作流",
|
||||
"workfowTip": "选择您偏好的工作流",
|
||||
"workflowManual": "手动",
|
||||
"workflowManualTip": "所有更改都必须在“历史记录”侧边栏中手动提交",
|
||||
"workflowAuto": "自动",
|
||||
"workflowAutoTip": "每次部署后都会自动提交更改",
|
||||
"sshKeys": "SSH密钥",
|
||||
"sshKeysTip": "允许您创建到远程git存储库的安全连接。",
|
||||
"add": "添加密钥",
|
||||
|
||||
@@ -243,19 +243,19 @@
|
||||
"args": "array, function",
|
||||
"desc": "返回满足参数function谓语的array参数中的唯一值 (比如:传递值时,函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数: `function(value [,index [,array []]])` 其中value是数组的每个输入,index是该值的位置,整个数组作为第三个参数传递。"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)组件进行编码。\n\n示例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"$encodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)进行编码。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器(URL)组件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"$decodeUrl": {
|
||||
"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=шеллы\"`"
|
||||
},
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"color": "顏色",
|
||||
"position": "位置",
|
||||
"enable": "啟用",
|
||||
"disable": "禁用"
|
||||
"disable": "禁用",
|
||||
"upload": "上傳"
|
||||
},
|
||||
"type": {
|
||||
"string": "字符串",
|
||||
@@ -197,6 +198,8 @@
|
||||
"flow_plural": "__count__ 多流程",
|
||||
"subflow": "__count__ 子流程",
|
||||
"subflow_plural": "__count__ 多子流程",
|
||||
"replacedNodes": "__count__ 個節點被置換",
|
||||
"replacedNodes_plural": "__count__ 個節點被置換",
|
||||
"pasteNodes": "在這裡粘貼節點",
|
||||
"selectFile": "匯入所選檔案",
|
||||
"importNodes": "匯入節點",
|
||||
@@ -212,6 +215,9 @@
|
||||
"groupCopied_plural": "已複製 __count__ 個groups",
|
||||
"groupStyleCopied": "已複製組風格",
|
||||
"invalidFlow": "無效的流程: __message__",
|
||||
"recoveredNodes": "復原的節點",
|
||||
"recoveredNodesInfo": "導入節點時,此流上的節點缺少有效的流ID。它們已被添加到此流中,您可以復原或刪除它們。",
|
||||
"recoveredNodesNotification": "<p>導入的節點缺少有效的流ID</p><p>已將它們添加到名為 '__flowName__'的新流中。</p>",
|
||||
"export": {
|
||||
"selected": "已選擇的節點",
|
||||
"current": "現在的節點",
|
||||
@@ -226,13 +232,19 @@
|
||||
},
|
||||
"import": {
|
||||
"import": "匯入到",
|
||||
"importSelected": "導入所選項",
|
||||
"importCopy": "導入副本",
|
||||
"viewNodes": "查看節點",
|
||||
"newFlow": "新流程",
|
||||
"replace": "置換",
|
||||
"errors": {
|
||||
"notArray": "輸入的不是JSON數組",
|
||||
"itemNotObject": "輸入的流程無效-項目 __index__ 不是節點對象",
|
||||
"missingId": "輸入的流程無效-項 __index__ 缺少“ id”屬性",
|
||||
"missingType": "輸入的流程無效-項 __index__ 缺少“類型”屬性"
|
||||
}
|
||||
},
|
||||
"conflictNotification1": "您要導入的某些節點已經存在於工作空間中。",
|
||||
"conflictNotification2": "選擇要導入的節點,並確認要替換現有的節點還是導入它們的副本"
|
||||
},
|
||||
"copyMessagePath": "已複製路徑",
|
||||
"copyMessageValue": "已複製數值",
|
||||
@@ -533,6 +545,7 @@
|
||||
"sortAZ": "a-z順序",
|
||||
"sortRecent": "日期順序",
|
||||
"more": "增加 __count__ 個",
|
||||
"upload": "上傳模塊tgz文件",
|
||||
"errors": {
|
||||
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊",
|
||||
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
|
||||
@@ -709,6 +722,12 @@
|
||||
"committerTip": "保留空白以使用系統默認值",
|
||||
"userName": "用戶名",
|
||||
"email": "電子郵件",
|
||||
"workflow": "工作流",
|
||||
"workfowTip": "選擇您偏好的工作流",
|
||||
"workflowManual": "手動",
|
||||
"workflowManualTip": "所有更改都必須在“歷史記錄”側邊欄中手動提交",
|
||||
"workflowAuto": "自動",
|
||||
"workflowAutoTip": "每次部署後都會自動提交更改",
|
||||
"sshKeys": "SSH密鑰",
|
||||
"sshKeysTip": "允許您創建到遠程git存儲庫的安全連接。",
|
||||
"add": "添加密鑰",
|
||||
|
||||
@@ -243,19 +243,19 @@
|
||||
"args": "array, function",
|
||||
"desc": "返回滿足參數function謂語的array參數中的唯一值 (比如:傳遞值時,函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數:`function(value [,index [,array []]])`其中value是數組的每個輸入,index是該值的位置,整個數組作為第三個參數傳遞。"
|
||||
},
|
||||
"$encodeUrl": {
|
||||
"$encodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)組件進行編碼。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
|
||||
},
|
||||
"$encodeUrlComponent": {
|
||||
"$encodeUrl": {
|
||||
"args": "str",
|
||||
"desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)進行編碼。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
|
||||
},
|
||||
"$decodeUrl": {
|
||||
"$decodeUrlComponent": {
|
||||
"args": "str",
|
||||
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
|
||||
},
|
||||
"$decodeUrlComponent": {
|
||||
"$decodeUrl": {
|
||||
"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=шеллы\"`"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "1.1.0",
|
||||
"version": "2.1.2",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -14,5 +14,5 @@
|
||||
"name": "Dave Conway-Jones"
|
||||
}
|
||||
],
|
||||
"main": "./lib/index.js"
|
||||
"main": "./index.js"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n# Node-RED Specific Funcs\nsnippet nodes\n node.send(${1:msg})\nsnippet clone\n RED.util.cloneMessage(${1:msg})\nsnippet nodel\n node.log($1)\nsnippet nodew\n node.warn($1)\nsnippet nodee\n node.error($1)\nsnippet noded\n node.debug($1)\nsnippet done\n node.done($1)\nsnippet flowg\n flow.get($1)\nsnippet flows\n flow.set($1, $2)\nsnippet globalg\n global.get($1)\nsnippet globals\n global.set($1, $2)\n',t.scope="nrjavascript"});
|
||||
(function() {
|
||||
; (function() {
|
||||
ace.require(["ace/snippets/nrjavascript"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -76,13 +76,12 @@ oop.inherits(NRJavaScriptWorker, Mirror);
|
||||
|
||||
(function() {
|
||||
this.setOptions = function(options) {
|
||||
this.options = options || {
|
||||
this.options = {
|
||||
// undef: true,
|
||||
// unused: true,
|
||||
esnext: true,
|
||||
moz: true,
|
||||
esversion: 9,
|
||||
devel: true,
|
||||
browser: true,
|
||||
browser: false,
|
||||
node: true,
|
||||
laxcomma: true,
|
||||
laxbreak: true,
|
||||
@@ -92,8 +91,17 @@ oop.inherits(NRJavaScriptWorker, Mirror);
|
||||
maxerr: 100,
|
||||
expr: true,
|
||||
multistr: true,
|
||||
globalstrict: true
|
||||
strict: false,
|
||||
sub: true,
|
||||
asi: true
|
||||
};
|
||||
if (options) {
|
||||
for (var opt in options) {
|
||||
if (options.hasOwnProperty(opt)) {
|
||||
this.options[opt] = options.opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.doc.getValue() && this.deferredUpdate.schedule(100);
|
||||
};
|
||||
|
||||
@@ -119,6 +127,8 @@ oop.inherits(NRJavaScriptWorker, Mirror);
|
||||
if (!value)
|
||||
return this.sender.emit("annotate", []);
|
||||
|
||||
var originalValue = value;
|
||||
|
||||
// [Node-RED] wrap the code in a function
|
||||
value = "async function __nodered__(msg) {\n"+value+"\n}";
|
||||
|
||||
@@ -138,6 +148,7 @@ oop.inherits(NRJavaScriptWorker, Mirror);
|
||||
continue;
|
||||
var raw = error.raw;
|
||||
var type = "warning";
|
||||
var line = error.line - 2;
|
||||
|
||||
if (raw == "Missing semicolon.") {
|
||||
var str = error.evidence.substr(error.character);
|
||||
@@ -166,9 +177,62 @@ oop.inherits(NRJavaScriptWorker, Mirror);
|
||||
type = "info";
|
||||
}
|
||||
|
||||
if (raw === "Unmatched '{a}'." && line === -1) {
|
||||
// This is an unmatched { error. It has incorrectly matched it
|
||||
// against the { in the added line. Need to find the next valid {
|
||||
// This code scans through the original code looking for the first '{'
|
||||
// that is not in a comment or string.
|
||||
// It will incorrectly find a '{' if it is inside a regex... but
|
||||
// at least the error will be shown somwhere. There are only
|
||||
// so many hours in the day to fix every tiny edge case of an
|
||||
// edge case.
|
||||
var inSingleComment = false;
|
||||
var inMultiComment = false;
|
||||
var inString = false;
|
||||
var stringQ;
|
||||
var lineNumber = 0;
|
||||
for (var pos = 0;pos<originalValue.length;pos++) {
|
||||
var c = originalValue[pos];
|
||||
if (c === "\\") {
|
||||
pos++;
|
||||
} else if (inSingleComment) {
|
||||
if (c === "\n") {
|
||||
lineNumber++;
|
||||
inSingleComment = false;
|
||||
}
|
||||
} else if (inMultiComment) {
|
||||
if (c === "*" && originalValue[pos+1] === "/") {
|
||||
pos++;
|
||||
inMultiComment = false;
|
||||
} else if (c === "\n") {
|
||||
lineNumber++;
|
||||
}
|
||||
} else if (inString) {
|
||||
if (c === stringQ) {
|
||||
inString = false;
|
||||
}
|
||||
} else if (c === "'" || c === "\"") {
|
||||
inString = true;
|
||||
stringQ = c;
|
||||
} else if (c === "/") {
|
||||
if (originalValue[pos+1] === "/") {
|
||||
inSingleComment = true;
|
||||
} else if (originalValue[pos+1] === "*") {
|
||||
inMultiComment = true;
|
||||
}
|
||||
} else if (c === "\n") {
|
||||
lineNumber++;
|
||||
} else if (c === "{") {
|
||||
// found it!
|
||||
break;
|
||||
}
|
||||
}
|
||||
line = lineNumber;
|
||||
}
|
||||
|
||||
errors.push({
|
||||
// [Node-RED] offset the row for the added line
|
||||
row: error.line-2,
|
||||
row: Math.max(0,line),
|
||||
column: error.character-1,
|
||||
text: error.reason,
|
||||
type: type,
|
||||
|
||||
BIN
packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/images/grip-horizontal.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 B |
1
packages/node_modules/@node-red/editor-client/src/images/node-red-256.svg
vendored
Normal file
1
packages/node_modules/@node-red/editor-client/src/images/node-red-256.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="512" width="512"><g transform="translate(0 -540.36)"><path fill="#8f0000" color="#000" d="M0 540.36h512v392H0z"/><rect ry="0" height="108.23" width="500.23" stroke="#fff" y="938.25" x="5.89" stroke-width="11.77" fill="#fff"/><path style="text-decoration-color:#000;isolation:auto;mix-blend-mode:normal;solid-color:#000;block-progression:tb;text-decoration-line:none;white-space:normal;text-indent:0;text-transform:none;text-decoration-style:solid" d="M122.88 305.82a4.46 4.46 0 0 0-4.38 4.42v.78c-2.23.1-4.04.54-5.33 1.43a10.5 10.5 0 0 0-3.18 3.87c-.71 1.3-1.3 2.41-2.15 3.2-.72.66-1.8 1.12-3.45 1.32a4.37 4.37 0 0 0-4.3-3.95H82.91a4.43 4.43 0 0 0-4.42 4.35v4.24a4.43 4.43 0 0 0 4.42 4.36h17.16a4.4 4.4 0 0 0 4.4-4.36v-1.6c9.72.14 12.46 2.6 15.59 5.33 3 2.62 6.66 5.38 15.43 5.5v.73a4.49 4.49 0 0 0 4.46 4.38h17.09c2.38 0 4.45-2 4.45-4.38v-4.24a4.49 4.49 0 0 0-4.45-4.38h-17.1c-2.38 0-4.45 2-4.45 4.38v.58c-8.1-.06-10.48-2.15-13.5-4.79-2.5-2.19-5.64-4.58-11.94-5.58 1.17-1.18 1.88-2.52 2.51-3.66.68-1.23 1.29-2.2 2.27-2.88.76-.52 1.98-.84 3.66-.94v.55c0 2.39 2 4.34 4.38 4.34h17.24a4.39 4.39 0 0 0 4.38-4.34v-4.24c0-2.38-2-4.42-4.38-4.42zm0 3h17.24c.8 0 1.38.62 1.38 1.42v4.24c0 .81-.57 1.34-1.38 1.34h-17.24c-.8 0-1.38-.53-1.38-1.34v-4.24c0-.8.57-1.42 1.38-1.42zm-39.96 11.02h17.16c.81 0 1.42.6 1.42 1.4v4.24c0 .81-.61 1.41-1.42 1.41H82.92c-.8 0-1.42-.6-1.42-1.4v-4.25c0-.8.61-1.4 1.42-1.4zm57.04 9.98h17.09c.8 0 1.45.57 1.45 1.38v4.17c0 .8-.65 1.45-1.45 1.45h-17.1c-.8 0-1.45-.65-1.45-1.45v-4.17c0-.8.65-1.38 1.46-1.38z" fill="#fff" color="#000" transform="matrix(4 0 0 4 -162 -450.91)"/><g fill="#8f0000"><path d="M91 954.34v8.45l-8 1.45v60.07H69.03l-28.53-47.1-.5.05v37.2l8 1.44v8.41H19v-8.4l7.45-1.45v-50.22L19 962.79v-8.46h21.48l28.37 47.1.15-.04v-37.15l-7-1.45v-8.45h29zM95 997.83q0-11.63 6.49-19.03 6.53-7.45 18.02-7.45 11.53 0 18.02 7.4 6.54 7.4 6.54 19.08v1q0 11.74-6.54 19.14-6.49 7.35-17.93 7.35-11.58 0-18.11-7.35-6.5-7.4-6.5-19.13v-1.01zm14.03 1q0 7.12 2.5 11.45 2.5 4.28 8.08 4.28 5.43 0 7.93-4.33 2.54-4.33 2.54-11.4v-1q0-6.92-2.54-11.3-2.55-4.37-8.03-4.37t-7.98 4.37-2.5 11.3v1zM184.48 1017.96a17.15 17.15 0 0 1-5.82 5.48 15.17 15.17 0 0 1-7.59 1.88c-6.4 0-11.4-2.35-14.95-7.03-3.52-4.67-5.13-10.86-5.13-18.55v-1c0-8.21 1.62-14.83 5.18-19.86 3.56-5.03 8.56-7.54 15-7.54 2.6 0 4.93.57 7.01 1.73a17.94 17.94 0 0 1 5.81 4.8v-18.64l-8-1.45v-8.46h22v65.13l6 1.44v8.43h-18.45l-1.06-6.36zm-19.49-18.22c0 4.55.63 8.14 2.14 10.77 1.54 2.6 4.04 3.9 7.5 3.9 2.05 0 3.83-.43 5.33-1.26 1.5-.83 3.07-2.03 4.03-3.6v-22.06a11.27 11.27 0 0 0-4.03-3.85 9.62 9.62 0 0 0-5.24-1.4c-3.43 0-5.92 1.53-7.5 4.57s-2.23 7.02-2.23 11.92v1.01zM233.7 1025.28c-7.5 0-13.5-2.4-17.98-7.21-4.48-4.81-6.73-10.91-6.73-18.32v-1.92c0-7.72 2.12-14.08 6.35-19.08 4.26-5 9.96-7.46 17.1-7.43 7.03 0 12.47 2.1 16.35 6.33a23.46 23.46 0 0 1 6.2 17.15v7.52h-31.43l-.1.41c.26 3.43 1.4 6.25 3.41 8.46 2.05 2.21 4.83 3.32 8.32 3.32 3.1 0 5.69-.3 7.74-.91 2.05-.64 4.29-1.64 6.73-2.98l3.8 8.65a27.59 27.59 0 0 1-8.37 4.28 35.28 35.28 0 0 1-11.4 1.73zm-1.25-43.16c-2.6 0-4.65.99-6.15 2.98s-2.44 4.6-2.8 7.83l.15.4H241v-1.41c0-2.98-.84-5.35-2.25-7.11-1.37-1.8-3.47-2.7-6.3-2.7zM291.99 1000.32h-27v-11h27zM331.88 954.34c7.95 0 14.18 1.82 18.7 5.47 4.52 3.63 6.4 8.64 6.4 15.05 0 3.52-.57 6.58-2.46 9.18-1.89 2.6-4.66 4.7-8.31 6.3 4.13 1.21 7.1 3.25 8.89 6.1a19.02 19.02 0 0 1 2.89 10.52v3.56c0 1.54.15 2.74.76 3.6.6.84 1.62 1.34 3.03 1.5l1.2.24v8.46h-6.73c-4.58 0-7.8-1.24-9.66-3.7s-2.6-5.66-2.6-9.57v-3.99c0-3.4-1.1-6.05-2.93-7.98-1.8-1.95-4.34-2.98-7.64-3.07H322v18.45l7 1.44v8.42h-29v-8.42l8-1.44v-50.22l-8-1.44v-8.46h31.89zm-9.95 30.85h9.71c3.91 0 6.84-.83 8.8-2.5s2.93-4.07 2.93-7.2c0-3.15-.98-5.65-2.93-7.5-1.92-1.9-4.78-2.84-8.56-2.84h-9.9v20.04zM412.99 993.32h-23v20h22.21l.63-8h10.2v18.99H368v-8.42l8-1.44v-50.22l-8-1.44v-8.47h54.95v19h-10.3l-.63-8H390v17h23v11zM462.48 954.36c8.55 0 15.6 2.71 21.14 8.19 5.55 5.45 8.36 12.42 8.36 20.98v11.58c0 8.59-2.81 15.63-8.36 21.08-5.54 5.41-12.59 8.12-21.14 8.12h-31.5v-8.41l7-1.52v-50.22l-7-1.37v-8.43l7.46-.08 24.04.08zm-10.5 10.76v48.4l9.77.02c5.03.02 8.98-1.7 11.83-5.1 2.85-3.39 4.4-7.8 4.4-13.28v-11.68c0-5.42-1.55-9.84-4.4-13.24-2.85-3.4-6.8-5.1-11.83-5.1l-9.77-.02z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -808,17 +808,7 @@ RED.nodes.fontAwesome = (function() {
|
||||
"fa-youtube": "\uf167",
|
||||
};
|
||||
|
||||
var iconList = [];
|
||||
var isUsed = {};
|
||||
Object.keys(iconMap).forEach(function(icon) {
|
||||
var unicode = iconMap[icon];
|
||||
// skip icons with a same unicode
|
||||
if (isUsed[unicode] !== true) {
|
||||
iconList.push(icon);
|
||||
isUsed[unicode] = true;
|
||||
}
|
||||
});
|
||||
isUsed = undefined;
|
||||
var iconList = Object.keys(iconMap);
|
||||
|
||||
return {
|
||||
getIconUnicode: function(name) {
|
||||
|
||||
@@ -37,25 +37,43 @@ RED.history = (function() {
|
||||
inverseEv.events.push(r);
|
||||
}
|
||||
} else if (ev.t == 'replace') {
|
||||
inverseEv = {
|
||||
t: 'replace',
|
||||
config: RED.nodes.createCompleteNodeSet(),
|
||||
changed: {},
|
||||
rev: RED.nodes.version()
|
||||
};
|
||||
RED.nodes.clear();
|
||||
var imported = RED.nodes.import(ev.config);
|
||||
imported[0].forEach(function(n) {
|
||||
if (ev.changed[n.id]) {
|
||||
n.changed = true;
|
||||
inverseEv.changed[n.id] = true;
|
||||
}
|
||||
})
|
||||
if (ev.complete) {
|
||||
// This is a replace of everything. We can short-cut
|
||||
// the logic by clearing everyting first, then importing
|
||||
// the ev.config.
|
||||
// Used by RED.diff.mergeDiff
|
||||
inverseEv = {
|
||||
t: 'replace',
|
||||
config: RED.nodes.createCompleteNodeSet(),
|
||||
changed: {},
|
||||
rev: RED.nodes.version()
|
||||
};
|
||||
RED.nodes.clear();
|
||||
var imported = RED.nodes.import(ev.config);
|
||||
imported.nodes.forEach(function(n) {
|
||||
if (ev.changed[n.id]) {
|
||||
n.changed = true;
|
||||
inverseEv.changed[n.id] = true;
|
||||
}
|
||||
})
|
||||
|
||||
RED.nodes.version(ev.rev);
|
||||
RED.nodes.version(ev.rev);
|
||||
} else {
|
||||
var importMap = {};
|
||||
ev.config.forEach(function(n) {
|
||||
importMap[n.id] = "replace";
|
||||
})
|
||||
var importedResult = RED.nodes.import(ev.config,{importMap: importMap})
|
||||
inverseEv = {
|
||||
t: 'replace',
|
||||
config: importedResult.removedNodes,
|
||||
dirty: RED.nodes.dirty()
|
||||
}
|
||||
}
|
||||
} else if (ev.t == 'add') {
|
||||
inverseEv = {
|
||||
t: "delete",
|
||||
dirty: RED.nodes.dirty()
|
||||
};
|
||||
if (ev.nodes) {
|
||||
inverseEv.nodes = [];
|
||||
@@ -85,7 +103,7 @@ RED.history = (function() {
|
||||
}
|
||||
if (ev.groups) {
|
||||
inverseEv.groups = [];
|
||||
for (i=0;i<ev.groups.length;i++) {
|
||||
for (i = ev.groups.length - 1;i>=0;i--) {
|
||||
group = ev.groups[i];
|
||||
modifiedTabs[group.z] = true;
|
||||
// The order of groups is important
|
||||
@@ -142,7 +160,8 @@ RED.history = (function() {
|
||||
|
||||
} else if (ev.t == "delete") {
|
||||
inverseEv = {
|
||||
t: "add"
|
||||
t: "add",
|
||||
dirty: RED.nodes.dirty()
|
||||
};
|
||||
if (ev.workspaces) {
|
||||
inverseEv.workspaces = [];
|
||||
@@ -214,7 +233,7 @@ RED.history = (function() {
|
||||
inverseEv.groups = [];
|
||||
var groupsToAdd = {};
|
||||
ev.groups.forEach(function(g) { groupsToAdd[g.id] = g; });
|
||||
for (i=0;i<ev.groups.length;i++) {
|
||||
for (i = ev.groups.length - 1;i>=0;i--) {
|
||||
RED.nodes.addGroup(ev.groups[i])
|
||||
modifiedTabs[ev.groups[i].z] = true;
|
||||
// The order of groups is important
|
||||
@@ -284,11 +303,12 @@ RED.history = (function() {
|
||||
} else if (ev.t == "move") {
|
||||
inverseEv = {
|
||||
t: 'move',
|
||||
nodes: []
|
||||
nodes: [],
|
||||
dirty: RED.nodes.dirty()
|
||||
};
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
var n = ev.nodes[i];
|
||||
var rn = {n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.moved};
|
||||
var rn = {n: n.n, ox: n.n.x, oy: n.n.y, dirty: true, moved: n.n.moved};
|
||||
inverseEv.nodes.push(rn);
|
||||
n.n.x = n.ox;
|
||||
n.n.y = n.oy;
|
||||
@@ -320,24 +340,38 @@ RED.history = (function() {
|
||||
} else if (ev.t == "edit") {
|
||||
inverseEv = {
|
||||
t: "edit",
|
||||
changes: {}
|
||||
changes: {},
|
||||
changed: ev.node.changed,
|
||||
dirty: RED.nodes.dirty()
|
||||
};
|
||||
inverseEv.node = ev.node;
|
||||
for (i in ev.changes) {
|
||||
if (ev.changes.hasOwnProperty(i)) {
|
||||
inverseEv.changes[i] = ev.node[i];
|
||||
if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) {
|
||||
// This is a config node property
|
||||
var currentConfigNode = RED.nodes.node(ev.node[i]);
|
||||
if (currentConfigNode) {
|
||||
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
|
||||
RED.events.emit("nodes:change",currentConfigNode);
|
||||
// This property is a reference to another node or nodes.
|
||||
var nodeList = ev.node[i];
|
||||
if (!Array.isArray(nodeList)) {
|
||||
nodeList = [nodeList];
|
||||
}
|
||||
var newConfigNode = RED.nodes.node(ev.changes[i]);
|
||||
if (newConfigNode) {
|
||||
newConfigNode.users.push(ev.node);
|
||||
RED.events.emit("nodes:change",newConfigNode);
|
||||
nodeList.forEach(function(id) {
|
||||
var currentConfigNode = RED.nodes.node(id);
|
||||
if (currentConfigNode && currentConfigNode._def.category === "config") {
|
||||
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
|
||||
RED.events.emit("nodes:change",currentConfigNode);
|
||||
}
|
||||
});
|
||||
nodeList = ev.changes[i];
|
||||
if (!Array.isArray(nodeList)) {
|
||||
nodeList = [nodeList];
|
||||
}
|
||||
nodeList.forEach(function(id) {
|
||||
var newConfigNode = RED.nodes.node(id);
|
||||
if (newConfigNode && newConfigNode._def.category === "config") {
|
||||
newConfigNode.users.push(ev.node);
|
||||
RED.events.emit("nodes:change",newConfigNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
ev.node[i] = ev.changes[i];
|
||||
}
|
||||
@@ -524,10 +558,22 @@ RED.history = (function() {
|
||||
} else if (ev.t == "reorder") {
|
||||
inverseEv = {
|
||||
t: 'reorder',
|
||||
order: RED.nodes.getWorkspaceOrder()
|
||||
dirty: RED.nodes.dirty()
|
||||
};
|
||||
if (ev.order) {
|
||||
RED.workspaces.order(ev.order);
|
||||
if (ev.workspaces) {
|
||||
inverseEv.workspaces = {
|
||||
from: ev.workspaces.to,
|
||||
to: ev.workspaces.from
|
||||
}
|
||||
RED.workspaces.order(ev.workspaces.from);
|
||||
}
|
||||
if (ev.nodes) {
|
||||
inverseEv.nodes = {
|
||||
z: ev.nodes.z,
|
||||
from: ev.nodes.to,
|
||||
to: ev.nodes.from
|
||||
}
|
||||
RED.nodes.setNodeOrder(ev.nodes.z,ev.nodes.from);
|
||||
}
|
||||
} else if (ev.t == "createGroup") {
|
||||
inverseEv = {
|
||||
@@ -623,6 +669,8 @@ RED.history = (function() {
|
||||
push: function(ev) {
|
||||
undoHistory.push(ev);
|
||||
redoHistory = [];
|
||||
RED.menu.setDisabled("menu-item-edit-undo", false);
|
||||
RED.menu.setDisabled("menu-item-edit-redo", true);
|
||||
},
|
||||
pop: function() {
|
||||
var ev = undoHistory.pop();
|
||||
@@ -630,6 +678,8 @@ RED.history = (function() {
|
||||
if (rev) {
|
||||
redoHistory.push(rev);
|
||||
}
|
||||
RED.menu.setDisabled("menu-item-edit-undo", undoHistory.length === 0);
|
||||
RED.menu.setDisabled("menu-item-edit-redo", redoHistory.length === 0);
|
||||
},
|
||||
peek: function() {
|
||||
return undoHistory[undoHistory.length-1];
|
||||
@@ -637,6 +687,8 @@ RED.history = (function() {
|
||||
clear: function() {
|
||||
undoHistory = [];
|
||||
redoHistory = [];
|
||||
RED.menu.setDisabled("menu-item-edit-undo", true);
|
||||
RED.menu.setDisabled("menu-item-edit-redo", true);
|
||||
},
|
||||
redo: function() {
|
||||
var ev = redoHistory.pop();
|
||||
@@ -646,6 +698,8 @@ RED.history = (function() {
|
||||
undoHistory.push(uev);
|
||||
}
|
||||
}
|
||||
RED.menu.setDisabled("menu-item-edit-undo", undoHistory.length === 0);
|
||||
RED.menu.setDisabled("menu-item-edit-redo", redoHistory.length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
156
packages/node_modules/@node-red/editor-client/src/js/hooks.js
vendored
Normal file
156
packages/node_modules/@node-red/editor-client/src/js/hooks.js
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
RED.hooks = (function() {
|
||||
|
||||
var VALID_HOOKS = [
|
||||
|
||||
]
|
||||
|
||||
var hooks = { }
|
||||
var labelledHooks = { }
|
||||
|
||||
function add(hookId, callback) {
|
||||
var parts = hookId.split(".");
|
||||
var id = parts[0], label = parts[1];
|
||||
|
||||
// if (VALID_HOOKS.indexOf(id) === -1) {
|
||||
// throw new Error("Invalid hook '"+id+"'");
|
||||
// }
|
||||
if (label && labelledHooks[label] && labelledHooks[label][id]) {
|
||||
throw new Error("Hook "+hookId+" already registered")
|
||||
}
|
||||
var hookItem = {cb:callback, previousHook: null, nextHook: null }
|
||||
|
||||
var tailItem = hooks[id];
|
||||
if (tailItem === undefined) {
|
||||
hooks[id] = hookItem;
|
||||
} else {
|
||||
while(tailItem.nextHook !== null) {
|
||||
tailItem = tailItem.nextHook
|
||||
}
|
||||
tailItem.nextHook = hookItem;
|
||||
hookItem.previousHook = tailItem;
|
||||
}
|
||||
|
||||
if (label) {
|
||||
labelledHooks[label] = labelledHooks[label]||{};
|
||||
labelledHooks[label][id] = hookItem;
|
||||
}
|
||||
}
|
||||
function remove(hookId) {
|
||||
var parts = hookId.split(".");
|
||||
var id = parts[0], label = parts[1];
|
||||
if ( !label) {
|
||||
throw new Error("Cannot remove hook without label: "+hookId)
|
||||
}
|
||||
if (labelledHooks[label]) {
|
||||
if (id === "*") {
|
||||
// Remove all hooks for this label
|
||||
var hookList = Object.keys(labelledHooks[label]);
|
||||
for (var i=0;i<hookList.length;i++) {
|
||||
removeHook(hookList[i],labelledHooks[label][hookList[i]])
|
||||
}
|
||||
delete labelledHooks[label];
|
||||
} else if (labelledHooks[label][id]) {
|
||||
removeHook(id,labelledHooks[label][id])
|
||||
delete labelledHooks[label][id];
|
||||
if (Object.keys(labelledHooks[label]).length === 0){
|
||||
delete labelledHooks[label];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeHook(id,hookItem) {
|
||||
var previousHook = hookItem.previousHook;
|
||||
var nextHook = hookItem.nextHook;
|
||||
|
||||
if (previousHook) {
|
||||
previousHook.nextHook = nextHook;
|
||||
} else {
|
||||
hooks[id] = nextHook;
|
||||
}
|
||||
if (nextHook) {
|
||||
nextHook.previousHook = previousHook;
|
||||
}
|
||||
hookItem.removed = true;
|
||||
if (!previousHook && !nextHook) {
|
||||
delete hooks[id];
|
||||
}
|
||||
}
|
||||
|
||||
function trigger(hookId, payload, done) {
|
||||
var hookItem = hooks[hookId];
|
||||
if (!hookItem) {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
function callNextHook(err) {
|
||||
if (!hookItem || err) {
|
||||
if (done) { done(err) }
|
||||
return err;
|
||||
}
|
||||
if (hookItem.removed) {
|
||||
hookItem = hookItem.nextHook;
|
||||
return callNextHook();
|
||||
}
|
||||
var callback = hookItem.cb;
|
||||
if (callback.length === 1) {
|
||||
try {
|
||||
let result = callback(payload);
|
||||
if (result === false) {
|
||||
// Halting the flow
|
||||
if (done) { done(false) }
|
||||
return result;
|
||||
}
|
||||
hookItem = hookItem.nextHook;
|
||||
return callNextHook();
|
||||
} catch(e) {
|
||||
console.warn(e);
|
||||
if (done) { done(e);}
|
||||
return e;
|
||||
}
|
||||
} else {
|
||||
// There is a done callback
|
||||
try {
|
||||
callback(payload,function(result) {
|
||||
if (result === undefined) {
|
||||
hookItem = hookItem.nextHook;
|
||||
callNextHook();
|
||||
} else {
|
||||
if (done) { done(result)}
|
||||
}
|
||||
})
|
||||
} catch(e) {
|
||||
console.warn(e);
|
||||
if (done) { done(e) }
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return callNextHook();
|
||||
}
|
||||
|
||||
function clear() {
|
||||
hooks = {}
|
||||
labelledHooks = {}
|
||||
}
|
||||
|
||||
function has(hookId) {
|
||||
var parts = hookId.split(".");
|
||||
var id = parts[0], label = parts[1];
|
||||
if (label) {
|
||||
return !!(labelledHooks[label] && labelledHooks[label][id])
|
||||
}
|
||||
return !!hooks[id]
|
||||
}
|
||||
|
||||
return {
|
||||
has: has,
|
||||
clear: clear,
|
||||
add: add,
|
||||
remove: remove,
|
||||
trigger: trigger
|
||||
}
|
||||
})();
|
||||
@@ -18,53 +18,67 @@ RED.i18n = (function() {
|
||||
|
||||
var apiRootUrl;
|
||||
|
||||
function detectLanguage() {
|
||||
return navigator.language
|
||||
}
|
||||
|
||||
return {
|
||||
init: function(options, done) {
|
||||
apiRootUrl = options.apiRootUrl||"";
|
||||
var preferredLanguage = localStorage.getItem("editor-language");
|
||||
var preferredLanguage = localStorage.getItem("editor-language") || detectLanguage();
|
||||
var opts = {
|
||||
resGetPath: apiRootUrl+'locales/__ns__?lng=__lng__',
|
||||
dynamicLoad: false,
|
||||
load:'current',
|
||||
ns: {
|
||||
namespaces: ["editor","node-red","jsonata","infotips"],
|
||||
defaultNs: "editor"
|
||||
compatibilityJSON: 'v3',
|
||||
backend: {
|
||||
loadPath: apiRootUrl+'locales/__ns__?lng=__lng__',
|
||||
},
|
||||
lng: 'en-US',
|
||||
// debug: true,
|
||||
preload:['en-US'],
|
||||
ns: ["editor","node-red","jsonata","infotips"],
|
||||
defaultNS: "editor",
|
||||
fallbackLng: ['en-US'],
|
||||
useCookie: false,
|
||||
returnObjectTrees: true
|
||||
returnObjects: true,
|
||||
interpolation: {
|
||||
unescapeSuffix: 'HTML',
|
||||
escapeValue: false,
|
||||
prefix: '__',
|
||||
suffix: '__'
|
||||
}
|
||||
};
|
||||
if (preferredLanguage) {
|
||||
opts.lng = preferredLanguage;
|
||||
}
|
||||
i18n.init(opts,function() {
|
||||
|
||||
i18next.use(i18nextHttpBackend).init(opts,function() {
|
||||
done();
|
||||
});
|
||||
jqueryI18next.init(i18next, $, { handleName: 'i18n' });
|
||||
|
||||
|
||||
RED["_"] = function() {
|
||||
var v = i18n.t.apply(null,arguments);
|
||||
var v = i18next.t.apply(i18next,arguments);
|
||||
if (typeof v === 'string') {
|
||||
return v;
|
||||
} else {
|
||||
return arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
lang: function() {
|
||||
// Gets the active message catalog language. This is based on what
|
||||
// locale the editor is using and what languages are available.
|
||||
//
|
||||
var preferredLangs = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
|
||||
var preferredLangs = [localStorage.getItem("editor-language")|| detectLanguage()].concat(i18next.languages);
|
||||
var knownLangs = RED.settings.theme("languages")||["en-US"];
|
||||
for (var i=0;i<preferredLangs.length;i++) {
|
||||
if (knownLangs.indexOf(preferredLangs[i]) > -1) {
|
||||
return preferredLangs[i]
|
||||
}
|
||||
}
|
||||
return 'end-US'
|
||||
return 'en-US'
|
||||
},
|
||||
loadNodeCatalog: function(namespace,done) {
|
||||
var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
|
||||
var languageList = [localStorage.getItem("editor-language")|| detectLanguage()].concat(i18next.languages);
|
||||
var toLoad = languageList.length;
|
||||
languageList.forEach(function(lang) {
|
||||
$.ajax({
|
||||
@@ -74,7 +88,7 @@ RED.i18n = (function() {
|
||||
cache: false,
|
||||
url: apiRootUrl+'nodes/'+namespace+'/messages?lng='+lang,
|
||||
success: function(data) {
|
||||
i18n.addResourceBundle(lang,namespace,data);
|
||||
i18next.addResourceBundle(lang,namespace,data);
|
||||
toLoad--;
|
||||
if (toLoad === 0) {
|
||||
done();
|
||||
@@ -86,7 +100,7 @@ RED.i18n = (function() {
|
||||
},
|
||||
|
||||
loadNodeCatalogs: function(done) {
|
||||
var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
|
||||
var languageList = [localStorage.getItem("editor-language")|| detectLanguage()].concat(i18next.languages);
|
||||
var toLoad = languageList.length;
|
||||
|
||||
languageList.forEach(function(lang) {
|
||||
@@ -99,7 +113,7 @@ RED.i18n = (function() {
|
||||
success: function(data) {
|
||||
var namespaces = Object.keys(data);
|
||||
namespaces.forEach(function(ns) {
|
||||
i18n.addResourceBundle(lang,ns,data[ns]);
|
||||
i18next.addResourceBundle(lang,ns,data[ns]);
|
||||
});
|
||||
toLoad--;
|
||||
if (toLoad === 0) {
|
||||
@@ -108,6 +122,32 @@ RED.i18n = (function() {
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
loadPluginCatalogs: function(done) {
|
||||
var languageList = [localStorage.getItem("editor-language")|| detectLanguage()].concat(i18next.languages);
|
||||
var toLoad = languageList.length;
|
||||
|
||||
languageList.forEach(function(lang) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: apiRootUrl+'plugins/messages?lng='+lang,
|
||||
success: function(data) {
|
||||
var namespaces = Object.keys(data);
|
||||
namespaces.forEach(function(ns) {
|
||||
i18next.addResourceBundle(lang,ns,data[ns]);
|
||||
});
|
||||
toLoad--;
|
||||
if (toLoad === 0) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
detectLanguage: detectLanguage
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"alt-shift-p":"core:manage-palette",
|
||||
"ctrl-f": "core:search",
|
||||
"ctrl-shift-f": "core:list-flows",
|
||||
"ctrl-=": "core:zoom-in",
|
||||
"ctrl-+": "core:zoom-in",
|
||||
"ctrl--": "core:zoom-out",
|
||||
"ctrl-0": "core:zoom-reset",
|
||||
"ctrl-enter": "core:confirm-edit-tray",
|
||||
@@ -25,7 +25,9 @@
|
||||
"ctrl-alt-o": "core:open-project",
|
||||
"ctrl-g v": "core:show-version-control-tab",
|
||||
"ctrl-shift-l": "core:show-event-log",
|
||||
"ctrl-shift-p":"core:show-action-list"
|
||||
"ctrl-shift-p":"core:show-action-list",
|
||||
"alt-w": "core:hide-flow",
|
||||
"alt-shift-w": "core:show-last-hidden-flow"
|
||||
},
|
||||
"red-ui-sidebar-node-config": {
|
||||
"backspace": "core:delete-config-selection",
|
||||
@@ -38,12 +40,17 @@
|
||||
"backspace": "core:delete-selection",
|
||||
"delete": "core:delete-selection",
|
||||
"enter": "core:edit-selected-node",
|
||||
"ctrl-enter": "core:go-to-selection",
|
||||
"ctrl-c": "core:copy-selection-to-internal-clipboard",
|
||||
"ctrl-x": "core:cut-selection-to-internal-clipboard",
|
||||
"ctrl-v": "core:paste-from-internal-clipboard",
|
||||
"ctrl-z": "core:undo",
|
||||
"ctrl-y": "core:redo",
|
||||
"ctrl-a": "core:select-all-nodes",
|
||||
"escape": "core:select-none",
|
||||
"alt-s u": "core:select-upstream-nodes",
|
||||
"alt-s d": "core:select-downstream-nodes",
|
||||
"alt-s c": "core:select-connected-nodes",
|
||||
"shift-?": "core:show-help",
|
||||
"w": "core:scroll-view-up",
|
||||
"d": "core:scroll-view-right",
|
||||
@@ -53,19 +60,34 @@
|
||||
"shift-d": "core:step-view-right",
|
||||
"shift-s": "core:step-view-down",
|
||||
"shift-a": "core:step-view-left",
|
||||
"up": "core:move-selection-up",
|
||||
"right": "core:move-selection-right",
|
||||
"down": "core:move-selection-down",
|
||||
"left": "core:move-selection-left",
|
||||
"ctrl-up": "core:move-selection-up",
|
||||
"ctrl-right": "core:move-selection-right",
|
||||
"ctrl-down": "core:move-selection-down",
|
||||
"ctrl-left": "core:move-selection-left",
|
||||
"shift-up": "core:step-selection-up",
|
||||
"shift-right": "core:step-selection-right",
|
||||
"shift-down": "core:step-selection-down",
|
||||
"shift-left": "core:step-selection-left",
|
||||
"ctrl-shift-j": "core:show-previous-tab",
|
||||
"ctrl-shift-k": "core:show-next-tab",
|
||||
"ctrl-[": "core:show-previous-tab",
|
||||
"ctrl-]": "core:show-next-tab",
|
||||
"ctrl-shift-left": "core:go-to-previous-location",
|
||||
"ctrl-shift-right": "core:go-to-next-location",
|
||||
"ctrl-shift-g": "core:group-selection",
|
||||
"ctrl-shift-u": "core:ungroup-selection",
|
||||
"ctrl-shift-c": "core:copy-group-style",
|
||||
"ctrl-shift-v": "core:paste-group-style"
|
||||
"ctrl-shift-v": "core:paste-group-style",
|
||||
"right": "core:go-to-nearest-node-on-right",
|
||||
"left": "core:go-to-nearest-node-on-left",
|
||||
"up": "core:go-to-nearest-node-above",
|
||||
"down": "core:go-to-nearest-node-below",
|
||||
"alt-a g": "core:align-selection-to-grid",
|
||||
"alt-a l": "core:align-selection-to-left",
|
||||
"alt-a r": "core:align-selection-to-right",
|
||||
"alt-a t": "core:align-selection-to-top",
|
||||
"alt-a b": "core:align-selection-to-bottom",
|
||||
"alt-a m": "core:align-selection-to-middle",
|
||||
"alt-a c": "core:align-selection-to-center",
|
||||
"alt-a h": "core:distribute-selection-horizontally",
|
||||
"alt-a v": "core:distribute-selection-vertically"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
46
packages/node_modules/@node-red/editor-client/src/js/plugins.js
vendored
Normal file
46
packages/node_modules/@node-red/editor-client/src/js/plugins.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
RED.plugins = (function() {
|
||||
var plugins = {};
|
||||
var pluginsByType = {};
|
||||
|
||||
function registerPlugin(id,definition) {
|
||||
plugins[id] = definition;
|
||||
if (definition.type) {
|
||||
pluginsByType[definition.type] = pluginsByType[definition.type] || [];
|
||||
pluginsByType[definition.type].push(definition);
|
||||
}
|
||||
if (RED._loadingModule) {
|
||||
definition.module = RED._loadingModule;
|
||||
definition["_"] = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var originalKey = args[0];
|
||||
if (!/:/.test(args[0])) {
|
||||
args[0] = definition.module+":"+args[0];
|
||||
}
|
||||
var result = RED._.apply(null,args);
|
||||
if (result === args[0]) {
|
||||
return originalKey;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
definition["_"] = RED["_"]
|
||||
}
|
||||
if (definition.onadd && typeof definition.onadd === 'function') {
|
||||
definition.onadd();
|
||||
}
|
||||
RED.events.emit("registry:plugin-added",id);
|
||||
}
|
||||
|
||||
function getPlugin(id) {
|
||||
return plugins[id]
|
||||
}
|
||||
|
||||
function getPluginsByType(type) {
|
||||
return pluginsByType[type] || [];
|
||||
}
|
||||
return {
|
||||
registerPlugin: registerPlugin,
|
||||
getPlugin: getPlugin,
|
||||
getPluginsByType: getPluginsByType
|
||||
}
|
||||
})();
|
||||
@@ -37,5 +37,20 @@
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (new Set([0]).size === 0) {
|
||||
// IE does not support passing an iterable to Set constructor
|
||||
var _Set = Set;
|
||||
/*global Set:true */
|
||||
Set = function Set(iterable) {
|
||||
var set = new _Set();
|
||||
if (iterable) {
|
||||
iterable.forEach(set.add, set);
|
||||
}
|
||||
return set;
|
||||
};
|
||||
Set.prototype = _Set.prototype;
|
||||
Set.prototype.constructor = Set;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -15,19 +15,65 @@
|
||||
**/
|
||||
var RED = (function() {
|
||||
|
||||
function appendNodeConfig(nodeConfig,done) {
|
||||
|
||||
function loadPluginList() {
|
||||
loader.reportProgress(RED._("event.loadPlugins"), 10)
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
cache: false,
|
||||
url: 'plugins',
|
||||
success: function(data) {
|
||||
loader.reportProgress(RED._("event.loadPlugins"), 13)
|
||||
RED.i18n.loadPluginCatalogs(function() {
|
||||
loadPlugins(function() {
|
||||
loadNodeList();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function loadPlugins(done) {
|
||||
loader.reportProgress(RED._("event.loadPlugins",{count:""}), 17)
|
||||
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html",
|
||||
"Accept-Language": lang
|
||||
},
|
||||
cache: false,
|
||||
url: 'plugins',
|
||||
success: function(data) {
|
||||
var configs = data.trim().split(/(?=<!-- --- \[red-plugin:\S+\] --- -->)/);
|
||||
var totalCount = configs.length;
|
||||
var stepConfig = function() {
|
||||
// loader.reportProgress(RED._("event.loadNodes",{count:(totalCount-configs.length)+"/"+totalCount}), 30 + ((totalCount-configs.length)/totalCount)*40 )
|
||||
if (configs.length === 0) {
|
||||
done();
|
||||
} else {
|
||||
var config = configs.shift();
|
||||
appendPluginConfig(config,stepConfig);
|
||||
}
|
||||
}
|
||||
stepConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function appendConfig(config, moduleIdMatch, targetContainer, done) {
|
||||
done = done || function(){};
|
||||
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
|
||||
var moduleId;
|
||||
if (m) {
|
||||
moduleId = m[1];
|
||||
if (moduleIdMatch) {
|
||||
moduleId = moduleIdMatch[1];
|
||||
RED._loadingModule = moduleId;
|
||||
} else {
|
||||
moduleId = "unknown";
|
||||
}
|
||||
try {
|
||||
var hasDeferred = false;
|
||||
|
||||
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
|
||||
var nodeConfigEls = $("<div>"+config+"</div>");
|
||||
var scripts = nodeConfigEls.find("script");
|
||||
var scriptCount = scripts.length;
|
||||
scripts.each(function(i,el) {
|
||||
@@ -38,14 +84,15 @@ var RED = (function() {
|
||||
newScript.onload = function() {
|
||||
scriptCount--;
|
||||
if (scriptCount === 0) {
|
||||
$("#red-ui-editor-node-configs").append(nodeConfigEls);
|
||||
$(targetContainer).append(nodeConfigEls);
|
||||
delete RED._loadingModule;
|
||||
done()
|
||||
}
|
||||
}
|
||||
if ($(el).attr('type') === "module") {
|
||||
newScript.type = "module";
|
||||
}
|
||||
$("#red-ui-editor-node-configs").append(newScript);
|
||||
$(targetContainer).append(newScript);
|
||||
newScript.src = RED.settings.apiRootUrl+srcUrl;
|
||||
hasDeferred = true;
|
||||
} else {
|
||||
@@ -61,7 +108,8 @@ var RED = (function() {
|
||||
}
|
||||
})
|
||||
if (!hasDeferred) {
|
||||
$("#red-ui-editor-node-configs").append(nodeConfigEls);
|
||||
$(targetContainer).append(nodeConfigEls);
|
||||
delete RED._loadingModule;
|
||||
done();
|
||||
}
|
||||
} catch(err) {
|
||||
@@ -70,9 +118,27 @@ var RED = (function() {
|
||||
timeout: 10000
|
||||
});
|
||||
console.log("["+moduleId+"] "+err.toString());
|
||||
delete RED._loadingModule;
|
||||
done();
|
||||
}
|
||||
}
|
||||
function appendPluginConfig(pluginConfig,done) {
|
||||
appendConfig(
|
||||
pluginConfig,
|
||||
/<!-- --- \[red-plugin:(\S+)\] --- -->/.exec(pluginConfig.trim()),
|
||||
"#red-ui-editor-plugin-configs",
|
||||
done
|
||||
);
|
||||
}
|
||||
|
||||
function appendNodeConfig(nodeConfig,done) {
|
||||
appendConfig(
|
||||
nodeConfig,
|
||||
/<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim()),
|
||||
"#red-ui-editor-node-configs",
|
||||
done
|
||||
);
|
||||
}
|
||||
|
||||
function loadNodeList() {
|
||||
loader.reportProgress(RED._("event.loadPalette"), 20)
|
||||
@@ -110,7 +176,7 @@ var RED = (function() {
|
||||
|
||||
function loadNodes() {
|
||||
loader.reportProgress(RED._("event.loadNodes",{count:""}), 30)
|
||||
var lang = localStorage.getItem("editor-language")||i18n.detectLanguage();
|
||||
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||
|
||||
$.ajax({
|
||||
headers: {
|
||||
@@ -135,6 +201,7 @@ var RED = (function() {
|
||||
RED.projects.refresh(function(activeProject) {
|
||||
loadFlows(function() {
|
||||
RED.sidebar.info.refresh()
|
||||
var showProjectWelcome = false;
|
||||
if (!activeProject) {
|
||||
// Projects enabled but no active project
|
||||
RED.menu.setDisabled('menu-item-projects-open',true);
|
||||
@@ -142,10 +209,10 @@ var RED = (function() {
|
||||
if (activeProject === false) {
|
||||
// User previously decline the migration to projects.
|
||||
} else { // null/undefined
|
||||
RED.projects.showStartup();
|
||||
showProjectWelcome = true;
|
||||
}
|
||||
}
|
||||
completeLoad();
|
||||
completeLoad(showProjectWelcome);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -178,11 +245,25 @@ var RED = (function() {
|
||||
var currentHash = window.location.hash;
|
||||
RED.nodes.version(nodes.rev);
|
||||
loader.reportProgress(RED._("event.importFlows"),90 )
|
||||
RED.nodes.import(nodes.flows);
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
RED.workspaces.show(currentHash.substring(6));
|
||||
try {
|
||||
RED.nodes.import(nodes.flows);
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
if (/^#flow\/.+$/.test(currentHash)) {
|
||||
RED.workspaces.show(currentHash.substring(6),true);
|
||||
}
|
||||
if (RED.workspaces.active() === 0 && RED.workspaces.count() > 0) {
|
||||
RED.workspaces.show(RED.nodes.getWorkspaceOrder()[0])
|
||||
}
|
||||
} catch(err) {
|
||||
console.warn(err);
|
||||
RED.notify(
|
||||
RED._("event.importError", {message: err.message}),
|
||||
{
|
||||
fixed: true,
|
||||
type: 'error'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
done();
|
||||
@@ -190,7 +271,7 @@ var RED = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
function completeLoad() {
|
||||
function completeLoad(showProjectWelcome) {
|
||||
var persistentNotifications = {};
|
||||
RED.comms.subscribe("notification/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
@@ -204,7 +285,7 @@ var RED = (function() {
|
||||
return;
|
||||
}
|
||||
if (notificationId === "project-update") {
|
||||
loader.start("Loading project",0)
|
||||
loader.start(RED._("event.loadingProject"), 0);
|
||||
RED.nodes.clear();
|
||||
RED.history.clear();
|
||||
RED.view.redraw(true);
|
||||
@@ -221,7 +302,7 @@ var RED = (function() {
|
||||
"merge-complete": RED._("notification.project.merge-complete")
|
||||
}[msg.action];
|
||||
loader.end()
|
||||
RED.notify("<p>"+message+"</p>");
|
||||
RED.notify($("<p>").text(message));
|
||||
RED.sidebar.info.refresh()
|
||||
});
|
||||
});
|
||||
@@ -238,6 +319,7 @@ var RED = (function() {
|
||||
id: notificationId
|
||||
}
|
||||
if (notificationId === "runtime-state") {
|
||||
RED.events.emit("runtime-state",msg);
|
||||
if (msg.error === "safe-mode") {
|
||||
options.buttons = [
|
||||
{
|
||||
@@ -259,7 +341,7 @@ var RED = (function() {
|
||||
}
|
||||
}
|
||||
]
|
||||
// } else if (RED.settings.theme('palette.editable') !== false) {
|
||||
// } else if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||
} else {
|
||||
options.buttons = [
|
||||
{
|
||||
@@ -270,6 +352,16 @@ var RED = (function() {
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (msg.error === "missing-modules") {
|
||||
text+="<ul><li>"+msg.modules.map(function(m) { return RED.utils.sanitize(m.module)+(m.error?(" - <small>"+RED.utils.sanitize(""+m.error)+"</small>"):"")}).join("</li><li>")+"</li></ul>";
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
} else if (msg.error === "credentials_load_failed") {
|
||||
if (RED.settings.theme("projects.enabled",false)) {
|
||||
// projects enabled
|
||||
@@ -360,6 +452,9 @@ var RED = (function() {
|
||||
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId].close();
|
||||
delete persistentNotifications[notificationId];
|
||||
if (notificationId === 'runtime-state') {
|
||||
RED.events.emit("runtime-state",msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||
@@ -372,7 +467,7 @@ var RED = (function() {
|
||||
node.status = msg;
|
||||
node.dirtyStatus = true;
|
||||
node.dirty = true;
|
||||
RED.view.redraw();
|
||||
RED.view.redrawStatus(node);
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||
@@ -386,13 +481,22 @@ var RED = (function() {
|
||||
RED.nodes.addNodeSet(m);
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
RED.i18n.loadNodeCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html",
|
||||
"Accept-Language": lang
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes/'+id,
|
||||
success: function(data) {
|
||||
appendNodeConfig(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||
}
|
||||
loadIconList();
|
||||
@@ -401,7 +505,7 @@ var RED = (function() {
|
||||
m = msg[i];
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||
typeList = "<ul><li>"+m.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
@@ -411,20 +515,29 @@ var RED = (function() {
|
||||
info = RED.nodes.getNodeSet(msg.id);
|
||||
if (info.added) {
|
||||
RED.nodes.enableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
|
||||
} else {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
appendNodeConfig(data);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||
var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html",
|
||||
"Accept-Language": lang
|
||||
},
|
||||
cache: false,
|
||||
url: 'nodes/'+msg.id,
|
||||
success: function(data) {
|
||||
appendNodeConfig(data);
|
||||
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (topic == "notification/node/disabled") {
|
||||
if (msg.types) {
|
||||
RED.nodes.disableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
} else if (topic == "notification/node/upgraded") {
|
||||
@@ -439,19 +552,29 @@ var RED = (function() {
|
||||
|
||||
$(".red-ui-header-toolbar").show();
|
||||
|
||||
|
||||
RED.sidebar.show(":first");
|
||||
|
||||
setTimeout(function() {
|
||||
loader.end();
|
||||
checkFirstRun(function() {
|
||||
if (showProjectWelcome) {
|
||||
RED.projects.showStartup();
|
||||
}
|
||||
});
|
||||
},100);
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
$.get('red/about', function(data) {
|
||||
var aboutHeader = '<div style="text-align:center;">'+
|
||||
'<img width="50px" src="red/images/node-red-icon.svg" />'+
|
||||
'</div>';
|
||||
|
||||
RED.sidebar.help.set(aboutHeader+RED.utils.renderMarkdown(data));
|
||||
});
|
||||
function checkFirstRun(done) {
|
||||
if (RED.settings.theme("tours") === false) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
if (!RED.settings.get("editor.view.view-show-welcome-tours", true)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
RED.actions.invoke("core:show-welcome-tour", RED.settings.get("editor.tours.welcome"), done);
|
||||
}
|
||||
|
||||
function buildMainMenu() {
|
||||
@@ -463,6 +586,22 @@ var RED = (function() {
|
||||
{id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
|
||||
]});
|
||||
}
|
||||
menuOptions.push({id:"menu-item-edit-menu", label:"Edit", options: [
|
||||
{id: "menu-item-edit-undo", label:RED._("keyboard.undoChange"), disabled: true, onselect: "core:undo"},
|
||||
{id: "menu-item-edit-redo", label:RED._("keyboard.redoChange"), disabled: true, onselect: "core:redo"},
|
||||
null,
|
||||
{id: "menu-item-edit-cut", label:RED._("keyboard.cutNode"), onselect: "core:cut-selection-to-internal-clipboard"},
|
||||
{id: "menu-item-edit-copy", label:RED._("keyboard.copyNode"), onselect: "core:copy-selection-to-internal-clipboard"},
|
||||
{id: "menu-item-edit-paste", label:RED._("keyboard.pasteNode"), disabled: true, onselect: "core:paste-from-internal-clipboard"},
|
||||
null,
|
||||
{id: "menu-item-edit-copy-group-style", label:RED._("keyboard.copyGroupStyle"), onselect: "core:copy-group-style"},
|
||||
{id: "menu-item-edit-paste-group-style", label:RED._("keyboard.pasteGroupStyle"), disabled: true, onselect: "core:paste-group-style"},
|
||||
null,
|
||||
{id: "menu-item-edit-select-all", label:RED._("keyboard.selectAll"), onselect: "core:select-all-nodes"},
|
||||
{id: "menu-item-edit-select-connected", label:RED._("keyboard.selectAllConnected"), onselect: "core:select-connected-nodes"},
|
||||
{id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"}
|
||||
]});
|
||||
|
||||
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
{id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true},
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
|
||||
@@ -470,6 +609,25 @@ var RED = (function() {
|
||||
{id:"menu-item-action-list",label:RED._("keyboard.actionList"),onselect:"core:show-action-list"},
|
||||
null
|
||||
]});
|
||||
|
||||
menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [
|
||||
{id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
|
||||
{id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
|
||||
{id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
|
||||
{id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"},
|
||||
null,
|
||||
{id: "menu-item-view-tools-align-left", label:RED._("menu.label.alignLeft"), disabled: true, onselect: "core:align-selection-to-left"},
|
||||
{id: "menu-item-view-tools-align-center", label:RED._("menu.label.alignCenter"), disabled: true, onselect: "core:align-selection-to-center"},
|
||||
{id: "menu-item-view-tools-align-right", label:RED._("menu.label.alignRight"), disabled: true, onselect: "core:align-selection-to-right"},
|
||||
null,
|
||||
{id: "menu-item-view-tools-align-top", label:RED._("menu.label.alignTop"), disabled: true, onselect: "core:align-selection-to-top"},
|
||||
{id: "menu-item-view-tools-align-middle", label:RED._("menu.label.alignMiddle"), disabled: true, onselect: "core:align-selection-to-middle"},
|
||||
{id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"},
|
||||
null,
|
||||
{id: "menu-item-view-tools-distribute-horizontally", label:RED._("menu.label.distributeHorizontally"), disabled: true, onselect: "core:distribute-selection-horizontally"},
|
||||
{id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"}
|
||||
]});
|
||||
|
||||
menuOptions.push(null);
|
||||
if (RED.settings.theme("menu.menu-item-import-library", true)) {
|
||||
menuOptions.push({id: "menu-item-import", label: RED._("menu.label.import"), onselect: "core:show-import-dialog"});
|
||||
@@ -499,7 +657,7 @@ var RED = (function() {
|
||||
]});
|
||||
|
||||
menuOptions.push(null);
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
|
||||
menuOptions.push(null);
|
||||
}
|
||||
@@ -530,11 +688,10 @@ var RED = (function() {
|
||||
RED.user.init();
|
||||
RED.notifications.init();
|
||||
RED.library.init();
|
||||
RED.keyboard.init();
|
||||
RED.palette.init();
|
||||
RED.eventLog.init();
|
||||
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
||||
RED.palette.editor.init();
|
||||
} else {
|
||||
console.log("Palette editor disabled");
|
||||
@@ -559,17 +716,14 @@ var RED = (function() {
|
||||
|
||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||
|
||||
buildMainMenu();
|
||||
RED.keyboard.init(buildMainMenu);
|
||||
|
||||
RED.nodes.init();
|
||||
RED.comms.connect();
|
||||
|
||||
$("#red-ui-main-container").show();
|
||||
|
||||
|
||||
RED.actions.add("core:show-about", showAbout);
|
||||
|
||||
loadNodeList();
|
||||
loadPluginList();
|
||||
}
|
||||
|
||||
|
||||
@@ -585,6 +739,7 @@ var RED = (function() {
|
||||
'<div id="red-ui-sidebar"></div>'+
|
||||
'<div id="red-ui-sidebar-separator"></div>'+
|
||||
'</div>').appendTo(options.target);
|
||||
$('<div id="red-ui-editor-plugin-configs"></div>').appendTo(options.target);
|
||||
$('<div id="red-ui-editor-node-configs"></div>').appendTo(options.target);
|
||||
$('<div id="red-ui-full-shade" class="hide"></div>').appendTo(options.target);
|
||||
|
||||
@@ -603,9 +758,12 @@ var RED = (function() {
|
||||
$('<span>').html(theme.header.title).appendTo(logo);
|
||||
}
|
||||
}
|
||||
if (theme.themes) {
|
||||
knownThemes = theme.themes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var knownThemes = null;
|
||||
var initialised = false;
|
||||
|
||||
function init(options) {
|
||||
@@ -613,7 +771,7 @@ var RED = (function() {
|
||||
throw new Error("RED already initialised");
|
||||
}
|
||||
initialised = true;
|
||||
ace.require("ace/ext/language_tools");
|
||||
if(window.ace) { window.ace.require("ace/ext/language_tools"); }
|
||||
options = options || {};
|
||||
options.apiRootUrl = options.apiRootUrl || "";
|
||||
if (options.apiRootUrl && !/\/$/.test(options.apiRootUrl)) {
|
||||
@@ -625,7 +783,13 @@ var RED = (function() {
|
||||
buildEditor(options);
|
||||
|
||||
RED.i18n.init(options, function() {
|
||||
RED.settings.init(options, loadEditor);
|
||||
RED.settings.init(options, function() {
|
||||
if (knownThemes) {
|
||||
RED.settings.editorTheme = RED.settings.editorTheme || {};
|
||||
RED.settings.editorTheme.themes = knownThemes;
|
||||
}
|
||||
loadEditor();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ RED.settings = (function () {
|
||||
|
||||
var loadedSettings = {};
|
||||
var userSettings = {};
|
||||
var settingsDirty = false;
|
||||
var pendingSave;
|
||||
|
||||
var hasLocalStorage = function () {
|
||||
@@ -57,12 +56,11 @@ RED.settings = (function () {
|
||||
return JSON.parse(localStorage.getItem(key));
|
||||
} else {
|
||||
var v;
|
||||
try {
|
||||
v = RED.utils.getMessageProperty(userSettings,key);
|
||||
if (v === undefined) {
|
||||
v = defaultIfUndefined;
|
||||
}
|
||||
} catch(err) {
|
||||
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
|
||||
if (v === undefined) {
|
||||
try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {}
|
||||
}
|
||||
if (v === undefined) {
|
||||
v = defaultIfUndefined;
|
||||
}
|
||||
return v;
|
||||
@@ -144,7 +142,8 @@ RED.settings = (function () {
|
||||
console.groupCollapsed("Versions");
|
||||
console.log("jQuery",$().jquery)
|
||||
console.log("jQuery UI",$.ui.version);
|
||||
console.log("ACE",ace.version);
|
||||
if(window.ace) { console.log("ACE",ace.version); }
|
||||
if(window.monaco) { console.log("MONACO",monaco.version || "unknown"); }
|
||||
console.log("D3",d3.version);
|
||||
console.groupEnd();
|
||||
loadUserSettings(done);
|
||||
@@ -220,6 +219,16 @@ RED.settings = (function () {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
function getLocal(key) {
|
||||
return localStorage.getItem(key)
|
||||
}
|
||||
function setLocal(key, value) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
function removeLocal(key) {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
@@ -228,6 +237,9 @@ RED.settings = (function () {
|
||||
set: set,
|
||||
get: get,
|
||||
remove: remove,
|
||||
theme: theme
|
||||
theme: theme,
|
||||
setLocal: setLocal,
|
||||
getLocal: getLocal,
|
||||
removeLocal: removeLocal
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -151,7 +151,6 @@ RED.actionList = (function() {
|
||||
}
|
||||
if (!visible) {
|
||||
previousActiveElement = document.activeElement;
|
||||
RED.keyboard.add("*","escape",function(){hide()});
|
||||
$("#red-ui-header-shade").show();
|
||||
$("#red-ui-editor-shade").show();
|
||||
$("#red-ui-palette-shade").show();
|
||||
@@ -185,7 +184,6 @@ RED.actionList = (function() {
|
||||
|
||||
function hide() {
|
||||
if (visible) {
|
||||
RED.keyboard.remove("escape");
|
||||
visible = false;
|
||||
$("#red-ui-header-shade").hide();
|
||||
$("#red-ui-editor-shade").hide();
|
||||
@@ -215,6 +213,9 @@ RED.actionList = (function() {
|
||||
RED.events.on("type-search:open",function() { disabled = true; });
|
||||
RED.events.on("type-search:close",function() { disabled = false; });
|
||||
|
||||
RED.keyboard.add("red-ui-actionList","escape",function(){hide()});
|
||||
|
||||
|
||||
$("#red-ui-header-shade").on('mousedown',hide);
|
||||
$("#red-ui-editor-shade").on('mousedown',hide);
|
||||
$("#red-ui-palette-shade").on('mousedown',hide);
|
||||
|
||||
@@ -4,6 +4,12 @@ RED.actions = (function() {
|
||||
}
|
||||
|
||||
function addAction(name,handler) {
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error("Action handler not a function");
|
||||
}
|
||||
if (actions[name]) {
|
||||
throw new Error("Cannot override existing action");
|
||||
}
|
||||
actions[name] = handler;
|
||||
}
|
||||
function removeAction(name) {
|
||||
@@ -12,16 +18,29 @@ RED.actions = (function() {
|
||||
function getAction(name) {
|
||||
return actions[name];
|
||||
}
|
||||
function invokeAction(name,args) {
|
||||
function invokeAction() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = args.shift();
|
||||
if (actions.hasOwnProperty(name)) {
|
||||
actions[name](args);
|
||||
actions[name].apply(null, args);
|
||||
}
|
||||
}
|
||||
function listActions() {
|
||||
var result = [];
|
||||
Object.keys(actions).forEach(function(action) {
|
||||
var shortcut = RED.keyboard.getShortcut(action);
|
||||
result.push({id:action,scope:shortcut?shortcut.scope:undefined,key:shortcut?shortcut.key:undefined,user:shortcut?shortcut.user:undefined})
|
||||
var isUser = false;
|
||||
if (shortcut) {
|
||||
isUser = shortcut.user;
|
||||
} else {
|
||||
isUser = !!RED.keyboard.getUserShortcut(action);
|
||||
}
|
||||
result.push({
|
||||
id:action,
|
||||
scope:shortcut?shortcut.scope:undefined,
|
||||
key:shortcut?shortcut.key:undefined,
|
||||
user:isUser
|
||||
})
|
||||
})
|
||||
return result;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
115
packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js
vendored
Normal file
115
packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Attach to an <input type="text"> to provide auto-complete
|
||||
*
|
||||
* $("#node-red-text").autoComplete({
|
||||
* search: function(value) { return ['a','b','c'] }
|
||||
* })
|
||||
*
|
||||
* options:
|
||||
*
|
||||
* search : function(value, [done])
|
||||
* A function that is passed the current contents of the input whenever
|
||||
* it changes.
|
||||
* The function must either return auto-complete options, or pass them
|
||||
* to the optional 'done' parameter.
|
||||
* If the function signature includes 'done', it must be used
|
||||
*
|
||||
* The auto-complete options should be an array of objects in the form:
|
||||
* {
|
||||
* value: String : the value to insert if selected
|
||||
* label: String|DOM Element : the label to display in the dropdown.
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
$.widget( "nodered.autoComplete", {
|
||||
_create: function() {
|
||||
var that = this;
|
||||
this.completionMenuShown = false;
|
||||
this.options.search = this.options.search || function() { return [] }
|
||||
this.element.addClass("red-ui-autoComplete")
|
||||
this.element.on("keydown.red-ui-autoComplete", function(evt) {
|
||||
if ((evt.keyCode === 13 || evt.keyCode === 9) && that.completionMenuShown) {
|
||||
var opts = that.menu.options();
|
||||
that.element.val(opts[0].value);
|
||||
that.menu.hide();
|
||||
evt.preventDefault();
|
||||
}
|
||||
})
|
||||
this.element.on("keyup.red-ui-autoComplete", function(evt) {
|
||||
if (evt.keyCode === 13 || evt.keyCode === 9 || evt.keyCode === 27) {
|
||||
// ENTER / TAB / ESCAPE
|
||||
return
|
||||
}
|
||||
if (evt.keyCode === 8 || evt.keyCode === 46) {
|
||||
// Delete/Backspace
|
||||
if (!that.completionMenuShown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
that._updateCompletions(this.value);
|
||||
});
|
||||
},
|
||||
_showCompletionMenu: function(completions) {
|
||||
if (this.completionMenuShown) {
|
||||
return;
|
||||
}
|
||||
this.menu = RED.popover.menu({
|
||||
tabSelect: true,
|
||||
width: 300,
|
||||
maxHeight: 200,
|
||||
class: "red-ui-autoComplete-container",
|
||||
options: completions,
|
||||
onselect: (opt) => { this.element.val(opt.value); this.element.focus() },
|
||||
onclose: () => { this.completionMenuShown = false; delete this.menu; this.element.focus()}
|
||||
});
|
||||
this.menu.show({
|
||||
target: this.element
|
||||
})
|
||||
this.completionMenuShown = true;
|
||||
},
|
||||
_updateCompletions: function(val) {
|
||||
var that = this;
|
||||
if (val.trim() === "") {
|
||||
if (this.completionMenuShown) {
|
||||
this.menu.hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
function displayResults(completions,requestId) {
|
||||
if (requestId && requestId !== that.pendingRequest) {
|
||||
// This request has been superseded
|
||||
return
|
||||
}
|
||||
if (!completions || completions.length === 0) {
|
||||
if (that.completionMenuShown) {
|
||||
that.menu.hide();
|
||||
}
|
||||
return
|
||||
}
|
||||
if (that.completionMenuShown) {
|
||||
that.menu.options(completions);
|
||||
} else {
|
||||
that._showCompletionMenu(completions);
|
||||
}
|
||||
}
|
||||
if (this.options.search.length === 2) {
|
||||
var requestId = 1+Math.floor(Math.random()*10000);
|
||||
this.pendingRequest = requestId;
|
||||
this.options.search(val,function(completions) { displayResults(completions,requestId);})
|
||||
} else {
|
||||
displayResults(this.options.search(val))
|
||||
}
|
||||
},
|
||||
_destroy: function() {
|
||||
this.element.removeClass("red-ui-autoComplete")
|
||||
this.element.off("keydown.red-ui-autoComplete")
|
||||
this.element.off("keyup.red-ui-autoComplete")
|
||||
if (this.completionMenuShown) {
|
||||
this.menu.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -27,26 +27,26 @@
|
||||
this.partialFlag = false;
|
||||
this.stateValue = 0;
|
||||
var initialState = this.element.prop('checked');
|
||||
this.options = [
|
||||
this.states = [
|
||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
|
||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
|
||||
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
|
||||
];
|
||||
if (initialState) {
|
||||
this.options[1].show();
|
||||
this.states[1].show();
|
||||
} else {
|
||||
this.options[0].show();
|
||||
this.states[0].show();
|
||||
}
|
||||
|
||||
this.element.on("change", function() {
|
||||
if (this.checked) {
|
||||
that.options[0].hide();
|
||||
that.options[1].show();
|
||||
that.options[2].hide();
|
||||
that.states[0].hide();
|
||||
that.states[1].show();
|
||||
that.states[2].hide();
|
||||
} else {
|
||||
that.options[1].hide();
|
||||
that.options[0].show();
|
||||
that.options[2].hide();
|
||||
that.states[1].hide();
|
||||
that.states[0].show();
|
||||
that.states[2].hide();
|
||||
}
|
||||
var isChecked = this.checked;
|
||||
that.children.forEach(function(child) {
|
||||
@@ -106,17 +106,17 @@
|
||||
var trueState = this.partialFlag||state;
|
||||
this.element.prop('checked',trueState);
|
||||
if (state === true) {
|
||||
this.options[0].hide();
|
||||
this.options[1].show();
|
||||
this.options[2].hide();
|
||||
this.states[0].hide();
|
||||
this.states[1].show();
|
||||
this.states[2].hide();
|
||||
} else if (state === false) {
|
||||
this.options[2].hide();
|
||||
this.options[1].hide();
|
||||
this.options[0].show();
|
||||
this.states[2].hide();
|
||||
this.states[1].hide();
|
||||
this.states[0].show();
|
||||
} else if (state === null) {
|
||||
this.options[0].hide();
|
||||
this.options[1].hide();
|
||||
this.options[2].show();
|
||||
this.states[0].hide();
|
||||
this.states[1].hide();
|
||||
this.states[2].show();
|
||||
}
|
||||
if (!suppressEvent) {
|
||||
this.element.trigger('change',null);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
/**
|
||||
* options:
|
||||
* - addButton : boolean|string - text for add label, default 'add'
|
||||
* - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click')
|
||||
* - height : number|'auto'
|
||||
* - resize : function - called when list as a whole is resized
|
||||
* - resizeItem : function(item) - called to resize individual item
|
||||
@@ -67,30 +68,64 @@
|
||||
this.topContainer.addClass(this.options.class);
|
||||
}
|
||||
|
||||
var buttons = this.options.buttons || [];
|
||||
|
||||
if (this.options.addButton !== false) {
|
||||
var addLabel;
|
||||
var addLabel, addTitle;
|
||||
if (typeof this.options.addButton === 'string') {
|
||||
addLabel = this.options.addButton
|
||||
} else {
|
||||
if (RED && RED._) {
|
||||
addLabel = RED._("editableList.add");
|
||||
addTitle = RED._("editableList.addTitle");
|
||||
} else {
|
||||
addLabel = 'add';
|
||||
addTitle = 'add new item';
|
||||
}
|
||||
}
|
||||
$('<a href="#" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
|
||||
.appendTo(this.topContainer)
|
||||
buttons.unshift({
|
||||
label: addLabel,
|
||||
icon: "fa fa-plus",
|
||||
click: function(evt) {
|
||||
that.addItem({});
|
||||
},
|
||||
title: addTitle
|
||||
});
|
||||
}
|
||||
|
||||
buttons.forEach(function(button) {
|
||||
var element = $('<button type="button" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></button>')
|
||||
.appendTo(that.topContainer)
|
||||
.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
that.addItem({});
|
||||
if (button.click !== undefined) {
|
||||
button.click(evt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (button.id) {
|
||||
element.attr("id", button.id);
|
||||
}
|
||||
if (button.title) {
|
||||
element.attr("title", button.title);
|
||||
}
|
||||
if (button.icon) {
|
||||
element.append($("<i></i>").attr("class", button.icon));
|
||||
}
|
||||
if (button.label) {
|
||||
element.append($("<span></span>").text(" " + button.label));
|
||||
}
|
||||
});
|
||||
|
||||
if (this.element.css("position") === "absolute") {
|
||||
["top","left","bottom","right"].forEach(function(s) {
|
||||
var v = that.element.css(s);
|
||||
if (v!=="auto" && v!=="") {
|
||||
that.topContainer.css(s,v);
|
||||
that.uiContainer.css(s,"0");
|
||||
if (s === "top" && that.options.header) {
|
||||
that.uiContainer.css(s,"20px")
|
||||
}
|
||||
that.element.css(s,'auto');
|
||||
}
|
||||
})
|
||||
|
||||
@@ -82,12 +82,19 @@ RED.menu = (function() {
|
||||
linkContent += '<span class="red-ui-menu-label-container"><span class="red-ui-menu-label">'+opt.label+'</span>'+
|
||||
'<span class="red-ui-menu-sublabel">'+opt.sublabel+'</span></span>'
|
||||
} else {
|
||||
linkContent += '<span class="red-ui-menu-label">'+opt.label+'</span>'
|
||||
linkContent += '<span class="red-ui-menu-label"><span>'+opt.label+'</span></span>'
|
||||
}
|
||||
|
||||
linkContent += '</a>';
|
||||
|
||||
var link = $(linkContent).appendTo(item);
|
||||
opt.link = link;
|
||||
if (typeof opt.onselect === 'string') {
|
||||
var shortcut = RED.keyboard.getShortcut(opt.onselect);
|
||||
if (shortcut && shortcut.key) {
|
||||
opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label"));
|
||||
}
|
||||
}
|
||||
|
||||
menuItems[opt.id] = opt;
|
||||
|
||||
@@ -276,6 +283,22 @@ RED.menu = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshShortcuts() {
|
||||
for (var id in menuItems) {
|
||||
if (menuItems.hasOwnProperty(id)) {
|
||||
var opt = menuItems[id];
|
||||
if (typeof opt.onselect === "string" && opt.shortcutSpan) {
|
||||
opt.shortcutSpan.remove();
|
||||
delete opt.shortcutSpan;
|
||||
var shortcut = RED.keyboard.getShortcut(opt.onselect);
|
||||
if (shortcut && shortcut.key) {
|
||||
opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(opt.link.find(".red-ui-menu-label"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: createMenu,
|
||||
setSelected: setSelected,
|
||||
@@ -284,6 +307,7 @@ RED.menu = (function() {
|
||||
setDisabled: setDisabled,
|
||||
addItem: addItem,
|
||||
removeItem: removeItem,
|
||||
setAction: setAction
|
||||
setAction: setAction,
|
||||
refreshShortcuts: refreshShortcuts
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -13,24 +13,138 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
/*
|
||||
* RED.popover.create(options) - create a popover callout box
|
||||
* RED.popover.tooltip(target,content, action) - add a tooltip to an element
|
||||
* RED.popover.menu(options) - create a dropdown menu
|
||||
* RED.popover.panel(content) - create a dropdown container element
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* RED.popover.create(options)
|
||||
*
|
||||
* options
|
||||
* - target : DOM element - the element to target with the popover
|
||||
* - direction : string - position of the popover relative to target
|
||||
* 'top', 'right'(default), 'bottom', 'left', 'inset-[top,right,bottom,left]'
|
||||
* - trigger : string - what triggers the popover to be displayed
|
||||
* 'hover' - display when hovering the target
|
||||
* 'click' - display when target is clicked
|
||||
* 'modal' - hmm not sure, need to find where we use that mode
|
||||
* - content : string|function - contents of the popover. If a string, handled
|
||||
* as raw HTML, so take care.
|
||||
* If a function, can return a String to be added
|
||||
* as text (not HTML), or a DOM element to append
|
||||
* - delay : object - sets show/hide delays after mouseover/out events
|
||||
* { show: 750, hide: 50 }
|
||||
* - autoClose : number - delay before closing the popover in some cases
|
||||
* if trigger is click - delay after mouseout
|
||||
* else if trigger not hover/modal - delay after showing
|
||||
* - width : number - width of popover, default 'auto'
|
||||
* - maxWidth : number - max width of popover, default 'auto'
|
||||
* - size : string - scale of popover. 'default', 'small'
|
||||
* - offset : number - px offset from target
|
||||
* - tooltip : boolean - if true, clicking on popover closes it
|
||||
* - class : string - optional css class to apply to popover
|
||||
* - interactive : if trigger is 'hover' and this is set to true, allow the mouse
|
||||
* to move over the popover without hiding it.
|
||||
*
|
||||
* Returns the popover object with the following properties/functions:
|
||||
* properties:
|
||||
* - element : DOM element - the popover dom element
|
||||
* functions:
|
||||
* - setContent(content) - change the popover content. This only works if the
|
||||
* popover is not currently displayed. It does not
|
||||
* change the content of a visible popover.
|
||||
* - open(instant) - show the popover. If 'instant' is true, don't fade in
|
||||
* - close(instant) - hide the popover. If 'instant' is true, don't fade out
|
||||
* - move(options) - move the popover. The options parameter can take many
|
||||
* of the options detailed above including:
|
||||
* target,direction,content,width,offset
|
||||
* Other settings probably won't work because we haven't needed to change them
|
||||
*/
|
||||
|
||||
/*
|
||||
* RED.popover.tooltip(target,content, action)
|
||||
*
|
||||
* - target : DOM element - the element to apply the tooltip to
|
||||
* - content : string - the text of the tooltip
|
||||
* - action : string - *optional* the name of an Action this tooltip is tied to
|
||||
* For example, it 'target' is a button that triggers a particular action.
|
||||
* The tooltip will include the keyboard shortcut for the action
|
||||
* if one is defined
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* RED.popover.menu(options)
|
||||
*
|
||||
* options
|
||||
* - options : array - list of menu options - see below for format
|
||||
* - width : number - width of the menu. Default: 'auto'
|
||||
* - class : string - class to apply to the menu container
|
||||
* - maxHeight : number - maximum height of menu before scrolling items. Default: none
|
||||
* - onselect : function(item) - called when a menu item is selected, if that item doesn't
|
||||
* have its own onselect function
|
||||
* - onclose : function(cancelled) - called when the menu is closed
|
||||
* - disposeOnClose : boolean - by default, the menu is discarded when it closes
|
||||
* and mustbe rebuilt to redisplay. Setting this to 'false'
|
||||
* keeps the menu on the DOM so it can be shown again.
|
||||
*
|
||||
* Menu Options array:
|
||||
* [
|
||||
* label : string|DOM element - the label of the item. Can be custom DOM element
|
||||
* onselect : function - called when the item is selected
|
||||
* ]
|
||||
*
|
||||
* Returns the menu object with the following functions:
|
||||
*
|
||||
* - options([menuItems]) - if menuItems is undefined, returns the current items.
|
||||
* otherwise, sets the current menu items
|
||||
* - show(opts) - shows the menu. `opts` is an object of options. See RED.popover.panel.show(opts)
|
||||
* for the full list of options. In most scenarios, this just needs:
|
||||
* - target : DOM element - the element to display the menu below
|
||||
* - hide(cancelled) - hide the menu
|
||||
*/
|
||||
|
||||
/*
|
||||
* RED.popover.panel(content)
|
||||
* Create a UI panel that can be displayed relative to any target element.
|
||||
* Handles auto-closing when mouse clicks outside the panel
|
||||
*
|
||||
* - 'content' - DOM element to display in the panel
|
||||
*
|
||||
* Returns the panel object with the following functions:
|
||||
*
|
||||
* properties:
|
||||
* - container : DOM element - the panel element
|
||||
*
|
||||
* functions:
|
||||
* - show(opts) - show the panel.
|
||||
* opts:
|
||||
* - onclose : function - called when the panel closes
|
||||
* - closeButton : DOM element - if the panel is closeable by a click of a button,
|
||||
* by providing a reference to it here, we can
|
||||
* handle the events properly to hide the panel
|
||||
* - target : DOM element - the element to display the panel relative to
|
||||
* - align : string - should the panel align to the left or right edge of target
|
||||
* default: 'right'
|
||||
* - offset : Array - px offset to apply from the target. [width, height]
|
||||
* - dispose : boolean - whether the panel should be removed from DOM when hidden
|
||||
* default: true
|
||||
* - hide(dispose) - hide the panel.
|
||||
*/
|
||||
|
||||
RED.popover = (function() {
|
||||
var deltaSizes = {
|
||||
"default": {
|
||||
top: 10,
|
||||
topTop: 30,
|
||||
leftRight: 17,
|
||||
leftLeft: 25,
|
||||
leftBottom: 8,
|
||||
leftTop: 11
|
||||
x: 12,
|
||||
y: 12
|
||||
},
|
||||
"small": {
|
||||
top: 6,
|
||||
topTop: 20,
|
||||
leftRight: 8,
|
||||
leftLeft: 26,
|
||||
leftBottom: 8,
|
||||
leftTop: 9
|
||||
x:8,
|
||||
y:8
|
||||
}
|
||||
}
|
||||
function createPopover(options) {
|
||||
@@ -41,7 +155,9 @@ RED.popover = (function() {
|
||||
var delay = options.delay || { show: 750, hide: 50 };
|
||||
var autoClose = options.autoClose;
|
||||
var width = options.width||"auto";
|
||||
var maxWidth = options.maxWidth;
|
||||
var size = options.size||"default";
|
||||
var popupOffset = options.offset || 0;
|
||||
if (!deltaSizes[size]) {
|
||||
throw new Error("Invalid RED.popover size value:",size);
|
||||
}
|
||||
@@ -49,6 +165,8 @@ RED.popover = (function() {
|
||||
var timer = null;
|
||||
var active;
|
||||
var div;
|
||||
var contentDiv;
|
||||
var currentStyle;
|
||||
|
||||
var openPopup = function(instant) {
|
||||
if (active) {
|
||||
@@ -58,6 +176,10 @@ RED.popover = (function() {
|
||||
return;
|
||||
}
|
||||
div = $('<div class="red-ui-popover"></div>');
|
||||
if (options.class) {
|
||||
div.addClass(options.class);
|
||||
}
|
||||
contentDiv = $('<div class="red-ui-popover-content">').appendTo(div);
|
||||
if (size !== "default") {
|
||||
div.addClass("red-ui-popover-size-"+size);
|
||||
}
|
||||
@@ -67,70 +189,23 @@ RED.popover = (function() {
|
||||
return;
|
||||
}
|
||||
if (typeof result === 'string') {
|
||||
div.text(result);
|
||||
contentDiv.text(result);
|
||||
} else {
|
||||
div.append(result);
|
||||
contentDiv.append(result);
|
||||
}
|
||||
} else {
|
||||
div.html(content);
|
||||
}
|
||||
if (width !== "auto") {
|
||||
div.width(width);
|
||||
contentDiv.html(content);
|
||||
}
|
||||
div.appendTo("body");
|
||||
|
||||
var targetPos = target.offset();
|
||||
var targetWidth = target.outerWidth();
|
||||
var targetHeight = target.outerHeight();
|
||||
var divHeight = div.height();
|
||||
var divWidth = div.width();
|
||||
movePopup({target,direction,width,maxWidth});
|
||||
|
||||
var viewportTop = $(window).scrollTop();
|
||||
var viewportLeft = $(window).scrollLeft();
|
||||
var viewportBottom = viewportTop + $(window).height();
|
||||
var viewportRight = viewportLeft + $(window).width();
|
||||
var top = 0;
|
||||
var left = 0;
|
||||
var d = direction;
|
||||
if (d === 'right') {
|
||||
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
|
||||
left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
|
||||
} else if (d === 'left') {
|
||||
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
|
||||
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
|
||||
} else if (d === 'bottom') {
|
||||
top = targetPos.top+targetHeight+deltaSizes[size].top;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
|
||||
if (left < 0) {
|
||||
d = "right";
|
||||
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
|
||||
left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
|
||||
} else if (left+divWidth > viewportRight) {
|
||||
d = "left";
|
||||
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
|
||||
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
|
||||
if (top+divHeight+targetHeight/2 + 5 > viewportBottom) {
|
||||
top -= (top+divHeight+targetHeight/2 - viewportBottom + 5)
|
||||
}
|
||||
} else if (top+divHeight > viewportBottom) {
|
||||
d = 'top';
|
||||
top = targetPos.top-deltaSizes[size].topTop-divHeight;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
|
||||
}
|
||||
} else if (d === 'top') {
|
||||
top = targetPos.top-deltaSizes[size].topTop-divHeight;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
|
||||
if (top < 0) {
|
||||
d = 'bottom';
|
||||
top = targetPos.top+targetHeight+deltaSizes[size].top;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
|
||||
}
|
||||
}
|
||||
div.addClass('red-ui-popover-'+d).css({top: top, left: left});
|
||||
if (existingPopover) {
|
||||
existingPopover.close(true);
|
||||
}
|
||||
target.data("red-ui-popover",res)
|
||||
if (options.trigger !== 'manual') {
|
||||
target.data("red-ui-popover",res)
|
||||
}
|
||||
if (options.tooltip) {
|
||||
div.on("mousedown", function(evt) {
|
||||
closePopup(true);
|
||||
@@ -160,6 +235,104 @@ RED.popover = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
var movePopup = function(options) {
|
||||
target = options.target || target;
|
||||
direction = options.direction || direction || "right";
|
||||
popupOffset = options.offset || popupOffset;
|
||||
var transition = options.transition;
|
||||
|
||||
var width = options.width||"auto";
|
||||
div.width(width);
|
||||
if (options.maxWidth) {
|
||||
div.css("max-width",options.maxWidth)
|
||||
} else {
|
||||
div.css("max-width", 'auto');
|
||||
}
|
||||
|
||||
var targetPos = target[0].getBoundingClientRect();
|
||||
var targetHeight = targetPos.height;
|
||||
var targetWidth = targetPos.width;
|
||||
|
||||
var divHeight = div.outerHeight();
|
||||
var divWidth = div.outerWidth();
|
||||
var paddingRight = 10;
|
||||
|
||||
var viewportTop = $(window).scrollTop();
|
||||
var viewportLeft = $(window).scrollLeft();
|
||||
var viewportBottom = viewportTop + $(window).height();
|
||||
var viewportRight = viewportLeft + $(window).width();
|
||||
var top = 0;
|
||||
var left = 0;
|
||||
if (direction === 'right') {
|
||||
top = targetPos.top+targetHeight/2-divHeight/2;
|
||||
left = targetPos.left+targetWidth+deltaSizes[size].x+popupOffset;
|
||||
} else if (direction === 'left') {
|
||||
top = targetPos.top+targetHeight/2-divHeight/2;
|
||||
left = targetPos.left-deltaSizes[size].x-divWidth-popupOffset;
|
||||
} else if (direction === 'bottom') {
|
||||
top = targetPos.top+targetHeight+deltaSizes[size].y+popupOffset;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2;
|
||||
if (left < 0) {
|
||||
direction = "right";
|
||||
top = targetPos.top+targetHeight/2-divHeight/2;
|
||||
left = targetPos.left+targetWidth+deltaSizes[size].x+popupOffset;
|
||||
} else if (left+divWidth+paddingRight > viewportRight) {
|
||||
direction = "left";
|
||||
top = targetPos.top+targetHeight/2-divHeight/2;
|
||||
left = targetPos.left-deltaSizes[size].x-divWidth-popupOffset;
|
||||
if (top+divHeight+targetHeight/2 + 5 > viewportBottom) {
|
||||
top -= (top+divHeight+targetHeight/2 - viewportBottom + 5)
|
||||
}
|
||||
} else if (top+divHeight > viewportBottom) {
|
||||
direction = 'top';
|
||||
top = targetPos.top-deltaSizes[size].y-divHeight-popupOffset;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2;
|
||||
}
|
||||
} else if (direction === 'top') {
|
||||
top = targetPos.top-deltaSizes[size].y-divHeight-popupOffset;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2;
|
||||
if (top < 0) {
|
||||
direction = 'bottom';
|
||||
top = targetPos.top+targetHeight+deltaSizes[size].y+popupOffset;
|
||||
left = targetPos.left+targetWidth/2-divWidth/2;
|
||||
}
|
||||
} else if (/inset/.test(direction)) {
|
||||
top = targetPos.top + targetHeight/2 - divHeight/2;
|
||||
left = targetPos.left + targetWidth/2 - divWidth/2;
|
||||
|
||||
if (/bottom/.test(direction)) {
|
||||
top = targetPos.top + targetHeight - divHeight-popupOffset;
|
||||
}
|
||||
if (/top/.test(direction)) {
|
||||
top = targetPos.top+popupOffset;
|
||||
}
|
||||
if (/left/.test(direction)) {
|
||||
left = targetPos.left+popupOffset;
|
||||
}
|
||||
if (/right/.test(direction)) {
|
||||
left = targetPos.left + targetWidth - divWidth-popupOffset;
|
||||
}
|
||||
}
|
||||
if (currentStyle) {
|
||||
div.removeClass(currentStyle);
|
||||
}
|
||||
if (transition) {
|
||||
div.css({
|
||||
"transition": "0.6s ease",
|
||||
"transition-property": "top,left,right,bottom"
|
||||
})
|
||||
}
|
||||
currentStyle = 'red-ui-popover-'+direction;
|
||||
div.addClass(currentStyle).css({top: top, left: left});
|
||||
if (transition) {
|
||||
setTimeout(function() {
|
||||
div.css({
|
||||
"transition": "none"
|
||||
});
|
||||
},600);
|
||||
}
|
||||
|
||||
}
|
||||
var closePopup = function(instant) {
|
||||
$(document).off('mousedown.red-ui-popover');
|
||||
if (!active) {
|
||||
@@ -235,8 +408,10 @@ RED.popover = (function() {
|
||||
},autoClose);
|
||||
}
|
||||
var res = {
|
||||
get element() { return div },
|
||||
setContent: function(_content) {
|
||||
content = _content;
|
||||
|
||||
return res;
|
||||
},
|
||||
open: function (instant) {
|
||||
@@ -248,6 +423,10 @@ RED.popover = (function() {
|
||||
active = false;
|
||||
closePopup(instant);
|
||||
return res;
|
||||
},
|
||||
move: function(options) {
|
||||
movePopup(options);
|
||||
return
|
||||
}
|
||||
}
|
||||
return res;
|
||||
@@ -257,18 +436,17 @@ RED.popover = (function() {
|
||||
return {
|
||||
create: createPopover,
|
||||
tooltip: function(target,content, action) {
|
||||
var label = content;
|
||||
if (action) {
|
||||
label = function() {
|
||||
var label = content;
|
||||
var label = function() {
|
||||
var label = content;
|
||||
if (action) {
|
||||
var shortcut = RED.keyboard.getShortcut(action);
|
||||
if (shortcut && shortcut.key) {
|
||||
label = $('<span>'+content+' <span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span></span>');
|
||||
}
|
||||
return label;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
return RED.popover.create({
|
||||
var popover = RED.popover.create({
|
||||
tooltip: true,
|
||||
target:target,
|
||||
trigger: "hover",
|
||||
@@ -277,6 +455,14 @@ RED.popover = (function() {
|
||||
content: label,
|
||||
delay: { show: 750, hide: 50 }
|
||||
});
|
||||
popover.setContent = function(newContent) {
|
||||
content = newContent;
|
||||
}
|
||||
popover.setAction = function(newAction) {
|
||||
action = newAction;
|
||||
}
|
||||
return popover;
|
||||
|
||||
},
|
||||
menu: function(options) {
|
||||
var list = $('<ul class="red-ui-menu"></ul>');
|
||||
@@ -285,20 +471,47 @@ RED.popover = (function() {
|
||||
}
|
||||
var menuOptions = options.options || [];
|
||||
var first;
|
||||
menuOptions.forEach(function(opt) {
|
||||
var item = $('<li>').appendTo(list);
|
||||
var link = $('<a href="#"></a>').text(opt.label).appendTo(item);
|
||||
link.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
if (opt.onselect) {
|
||||
opt.onselect();
|
||||
}
|
||||
menu.hide();
|
||||
})
|
||||
if (!first) { first = link}
|
||||
})
|
||||
|
||||
var container = RED.popover.panel(list);
|
||||
if (options.width) {
|
||||
container.container.width(options.width);
|
||||
}
|
||||
if (options.class) {
|
||||
container.container.addClass(options.class);
|
||||
}
|
||||
if (options.maxHeight) {
|
||||
container.container.css({
|
||||
"max-height": options.maxHeight,
|
||||
"overflow-y": 'auto'
|
||||
})
|
||||
}
|
||||
var menu = {
|
||||
options: function(opts) {
|
||||
if (opts === undefined) {
|
||||
return menuOptions
|
||||
}
|
||||
menuOptions = opts || [];
|
||||
list.empty();
|
||||
menuOptions.forEach(function(opt) {
|
||||
var item = $('<li>').appendTo(list);
|
||||
var link = $('<a href="#"></a>').appendTo(item);
|
||||
if (typeof opt.label == "string") {
|
||||
link.text(opt.label)
|
||||
} else if (opt.label){
|
||||
opt.label.appendTo(link);
|
||||
}
|
||||
link.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
if (opt.onselect) {
|
||||
opt.onselect();
|
||||
} else if (options.onselect) {
|
||||
options.onselect(opt);
|
||||
}
|
||||
menu.hide();
|
||||
})
|
||||
if (!first) { first = link}
|
||||
})
|
||||
},
|
||||
show: function(opts) {
|
||||
$(document).on("keydown.red-ui-menu", function(evt) {
|
||||
var currentItem = list.find(":focus").parent();
|
||||
@@ -307,11 +520,9 @@ RED.popover = (function() {
|
||||
// DOWN
|
||||
if (currentItem.length > 0) {
|
||||
if (currentItem.index() === menuOptions.length-1) {
|
||||
console.log("WARP TO TOP")
|
||||
// Wrap to top of list
|
||||
list.children().first().children().first().focus();
|
||||
} else {
|
||||
console.log("GO DOWN ONE")
|
||||
currentItem.next().children().first().focus();
|
||||
}
|
||||
} else {
|
||||
@@ -322,11 +533,9 @@ RED.popover = (function() {
|
||||
// UP
|
||||
if (currentItem.length > 0) {
|
||||
if (currentItem.index() === 0) {
|
||||
console.log("WARP TO BOTTOM")
|
||||
// Wrap to bottom of list
|
||||
list.children().last().children().first().focus();
|
||||
} else {
|
||||
console.log("GO UP ONE")
|
||||
currentItem.prev().children().first().focus();
|
||||
}
|
||||
} else {
|
||||
@@ -336,6 +545,11 @@ RED.popover = (function() {
|
||||
// ESCAPE
|
||||
evt.preventDefault();
|
||||
menu.hide(true);
|
||||
} else if (evt.keyCode === 9 && options.tabSelect) {
|
||||
// TAB - with tabSelect enabled
|
||||
evt.preventDefault();
|
||||
currentItem.find("a").trigger("click");
|
||||
|
||||
}
|
||||
evt.stopPropagation();
|
||||
})
|
||||
@@ -355,6 +569,7 @@ RED.popover = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.options(menuOptions);
|
||||
return menu;
|
||||
},
|
||||
panel: function(content) {
|
||||
@@ -362,7 +577,6 @@ RED.popover = (function() {
|
||||
panel.css({ display: "none" });
|
||||
panel.appendTo(document.body);
|
||||
content.appendTo(panel);
|
||||
var closeCallback;
|
||||
|
||||
function hide(dispose) {
|
||||
$(document).off("mousedown.red-ui-popover-panel-close");
|
||||
@@ -377,22 +591,23 @@ RED.popover = (function() {
|
||||
}
|
||||
function show(options) {
|
||||
var closeCallback = options.onclose;
|
||||
var closeButton = options.closeButton;
|
||||
var target = options.target;
|
||||
var align = options.align || "right";
|
||||
var offset = options.offset || [0,0];
|
||||
|
||||
var pos = target.offset();
|
||||
var targetWidth = target.width();
|
||||
var targetHeight = target.height();
|
||||
var targetHeight = target.outerHeight();
|
||||
var panelHeight = panel.height();
|
||||
var panelWidth = panel.width();
|
||||
|
||||
var top = (targetHeight+pos.top) + offset[1];
|
||||
if (top+panelHeight > $(window).height()) {
|
||||
if (top+panelHeight-$(document).scrollTop() > $(window).height()) {
|
||||
top -= (top+panelHeight)-$(window).height() + 5;
|
||||
}
|
||||
if (top < 0) {
|
||||
panelHeight.height(panelHeight+top)
|
||||
panel.height(panelHeight+top)
|
||||
top = 0;
|
||||
}
|
||||
if (align === "right") {
|
||||
@@ -419,7 +634,8 @@ RED.popover = (function() {
|
||||
});
|
||||
|
||||
$(document).on("mousedown.red-ui-popover-panel-close", function(event) {
|
||||
if(!$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) {
|
||||
var hitCloseButton = closeButton && $(event.target).closest(closeButton).length;
|
||||
if(!hitCloseButton && !$(event.target).closest(panel).length && !$(event.target).closest(".red-ui-editor-dialog").length) {
|
||||
if (closeCallback) {
|
||||
closeCallback();
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
if (evt.keyCode === 27) {
|
||||
that.element.val("");
|
||||
}
|
||||
if (evt.keyCode === 13) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
})
|
||||
this.element.on("keyup",function(evt) {
|
||||
that._change($(this).val());
|
||||
|
||||
@@ -29,7 +29,8 @@ RED.tabs = (function() {
|
||||
var currentTabWidth;
|
||||
var currentActiveTabWidth = 0;
|
||||
var collapsibleMenu;
|
||||
|
||||
var mousedownTab;
|
||||
var preferredOrder = options.order;
|
||||
var ul = options.element || $("#"+options.id);
|
||||
var wrapper = ul.wrap( "<div>" ).parent();
|
||||
var scrollContainer = ul.wrap( "<div>" ).parent();
|
||||
@@ -37,6 +38,7 @@ RED.tabs = (function() {
|
||||
if (options.vertical) {
|
||||
wrapper.addClass("red-ui-tabs-vertical");
|
||||
}
|
||||
|
||||
if (options.addButton) {
|
||||
wrapper.addClass("red-ui-tabs-add");
|
||||
var addButton = $('<div class="red-ui-tab-button red-ui-tabs-add"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
|
||||
@@ -74,6 +76,8 @@ RED.tabs = (function() {
|
||||
});
|
||||
}
|
||||
if (options.searchButton) {
|
||||
// This is soft-deprecated as we don't use this in the core anymore
|
||||
// We no use the `menu` option to provide a drop-down list of actions
|
||||
wrapper.addClass("red-ui-tabs-search");
|
||||
var searchButton = $('<div class="red-ui-tab-button red-ui-tabs-search"><a href="#"><i class="fa fa-list-ul"></i></a></div>').appendTo(wrapper);
|
||||
searchButton.find('a').on("click", function(evt) {
|
||||
@@ -93,17 +97,76 @@ RED.tabs = (function() {
|
||||
}
|
||||
|
||||
}
|
||||
if (options.menu) {
|
||||
wrapper.addClass("red-ui-tabs-menu");
|
||||
var menuButton = $('<div class="red-ui-tab-button red-ui-tabs-menu"><a href="#"><i class="fa fa-caret-down"></i></a></div>').appendTo(wrapper);
|
||||
var menuButtonLink = menuButton.find('a')
|
||||
var menuOpen = false;
|
||||
var menu;
|
||||
menuButtonLink.on("click", function(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
if (menuOpen) {
|
||||
menu.remove();
|
||||
menuOpen = false;
|
||||
return;
|
||||
}
|
||||
menuOpen = true;
|
||||
var menuOptions = [];
|
||||
if (typeof options.searchButton === 'function') {
|
||||
menuOptions = options.menu()
|
||||
} else if (Array.isArray(options.menu)) {
|
||||
menuOptions = options.menu;
|
||||
}
|
||||
menu = RED.menu.init({options: menuOptions});
|
||||
menu.attr("id",options.id+"-menu");
|
||||
menu.css({
|
||||
position: "absolute"
|
||||
})
|
||||
menu.appendTo("body");
|
||||
var elementPos = menuButton.offset();
|
||||
menu.css({
|
||||
top: (elementPos.top+menuButton.height()-2)+"px",
|
||||
left: (elementPos.left - menu.width() + menuButton.width())+"px"
|
||||
})
|
||||
$(".red-ui-menu.red-ui-menu-dropdown").hide();
|
||||
$(document).on("click.red-ui-tabmenu", function(evt) {
|
||||
$(document).off("click.red-ui-tabmenu");
|
||||
menuOpen = false;
|
||||
menu.remove();
|
||||
});
|
||||
menu.show();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
var scrollLeft;
|
||||
var scrollRight;
|
||||
|
||||
if (options.scrollable) {
|
||||
wrapper.addClass("red-ui-tabs-scrollable");
|
||||
scrollContainer.addClass("red-ui-tabs-scroll-container");
|
||||
scrollContainer.on("scroll",updateScroll);
|
||||
scrollContainer.on("scroll",function(evt) {
|
||||
// Generated by trackpads - not mousewheel
|
||||
updateScroll(evt);
|
||||
});
|
||||
scrollContainer.on("wheel", function(evt) {
|
||||
if (evt.originalEvent.deltaX === 0) {
|
||||
// Prevent the scroll event from firing
|
||||
evt.preventDefault();
|
||||
|
||||
// Assume this is wheel event which might not trigger
|
||||
// the scroll event, so do things manually
|
||||
var sl = scrollContainer.scrollLeft();
|
||||
sl -= evt.originalEvent.deltaY;
|
||||
scrollContainer.scrollLeft(sl);
|
||||
}
|
||||
})
|
||||
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
|
||||
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||
scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
|
||||
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,evt.shiftKey?('+='+(scrollContainer[0].scrollWidth - scrollContainer.width()-scrollContainer.scrollLeft())):'+=150') }).on('click',function(evt){ evt.preventDefault();});
|
||||
}
|
||||
|
||||
if (options.collapsible) {
|
||||
@@ -132,11 +195,11 @@ RED.tabs = (function() {
|
||||
activateTab(id);
|
||||
}
|
||||
};
|
||||
if (tabs[id].pinned) {
|
||||
pinnedOptions.push(opt);
|
||||
} else {
|
||||
// if (tabs[id].pinned) {
|
||||
// pinnedOptions.push(opt);
|
||||
// } else {
|
||||
options.push(opt);
|
||||
}
|
||||
// }
|
||||
});
|
||||
options = pinnedOptions.concat(options);
|
||||
collapsibleMenu = RED.menu.init({options: options});
|
||||
@@ -207,6 +270,11 @@ RED.tabs = (function() {
|
||||
if (dragActive) {
|
||||
return
|
||||
}
|
||||
if (evt.currentTarget !== mousedownTab) {
|
||||
mousedownTab = null;
|
||||
return;
|
||||
}
|
||||
mousedownTab = null;
|
||||
if (dblClickTime && Date.now()-dblClickTime < 400) {
|
||||
dblClickTime = 0;
|
||||
dblClickArmed = true;
|
||||
@@ -316,6 +384,12 @@ RED.tabs = (function() {
|
||||
if (link.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (link.parent().hasClass("hide-tab")) {
|
||||
link.parent().removeClass("hide-tab").removeClass("hide");
|
||||
if (options.onshow) {
|
||||
options.onshow(tabs[link.attr('href').slice(1)])
|
||||
}
|
||||
}
|
||||
if (!link.parent().hasClass("active")) {
|
||||
ul.children().removeClass("active");
|
||||
ul.children().css({"transition": "width 100ms"});
|
||||
@@ -341,13 +415,13 @@ RED.tabs = (function() {
|
||||
}
|
||||
}
|
||||
function activatePreviousTab() {
|
||||
var previous = ul.find("li.active").prev();
|
||||
var previous = findPreviousVisibleTab();
|
||||
if (previous.length > 0) {
|
||||
activateTab(previous.find("a"));
|
||||
}
|
||||
}
|
||||
function activateNextTab() {
|
||||
var next = ul.find("li.active").next();
|
||||
var next = findNextVisibleTab();
|
||||
if (next.length > 0) {
|
||||
activateTab(next.find("a"));
|
||||
}
|
||||
@@ -357,29 +431,47 @@ RED.tabs = (function() {
|
||||
if (options.vertical) {
|
||||
return;
|
||||
}
|
||||
var tabs = ul.find("li.red-ui-tab");
|
||||
var allTabs = ul.find("li.red-ui-tab");
|
||||
var tabs = allTabs.filter(":not(.hide-tab)");
|
||||
var hiddenTabs = allTabs.filter(".hide-tab");
|
||||
var width = wrapper.width();
|
||||
var tabCount = tabs.length;
|
||||
var tabWidth;
|
||||
|
||||
if (options.collapsible) {
|
||||
var availableCount = collapsedButtonsRow.children().length;
|
||||
var visibleCount = collapsedButtonsRow.children(":visible").length;
|
||||
tabWidth = width - collapsedButtonsRow.width()-10;
|
||||
if (tabWidth < 198) {
|
||||
var delta = 198 - tabWidth;
|
||||
var maxTabWidth = 198;
|
||||
var minTabWidth = 120;
|
||||
if (tabWidth <= minTabWidth || (tabWidth < maxTabWidth && visibleCount > 5)) {
|
||||
// The tab is too small. Hide the next button to make room
|
||||
// Start at the end of the button row, -1 for the menu button
|
||||
var b = collapsedButtonsRow.find("a:last").prev();
|
||||
var index = collapsedButtonsRow.children().length - 2;
|
||||
// Work backwards to find the first visible button
|
||||
while (b.is(":not(:visible)")) {
|
||||
b = b.prev();
|
||||
index--;
|
||||
}
|
||||
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
|
||||
// If it isn't a pinned button, hide it to get the room
|
||||
if (tabWidth <= minTabWidth || visibleCount>6) {//}!b.hasClass("red-ui-tab-link-button-pinned")) {
|
||||
b.hide();
|
||||
}
|
||||
tabWidth = width - collapsedButtonsRow.width()-10;
|
||||
tabWidth = Math.max(minTabWidth,width - collapsedButtonsRow.width()-10);
|
||||
} else {
|
||||
var space = width - 198 - collapsedButtonsRow.width();
|
||||
if (visibleCount !== availableCount) {
|
||||
if (visibleCount < 6) {
|
||||
tabWidth = minTabWidth;
|
||||
} else {
|
||||
tabWidth = maxTabWidth;
|
||||
}
|
||||
}
|
||||
var space = width - tabWidth - collapsedButtonsRow.width();
|
||||
if (space > 40) {
|
||||
collapsedButtonsRow.find("a:not(:visible):first").show();
|
||||
tabWidth = width - collapsedButtonsRow.width()-10;
|
||||
}
|
||||
tabWidth = width - collapsedButtonsRow.width()-10;
|
||||
}
|
||||
tabs.css({width:tabWidth});
|
||||
|
||||
@@ -409,6 +501,7 @@ RED.tabs = (function() {
|
||||
// }
|
||||
|
||||
tabs.css({width:currentTabWidth});
|
||||
hiddenTabs.css({width:"0px"});
|
||||
if (tabWidth < 50) {
|
||||
// ul.find(".red-ui-tab-close").hide();
|
||||
ul.find(".red-ui-tab-icon").hide();
|
||||
@@ -429,6 +522,7 @@ RED.tabs = (function() {
|
||||
}
|
||||
|
||||
ul.find("li.red-ui-tab a")
|
||||
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||
.on("mouseup",onTabClick)
|
||||
.on("click", function(evt) {evt.preventDefault(); })
|
||||
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
|
||||
@@ -448,28 +542,108 @@ RED.tabs = (function() {
|
||||
}
|
||||
var li = ul.find("a[href='#"+id+"']").parent();
|
||||
if (li.hasClass("active")) {
|
||||
var tab = li.prev();
|
||||
var tab = findPreviousVisibleTab(li);
|
||||
if (tab.length === 0) {
|
||||
tab = li.next();
|
||||
tab = findNextVisibleTab(li);
|
||||
}
|
||||
if (tab.length > 0) {
|
||||
activateTab(tab.find("a"));
|
||||
} else {
|
||||
if (options.onchange) {
|
||||
options.onchange(null);
|
||||
}
|
||||
}
|
||||
activateTab(tab.find("a"));
|
||||
}
|
||||
li.remove();
|
||||
if (tabs[id].pinned) {
|
||||
pinnedTabsCount--;
|
||||
|
||||
li.one("transitionend", function(evt) {
|
||||
li.remove();
|
||||
if (tabs[id].pinned) {
|
||||
pinnedTabsCount--;
|
||||
}
|
||||
if (options.onremove) {
|
||||
options.onremove(tabs[id]);
|
||||
}
|
||||
delete tabs[id];
|
||||
updateTabWidths();
|
||||
if (collapsibleMenu) {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
}
|
||||
})
|
||||
li.addClass("hide-tab");
|
||||
li.width(0);
|
||||
}
|
||||
|
||||
function findPreviousVisibleTab(li) {
|
||||
if (!li) {
|
||||
li = ul.find("li.active").parent();
|
||||
}
|
||||
if (options.onremove) {
|
||||
options.onremove(tabs[id]);
|
||||
var previous = li.prev();
|
||||
while(previous.length > 0 && previous.hasClass("hide-tab")) {
|
||||
previous = previous.prev();
|
||||
}
|
||||
delete tabs[id];
|
||||
updateTabWidths();
|
||||
if (collapsibleMenu) {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
return previous;
|
||||
}
|
||||
function findNextVisibleTab(li) {
|
||||
if (!li) {
|
||||
li = ul.find("li.active").parent();
|
||||
}
|
||||
var next = ul.find("li.active").next();
|
||||
while(next.length > 0 && next.hasClass("hide-tab")) {
|
||||
next = next.next();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
function showTab(id) {
|
||||
if (tabs[id]) {
|
||||
var li = ul.find("a[href='#"+id+"']").parent();
|
||||
if (li.hasClass("hide-tab")) {
|
||||
li.removeClass("hide-tab").removeClass("hide");
|
||||
if (ul.find("li.red-ui-tab:not(.hide-tab)").length === 1) {
|
||||
activateTab(li.find("a"))
|
||||
}
|
||||
updateTabWidths();
|
||||
if (options.onshow) {
|
||||
options.onshow(tabs[id])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function hideTab(id) {
|
||||
if (tabs[id]) {
|
||||
var li = ul.find("a[href='#"+id+"']").parent();
|
||||
if (!li.hasClass("hide-tab")) {
|
||||
if (li.hasClass("active")) {
|
||||
var tab = findPreviousVisibleTab(li);
|
||||
if (tab.length === 0) {
|
||||
tab = findNextVisibleTab(li);
|
||||
}
|
||||
if (tab.length > 0) {
|
||||
activateTab(tab.find("a"));
|
||||
} else {
|
||||
if (options.onchange) {
|
||||
options.onchange(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
li.removeClass("active");
|
||||
li.one("transitionend", function(evt) {
|
||||
li.addClass("hide");
|
||||
updateTabWidths();
|
||||
if (options.onhide) {
|
||||
options.onhide(tabs[id])
|
||||
}
|
||||
setTimeout(function() {
|
||||
updateScroll()
|
||||
},200)
|
||||
})
|
||||
li.addClass("hide-tab");
|
||||
li.css({width:0})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
var tabAPI = {
|
||||
addTab: function(tab,targetIndex) {
|
||||
if (options.onselect) {
|
||||
var selection = ul.find("li.red-ui-tab.selected");
|
||||
@@ -493,8 +667,8 @@ RED.tabs = (function() {
|
||||
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
|
||||
li.data("tabId",tab.id);
|
||||
|
||||
if (options.maximumTabWidth) {
|
||||
li.css("maxWidth",options.maximumTabWidth+"px");
|
||||
if (options.maximumTabWidth || tab.maximumTabWidth) {
|
||||
li.css("maxWidth",(options.maximumTabWidth || tab.maximumTabWidth) +"px");
|
||||
}
|
||||
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
|
||||
if (tab.icon) {
|
||||
@@ -531,17 +705,101 @@ RED.tabs = (function() {
|
||||
evt.preventDefault();
|
||||
activateTab(tab.id);
|
||||
});
|
||||
pinnedLink.data("tabId",tab.id)
|
||||
if (tab.pinned) {
|
||||
pinnedLink.addClass("red-ui-tab-link-button-pinned");
|
||||
pinnedTabsCount++;
|
||||
}
|
||||
RED.popover.tooltip($(pinnedLink), tab.name, tab.action);
|
||||
if (options.onreorder) {
|
||||
var pinnedLinkIndex;
|
||||
var pinnedLinks = [];
|
||||
var startPinnedIndex;
|
||||
pinnedLink.draggable({
|
||||
distance: 10,
|
||||
axis:"x",
|
||||
containment: ".red-ui-tab-link-buttons",
|
||||
start: function(event,ui) {
|
||||
dragActive = true;
|
||||
$(".red-ui-tab-link-buttons").width($(".red-ui-tab-link-buttons").width());
|
||||
if (dblClickArmed) { dblClickArmed = false; return false }
|
||||
collapsedButtonsRow.children().each(function(i) {
|
||||
pinnedLinks[i] = {
|
||||
el:$(this),
|
||||
text: $(this).text(),
|
||||
left: $(this).position().left,
|
||||
width: $(this).width(),
|
||||
menu: $(this).hasClass("red-ui-tab-link-button-menu")
|
||||
};
|
||||
if ($(this).is(pinnedLink)) {
|
||||
pinnedLinkIndex = i;
|
||||
startPinnedIndex = i;
|
||||
}
|
||||
});
|
||||
collapsedButtonsRow.children().each(function(i) {
|
||||
if (i!==pinnedLinkIndex) {
|
||||
$(this).css({
|
||||
position: 'absolute',
|
||||
left: pinnedLinks[i].left+"px",
|
||||
width: pinnedLinks[i].width+2,
|
||||
transition: "left 0.3s"
|
||||
});
|
||||
}
|
||||
})
|
||||
if (!pinnedLink.hasClass('active')) {
|
||||
pinnedLink.css({'zIndex':1});
|
||||
}
|
||||
},
|
||||
drag: function(event,ui) {
|
||||
ui.position.left += pinnedLinks[pinnedLinkIndex].left;
|
||||
var tabCenter = ui.position.left + pinnedLinks[pinnedLinkIndex].width/2;
|
||||
for (var i=0;i<pinnedLinks.length;i++) {
|
||||
if (i === pinnedLinkIndex || pinnedLinks[i].menu || pinnedLinks[i].el.is(":not(:visible)")) {
|
||||
continue;
|
||||
}
|
||||
if (tabCenter > pinnedLinks[i].left && tabCenter < pinnedLinks[i].left+pinnedLinks[i].width) {
|
||||
if (i < pinnedLinkIndex) {
|
||||
pinnedLinks[i].left += pinnedLinks[pinnedLinkIndex].width+8;
|
||||
pinnedLinks[pinnedLinkIndex].el.detach().insertBefore(pinnedLinks[i].el);
|
||||
} else {
|
||||
pinnedLinks[i].left -= pinnedLinks[pinnedLinkIndex].width+8;
|
||||
pinnedLinks[pinnedLinkIndex].el.detach().insertAfter(pinnedLinks[i].el);
|
||||
}
|
||||
pinnedLinks[i].el.css({left:pinnedLinks[i].left+"px"});
|
||||
|
||||
pinnedLinks.splice(i, 0, pinnedLinks.splice(pinnedLinkIndex, 1)[0]);
|
||||
|
||||
pinnedLinkIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
stop: function(event,ui) {
|
||||
dragActive = false;
|
||||
collapsedButtonsRow.children().css({position:"relative",left:"",transition:""});
|
||||
$(".red-ui-tab-link-buttons").width('auto');
|
||||
pinnedLink.css({zIndex:""});
|
||||
updateTabWidths();
|
||||
if (startPinnedIndex !== pinnedLinkIndex) {
|
||||
if (collapsibleMenu) {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
}
|
||||
var newOrder = $.makeArray(collapsedButtonsRow.children().map(function() { return $(this).data('tabId');}));
|
||||
tabAPI.order(newOrder);
|
||||
options.onreorder(newOrder);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
|
||||
link.on("mouseup",onTabClick);
|
||||
link.on("click", function(evt) { evt.preventDefault(); })
|
||||
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
|
||||
|
||||
$('<span class="red-ui-tabs-fade"></span>').appendTo(li);
|
||||
|
||||
if (tab.closeable) {
|
||||
li.addClass("red-ui-tabs-closeable")
|
||||
@@ -552,20 +810,31 @@ RED.tabs = (function() {
|
||||
removeTab(tab.id);
|
||||
});
|
||||
}
|
||||
if (tab.hideable) {
|
||||
li.addClass("red-ui-tabs-closeable")
|
||||
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
|
||||
closeLink.append('<i class="fa fa-times" />');
|
||||
closeLink.on("click",function(event) {
|
||||
event.preventDefault();
|
||||
hideTab(tab.id);
|
||||
});
|
||||
}
|
||||
|
||||
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
|
||||
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);
|
||||
}
|
||||
|
||||
link.attr("title",tab.label);
|
||||
|
||||
if (options.onadd) {
|
||||
options.onadd(tab);
|
||||
}
|
||||
link.attr("title",tab.label);
|
||||
if (ul.find("li.red-ui-tab").length == 1) {
|
||||
activateTab(link);
|
||||
}
|
||||
if (options.onreorder) {
|
||||
if (options.onreorder && !options.collapsible) {
|
||||
var originalTabOrder;
|
||||
var tabDragIndex;
|
||||
var tabElements = [];
|
||||
@@ -652,6 +921,9 @@ RED.tabs = (function() {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
}
|
||||
if (preferredOrder) {
|
||||
tabAPI.order(preferredOrder);
|
||||
}
|
||||
},
|
||||
removeTab: removeTab,
|
||||
activateTab: activateTab,
|
||||
@@ -659,11 +931,17 @@ RED.tabs = (function() {
|
||||
previousTab: activatePreviousTab,
|
||||
resize: updateTabWidths,
|
||||
count: function() {
|
||||
return ul.find("li.red-ui-tab").length;
|
||||
return ul.find("li.red-ui-tab:not(.hide)").length;
|
||||
},
|
||||
activeIndex: function() {
|
||||
return ul.find("li.active").index()
|
||||
},
|
||||
contains: function(id) {
|
||||
return ul.find("a[href='#"+id+"']").length > 0;
|
||||
},
|
||||
showTab: showTab,
|
||||
hideTab: hideTab,
|
||||
|
||||
renameTab: function(id,label) {
|
||||
tabs[id].label = label;
|
||||
var tab = ul.find("a[href='#"+id+"']");
|
||||
@@ -671,12 +949,23 @@ RED.tabs = (function() {
|
||||
tab.find("span.red-ui-text-bidi-aware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
|
||||
updateTabWidths();
|
||||
},
|
||||
listTabs: function() {
|
||||
return $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
|
||||
},
|
||||
selection: getSelection,
|
||||
order: function(order) {
|
||||
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
|
||||
if (existingTabOrder.length !== order.length) {
|
||||
return
|
||||
clearSelection: function() {
|
||||
if (options.onselect) {
|
||||
var selection = ul.find("li.red-ui-tab.selected");
|
||||
if (selection.length > 0) {
|
||||
selection.removeClass("selected");
|
||||
selectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
order: function(order) {
|
||||
preferredOrder = order;
|
||||
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
|
||||
var i;
|
||||
var match = true;
|
||||
for (i=0;i<order.length;i++) {
|
||||
@@ -692,12 +981,41 @@ RED.tabs = (function() {
|
||||
var existingTabs = ul.children().detach().each(function() {
|
||||
existingTabMap[$(this).data("tabId")] = $(this);
|
||||
});
|
||||
var pinnedButtons = {};
|
||||
if (options.collapsible) {
|
||||
collapsedButtonsRow.children().detach().each(function() {
|
||||
var id = $(this).data("tabId");
|
||||
if (!id) {
|
||||
id = "__menu__"
|
||||
}
|
||||
pinnedButtons[id] = $(this);
|
||||
});
|
||||
}
|
||||
for (i=0;i<order.length;i++) {
|
||||
existingTabMap[order[i]].appendTo(ul);
|
||||
if (existingTabMap[order[i]]) {
|
||||
existingTabMap[order[i]].appendTo(ul);
|
||||
if (options.collapsible) {
|
||||
pinnedButtons[order[i]].appendTo(collapsedButtonsRow);
|
||||
}
|
||||
delete existingTabMap[order[i]];
|
||||
}
|
||||
}
|
||||
// Add any tabs that aren't known in the order
|
||||
for (i in existingTabMap) {
|
||||
if (existingTabMap.hasOwnProperty(i)) {
|
||||
existingTabMap[i].appendTo(ul);
|
||||
if (options.collapsible) {
|
||||
pinnedButtons[i].appendTo(collapsedButtonsRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.collapsible) {
|
||||
pinnedButtons["__menu__"].appendTo(collapsedButtonsRow);
|
||||
updateTabWidths();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return tabAPI;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
invertState = this.options.invertState;
|
||||
}
|
||||
var baseClass = this.options.baseClass || "red-ui-button";
|
||||
var enabledIcon = this.options.enabledIcon || "fa-check-square-o";
|
||||
var disabledIcon = this.options.disabledIcon || "fa-square-o";
|
||||
var enabledIcon = this.options.hasOwnProperty('enabledIcon')?this.options.enabledIcon : "fa-check-square-o";
|
||||
var disabledIcon = this.options.hasOwnProperty('disabledIcon')?this.options.disabledIcon : "fa-square-o";
|
||||
var enabledLabel = this.options.hasOwnProperty('enabledLabel') ? this.options.enabledLabel : RED._("editor:workspace.enabled");
|
||||
var disabledLabel = this.options.hasOwnProperty('disabledLabel') ? this.options.disabledLabel : RED._("editor:workspace.disabled");
|
||||
|
||||
@@ -46,25 +46,41 @@
|
||||
this.element.on("focus", function() {
|
||||
that.button.focus();
|
||||
});
|
||||
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"><i class="fa"></i> <span></span></button>');
|
||||
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"></button>');
|
||||
if (enabledLabel || disabledLabel) {
|
||||
this.buttonLabel = $("<span>").appendTo(this.button).css("margin-left", "5px");
|
||||
}
|
||||
|
||||
if (this.options.class) {
|
||||
this.button.addClass(this.options.class)
|
||||
}
|
||||
this.element.after(this.button);
|
||||
this.buttonIcon = this.button.find("i");
|
||||
this.buttonLabel = this.button.find("span");
|
||||
|
||||
if (enabledIcon && disabledIcon) {
|
||||
this.buttonIcon = $('<i class="fa"></i>').prependTo(this.button);
|
||||
}
|
||||
|
||||
// Quick hack to find the maximum width of the button
|
||||
this.button.addClass("selected");
|
||||
this.buttonIcon.addClass(enabledIcon);
|
||||
this.buttonLabel.text(enabledLabel);
|
||||
if (this.buttonIcon) {
|
||||
this.buttonIcon.addClass(enabledIcon);
|
||||
}
|
||||
if (this.buttonLabel) {
|
||||
this.buttonLabel.text(enabledLabel);
|
||||
}
|
||||
var width = this.button.width();
|
||||
this.button.removeClass("selected");
|
||||
this.buttonIcon.removeClass(enabledIcon);
|
||||
that.buttonIcon.addClass(disabledIcon);
|
||||
that.buttonLabel.text(disabledLabel);
|
||||
if (this.buttonIcon) {
|
||||
this.buttonIcon.removeClass(enabledIcon);
|
||||
that.buttonIcon.addClass(disabledIcon);
|
||||
}
|
||||
if (this.buttonLabel) {
|
||||
that.buttonLabel.text(disabledLabel);
|
||||
}
|
||||
width = Math.max(width,this.button.width());
|
||||
this.buttonIcon.removeClass(disabledIcon);
|
||||
if (this.buttonIcon) {
|
||||
this.buttonIcon.removeClass(disabledIcon);
|
||||
}
|
||||
|
||||
// Fix the width of the button so it doesn't jump around when toggled
|
||||
if (width > 0) {
|
||||
@@ -73,7 +89,7 @@
|
||||
|
||||
this.button.on("click",function(e) {
|
||||
e.stopPropagation();
|
||||
if (that.buttonIcon.hasClass(disabledIcon)) {
|
||||
if (!that.state) {
|
||||
that.element.prop("checked",!invertState);
|
||||
} else {
|
||||
that.element.prop("checked",invertState);
|
||||
@@ -84,14 +100,24 @@
|
||||
this.element.on("change", function(e) {
|
||||
if ($(this).prop("checked") !== invertState) {
|
||||
that.button.addClass("selected");
|
||||
that.buttonIcon.addClass(enabledIcon);
|
||||
that.buttonIcon.removeClass(disabledIcon);
|
||||
that.buttonLabel.text(enabledLabel);
|
||||
that.state = true;
|
||||
if (that.buttonIcon) {
|
||||
that.buttonIcon.addClass(enabledIcon);
|
||||
that.buttonIcon.removeClass(disabledIcon);
|
||||
}
|
||||
if (that.buttonLabel) {
|
||||
that.buttonLabel.text(enabledLabel);
|
||||
}
|
||||
} else {
|
||||
that.button.removeClass("selected");
|
||||
that.buttonIcon.addClass(disabledIcon);
|
||||
that.buttonIcon.removeClass(enabledIcon);
|
||||
that.buttonLabel.text(disabledLabel);
|
||||
that.state = false;
|
||||
if (that.buttonIcon) {
|
||||
that.buttonIcon.addClass(disabledIcon);
|
||||
that.buttonIcon.removeClass(enabledIcon);
|
||||
}
|
||||
if (that.buttonLabel) {
|
||||
that.buttonLabel.text(disabledLabel);
|
||||
}
|
||||
}
|
||||
})
|
||||
this.element.trigger("change");
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
* - rootSortable: boolean - if 'sortable' is set, then setting this to
|
||||
* false, prevents items being sorted to the
|
||||
* top level of the tree
|
||||
* - autoSelect: boolean - default true - triggers item selection when navigating
|
||||
* list by keyboard. If the list has checkboxed items
|
||||
* you probably want to set this to false
|
||||
*
|
||||
* methods:
|
||||
* - data(items) - clears existing items and replaces with new data
|
||||
@@ -41,6 +44,7 @@
|
||||
* sublabel: 'Local', // a sub-label for the item
|
||||
* icon: 'fa fa-rocket', // (optional) icon for the item
|
||||
* checkbox: true/false, // (optional) if present, display checkbox accordingly
|
||||
* radio: 'group-name', // (optional) if present, display radio box - using group-name to set radio group
|
||||
* selected: true/false, // (optional) whether the item is selected or not
|
||||
* children: [] | function(done,item) // (optional) an array of child items, or a function
|
||||
* // that will call the `done` callback with an array
|
||||
@@ -49,6 +53,7 @@
|
||||
* deferBuild: true/false, // don't build any ui elements for the item's children
|
||||
* until it is expanded by the user.
|
||||
* element: // custom dom element to use for the item - ignored if `label` is set
|
||||
* collapsible: true/false, // prevent a parent item from being collapsed. default true.
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
@@ -89,77 +94,99 @@
|
||||
$.widget( "nodered.treeList", {
|
||||
_create: function() {
|
||||
var that = this;
|
||||
|
||||
var autoSelect = true;
|
||||
if (that.options.autoSelect === false) {
|
||||
autoSelect = false;
|
||||
}
|
||||
this.element.addClass('red-ui-treeList');
|
||||
this.element.attr("tabIndex",0);
|
||||
var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
|
||||
this.element.on('keydown', function(evt) {
|
||||
var selected = that._topList.find(".selected").parent().data('data');
|
||||
if (!selected && (evt.keyCode === 40 || evt.keyCode === 38)) {
|
||||
that.select(that._data[0]);
|
||||
var focussed = that._topList.find(".focus").parent().data('data');
|
||||
if (!focussed && (evt.keyCode === 40 || evt.keyCode === 38)) {
|
||||
if (that._data[0]) {
|
||||
if (autoSelect) {
|
||||
that.select(that._data[0]);
|
||||
} else {
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
}
|
||||
that._data[0].treeList.label.addClass('focus')
|
||||
}
|
||||
return;
|
||||
}
|
||||
var target;
|
||||
switch(evt.keyCode) {
|
||||
case 32: // SPACE
|
||||
case 13: // ENTER
|
||||
if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
|
||||
return
|
||||
}
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (selected.children) {
|
||||
if (selected.treeList.container.hasClass("expanded")) {
|
||||
selected.treeList.collapse()
|
||||
if (focussed.checkbox) {
|
||||
focussed.treeList.checkbox.trigger("click");
|
||||
} else if (focussed.radio) {
|
||||
focussed.treeList.radio.trigger("click");
|
||||
} else if (focussed.children) {
|
||||
if (focussed.treeList.container.hasClass("expanded")) {
|
||||
focussed.treeList.collapse()
|
||||
} else {
|
||||
selected.treeList.expand()
|
||||
focussed.treeList.expand()
|
||||
}
|
||||
} else {
|
||||
that._trigger("confirm",null,selected)
|
||||
that._trigger("confirm",null,focussed)
|
||||
}
|
||||
|
||||
break;
|
||||
case 37: // LEFT
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (selected.children&& selected.treeList.container.hasClass("expanded")) {
|
||||
selected.treeList.collapse()
|
||||
} else if (selected.parent) {
|
||||
target = selected.parent;
|
||||
if (focussed.children&& focussed.treeList.container.hasClass("expanded")) {
|
||||
focussed.treeList.collapse()
|
||||
} else if (focussed.parent) {
|
||||
target = focussed.parent;
|
||||
}
|
||||
break;
|
||||
case 38: // UP
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
target = that._getPreviousSibling(selected);
|
||||
target = that._getPreviousSibling(focussed);
|
||||
if (target) {
|
||||
target = that._getLastDescendant(target);
|
||||
}
|
||||
if (!target && selected.parent) {
|
||||
target = selected.parent;
|
||||
if (!target && focussed.parent) {
|
||||
target = focussed.parent;
|
||||
}
|
||||
break;
|
||||
case 39: // RIGHT
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (selected.children) {
|
||||
if (!selected.treeList.container.hasClass("expanded")) {
|
||||
selected.treeList.expand()
|
||||
if (focussed.children) {
|
||||
if (!focussed.treeList.container.hasClass("expanded")) {
|
||||
focussed.treeList.expand()
|
||||
}
|
||||
}
|
||||
break
|
||||
case 40: //DOWN
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (selected.children && Array.isArray(selected.children) && selected.children.length > 0 && selected.treeList.container.hasClass("expanded")) {
|
||||
target = selected.children[0];
|
||||
if (focussed.children && Array.isArray(focussed.children) && focussed.children.length > 0 && focussed.treeList.container.hasClass("expanded")) {
|
||||
target = focussed.children[0];
|
||||
} else {
|
||||
target = that._getNextSibling(selected);
|
||||
while (!target && selected.parent) {
|
||||
selected = selected.parent;
|
||||
target = that._getNextSibling(selected);
|
||||
target = that._getNextSibling(focussed);
|
||||
while (!target && focussed.parent) {
|
||||
focussed = focussed.parent;
|
||||
target = that._getNextSibling(focussed);
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if (target) {
|
||||
that.select(target);
|
||||
if (autoSelect) {
|
||||
that.select(target);
|
||||
} else {
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
}
|
||||
target.treeList.label.addClass('focus')
|
||||
}
|
||||
});
|
||||
this._data = [];
|
||||
@@ -167,7 +194,7 @@
|
||||
this._selected = new Set();
|
||||
this._topList = $('<ol class="red-ui-treeList-list">').css({
|
||||
position:'absolute',
|
||||
top: 0,
|
||||
top:0,
|
||||
left:0,
|
||||
right:0,
|
||||
bottom:0
|
||||
@@ -181,6 +208,9 @@
|
||||
that._addSubtree(that._topList,container,item,0);
|
||||
}
|
||||
};
|
||||
if (this.options.header) {
|
||||
topListOptions.header = this.options.header;
|
||||
}
|
||||
if (this.options.rootSortable !== false && !!this.options.sortable) {
|
||||
topListOptions.sortable = this.options.sortable;
|
||||
topListOptions.connectWith = '.red-ui-treeList-sortable';
|
||||
@@ -309,6 +339,7 @@
|
||||
}
|
||||
if (child.depth !== parent.depth+1) {
|
||||
child.depth = parent.depth+1;
|
||||
// var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
|
||||
var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
|
||||
child.treeList.labelPadding.width(labelPaddingWidth+'px');
|
||||
if (child.element) {
|
||||
@@ -345,6 +376,18 @@
|
||||
that._selected.delete(item);
|
||||
delete item.treeList;
|
||||
delete that._items[item.id];
|
||||
if(item.depth === 0) {
|
||||
for(var key in that._items) {
|
||||
if (that._items.hasOwnProperty(key)) {
|
||||
var child = that._items[key];
|
||||
if(child.parent && child.parent.id === item.id) {
|
||||
delete that._items[key].treeList;
|
||||
delete that._items[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
that._data = that._data.filter(function(data) { return data.id !== item.id})
|
||||
}
|
||||
}
|
||||
item.treeList.insertChildAt = function(newItem,position,select) {
|
||||
newItem.parent = item;
|
||||
@@ -446,6 +489,9 @@
|
||||
container.addClass("expanded");
|
||||
}
|
||||
item.treeList.collapse = function() {
|
||||
if (item.collapsible === false) {
|
||||
return
|
||||
}
|
||||
if (!item.children) {
|
||||
return;
|
||||
}
|
||||
@@ -477,7 +523,10 @@
|
||||
if (item.treeList.container) {
|
||||
$(item.element).remove();
|
||||
$(element).appendTo(item.treeList.label);
|
||||
var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(item.depth*20);
|
||||
// using the JQuery Object, the gutter width will
|
||||
// be wrong when the element is reattached the second time
|
||||
var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20);
|
||||
|
||||
$(element).css({
|
||||
width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
|
||||
})
|
||||
@@ -513,9 +562,11 @@
|
||||
}).appendTo(label)
|
||||
|
||||
}
|
||||
var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
|
||||
// var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
|
||||
var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20)
|
||||
item.treeList.labelPadding = $('<span>').css({
|
||||
display: "inline-block",
|
||||
"flex-shrink": 0,
|
||||
width: labelPaddingWidth+'px'
|
||||
}).appendTo(label);
|
||||
|
||||
@@ -561,7 +612,7 @@
|
||||
// Already a parent because we've got the angle-right icon
|
||||
return;
|
||||
}
|
||||
$('<i class="fa fa-angle-right" />').appendTo(treeListIcon);
|
||||
$('<i class="fa fa-angle-right" />').toggleClass("hide",item.collapsible === false).appendTo(treeListIcon);
|
||||
treeListIcon.on("click.red-ui-treeList-expand", function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -612,6 +663,46 @@
|
||||
label.on("click", function(e) {
|
||||
e.stopPropagation();
|
||||
cb.trigger("click");
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
label.addClass('focus')
|
||||
})
|
||||
}
|
||||
item.treeList.select = function(v) {
|
||||
if (v !== item.selected) {
|
||||
cb.trigger("click");
|
||||
}
|
||||
}
|
||||
item.treeList.checkbox = cb;
|
||||
selectWrapper.appendTo(label)
|
||||
} else if (item.radio) {
|
||||
var selectWrapper = $('<span class="red-ui-treeList-icon"></span>');
|
||||
var cb = $('<input class="red-ui-treeList-radio" type="radio">').prop('name', item.radio).prop('checked',item.selected).appendTo(selectWrapper);
|
||||
cb.on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
cb.on('change', function(e) {
|
||||
item.selected = this.checked;
|
||||
that._selected.forEach(function(selectedItem) {
|
||||
if (selectedItem.radio === item.radio) {
|
||||
selectedItem.treeList.label.removeClass("selected");
|
||||
selectedItem.selected = false;
|
||||
that._selected.delete(selectedItem);
|
||||
}
|
||||
})
|
||||
if (item.selected) {
|
||||
that._selected.add(item);
|
||||
} else {
|
||||
that._selected.delete(item);
|
||||
}
|
||||
label.toggleClass("selected",this.checked);
|
||||
that._trigger("select",e,item);
|
||||
})
|
||||
if (!item.children) {
|
||||
label.on("click", function(e) {
|
||||
e.stopPropagation();
|
||||
cb.trigger("click");
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
label.addClass('focus')
|
||||
})
|
||||
}
|
||||
item.treeList.select = function(v) {
|
||||
@@ -620,6 +711,7 @@
|
||||
}
|
||||
}
|
||||
selectWrapper.appendTo(label)
|
||||
item.treeList.radio = cb;
|
||||
} else {
|
||||
label.on("click", function(e) {
|
||||
if (!that.options.multi) {
|
||||
@@ -627,10 +719,14 @@
|
||||
}
|
||||
label.addClass("selected");
|
||||
that._selected.add(item);
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
label.addClass('focus')
|
||||
|
||||
that._trigger("select",e,item)
|
||||
})
|
||||
label.on("dblclick", function(e) {
|
||||
that._topList.find(".focus").removeClass("focus")
|
||||
label.addClass('focus')
|
||||
if (!item.children) {
|
||||
that._trigger("confirm",e,item);
|
||||
}
|
||||
@@ -778,6 +874,9 @@
|
||||
if (item.treeList.label) {
|
||||
item.treeList.label.addClass("selected");
|
||||
}
|
||||
|
||||
that._topList.find(".focus").removeClass("focus");
|
||||
|
||||
if (triggerEvent !== false) {
|
||||
this._trigger("select",null,item)
|
||||
}
|
||||
@@ -785,6 +884,9 @@
|
||||
clearSelection: function() {
|
||||
this._selected.forEach(function(item) {
|
||||
item.selected = false;
|
||||
if (item.treeList.checkbox) {
|
||||
item.treeList.checkbox.prop('checked',false)
|
||||
}
|
||||
if (item.treeList.label) {
|
||||
item.treeList.label.removeClass("selected")
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
(function($) {
|
||||
var contextParse = function(v) {
|
||||
var parts = RED.utils.parseContextKey(v);
|
||||
var contextParse = function(v,defaultStore) {
|
||||
var parts = RED.utils.parseContextKey(v, defaultStore&&defaultStore.value);
|
||||
return {
|
||||
option: parts.store,
|
||||
value: parts.key
|
||||
@@ -32,25 +32,122 @@
|
||||
return v;
|
||||
}
|
||||
}
|
||||
var contextLabel = function(container,value) {
|
||||
var that = this;
|
||||
container.css("pointer-events","none");
|
||||
container.css("flex-grow",0);
|
||||
container.css("position",'relative');
|
||||
container.css("overflow",'visible');
|
||||
$('<div></div>').text(value).css({
|
||||
position: "absolute",
|
||||
bottom:"-2px",
|
||||
right: "5px",
|
||||
"font-size": "0.7em",
|
||||
opacity: 0.3
|
||||
}).appendTo(container);
|
||||
this.elementDiv.show();
|
||||
}
|
||||
var mapDeprecatedIcon = function(icon) {
|
||||
if (/^red\/images\/typedInput\/.+\.png$/.test(icon)) {
|
||||
icon = icon.replace(/.png$/,".svg");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
var autoComplete = function(options) {
|
||||
return function(val) {
|
||||
var matches = [];
|
||||
options.forEach(opt => {
|
||||
let v = opt.value;
|
||||
var i = v.toLowerCase().indexOf(val.toLowerCase());
|
||||
if (i > -1) {
|
||||
var pre = v.substring(0,i);
|
||||
var matchedVal = v.substring(i,i+val.length);
|
||||
var post = v.substring(i+val.length)
|
||||
|
||||
var el = $('<div/>',{style:"white-space:nowrap; overflow: hidden; flex-grow:1"});
|
||||
$('<span/>').text(pre).appendTo(el);
|
||||
$('<span/>',{style:"font-weight: bold"}).text(matchedVal).appendTo(el);
|
||||
$('<span/>').text(post).appendTo(el);
|
||||
|
||||
var element = $('<div>',{style: "display: flex"});
|
||||
el.appendTo(element);
|
||||
if (opt.source) {
|
||||
$('<div>').css({
|
||||
"font-size": "0.8em"
|
||||
}).text(opt.source.join(",")).appendTo(element);
|
||||
}
|
||||
|
||||
matches.push({
|
||||
value: v,
|
||||
label: element,
|
||||
i:i
|
||||
})
|
||||
}
|
||||
})
|
||||
matches.sort(function(A,B){return A.i-B.i})
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
||||
var msgCompletions = [
|
||||
{ value: "payload" },
|
||||
{ value: "req", source: ["http in"]},
|
||||
{ value: "req.body", source: ["http in"]},
|
||||
{ value: "req.headers", source: ["http in"]},
|
||||
{ value: "req.query", source: ["http in"]},
|
||||
{ value: "req.params", source: ["http in"]},
|
||||
{ value: "req.cookies", source: ["http in"]},
|
||||
{ value: "req.files", source: ["http in"]},
|
||||
{ value: "complete", source: ["join"] },
|
||||
{ value: "contentType", source: ["mqtt"] },
|
||||
{ value: "cookies", source: ["http in","http request"] },
|
||||
{ value: "correlationData", source: ["mqtt"] },
|
||||
{ value: "delay", source: ["delay","trigger"] },
|
||||
{ value: "encoding", source: ["file"] },
|
||||
{ value: "error", source: ["catch"] },
|
||||
{ value: "filename", source: ["file","file in"] },
|
||||
{ value: "flush", source: ["delay"] },
|
||||
{ value: "followRedirects", source: ["http request"] },
|
||||
{ value: "headers", source: ["http in"," http request"] },
|
||||
{ value: "kill", source: ["exec"] },
|
||||
{ value: "messageExpiryInterval", source: ["mqtt"] },
|
||||
{ value: "method", source: ["http-request"] },
|
||||
{ value: "options", source: ["xml"] },
|
||||
{ value: "parts", source: ["split","join"] },
|
||||
{ value: "pid", source: ["exec"] },
|
||||
{ value: "qos", source: ["mqtt"] },
|
||||
{ value: "rate", source: ["delay"] },
|
||||
{ value: "rejectUnauthorized", source: ["http request"] },
|
||||
{ value: "requestTimeout", source: ["http request"] },
|
||||
{ value: "reset", source: ["delay","trigger","join","rbe"] },
|
||||
{ value: "responseTopic", source: ["mqtt"] },
|
||||
{ value: "restartTimeout", source: ["join"] },
|
||||
{ value: "retain", source: ["mqtt"] },
|
||||
{ value: "select", source: ["html"] },
|
||||
{ value: "statusCode", source: ["http in"] },
|
||||
{ value: "template", source: ["template"] },
|
||||
{ value: "toFront", source: ["delay"] },
|
||||
{ value: "topic", source: ["inject","mqtt","rbe"] },
|
||||
{ value: "url", source: ["http request"] },
|
||||
{ value: "userProperties", source: ["mqtt"] }
|
||||
]
|
||||
var allOptions = {
|
||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
|
||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||
options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
},
|
||||
global: {value:"global",label:"global.",hasValue:true,
|
||||
options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
},
|
||||
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]+)?$/},
|
||||
@@ -173,6 +270,7 @@
|
||||
valueLabel: function(container,value) {
|
||||
var that = this;
|
||||
container.css("pointer-events","none");
|
||||
container.css("flex-grow",0);
|
||||
this.elementDiv.hide();
|
||||
var buttons = $('<div>').css({
|
||||
position: "absolute",
|
||||
@@ -184,22 +282,25 @@
|
||||
width:"20px"
|
||||
}).appendTo(buttons).on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
var cursorPosition = that.input[0].selectionStart;
|
||||
var currentType = that.input.attr("type");
|
||||
if (currentType === "text") {
|
||||
that.input.attr("type","password");
|
||||
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
|
||||
setTimeout(function() {
|
||||
that.input.focus();
|
||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||
},50);
|
||||
} else {
|
||||
that.input.attr("type","text");
|
||||
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
|
||||
setTimeout(function() {
|
||||
that.input.focus();
|
||||
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
|
||||
},50);
|
||||
}
|
||||
}).hide();
|
||||
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-1px").appendTo(eyeButton);
|
||||
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-2px").appendTo(eyeButton);
|
||||
|
||||
if (value === "__PWRD__") {
|
||||
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
|
||||
@@ -244,6 +345,47 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// For a type with options, check value is a valid selection
|
||||
// If !opt.multiple, returns the valid option object
|
||||
// if opt.multiple, returns an array of valid option objects
|
||||
// If not valid, returns null;
|
||||
|
||||
function isOptionValueValid(opt, currentVal) {
|
||||
if (!opt.multiple) {
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
if (typeof op === "string" && op === currentVal) {
|
||||
return {value:currentVal}
|
||||
} else if (op.value === currentVal) {
|
||||
return op;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check to see if value is a valid csv of
|
||||
// options.
|
||||
var currentValues = {};
|
||||
var selected = [];
|
||||
currentVal.split(",").forEach(function(v) {
|
||||
if (v) {
|
||||
currentValues[v] = true;
|
||||
}
|
||||
});
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
var val = typeof op === "string" ? op : op.value;
|
||||
if (currentValues.hasOwnProperty(val)) {
|
||||
delete currentValues[val];
|
||||
selected.push(typeof op === "string" ? {value:op} : op.value)
|
||||
}
|
||||
}
|
||||
if (!$.isEmptyObject(currentValues)) {
|
||||
return null;
|
||||
}
|
||||
return selected
|
||||
}
|
||||
}
|
||||
|
||||
var nlsd = false;
|
||||
|
||||
$.widget( "nodered.typedInput", {
|
||||
@@ -258,6 +400,14 @@
|
||||
var contextStores = RED.settings.context.stores;
|
||||
var contextOptions = contextStores.map(function(store) {
|
||||
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
||||
}).sort(function(A,B) {
|
||||
if (A.value === RED.settings.context.default) {
|
||||
return -1;
|
||||
} else if (B.value === RED.settings.context.default) {
|
||||
return 1;
|
||||
} else {
|
||||
return A.value.localeCompare(B.value);
|
||||
}
|
||||
})
|
||||
if (contextOptions.length < 2) {
|
||||
allOptions.flow.options = [];
|
||||
@@ -269,7 +419,8 @@
|
||||
}
|
||||
nlsd = true;
|
||||
var that = this;
|
||||
|
||||
this.identifier = this.element.attr('id') || "TypedInput-"+Math.floor(Math.random()*100);
|
||||
if (this.options.debug) { console.log(this.identifier,"Create",{defaultType:this.options.default, value:this.element.val()}) }
|
||||
this.disarmClick = false;
|
||||
this.input = $('<input class="red-ui-typedInput-input" type="text"></input>');
|
||||
this.input.insertAfter(this.element);
|
||||
@@ -284,7 +435,7 @@
|
||||
this.input.css('width','100%');
|
||||
this.uiSelect.width(m[1]);
|
||||
this.uiWidth = null;
|
||||
} else {
|
||||
} else if (this.uiWidth !== 0){
|
||||
this.uiSelect.width(this.uiWidth);
|
||||
}
|
||||
["Right","Left"].forEach(function(d) {
|
||||
@@ -299,12 +450,18 @@
|
||||
});
|
||||
|
||||
this.defaultInputType = this.input.attr('type');
|
||||
// Used to remember selections per-type to restore them when switching between types
|
||||
this.oldValues = {};
|
||||
|
||||
this.uiSelect.addClass("red-ui-typedInput-container");
|
||||
|
||||
this.element.attr('type','hidden');
|
||||
|
||||
this.options.types = this.options.types||Object.keys(allOptions);
|
||||
if (!this.options.types && this.options.type) {
|
||||
this.options.types = [this.options.type]
|
||||
} else {
|
||||
this.options.types = this.options.types||Object.keys(allOptions);
|
||||
}
|
||||
|
||||
this.selectTrigger = $('<button class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
|
||||
$('<i class="red-ui-typedInput-icon fa fa-caret-down"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
|
||||
@@ -334,9 +491,22 @@
|
||||
this.input.on('change', function() {
|
||||
that.validate();
|
||||
that.element.val(that.value());
|
||||
that.element.trigger('change',that.propertyType,that.value());
|
||||
that.element.trigger('change',[that.propertyType,that.value()]);
|
||||
});
|
||||
this.input.on('keyup', function(evt) {
|
||||
that.validate();
|
||||
that.element.val(that.value());
|
||||
that.element.trigger('keyup',evt);
|
||||
});
|
||||
this.input.on('paste', function(evt) {
|
||||
that.validate();
|
||||
that.element.val(that.value());
|
||||
that.element.trigger('paste',evt);
|
||||
});
|
||||
this.input.on('keydown', function(evt) {
|
||||
if (that.typeMap[that.propertyType].autoComplete) {
|
||||
return
|
||||
}
|
||||
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
@@ -354,14 +524,19 @@
|
||||
evt.stopPropagation();
|
||||
}).on('focus', function() {
|
||||
that.uiSelect.addClass('red-ui-typedInput-focus');
|
||||
}).on('blur', function() {
|
||||
var opt = that.typeMap[that.propertyType];
|
||||
if (opt.hasValue === false) {
|
||||
that.uiSelect.removeClass('red-ui-typedInput-focus');
|
||||
}
|
||||
})
|
||||
|
||||
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
|
||||
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
|
||||
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
|
||||
RED.popover.tooltip(this.optionSelectLabel,function() {
|
||||
return that.optionValue;
|
||||
});
|
||||
// RED.popover.tooltip(this.optionSelectLabel,function() {
|
||||
// return that.optionValue;
|
||||
// });
|
||||
this.optionSelectTrigger.on("click", function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -380,7 +555,9 @@
|
||||
|
||||
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
|
||||
this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
|
||||
|
||||
this.type(this.options.default||this.typeList[0].value);
|
||||
this.typeChanged = !!this.options.default;
|
||||
}catch(err) {
|
||||
console.log(err.stack);
|
||||
}
|
||||
@@ -403,7 +580,11 @@
|
||||
});
|
||||
|
||||
this._showMenu(this.optionMenu,this.optionSelectTrigger);
|
||||
var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
|
||||
var targetValue = this.optionValue;
|
||||
if (this.optionValue === null || this.optionValue === undefined) {
|
||||
targetValue = this.value();
|
||||
}
|
||||
var selectedOption = this.optionMenu.find("[value='"+targetValue+"']");
|
||||
if (selectedOption.length === 0) {
|
||||
selectedOption = this.optionMenu.children(":first");
|
||||
}
|
||||
@@ -527,7 +708,7 @@
|
||||
var height = relativeTo.height();
|
||||
var menuHeight = menu.height();
|
||||
var top = (height+pos.top);
|
||||
if (top+menuHeight > $(window).height()) {
|
||||
if (top+menuHeight-$(document).scrollTop() > $(window).height()) {
|
||||
top -= (top+menuHeight)-$(window).height()+5;
|
||||
}
|
||||
if (top < 0) {
|
||||
@@ -574,34 +755,43 @@
|
||||
_updateOptionSelectLabel: function(o) {
|
||||
var opt = this.typeMap[this.propertyType];
|
||||
this.optionSelectLabel.empty();
|
||||
if (opt.hasValue) {
|
||||
this.valueLabelContainer.empty();
|
||||
this.valueLabelContainer.show();
|
||||
} else {
|
||||
this.valueLabelContainer.hide();
|
||||
}
|
||||
if (this.typeMap[this.propertyType].valueLabel) {
|
||||
if (opt.multiple) {
|
||||
this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o);
|
||||
this.typeMap[this.propertyType].valueLabel.call(this,opt.hasValue?this.valueLabelContainer:this.optionSelectLabel,o);
|
||||
} else {
|
||||
this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o.value);
|
||||
this.typeMap[this.propertyType].valueLabel.call(this,opt.hasValue?this.valueLabelContainer:this.optionSelectLabel,o.value);
|
||||
}
|
||||
} else if (!opt.multiple) {
|
||||
if (o.icon) {
|
||||
if (o.icon.indexOf("<") === 0) {
|
||||
$(o.icon).prependTo(this.optionSelectLabel);
|
||||
} else if (o.icon.indexOf("/") !== -1) {
|
||||
// url
|
||||
$('<img>',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel);
|
||||
}
|
||||
if (!this.typeMap[this.propertyType].valueLabel || opt.hasValue) {
|
||||
if (!opt.multiple) {
|
||||
if (o.icon) {
|
||||
if (o.icon.indexOf("<") === 0) {
|
||||
$(o.icon).prependTo(this.optionSelectLabel);
|
||||
} else if (o.icon.indexOf("/") !== -1) {
|
||||
// url
|
||||
$('<img>',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel);
|
||||
} else {
|
||||
// icon class
|
||||
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
|
||||
}
|
||||
} else if (o.label) {
|
||||
this.optionSelectLabel.text(o.label);
|
||||
} else {
|
||||
// icon class
|
||||
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
|
||||
this.optionSelectLabel.text(o.value);
|
||||
}
|
||||
if (opt.hasValue) {
|
||||
this.optionValue = o.value;
|
||||
this.input.trigger('change',[this.propertyType,this.value()]);
|
||||
}
|
||||
} else if (o.label) {
|
||||
this.optionSelectLabel.text(o.label);
|
||||
} else {
|
||||
this.optionSelectLabel.text(o.value);
|
||||
this.optionSelectLabel.text(o.length+" selected");
|
||||
}
|
||||
if (opt.hasValue) {
|
||||
this.optionValue = o.value;
|
||||
this.input.trigger('change',this.propertyType,this.value());
|
||||
}
|
||||
} else {
|
||||
this.optionSelectLabel.text(o.length+" selected");
|
||||
}
|
||||
},
|
||||
_destroy: function() {
|
||||
@@ -625,6 +815,13 @@
|
||||
that.typeMap[result.value] = result;
|
||||
return result;
|
||||
});
|
||||
if (this.typeList.length < 2) {
|
||||
this.selectTrigger.attr("tabindex", -1)
|
||||
this.selectTrigger.on("mousedown.red-ui-typedInput-focus-block", function(evt) { evt.preventDefault(); })
|
||||
} else {
|
||||
this.selectTrigger.attr("tabindex", 0)
|
||||
this.selectTrigger.off("mousedown.red-ui-typedInput-focus-block")
|
||||
}
|
||||
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
|
||||
this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1)
|
||||
if (this.menu) {
|
||||
@@ -637,6 +834,11 @@
|
||||
this.propertyType = null;
|
||||
this.type(currentType);
|
||||
}
|
||||
if (this.typeList.length === 1 && !this.typeList[0].icon && (!this.typeList[0].label || this.typeList[0].showLabel === false)) {
|
||||
this.selectTrigger.hide()
|
||||
} else {
|
||||
this.selectTrigger.show()
|
||||
}
|
||||
},
|
||||
width: function(desiredWidth) {
|
||||
this.uiWidth = desiredWidth;
|
||||
@@ -646,7 +848,10 @@
|
||||
},
|
||||
value: function(value) {
|
||||
var that = this;
|
||||
var opt = this.typeMap[this.propertyType];
|
||||
// If the default type has been set to an invalid type, then on first
|
||||
// creation, the current propertyType will not exist. Default to an
|
||||
// empty object on the assumption the corrent type will be set shortly
|
||||
var opt = this.typeMap[this.propertyType] || {};
|
||||
if (!arguments.length) {
|
||||
var v = this.input.val();
|
||||
if (opt.export) {
|
||||
@@ -654,27 +859,38 @@
|
||||
}
|
||||
return v;
|
||||
} else {
|
||||
if (this.options.debug) { console.log(this.identifier,"----- SET VALUE ------",value) }
|
||||
var selectedOption = [];
|
||||
var valueToCheck = value;
|
||||
if (opt.options) {
|
||||
var checkValues = [value];
|
||||
if (opt.hasValue && opt.parse) {
|
||||
var parts = opt.parse(value);
|
||||
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
||||
value = parts.value;
|
||||
valueToCheck = parts.option || parts.value;
|
||||
}
|
||||
|
||||
var checkValues = [valueToCheck];
|
||||
if (opt.multiple) {
|
||||
selectedOption = [];
|
||||
checkValues = value.split(",");
|
||||
checkValues = valueToCheck.split(",");
|
||||
}
|
||||
checkValues.forEach(function(value) {
|
||||
checkValues.forEach(function(valueToCheck) {
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
var op = opt.options[i];
|
||||
if (typeof op === "string") {
|
||||
if (op === value || op === ""+value) {
|
||||
if (op === valueToCheck || op === ""+valueToCheck) {
|
||||
selectedOption.push(that.activeOptions[op]);
|
||||
break;
|
||||
}
|
||||
} else if (op.value === value) {
|
||||
} else if (op.value === valueToCheck) {
|
||||
selectedOption.push(op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
if (this.options.debug) { console.log(this.identifier,"set value to",value) }
|
||||
|
||||
this.input.val(value);
|
||||
if (!opt.multiple) {
|
||||
if (selectedOption.length === 0) {
|
||||
@@ -691,7 +907,7 @@
|
||||
opt.valueLabel.call(this,this.valueLabelContainer,value);
|
||||
}
|
||||
}
|
||||
this.input.trigger('change',this.type(),value);
|
||||
this.input.trigger('change',[this.type(),value]);
|
||||
}
|
||||
},
|
||||
type: function(type) {
|
||||
@@ -699,9 +915,64 @@
|
||||
return this.propertyType;
|
||||
} else {
|
||||
var that = this;
|
||||
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
||||
var previousValue = null;
|
||||
var opt = this.typeMap[type];
|
||||
if (opt && this.propertyType !== type) {
|
||||
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
||||
var previousType = this.typeMap[this.propertyType];
|
||||
previousValue = this.input.val();
|
||||
|
||||
if (this.typeChanged) {
|
||||
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
||||
if (previousType.options && opt.hasValue !== true) {
|
||||
this.oldValues[previousType.value] = previousValue;
|
||||
} else if (previousType.hasValue === false) {
|
||||
this.oldValues[previousType.value] = previousValue;
|
||||
} else {
|
||||
this.oldValues["_"] = previousValue;
|
||||
}
|
||||
if ((opt.options && opt.hasValue !== true) || opt.hasValue === false) {
|
||||
if (this.oldValues.hasOwnProperty(opt.value)) {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored previous (1)",this.oldValues[opt.value]) }
|
||||
this.input.val(this.oldValues[opt.value]);
|
||||
} else if (opt.options) {
|
||||
// No old value for the option type.
|
||||
// It is possible code has called 'value' then 'type'
|
||||
// to set the selected option. This is what the Inject/Switch/Change
|
||||
// nodes did before 2.1.
|
||||
// So we need to be careful to not reset the value if it is a valid option.
|
||||
var validOptions = isOptionValueValid(opt,previousValue);
|
||||
if (this.options.debug) { console.log(this.identifier,{previousValue,opt,validOptions}) }
|
||||
if ((previousValue || previousValue === '') && validOptions) {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored previous (2)") }
|
||||
this.input.val(previousValue);
|
||||
} else {
|
||||
if (typeof opt.default === "string") {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored previous (3)",opt.default) }
|
||||
this.input.val(opt.default);
|
||||
} else if (Array.isArray(opt.default)) {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored previous (4)",opt.default.join(",")) }
|
||||
this.input.val(opt.default.join(","))
|
||||
} else {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored previous (5)") }
|
||||
this.input.val("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored default/blank",opt.default||"") }
|
||||
this.input.val(opt.default||"")
|
||||
}
|
||||
} else {
|
||||
if (this.options.debug) { console.log(this.identifier,"restored old/default/blank") }
|
||||
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
||||
}
|
||||
if (previousType.autoComplete) {
|
||||
this.input.autoComplete("destroy");
|
||||
}
|
||||
}
|
||||
this.propertyType = type;
|
||||
this.typeChanged = true;
|
||||
if (this.typeField) {
|
||||
this.typeField.val(type);
|
||||
}
|
||||
@@ -724,6 +995,11 @@
|
||||
if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
|
||||
this.selectLabel.text(opt.label);
|
||||
}
|
||||
if (opt.label) {
|
||||
this.selectTrigger.attr("title",opt.label);
|
||||
} else {
|
||||
this.selectTrigger.attr("title","");
|
||||
}
|
||||
if (opt.hasValue === false) {
|
||||
this.selectTrigger.addClass("red-ui-typedInput-full-width");
|
||||
} else {
|
||||
@@ -765,22 +1041,12 @@
|
||||
|
||||
var op;
|
||||
if (!opt.hasValue) {
|
||||
var validValue = false;
|
||||
var currentVal = this.input.val();
|
||||
// Check the value is valid for the available options
|
||||
var validValues = isOptionValueValid(opt,this.input.val());
|
||||
if (!opt.multiple) {
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
if (typeof op === "string" && op === currentVal) {
|
||||
that._updateOptionSelectLabel({value:currentVal});
|
||||
validValue = true;
|
||||
break;
|
||||
} else if (op.value === currentVal) {
|
||||
that._updateOptionSelectLabel(op);
|
||||
validValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!validValue) {
|
||||
if (validValues) {
|
||||
that._updateOptionSelectLabel(validValues)
|
||||
} else {
|
||||
op = opt.options[0];
|
||||
if (typeof op === "string") {
|
||||
this.value(op);
|
||||
@@ -791,27 +1057,19 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check to see if value is a valid csv of
|
||||
// options.
|
||||
var currentValues = {};
|
||||
currentVal.split(",").forEach(function(v) {
|
||||
if (v) {
|
||||
currentValues[v] = true;
|
||||
}
|
||||
});
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
delete currentValues[op.value||op];
|
||||
}
|
||||
if (!$.isEmptyObject(currentValues)) {
|
||||
// Invalid, set to default/empty
|
||||
this.value((opt.default||[]).join(","));
|
||||
if (!validValues) {
|
||||
validValues = (opt.default || []).map(function(v) {
|
||||
return typeof v === "string"?v:v.value
|
||||
});
|
||||
this.value(validValues.join(","));
|
||||
}
|
||||
that._updateOptionSelectLabel(validValues);
|
||||
}
|
||||
} else {
|
||||
var selectedOption = this.optionValue||opt.options[0];
|
||||
if (opt.parse) {
|
||||
var parts = opt.parse(this.input.val());
|
||||
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
||||
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
||||
if (parts.option) {
|
||||
selectedOption = parts.option;
|
||||
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
|
||||
@@ -835,6 +1093,7 @@
|
||||
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
|
||||
}
|
||||
} else if (selectedOption) {
|
||||
if (this.options.debug) { console.log(this.identifier,"HERE",{optionValue:selectedOption.value}) }
|
||||
this.optionValue = selectedOption.value;
|
||||
this._updateOptionSelectLabel(selectedOption);
|
||||
} else {
|
||||
@@ -856,7 +1115,7 @@
|
||||
});
|
||||
}
|
||||
this._trigger("typechange",null,this.propertyType);
|
||||
this.input.trigger('change',this.propertyType,this.value());
|
||||
this.input.trigger('change',[this.propertyType,this.value()]);
|
||||
} else {
|
||||
if (this.optionSelectTrigger) {
|
||||
this.optionSelectTrigger.hide();
|
||||
@@ -867,22 +1126,25 @@
|
||||
this.input.attr('type',this.defaultInputType)
|
||||
}
|
||||
if (opt.hasValue === false) {
|
||||
this.oldValue = this.input.val();
|
||||
this.input.val("");
|
||||
this.elementDiv.hide();
|
||||
this.valueLabelContainer.hide();
|
||||
} else if (opt.valueLabel) {
|
||||
// Reset any CSS the custom label may have set
|
||||
this.valueLabelContainer.css("pointer-events","");
|
||||
this.valueLabelContainer.css("flex-grow",1);
|
||||
this.valueLabelContainer.css("overflow","hidden");
|
||||
this.valueLabelContainer.show();
|
||||
this.valueLabelContainer.empty();
|
||||
this.elementDiv.hide();
|
||||
opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
|
||||
} else {
|
||||
if (this.oldValue !== undefined) {
|
||||
this.input.val(this.oldValue);
|
||||
delete this.oldValue;
|
||||
}
|
||||
this.valueLabelContainer.hide();
|
||||
this.elementDiv.show();
|
||||
if (opt.autoComplete) {
|
||||
this.input.autoComplete({
|
||||
search: opt.autoComplete
|
||||
})
|
||||
}
|
||||
}
|
||||
if (this.optionExpandButton) {
|
||||
if (opt.expand) {
|
||||
@@ -923,7 +1185,7 @@
|
||||
}
|
||||
}
|
||||
this._trigger("typechange",null,this.propertyType);
|
||||
this.input.trigger('change',this.propertyType,this.value());
|
||||
this.input.trigger('change',[this.propertyType,this.value()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -954,6 +1216,22 @@
|
||||
},
|
||||
hide: function() {
|
||||
this.uiSelect.hide();
|
||||
},
|
||||
disable: function(val) {
|
||||
if(val === undefined || !!val ) {
|
||||
this.uiSelect.attr("disabled", "disabled");
|
||||
} else {
|
||||
this.uiSelect.attr("disabled", null); //remove attr
|
||||
}
|
||||
},
|
||||
enable: function() {
|
||||
this.uiSelect.attr("disabled", null); //remove attr
|
||||
},
|
||||
disabled: function() {
|
||||
return this.uiSelect.attr("disabled") === "disabled";
|
||||
},
|
||||
focus: function() {
|
||||
this.input.focus();
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
@@ -1376,6 +1376,7 @@ RED.diff = (function() {
|
||||
|
||||
function mergeDiff(diff) {
|
||||
//console.log(diff);
|
||||
var selectedTab = RED.workspaces.active();
|
||||
var appliedDiff = applyDiff(diff);
|
||||
|
||||
var newConfig = appliedDiff.config;
|
||||
@@ -1411,7 +1412,7 @@ RED.diff = (function() {
|
||||
// Restore the original flow so subsequent merge resolutions can properly
|
||||
// identify new-vs-old
|
||||
RED.nodes.originalFlow(originalFlow);
|
||||
imported[0].forEach(function(n) {
|
||||
imported.nodes.forEach(function(n) {
|
||||
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
|
||||
n.changed = true;
|
||||
}
|
||||
@@ -1426,6 +1427,7 @@ RED.diff = (function() {
|
||||
RED.view.redraw(true);
|
||||
RED.palette.refresh();
|
||||
RED.workspaces.refresh();
|
||||
RED.workspaces.show(selectedTab, true);
|
||||
RED.sidebar.config.refresh();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
107
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
vendored
Normal file
107
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @namespace RED.editor.codeEditor
|
||||
*/
|
||||
RED.editor.codeEditor = (function() {
|
||||
|
||||
const MONACO = "monaco";
|
||||
const ACE = "ace";
|
||||
const defaultEditor = ACE;
|
||||
const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} };
|
||||
var selectedCodeEditor = null;
|
||||
var initialised = false;
|
||||
|
||||
function init() {
|
||||
var codeEditorSettings = RED.editor.codeEditor.settings;
|
||||
var editorChoice = codeEditorSettings.lib === MONACO ? MONACO : ACE;
|
||||
try {
|
||||
var browser = RED.utils.getBrowserInfo();
|
||||
selectedCodeEditor = RED.editor.codeEditor[editorChoice];
|
||||
//fall back to default code editor if there are any issues
|
||||
if (!selectedCodeEditor || (editorChoice === MONACO && (browser.ie || !window.monaco))) {
|
||||
selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
|
||||
}
|
||||
initialised = selectedCodeEditor.init();
|
||||
} catch (error) {
|
||||
selectedCodeEditor = null;
|
||||
console.warn("Problem initialising '" + editorChoice + "' code editor", error);
|
||||
}
|
||||
if(!initialised) {
|
||||
selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
|
||||
initialised = selectedCodeEditor.init();
|
||||
}
|
||||
}
|
||||
|
||||
function create(options) {
|
||||
//TODO: (quandry - for consideration)
|
||||
// Below, I had to create a hidden element if options.id || options.element is not in the DOM
|
||||
// I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an
|
||||
// invalid (non existing html element selector) (e.g. node-red-contrib-components does this)
|
||||
// This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre'
|
||||
// code is thus skipped.
|
||||
// In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI)
|
||||
// Because one (or more) contrib nodes have left this bad code in place, how would we handle this?
|
||||
// For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur.
|
||||
// IMO, we should warn and exit as it is a coding error by the contrib author.
|
||||
|
||||
if (!options) {
|
||||
console.warn("createEditor() options are missing");
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (this.editor.type === MONACO) {
|
||||
// compatibility (see above note)
|
||||
if (!options.element && !options.id) {
|
||||
options.id = 'node-backwards-compatability-dummy-editor';
|
||||
}
|
||||
options.element = options.element || $("#" + options.id)[0];
|
||||
if (!options.element) {
|
||||
console.warn("createEditor() options.element or options.id is not valid", options);
|
||||
$("#dialog-form").append('<div id="' + options.id + '" style="display: none;" />');
|
||||
}
|
||||
return this.editor.create(options);
|
||||
} else {
|
||||
return this.editor.create(options);//fallback to ACE
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
/**
|
||||
* Get editor settings object
|
||||
* @memberof RED.editor.codeEditor
|
||||
*/
|
||||
get settings() {
|
||||
return RED.settings.get('codeEditor') || DEFAULT_SETTINGS;
|
||||
},
|
||||
/**
|
||||
* Get user selected code editor
|
||||
* @return {string} Returns
|
||||
* @memberof RED.editor.codeEditor
|
||||
*/
|
||||
get editor() {
|
||||
return selectedCodeEditor;
|
||||
},
|
||||
/**
|
||||
* Create a editor ui component
|
||||
* @param {object} options - the editor options
|
||||
* @memberof RED.editor.codeEditor
|
||||
*/
|
||||
create: create
|
||||
}
|
||||
})();
|
||||
153
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
vendored
Normal file
153
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @namespace RED.editor.codeEditor.ace
|
||||
*/
|
||||
RED.editor.codeEditor.ace = (function() {
|
||||
|
||||
const type = "ace";
|
||||
var initialised = false;
|
||||
var initOptions = {};
|
||||
|
||||
function init(options) {
|
||||
initOptions = options || {};
|
||||
initialised = true;
|
||||
return initialised;
|
||||
}
|
||||
|
||||
function create(options) {
|
||||
var editorSettings = RED.editor.codeEditor.settings || {};
|
||||
var el = options.element || $("#"+options.id)[0];
|
||||
var toolbarRow = $("<div>").appendTo(el);
|
||||
el = $("<div>").appendTo(el).addClass("red-ui-editor-text-container")[0];
|
||||
var editor = window.ace.edit(el);
|
||||
editor.setTheme(editorSettings.theme || initOptions.theme || "ace/theme/tomorrow");
|
||||
var session = editor.getSession();
|
||||
session.on("changeAnnotation", function () {
|
||||
var annotations = session.getAnnotations() || [];
|
||||
var i = annotations.length;
|
||||
var len = annotations.length;
|
||||
while (i--) {
|
||||
if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
|
||||
else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
|
||||
}
|
||||
if (len > annotations.length) { session.setAnnotations(annotations); }
|
||||
});
|
||||
if (options.mode) {
|
||||
session.setMode(options.mode);
|
||||
}
|
||||
if (options.foldStyle) {
|
||||
session.setFoldStyle(options.foldStyle);
|
||||
} else {
|
||||
session.setFoldStyle('markbeginend');
|
||||
}
|
||||
if (options.options) {
|
||||
editor.setOptions(options.options);
|
||||
} else {
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion:true,
|
||||
enableSnippets:true,
|
||||
tooltipFollowsMouse: false
|
||||
});
|
||||
}
|
||||
if (options.readOnly) {
|
||||
editor.setOption('readOnly',options.readOnly);
|
||||
editor.container.classList.add("ace_read-only");
|
||||
}
|
||||
if (options.hasOwnProperty('lineNumbers')) {
|
||||
editor.renderer.setOption('showGutter',options.lineNumbers);
|
||||
}
|
||||
editor.$blockScrolling = Infinity;
|
||||
if (options.value) {
|
||||
session.setValue(options.value,-1);
|
||||
}
|
||||
if (options.globals) {
|
||||
setTimeout(function() {
|
||||
if (!!session.$worker) {
|
||||
session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]);
|
||||
}
|
||||
},100);
|
||||
}
|
||||
if (options.mode === 'ace/mode/markdown') {
|
||||
$(el).addClass("red-ui-editor-text-container-toolbar");
|
||||
editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
|
||||
if (options.expandable !== false) {
|
||||
var expandButton = $('<button type="button" class="red-ui-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
|
||||
RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
|
||||
expandButton.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var value = editor.getValue();
|
||||
RED.editor.editMarkdown({
|
||||
value: value,
|
||||
width: "Infinity",
|
||||
cursor: editor.getCursorPosition(),
|
||||
complete: function(v,cursor) {
|
||||
editor.setValue(v, -1);
|
||||
editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||
setTimeout(function() {
|
||||
editor.focus();
|
||||
},300);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
var helpButton = $('<button type="button" class="red-ui-editor-text-help red-ui-button red-ui-button-small"><i class="fa fa-question"></i></button>').appendTo($(el).parent());
|
||||
RED.popover.create({
|
||||
target: helpButton,
|
||||
trigger: 'click',
|
||||
size: "small",
|
||||
direction: "left",
|
||||
content: RED._("markdownEditor.format"),
|
||||
autoClose: 50
|
||||
});
|
||||
session.setUseWrapMode(true);
|
||||
}
|
||||
editor._destroy = editor.destroy;
|
||||
editor.destroy = function() {
|
||||
try {
|
||||
this._destroy();
|
||||
} catch (e) { }
|
||||
$(el).remove();
|
||||
$(toolbarRow).remove();
|
||||
}
|
||||
editor.type = type;
|
||||
return editor;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Editor type
|
||||
* @memberof RED.editor.codeEditor.ace
|
||||
*/
|
||||
get type() { return type; },
|
||||
/**
|
||||
* Editor initialised
|
||||
* @memberof RED.editor.codeEditor.ace
|
||||
*/
|
||||
get initialised() { return initialised; },
|
||||
/**
|
||||
* Initialise code editor
|
||||
* @param {object} options - initialisation options
|
||||
* @memberof RED.editor.codeEditor.ace
|
||||
*/
|
||||
init: init,
|
||||
/**
|
||||
* Create a code editor
|
||||
* @param {object} options - the editor options
|
||||
* @memberof RED.editor.codeEditor.ace
|
||||
*/
|
||||
create: create
|
||||
}
|
||||
})();
|
||||
1391
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
vendored
Normal file
1391
packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
RED.colorPicker = (function() {
|
||||
RED.editor.colorPicker = RED.colorPicker = (function() {
|
||||
|
||||
function create(options) {
|
||||
var color = options.value;
|
||||
616
packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js
vendored
Normal file
616
packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js
vendored
Normal file
@@ -0,0 +1,616 @@
|
||||
RED.editor.envVarList = (function() {
|
||||
|
||||
var currentLocale = 'en-US';
|
||||
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
||||
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
|
||||
|
||||
/**
|
||||
* Create env var edit interface
|
||||
* @param container - container
|
||||
* @param node - subflow node
|
||||
*/
|
||||
function buildPropertiesList(envContainer, node) {
|
||||
|
||||
var isTemplateNode = (node.type === "subflow");
|
||||
|
||||
envContainer
|
||||
.css({
|
||||
'min-height':'150px',
|
||||
'min-width':'450px'
|
||||
})
|
||||
.editableList({
|
||||
header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined,
|
||||
addItem: function(container, i, opt) {
|
||||
// If this is an instance node, these are properties unique to
|
||||
// this instance - ie opt.parent will not be defined.
|
||||
|
||||
if (isTemplateNode) {
|
||||
container.addClass("red-ui-editor-subflow-env-editable")
|
||||
}
|
||||
|
||||
var envRow = $('<div/>').appendTo(container);
|
||||
var nameField = null;
|
||||
var valueField = null;
|
||||
|
||||
nameField = $('<input/>', {
|
||||
class: "node-input-env-name",
|
||||
type: "text",
|
||||
placeholder: RED._("common.label.name")
|
||||
}).attr("autocomplete","disable").appendTo(envRow).val(opt.name);
|
||||
valueField = $('<input/>',{
|
||||
style: "width:100%",
|
||||
class: "node-input-env-value",
|
||||
type: "text",
|
||||
}).attr("autocomplete","disable").appendTo(envRow)
|
||||
valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED});
|
||||
valueField.typedInput('type', opt.type);
|
||||
if (opt.type === "cred") {
|
||||
if (opt.value) {
|
||||
valueField.typedInput('value', opt.value);
|
||||
} else if (node.credentials && node.credentials[opt.name]) {
|
||||
valueField.typedInput('value', node.credentials[opt.name]);
|
||||
} else if (node.credentials && node.credentials['has_'+opt.name]) {
|
||||
valueField.typedInput('value', "__PWRD__");
|
||||
} else {
|
||||
valueField.typedInput('value', "");
|
||||
}
|
||||
} else {
|
||||
valueField.typedInput('value', opt.value);
|
||||
}
|
||||
|
||||
|
||||
opt.nameField = nameField;
|
||||
opt.valueField = valueField;
|
||||
|
||||
var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow);
|
||||
$('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
|
||||
var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
|
||||
actionButton.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
removeTip.close();
|
||||
container.parent().addClass("red-ui-editableList-item-deleting")
|
||||
container.fadeOut(300, function() {
|
||||
envContainer.editableList('removeItem',opt);
|
||||
});
|
||||
});
|
||||
|
||||
if (isTemplateNode) {
|
||||
// Add the UI customisation row
|
||||
// if `opt.ui` does not exist, then apply defaults. If these
|
||||
// defaults do not change then they will get stripped off
|
||||
// before saving.
|
||||
if (opt.type === 'cred') {
|
||||
opt.ui = opt.ui || {
|
||||
icon: "",
|
||||
type: "cred"
|
||||
}
|
||||
opt.ui.type = "cred";
|
||||
} else {
|
||||
opt.ui = opt.ui || {
|
||||
icon: "",
|
||||
type: "input",
|
||||
opts: {types:DEFAULT_ENV_TYPE_LIST}
|
||||
}
|
||||
}
|
||||
opt.ui.label = opt.ui.label || {};
|
||||
opt.ui.type = opt.ui.type || "input";
|
||||
|
||||
var uiRow = $('<div/>').appendTo(container).hide();
|
||||
// save current info for reverting on cancel
|
||||
// var copy = $.extend(true, {}, ui);
|
||||
|
||||
$('<a href="#"><i class="fa fa-angle-right"></a>').prependTo(envRow).on("click", function (evt) {
|
||||
evt.preventDefault();
|
||||
if ($(this).hasClass('expanded')) {
|
||||
uiRow.slideUp();
|
||||
$(this).removeClass('expanded');
|
||||
} else {
|
||||
uiRow.slideDown();
|
||||
$(this).addClass('expanded');
|
||||
}
|
||||
});
|
||||
|
||||
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
|
||||
nameField.trigger('change');
|
||||
}
|
||||
},
|
||||
sortable: ".red-ui-editableList-item-handle",
|
||||
removable: false
|
||||
});
|
||||
var parentEnv = {};
|
||||
var envList = [];
|
||||
if (/^subflow:/.test(node.type)) {
|
||||
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
||||
if (subflowDef.env) {
|
||||
subflowDef.env.forEach(function(env) {
|
||||
var item = {
|
||||
name:env.name,
|
||||
parent: {
|
||||
type: env.type,
|
||||
value: env.value,
|
||||
ui: env.ui
|
||||
}
|
||||
}
|
||||
envList.push(item);
|
||||
parentEnv[env.name] = item;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (node.env) {
|
||||
for (var i = 0; i < node.env.length; i++) {
|
||||
var env = node.env[i];
|
||||
if (parentEnv.hasOwnProperty(env.name)) {
|
||||
parentEnv[env.name].type = env.type;
|
||||
parentEnv[env.name].value = env.value;
|
||||
} else {
|
||||
envList.push({
|
||||
name: env.name,
|
||||
type: env.type,
|
||||
value: env.value,
|
||||
ui: env.ui
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
envList.forEach(function(env) {
|
||||
if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') {
|
||||
return;
|
||||
}
|
||||
if (!isTemplateNode && env.parent) {
|
||||
return;
|
||||
}
|
||||
envContainer.editableList('addItem', JSON.parse(JSON.stringify(env)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create UI edit interface for environment variable
|
||||
* @param container - container
|
||||
* @param env - env var definition
|
||||
* @param nameField - name field of env var
|
||||
* @param valueField - value field of env var
|
||||
*/
|
||||
function buildEnvEditRow(container, ui, nameField, valueField) {
|
||||
container.addClass("red-ui-editor-subflow-env-ui-row")
|
||||
var topRow = $('<div></div>').appendTo(container);
|
||||
$('<div></div>').appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
|
||||
|
||||
var row = $('<div></div>').appendTo(container);
|
||||
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
||||
var typeOptions = {
|
||||
'input': {types:DEFAULT_ENV_TYPE_LIST},
|
||||
'select': {opts:[]},
|
||||
'spinner': {},
|
||||
'cred': {}
|
||||
};
|
||||
if (ui.opts) {
|
||||
typeOptions[ui.type] = ui.opts;
|
||||
} else {
|
||||
// Pick up the default values if not otherwise provided
|
||||
ui.opts = typeOptions[ui.type];
|
||||
}
|
||||
var iconCell = $('<div></div>').appendTo(row);
|
||||
|
||||
var iconButton = $('<a href="#"></a>').appendTo(iconCell);
|
||||
iconButton.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
var icon = ui.icon || "";
|
||||
var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
|
||||
RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) {
|
||||
iconButton.empty();
|
||||
var path = newIcon || "";
|
||||
var newPath = RED.utils.separateIconPath(path);
|
||||
if (newPath) {
|
||||
$('<i class="fa"></i>').addClass(newPath.file).appendTo(iconButton);
|
||||
}
|
||||
ui.icon = path;
|
||||
});
|
||||
})
|
||||
|
||||
if (ui.icon) {
|
||||
var newPath = RED.utils.separateIconPath(ui.icon);
|
||||
$('<i class="fa '+newPath.file+'"></i>').appendTo(iconButton);
|
||||
}
|
||||
|
||||
var labelCell = $('<div></div>').appendTo(row);
|
||||
|
||||
var label = ui.label && ui.label[currentLocale] || "";
|
||||
var labelInput = $('<input type="text">').val(label).appendTo(labelCell);
|
||||
ui.labelField = labelInput;
|
||||
labelInput.on('change', function(evt) {
|
||||
ui.label = ui.label || {};
|
||||
var val = $(this).val().trim();
|
||||
if (val === "") {
|
||||
delete ui.label[currentLocale];
|
||||
} else {
|
||||
ui.label[currentLocale] = val;
|
||||
}
|
||||
})
|
||||
var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelCell);
|
||||
RED.popover.tooltip(labelIcon,function() {
|
||||
var langs = Object.keys(ui.label);
|
||||
var content = $("<div>");
|
||||
if (langs.indexOf(currentLocale) === -1) {
|
||||
langs.push(currentLocale);
|
||||
langs.sort();
|
||||
}
|
||||
langs.forEach(function(l) {
|
||||
var row = $('<div>').appendTo(content);
|
||||
$('<span>').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row);
|
||||
$('<span>').text(ui.label[l]||"").appendTo(row);
|
||||
});
|
||||
return content;
|
||||
})
|
||||
|
||||
nameField.on('change',function(evt) {
|
||||
labelInput.attr("placeholder",$(this).val())
|
||||
});
|
||||
|
||||
var inputCell = $('<div></div>').appendTo(row);
|
||||
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
||||
if (ui.type === "input") {
|
||||
inputCellInput.val(ui.opts.types.join(","));
|
||||
}
|
||||
var checkbox;
|
||||
var selectBox;
|
||||
|
||||
inputCellInput.typedInput({
|
||||
types: [
|
||||
{
|
||||
value:"input",
|
||||
label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[
|
||||
{value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"},
|
||||
{value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"},
|
||||
{value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"},
|
||||
{value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"},
|
||||
{value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"},
|
||||
{value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"},
|
||||
{value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"}
|
||||
],
|
||||
default: DEFAULT_ENV_TYPE_LIST,
|
||||
valueLabel: function(container,value) {
|
||||
container.css("padding",0);
|
||||
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
|
||||
|
||||
var input = $('<div class="placeholder-input">').appendTo(innerContainer);
|
||||
$('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input);
|
||||
if (value.length) {
|
||||
value.forEach(function(v) {
|
||||
if (!/^fa /.test(v.icon)) {
|
||||
$('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
|
||||
} else {
|
||||
var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
|
||||
$("<i>",{class: v.icon}).appendTo(s);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$('<span class="red-ui-editor-subflow-env-input-type-placeholder"></span>').text(RED._("editor.selectType")).appendTo(input);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value: "cred",
|
||||
label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false,
|
||||
valueLabel: function(container,value) {
|
||||
container.css("padding",0);
|
||||
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type">').css({
|
||||
"border-top-right-radius": "4px",
|
||||
"border-bottom-right-radius": "4px"
|
||||
}).appendTo(container);
|
||||
$('<div class="placeholder-input">').html("••••••••").appendTo(innerContainer);
|
||||
}
|
||||
},
|
||||
{
|
||||
value:"select",
|
||||
label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
|
||||
valueLabel: function(container,value) {
|
||||
container.css("padding","0");
|
||||
|
||||
selectBox = $('<select></select>').appendTo(container);
|
||||
if (ui.opts && Array.isArray(ui.opts.opts)) {
|
||||
ui.opts.opts.forEach(function(o) {
|
||||
var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale);
|
||||
// $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox);
|
||||
$('<option>').val(o.v).text(label).appendTo(selectBox);
|
||||
})
|
||||
}
|
||||
selectBox.on('change', function(evt) {
|
||||
var v = selectBox.val();
|
||||
// var parts = v.split(":");
|
||||
// var t = parts.shift();
|
||||
// v = parts.join(":");
|
||||
//
|
||||
// valueField.typedInput("type",'str')
|
||||
valueField.typedInput("value",v)
|
||||
});
|
||||
selectBox.val(valueField.typedInput("value"));
|
||||
// selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value"));
|
||||
},
|
||||
expand: {
|
||||
icon: "fa-caret-down",
|
||||
minWidth: 400,
|
||||
content: function(container) {
|
||||
var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
|
||||
var optList = $('<ol>').appendTo(content).editableList({
|
||||
header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"),
|
||||
addItem: function(row,index,itemData) {
|
||||
var labelDiv = $('<div>').appendTo(row);
|
||||
var label = lookupLabel(itemData.l, "", currentLocale);
|
||||
itemData.label = $('<input type="text">').val(label).appendTo(labelDiv);
|
||||
itemData.label.on('keydown', function(evt) {
|
||||
if (evt.keyCode === 13) {
|
||||
itemData.input.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv);
|
||||
RED.popover.tooltip(labelIcon,function() {
|
||||
return currentLocale;
|
||||
})
|
||||
itemData.input = $('<input type="text">').val(itemData.v).appendTo(row);
|
||||
|
||||
// Problem using a TI here:
|
||||
// - this is in a popout panel
|
||||
// - clicking the expand button in the TI will close the parent edit tray
|
||||
// and open the type editor.
|
||||
// - but it leaves the popout panel over the top.
|
||||
// - there is no way to get back to the popout panel after closing the type editor
|
||||
//.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST});
|
||||
itemData.input.on('keydown', function(evt) {
|
||||
if (evt.keyCode === 13) {
|
||||
// Enter or Tab
|
||||
var index = optList.editableList('indexOf',itemData);
|
||||
var length = optList.editableList('length');
|
||||
if (index + 1 === length) {
|
||||
var newItem = {};
|
||||
optList.editableList('addItem',newItem);
|
||||
setTimeout(function() {
|
||||
if (newItem.label) {
|
||||
newItem.label.focus();
|
||||
}
|
||||
},100)
|
||||
} else {
|
||||
var nextItem = optList.editableList('getItemAt',index+1);
|
||||
if (nextItem.label) {
|
||||
nextItem.label.focus()
|
||||
}
|
||||
}
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
sortable: true,
|
||||
removable: true,
|
||||
height: 160
|
||||
})
|
||||
if (ui.opts.opts.length > 0) {
|
||||
ui.opts.opts.forEach(function(o) {
|
||||
optList.editableList('addItem',$.extend(true,{},o))
|
||||
})
|
||||
} else {
|
||||
optList.editableList('addItem',{})
|
||||
}
|
||||
return {
|
||||
onclose: function() {
|
||||
var items = optList.editableList('items');
|
||||
var vals = [];
|
||||
items.each(function (i,el) {
|
||||
var data = el.data('data');
|
||||
var l = data.label.val().trim();
|
||||
var v = data.input.val();
|
||||
// var t = data.input.typedInput('type');
|
||||
// var v = data.input.typedInput('value');
|
||||
if (l.length > 0) {
|
||||
data.l = data.l || {};
|
||||
data.l[currentLocale] = l;
|
||||
}
|
||||
data.v = v;
|
||||
|
||||
if (l.length > 0 || v.length > 0) {
|
||||
var val = {l:data.l,v:data.v};
|
||||
// if (t !== 'str') {
|
||||
// val.t = t;
|
||||
// }
|
||||
vals.push(val);
|
||||
}
|
||||
});
|
||||
ui.opts.opts = vals;
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value:"checkbox",
|
||||
label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false,
|
||||
valueLabel: function(container,value) {
|
||||
container.css("padding",0);
|
||||
checkbox = $('<input type="checkbox">').appendTo(container);
|
||||
checkbox.on('change', function(evt) {
|
||||
valueField.typedInput('value',$(this).prop('checked')?"true":"false");
|
||||
})
|
||||
checkbox.prop('checked',valueField.typedInput('value')==="true");
|
||||
}
|
||||
},
|
||||
{
|
||||
value:"spinner",
|
||||
label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false,
|
||||
valueLabel: function(container,value) {
|
||||
container.css("padding",0);
|
||||
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').appendTo(container);
|
||||
|
||||
var input = $('<div class="placeholder-input">').appendTo(innerContainer);
|
||||
$('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input);
|
||||
|
||||
var min = ui.opts && ui.opts.min;
|
||||
var max = ui.opts && ui.opts.max;
|
||||
var label = "";
|
||||
if (min !== undefined && max !== undefined) {
|
||||
label = Math.min(min,max)+" - "+Math.max(min,max);
|
||||
} else if (min !== undefined) {
|
||||
label = "> "+min;
|
||||
} else if (max !== undefined) {
|
||||
label = "< "+max;
|
||||
}
|
||||
$('<span>').css("margin-left","15px").text(label).appendTo(input);
|
||||
},
|
||||
expand: {
|
||||
icon: "fa-caret-down",
|
||||
content: function(container) {
|
||||
var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
|
||||
content.css("padding","8px 5px")
|
||||
var min = ui.opts.min;
|
||||
var max = ui.opts.max;
|
||||
var minInput = $('<input type="number" style="margin-bottom:0; width:60px">');
|
||||
minInput.val(min);
|
||||
var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">');
|
||||
maxInput.val(max);
|
||||
$('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content);
|
||||
$('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').append(maxInput).appendTo(content);
|
||||
return {
|
||||
onclose: function() {
|
||||
var min = minInput.val().trim();
|
||||
var max = maxInput.val().trim();
|
||||
if (min !== "") {
|
||||
ui.opts.min = parseInt(min);
|
||||
} else {
|
||||
delete ui.opts.min;
|
||||
}
|
||||
if (max !== "") {
|
||||
ui.opts.max = parseInt(max);
|
||||
} else {
|
||||
delete ui.opts.max;
|
||||
}
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value:"none",
|
||||
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
||||
},
|
||||
{
|
||||
value:"hide",
|
||||
label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false
|
||||
}
|
||||
],
|
||||
default: 'none'
|
||||
}).on("typedinputtypechange", function(evt,type) {
|
||||
ui.type = $(this).typedInput("type");
|
||||
ui.opts = typeOptions[ui.type];
|
||||
if (ui.type === 'input') {
|
||||
// In the case of 'input' type, the typedInput uses the multiple-option
|
||||
// mode. Its value needs to be set to a comma-separately list of the
|
||||
// selected options.
|
||||
inputCellInput.typedInput('value',ui.opts.types.join(","))
|
||||
} else {
|
||||
// No other type cares about `value`, but doing this will
|
||||
// force a refresh of the label now that `ui.opts` has
|
||||
// been updated.
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
}
|
||||
|
||||
switch (ui.type) {
|
||||
case 'input':
|
||||
valueField.typedInput('types',ui.opts.types);
|
||||
break;
|
||||
case 'select':
|
||||
valueField.typedInput('types',['str']);
|
||||
break;
|
||||
case 'checkbox':
|
||||
valueField.typedInput('types',['bool']);
|
||||
break;
|
||||
case 'spinner':
|
||||
valueField.typedInput('types',['num']);
|
||||
break;
|
||||
case 'cred':
|
||||
valueField.typedInput('types',['cred']);
|
||||
break;
|
||||
default:
|
||||
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
|
||||
}
|
||||
if (ui.type === 'checkbox') {
|
||||
valueField.typedInput('type','bool');
|
||||
} else if (ui.type === 'spinner') {
|
||||
valueField.typedInput('type','num');
|
||||
}
|
||||
if (ui.type !== 'checkbox') {
|
||||
checkbox = null;
|
||||
}
|
||||
|
||||
}).on("change", function(evt,type) {
|
||||
if (ui.type === 'input') {
|
||||
var types = inputCellInput.typedInput('value');
|
||||
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
||||
valueField.typedInput('types',ui.opts.types);
|
||||
}
|
||||
});
|
||||
valueField.on("change", function(evt) {
|
||||
if (checkbox) {
|
||||
checkbox.prop('checked',$(this).typedInput('value')==="true")
|
||||
}
|
||||
})
|
||||
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
||||
// event handler (just above ^^) to update the value if needed
|
||||
inputCellInput.typedInput('type',ui.type)
|
||||
}
|
||||
|
||||
function setLocale(l, list) {
|
||||
currentLocale = l;
|
||||
if (list) {
|
||||
var items = list.editableList("items");
|
||||
items.each(function (i, item) {
|
||||
var entry = $(this).data('data');
|
||||
var labelField = entry.ui.labelField;
|
||||
labelField.val(lookupLabel(entry.ui.label, "", currentLocale));
|
||||
if (labelField.timeout) {
|
||||
clearTimeout(labelField.timeout);
|
||||
delete labelField.timeout;
|
||||
}
|
||||
labelField.addClass("input-updated");
|
||||
labelField.timeout = setTimeout(function() {
|
||||
delete labelField.timeout
|
||||
labelField.removeClass("input-updated");
|
||||
},3000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup text for specific locale
|
||||
* @param labels - dict of labels
|
||||
* @param defaultLabel - fallback label if not found
|
||||
* @param locale - target locale
|
||||
* @returns {string} text for specified locale
|
||||
*/
|
||||
function lookupLabel(labels, defaultLabel, locale) {
|
||||
if (labels) {
|
||||
if (labels[locale]) {
|
||||
return labels[locale];
|
||||
}
|
||||
if (locale) {
|
||||
var lang = locale.substring(0, 2);
|
||||
if (labels[lang]) {
|
||||
return labels[lang];
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultLabel;
|
||||
}
|
||||
|
||||
return {
|
||||
create: buildPropertiesList,
|
||||
setLocale: setLocale,
|
||||
lookupLabel: lookupLabel,
|
||||
DEFAULT_ENV_TYPE_LIST: DEFAULT_ENV_TYPE_LIST,
|
||||
DEFAULT_ENV_TYPE_LIST_INC_CRED: DEFAULT_ENV_TYPE_LIST_INC_CRED
|
||||
}
|
||||
})();
|
||||
@@ -121,73 +121,75 @@
|
||||
var currentFunctionMarker = null;
|
||||
|
||||
expressionEditor.getSession().setValue(value||"",-1);
|
||||
expressionEditor.on("changeSelection", function() {
|
||||
var c = expressionEditor.getCursorPosition();
|
||||
var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
|
||||
if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
|
||||
currentToken = token;
|
||||
var r,p;
|
||||
var scopedFunction = null;
|
||||
if (token && token.type === 'keyword') {
|
||||
r = c.row;
|
||||
scopedFunction = token;
|
||||
} else {
|
||||
var depth = 0;
|
||||
var next = false;
|
||||
if (token) {
|
||||
if (token.type === 'paren.rparen') {
|
||||
// If this is a block of parens ')))', set
|
||||
// depth to offset against the cursor position
|
||||
// within the block
|
||||
currentTokenPos = c.column;
|
||||
depth = c.column - (token.start + token.value.length);
|
||||
}
|
||||
//ace only (monaco has jsonata tokeniser)
|
||||
if(expressionEditor.type == "ace") {
|
||||
expressionEditor.on("changeSelection", function() {
|
||||
var c = expressionEditor.getCursorPosition();
|
||||
var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
|
||||
if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
|
||||
currentToken = token;
|
||||
var r,p;
|
||||
var scopedFunction = null;
|
||||
if (token && token.type === 'keyword') {
|
||||
r = c.row;
|
||||
p = token.index;
|
||||
scopedFunction = token;
|
||||
} else {
|
||||
r = c.row-1;
|
||||
p = -1;
|
||||
}
|
||||
while ( scopedFunction === null && r > -1) {
|
||||
var rowTokens = expressionEditor.getSession().getTokens(r);
|
||||
if (p === -1) {
|
||||
p = rowTokens.length-1;
|
||||
var depth = 0;
|
||||
var next = false;
|
||||
if (token) {
|
||||
if (token.type === 'paren.rparen') {
|
||||
// If this is a block of parens ')))', set
|
||||
// depth to offset against the cursor position
|
||||
// within the block
|
||||
currentTokenPos = c.column;
|
||||
depth = c.column - (token.start + token.value.length);
|
||||
}
|
||||
r = c.row;
|
||||
p = token.index;
|
||||
} else {
|
||||
r = c.row-1;
|
||||
p = -1;
|
||||
}
|
||||
while (p > -1) {
|
||||
var type = rowTokens[p].type;
|
||||
if (next) {
|
||||
if (type === 'keyword') {
|
||||
scopedFunction = rowTokens[p];
|
||||
// console.log("HIT",scopedFunction);
|
||||
break;
|
||||
while ( scopedFunction === null && r > -1) {
|
||||
var rowTokens = expressionEditor.getSession().getTokens(r);
|
||||
if (p === -1) {
|
||||
p = rowTokens.length-1;
|
||||
}
|
||||
while (p > -1) {
|
||||
var type = rowTokens[p].type;
|
||||
if (next) {
|
||||
if (type === 'keyword') {
|
||||
scopedFunction = rowTokens[p];
|
||||
// console.log("HIT",scopedFunction);
|
||||
break;
|
||||
}
|
||||
next = false;
|
||||
}
|
||||
next = false;
|
||||
if (type === 'paren.lparen') {
|
||||
depth-=rowTokens[p].value.length;
|
||||
} else if (type === 'paren.rparen') {
|
||||
depth+=rowTokens[p].value.length;
|
||||
}
|
||||
if (depth < 0) {
|
||||
next = true;
|
||||
depth = 0;
|
||||
}
|
||||
// console.log(r,p,depth,next,rowTokens[p]);
|
||||
p--;
|
||||
}
|
||||
if (type === 'paren.lparen') {
|
||||
depth-=rowTokens[p].value.length;
|
||||
} else if (type === 'paren.rparen') {
|
||||
depth+=rowTokens[p].value.length;
|
||||
if (!scopedFunction) {
|
||||
r--;
|
||||
}
|
||||
if (depth < 0) {
|
||||
next = true;
|
||||
depth = 0;
|
||||
}
|
||||
// console.log(r,p,depth,next,rowTokens[p]);
|
||||
p--;
|
||||
}
|
||||
if (!scopedFunction) {
|
||||
r--;
|
||||
}
|
||||
}
|
||||
expressionEditor.session.removeMarker(currentFunctionMarker);
|
||||
if (scopedFunction) {
|
||||
//console.log(token,.map(function(t) { return t.type}));
|
||||
funcSelect.val(scopedFunction.value).trigger("change");
|
||||
}
|
||||
}
|
||||
expressionEditor.session.removeMarker(currentFunctionMarker);
|
||||
if (scopedFunction) {
|
||||
//console.log(token,.map(function(t) { return t.type}));
|
||||
funcSelect.val(scopedFunction.value).trigger("change");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
dialogForm.i18n();
|
||||
$("#red-ui-editor-type-expression-func-insert").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
@@ -343,6 +345,7 @@
|
||||
}
|
||||
expressionEditor.destroy();
|
||||
testDataEditor.destroy();
|
||||
testResultEditor.destroy();
|
||||
},
|
||||
show: function() {}
|
||||
}
|
||||
|
||||
99
packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js
vendored
Normal file
99
packages/node_modules/@node-red/editor-client/src/js/ui/editors/iconPicker.js
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
RED.editor.iconPicker = (function() {
|
||||
function showIconPicker(container, backgroundColor, iconPath, faOnly, done) {
|
||||
var picker = $('<div class="red-ui-icon-picker">');
|
||||
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(picker);
|
||||
searchInput = $('<input type="text">').attr("placeholder",RED._("editor.searchIcons")).appendTo(searchDiv).searchBox({
|
||||
delay: 50,
|
||||
change: function() {
|
||||
var searchTerm = $(this).val().trim();
|
||||
if (searchTerm === "") {
|
||||
iconList.find(".red-ui-icon-list-module").show();
|
||||
iconList.find(".red-ui-icon-list-icon").show();
|
||||
} else {
|
||||
iconList.find(".red-ui-icon-list-module").hide();
|
||||
iconList.find(".red-ui-icon-list-icon").each(function(i,n) {
|
||||
if ($(n).data('icon').indexOf(searchTerm) === -1) {
|
||||
$(n).hide();
|
||||
} else {
|
||||
$(n).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var row = $('<div>').appendTo(picker);
|
||||
var iconList = $('<div class="red-ui-icon-list">').appendTo(picker);
|
||||
var metaRow = $('<div class="red-ui-icon-meta"></div>').appendTo(picker);
|
||||
var summary = $('<span>').appendTo(metaRow);
|
||||
var resetButton = $('<button type="button" class="red-ui-button red-ui-button-small">'+RED._("editor.useDefault")+'</button>').appendTo(metaRow).on("click", function(e) {
|
||||
e.preventDefault();
|
||||
iconPanel.hide();
|
||||
done(null);
|
||||
});
|
||||
if (!backgroundColor && faOnly) {
|
||||
iconList.addClass("red-ui-icon-list-dark");
|
||||
}
|
||||
setTimeout(function() {
|
||||
var iconSets = RED.nodes.getIconSets();
|
||||
Object.keys(iconSets).forEach(function(moduleName) {
|
||||
if (faOnly && (moduleName !== "font-awesome")) {
|
||||
return;
|
||||
}
|
||||
var icons = iconSets[moduleName];
|
||||
if (icons.length > 0) {
|
||||
// selectIconModule.append($("<option></option>").val(moduleName).text(moduleName));
|
||||
var header = $('<div class="red-ui-icon-list-module"></div>').text(moduleName).appendTo(iconList);
|
||||
$('<i class="fa fa-cube"></i>').prependTo(header);
|
||||
icons.forEach(function(icon) {
|
||||
var iconDiv = $('<div>',{class:"red-ui-icon-list-icon"}).appendTo(iconList);
|
||||
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconDiv);
|
||||
var icon_url = RED.settings.apiRootUrl+"icons/"+moduleName+"/"+icon;
|
||||
iconDiv.data('icon',icon_url);
|
||||
if (backgroundColor) {
|
||||
nodeDiv.css({
|
||||
'backgroundColor': backgroundColor
|
||||
});
|
||||
var borderColor = RED.utils.getDarkerColor(backgroundColor);
|
||||
if (borderColor !== backgroundColor) {
|
||||
nodeDiv.css('border-color',borderColor)
|
||||
}
|
||||
|
||||
}
|
||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
||||
RED.utils.createIconElement(icon_url, iconContainer, true);
|
||||
|
||||
if (iconPath.module === moduleName && iconPath.file === icon) {
|
||||
iconDiv.addClass("selected");
|
||||
}
|
||||
iconDiv.on("mouseover", function() {
|
||||
summary.text(icon);
|
||||
})
|
||||
iconDiv.on("mouseout", function() {
|
||||
summary.html(" ");
|
||||
})
|
||||
iconDiv.on("click", function() {
|
||||
iconPanel.hide();
|
||||
done(moduleName+"/"+icon);
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
setTimeout(function() {
|
||||
spinner.remove();
|
||||
},50);
|
||||
},300);
|
||||
var spinner = RED.utils.addSpinnerOverlay(iconList,true);
|
||||
var iconPanel = RED.popover.panel(picker);
|
||||
iconPanel.show({
|
||||
target: container
|
||||
})
|
||||
|
||||
|
||||
picker.slideDown(100);
|
||||
searchInput.trigger("focus");
|
||||
}
|
||||
return {
|
||||
show: showIconPicker
|
||||
}
|
||||
})();
|
||||
@@ -602,10 +602,10 @@
|
||||
|
||||
},
|
||||
close: function() {
|
||||
// expressionEditor.destroy();
|
||||
if (options.onclose) {
|
||||
options.onclose();
|
||||
}
|
||||
expressionEditor.destroy();
|
||||
},
|
||||
show: function() {}
|
||||
}
|
||||
|
||||
517
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js
vendored
Normal file
517
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
;(function() {
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-appearance", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.appearance"),
|
||||
name: RED._("editor-tab.appearance"),
|
||||
iconClass: "fa fa-object-group",
|
||||
create: function(container) {
|
||||
this.content = container;
|
||||
buildAppearanceForm(this.content,node);
|
||||
|
||||
if (node.type === 'subflow') {
|
||||
this.defaultIcon = "node-red/subflow.svg";
|
||||
} else {
|
||||
var iconPath = RED.utils.getDefaultNodeIcon(node._def,node);
|
||||
this.defaultIcon = iconPath.module+"/"+iconPath.file;
|
||||
if (node.icon && node.icon !== this.defaultIcon) {
|
||||
this.isDefaultIcon = false;
|
||||
} else {
|
||||
this.isDefaultIcon = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
resize: function(size) {
|
||||
|
||||
},
|
||||
close: function() {
|
||||
|
||||
},
|
||||
show: function() {
|
||||
refreshLabelForm(this.content, node);
|
||||
},
|
||||
apply: function(editState) {
|
||||
if (updateLabels(node, editState.changes, editState.outputMap)) {
|
||||
editState.changed = true;
|
||||
}
|
||||
if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
|
||||
var icon = $("#red-ui-editor-node-icon").val()||""
|
||||
if (!this.isDefaultIcon) {
|
||||
if (icon !== node.icon) {
|
||||
editState.changes.icon = node.icon;
|
||||
node.icon = icon;
|
||||
editState.changed = true;
|
||||
}
|
||||
} else {
|
||||
if (icon !== "" && icon !== this.defaultIcon) {
|
||||
editState.changes.icon = node.icon;
|
||||
node.icon = icon;
|
||||
editState.changed = true;
|
||||
} else {
|
||||
var iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
|
||||
var currentDefaultIcon = iconPath.module+"/"+iconPath.file;
|
||||
if (this.defaultIcon !== currentDefaultIcon) {
|
||||
editState.changes.icon = node.icon;
|
||||
node.icon = currentDefaultIcon;
|
||||
editState.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.type === "subflow") {
|
||||
var newCategory = $("#subflow-appearance-input-category").val().trim();
|
||||
if (newCategory === "_custom_") {
|
||||
newCategory = $("#subflow-appearance-input-custom-category").val().trim();
|
||||
if (newCategory === "") {
|
||||
newCategory = node.category;
|
||||
}
|
||||
}
|
||||
if (newCategory === 'subflows') {
|
||||
newCategory = '';
|
||||
}
|
||||
if (newCategory != node.category) {
|
||||
editState.changes['category'] = node.category;
|
||||
node.category = newCategory;
|
||||
editState.changed = true;
|
||||
}
|
||||
|
||||
var oldColor = node.color;
|
||||
var newColor = $("#red-ui-editor-node-color").val();
|
||||
if (oldColor !== newColor) {
|
||||
editState.changes.color = node.color;
|
||||
node.color = newColor;
|
||||
editState.changed = true;
|
||||
RED.utils.clearNodeColorCache();
|
||||
if (node.type === "subflow") {
|
||||
var nodeDefinition = RED.nodes.getType(
|
||||
"subflow:" + node.id
|
||||
);
|
||||
nodeDefinition["color"] = newColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
var showLabel = node._def.hasOwnProperty("showLabel")?node._def.showLabel:true;
|
||||
|
||||
if (!$("#node-input-show-label").prop('checked')) {
|
||||
// Not checked - hide label
|
||||
|
||||
if (showLabel) {
|
||||
// Default to show label
|
||||
if (node.l !== false) {
|
||||
editState.changes.l = node.l
|
||||
editState.changed = true;
|
||||
}
|
||||
node.l = false;
|
||||
} else {
|
||||
// Node has showLabel:false (eg link nodes)
|
||||
if (node.hasOwnProperty('l') && node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changed = true;
|
||||
}
|
||||
delete node.l;
|
||||
}
|
||||
} else {
|
||||
// Checked - show label
|
||||
if (showLabel) {
|
||||
// Default to show label
|
||||
if (node.hasOwnProperty('l') && !node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changed = true;
|
||||
}
|
||||
delete node.l;
|
||||
} else {
|
||||
if (!node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changed = true;
|
||||
}
|
||||
node.l = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function buildAppearanceForm(container,node) {
|
||||
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
|
||||
|
||||
var i,row;
|
||||
|
||||
if (node.type === "subflow") {
|
||||
var categoryRow = $("<div/>", {
|
||||
class: "form-row"
|
||||
}).appendTo(dialogForm);
|
||||
$("<label/>", {
|
||||
for: "subflow-appearance-input-category",
|
||||
"data-i18n": "editor:subflow.category"
|
||||
}).appendTo(categoryRow);
|
||||
var categorySelector = $("<select/>", {
|
||||
id: "subflow-appearance-input-category"
|
||||
}).css({
|
||||
width: "250px"
|
||||
}).appendTo(categoryRow);
|
||||
$("<input/>", {
|
||||
type: "text",
|
||||
id: "subflow-appearance-input-custom-category"
|
||||
}).css({
|
||||
display: "none",
|
||||
"margin-left": "10px",
|
||||
width: "calc(100% - 250px)"
|
||||
}).appendTo(categoryRow);
|
||||
|
||||
var categories = RED.palette.getCategories();
|
||||
categories.sort(function(A,B) {
|
||||
return A.label.localeCompare(B.label);
|
||||
})
|
||||
categories.forEach(function(cat) {
|
||||
categorySelector.append($("<option/>").val(cat.id).text(cat.label));
|
||||
})
|
||||
categorySelector.append($("<option/>").attr('disabled',true).text("---"));
|
||||
categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
|
||||
|
||||
$("#subflow-appearance-input-category").on("change", function() {
|
||||
var val = $(this).val();
|
||||
if (val === "_custom_") {
|
||||
$("#subflow-appearance-input-category").width(120);
|
||||
$("#subflow-appearance-input-custom-category").show();
|
||||
} else {
|
||||
$("#subflow-appearance-input-category").width(250);
|
||||
$("#subflow-appearance-input-custom-category").hide();
|
||||
}
|
||||
})
|
||||
|
||||
$("#subflow-appearance-input-category").val(node.category||"subflows");
|
||||
var userCount = 0;
|
||||
var subflowType = "subflow:"+node.id;
|
||||
|
||||
// RED.nodes.eachNode(function(n) {
|
||||
// if (n.type === subflowType) {
|
||||
// userCount++;
|
||||
// }
|
||||
// });
|
||||
$("#red-ui-editor-subflow-user-count")
|
||||
.text(RED._("subflow.subflowInstances", {count:node.instances.length})).show();
|
||||
}
|
||||
|
||||
$('<div class="form-row">'+
|
||||
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
|
||||
'<span style="margin-right: 2px;"/>'+
|
||||
'<input type="checkbox" id="node-input-show-label"/>'+
|
||||
'</div>').appendTo(dialogForm);
|
||||
|
||||
$("#node-input-show-label").toggleButton({
|
||||
enabledLabel: RED._("editor.show"),
|
||||
disabledLabel: RED._("editor.hide")
|
||||
})
|
||||
|
||||
if (!node.hasOwnProperty("l")) {
|
||||
// Show label unless def.showLabel set to false
|
||||
node.l = node._def.hasOwnProperty("showLabel")?node._def.showLabel:true;
|
||||
}
|
||||
$("#node-input-show-label").prop("checked",node.l).trigger("change");
|
||||
|
||||
if (node.type === "subflow") {
|
||||
// subflow template can select its color
|
||||
var color = node.color ? node.color : "#DDAA99";
|
||||
var colorRow = $("<div/>", {
|
||||
class: "form-row"
|
||||
}).appendTo(dialogForm);
|
||||
$("<label/>").text(RED._("editor.color")).appendTo(colorRow);
|
||||
|
||||
var recommendedColors = [
|
||||
"#DDAA99",
|
||||
"#3FADB5", "#87A980", "#A6BBCF",
|
||||
"#AAAA66", "#C0C0C0", "#C0DEED",
|
||||
"#C7E9C0", "#D7D7A0", "#D8BFD8",
|
||||
"#DAC4B4", "#DEB887", "#DEBD5C",
|
||||
"#E2D96E", "#E6E0F8", "#E7E7AE",
|
||||
"#E9967A", "#F3B567", "#FDD0A2",
|
||||
"#FDF0C2", "#FFAAAA", "#FFCC66",
|
||||
"#FFF0F0", "#FFFFFF"
|
||||
]
|
||||
|
||||
RED.editor.colorPicker.create({
|
||||
id: "red-ui-editor-node-color",
|
||||
value: color,
|
||||
palette: recommendedColors,
|
||||
sortPalette: function (a, b) {return a.l - b.l;}
|
||||
}).appendTo(colorRow);
|
||||
|
||||
$("#red-ui-editor-node-color").on('change', function(ev) {
|
||||
// Horribly out of scope...
|
||||
var colour = $(this).val();
|
||||
nodeDiv.css('backgroundColor',colour);
|
||||
var borderColor = RED.utils.getDarkerColor(colour);
|
||||
if (borderColor !== colour) {
|
||||
nodeDiv.css('border-color',borderColor)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// If a node has icon property in defaults, the icon of the node cannot be modified. (e.g, ui_button node in dashboard)
|
||||
if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) {
|
||||
var iconRow = $('<div class="form-row"></div>').appendTo(dialogForm);
|
||||
$('<label data-i18n="editor.settingIcon">').appendTo(iconRow);
|
||||
|
||||
var iconButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(iconRow);
|
||||
$('<i class="fa fa-caret-down"></i>').appendTo(iconButton);
|
||||
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconButton);
|
||||
var colour = RED.utils.getNodeColor(node.type, node._def);
|
||||
var icon_url = RED.utils.getNodeIcon(node._def,node);
|
||||
nodeDiv.css('backgroundColor',colour);
|
||||
var borderColor = RED.utils.getDarkerColor(colour);
|
||||
if (borderColor !== colour) {
|
||||
nodeDiv.css('border-color',borderColor)
|
||||
}
|
||||
|
||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
||||
RED.utils.createIconElement(icon_url, iconContainer, true);
|
||||
|
||||
iconButton.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
var iconPath;
|
||||
var icon = $("#red-ui-editor-node-icon").val()||"";
|
||||
if (icon) {
|
||||
iconPath = RED.utils.separateIconPath(icon);
|
||||
} else {
|
||||
iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
|
||||
}
|
||||
var backgroundColor = RED.utils.getNodeColor(node.type, node._def);
|
||||
if (node.type === "subflow") {
|
||||
backgroundColor = $("#red-ui-editor-node-color").val();
|
||||
}
|
||||
RED.editor.iconPicker.show(iconButton,backgroundColor,iconPath,false,function(newIcon) {
|
||||
$("#red-ui-editor-node-icon").val(newIcon||"");
|
||||
var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
|
||||
RED.utils.createIconElement(icon_url, iconContainer, true);
|
||||
});
|
||||
});
|
||||
|
||||
RED.popover.tooltip(iconButton, function() {
|
||||
return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
|
||||
})
|
||||
$('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
|
||||
}
|
||||
|
||||
|
||||
$('<div class="form-row"><span data-i18n="editor.portLabels"></span></div>').appendTo(dialogForm);
|
||||
|
||||
var inputCount = node.inputs || node._def.inputs || 0;
|
||||
var outputCount = node.outputs || node._def.outputs || 0;
|
||||
if (node.type === 'subflow') {
|
||||
inputCount = node.in.length;
|
||||
outputCount = node.out.length;
|
||||
}
|
||||
|
||||
var inputLabels = node.inputLabels || [];
|
||||
var outputLabels = node.outputLabels || [];
|
||||
|
||||
var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
|
||||
var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
|
||||
|
||||
$('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelInputs"></span><div id="red-ui-editor-node-label-form-inputs"></div></div>').appendTo(dialogForm);
|
||||
var inputsDiv = $("#red-ui-editor-node-label-form-inputs");
|
||||
if (inputCount > 0) {
|
||||
for (i=0;i<inputCount;i++) {
|
||||
buildLabelRow("input",i,inputLabels[i],inputPlaceholder).appendTo(inputsDiv);
|
||||
}
|
||||
} else {
|
||||
buildLabelRow().appendTo(inputsDiv);
|
||||
}
|
||||
$('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelOutputs"></span><div id="red-ui-editor-node-label-form-outputs"></div></div>').appendTo(dialogForm);
|
||||
var outputsDiv = $("#red-ui-editor-node-label-form-outputs");
|
||||
if (outputCount > 0) {
|
||||
for (i=0;i<outputCount;i++) {
|
||||
buildLabelRow("output",i,outputLabels[i],outputPlaceholder).appendTo(outputsDiv);
|
||||
}
|
||||
} else {
|
||||
buildLabelRow().appendTo(outputsDiv);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshLabelForm(container,node) {
|
||||
|
||||
var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
|
||||
var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
|
||||
|
||||
var inputsDiv = $("#red-ui-editor-node-label-form-inputs");
|
||||
var outputsDiv = $("#red-ui-editor-node-label-form-outputs");
|
||||
|
||||
var inputCount;
|
||||
var formInputs = $("#node-input-inputs").val();
|
||||
if (formInputs === undefined) {
|
||||
if (node.type === 'subflow') {
|
||||
inputCount = node.in.length;
|
||||
} else {
|
||||
inputCount = node.inputs || node._def.inputs || 0;
|
||||
}
|
||||
} else {
|
||||
inputCount = Math.min(1,Math.max(0,parseInt(formInputs)));
|
||||
if (isNaN(inputCount)) {
|
||||
inputCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var children = inputsDiv.children();
|
||||
var childCount = children.length;
|
||||
if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
|
||||
childCount--;
|
||||
}
|
||||
|
||||
if (childCount < inputCount) {
|
||||
if (childCount === 0) {
|
||||
// remove the 'none' placeholder
|
||||
$(children[0]).remove();
|
||||
}
|
||||
for (i = childCount;i<inputCount;i++) {
|
||||
buildLabelRow("input",i,"",inputPlaceholder).appendTo(inputsDiv);
|
||||
}
|
||||
} else if (childCount > inputCount) {
|
||||
for (i=inputCount;i<childCount;i++) {
|
||||
$(children[i]).remove();
|
||||
}
|
||||
if (inputCount === 0) {
|
||||
buildLabelRow().appendTo(inputsDiv);
|
||||
}
|
||||
}
|
||||
|
||||
var outputCount;
|
||||
var i;
|
||||
var formOutputs = $("#node-input-outputs").val();
|
||||
|
||||
if (formOutputs === undefined) {
|
||||
if (node.type === 'subflow') {
|
||||
outputCount = node.out.length;
|
||||
} else {
|
||||
inputCount = node.outputs || node._def.outputs || 0;
|
||||
}
|
||||
} else if (isNaN(formOutputs)) {
|
||||
var outputMap = JSON.parse(formOutputs);
|
||||
var keys = Object.keys(outputMap);
|
||||
children = outputsDiv.children();
|
||||
childCount = children.length;
|
||||
if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
|
||||
childCount--;
|
||||
}
|
||||
|
||||
outputCount = 0;
|
||||
var rows = [];
|
||||
keys.forEach(function(p) {
|
||||
var row = $("#red-ui-editor-node-label-form-output-"+p).parent();
|
||||
if (row.length === 0 && outputMap[p] !== -1) {
|
||||
if (childCount === 0) {
|
||||
$(children[0]).remove();
|
||||
childCount = -1;
|
||||
}
|
||||
row = buildLabelRow("output",p,"",outputPlaceholder);
|
||||
} else {
|
||||
row.detach();
|
||||
}
|
||||
if (outputMap[p] !== -1) {
|
||||
outputCount++;
|
||||
rows.push({i:parseInt(outputMap[p]),r:row});
|
||||
}
|
||||
});
|
||||
rows.sort(function(A,B) {
|
||||
return A.i-B.i;
|
||||
})
|
||||
rows.forEach(function(r,i) {
|
||||
r.r.find("label").text((i+1)+".");
|
||||
r.r.appendTo(outputsDiv);
|
||||
})
|
||||
if (rows.length === 0) {
|
||||
buildLabelRow("output",i,"").appendTo(outputsDiv);
|
||||
} else {
|
||||
|
||||
}
|
||||
} else {
|
||||
outputCount = Math.max(0,parseInt(formOutputs));
|
||||
}
|
||||
children = outputsDiv.children();
|
||||
childCount = children.length;
|
||||
if (childCount === 1 && $(children[0]).hasClass('red-ui-editor-node-label-form-none')) {
|
||||
childCount--;
|
||||
}
|
||||
if (childCount < outputCount) {
|
||||
if (childCount === 0) {
|
||||
// remove the 'none' placeholder
|
||||
$(children[0]).remove();
|
||||
}
|
||||
for (i = childCount;i<outputCount;i++) {
|
||||
buildLabelRow("output",i,"").appendTo(outputsDiv);
|
||||
}
|
||||
} else if (childCount > outputCount) {
|
||||
for (i=outputCount;i<childCount;i++) {
|
||||
$(children[i]).remove();
|
||||
}
|
||||
if (outputCount === 0) {
|
||||
buildLabelRow().appendTo(outputsDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildLabelRow(type, index, value, placeHolder) {
|
||||
var result = $('<div>',{class:"red-ui-editor-node-label-form-row"});
|
||||
if (type === undefined) {
|
||||
$('<span>').text(RED._("editor.noDefaultLabel")).appendTo(result);
|
||||
result.addClass("red-ui-editor-node-label-form-none");
|
||||
} else {
|
||||
result.addClass("");
|
||||
var id = "red-ui-editor-node-label-form-"+type+"-"+index;
|
||||
$('<label>',{for:id}).text((index+1)+".").appendTo(result);
|
||||
var input = $('<input>',{type:"text",id:id, placeholder: placeHolder}).val(value).appendTo(result);
|
||||
var clear = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').appendTo(result);
|
||||
clear.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
input.val("");
|
||||
})
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateLabels(node, changes, outputMap) {
|
||||
var inputLabels = $("#red-ui-editor-node-label-form-inputs").children().find("input");
|
||||
var outputLabels = $("#red-ui-editor-node-label-form-outputs").children().find("input");
|
||||
|
||||
var hasNonBlankLabel = false;
|
||||
var changed = false;
|
||||
var newValue = inputLabels.map(function() {
|
||||
var v = $(this).val();
|
||||
hasNonBlankLabel = hasNonBlankLabel || v!== "";
|
||||
return v;
|
||||
}).toArray().slice(0,node.inputs);
|
||||
if ((node.inputLabels === undefined && hasNonBlankLabel) ||
|
||||
(node.inputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(node.inputLabels))) {
|
||||
changes.inputLabels = node.inputLabels;
|
||||
node.inputLabels = newValue;
|
||||
changed = true;
|
||||
}
|
||||
hasNonBlankLabel = false;
|
||||
newValue = new Array(node.outputs);
|
||||
outputLabels.each(function() {
|
||||
var index = $(this).attr('id').substring("red-ui-editor-node-label-form-output-".length);
|
||||
if (outputMap && outputMap.hasOwnProperty(index)) {
|
||||
index = parseInt(outputMap[index]);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var v = $(this).val();
|
||||
hasNonBlankLabel = hasNonBlankLabel || v!== "";
|
||||
newValue[index] = v;
|
||||
});
|
||||
|
||||
if ((node.outputLabels === undefined && hasNonBlankLabel) ||
|
||||
(node.outputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(node.outputLabels))) {
|
||||
changes.outputLabels = node.outputLabels;
|
||||
node.outputLabels = newValue;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
70
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js
vendored
Normal file
70
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/description.js
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
;(function() {
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-description", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.description"),
|
||||
name: RED._("editor-tab.description"),
|
||||
iconClass: "fa fa-file-text-o",
|
||||
|
||||
create: function(container) {
|
||||
this.editor = buildDescriptionForm(container,node);
|
||||
RED.e = this.editor;
|
||||
},
|
||||
resize: function(size) {
|
||||
this.editor.resize();
|
||||
},
|
||||
close: function() {
|
||||
this.editor.destroy();
|
||||
this.editor = null;
|
||||
},
|
||||
show: function() {
|
||||
this.editor.focus();
|
||||
},
|
||||
apply: function(editState) {
|
||||
var oldInfo = node.info;
|
||||
var newInfo = this.editor.getValue();
|
||||
if (!!oldInfo) {
|
||||
// Has existing info property
|
||||
if (newInfo.trim() === "") {
|
||||
// New value is blank - remove the property
|
||||
editState.changed = true;
|
||||
editState.changes.info = oldInfo;
|
||||
delete node.info;
|
||||
} else if (newInfo !== oldInfo) {
|
||||
// New value is different
|
||||
editState.changed = true;
|
||||
editState.changes.info = oldInfo;
|
||||
node.info = newInfo;
|
||||
}
|
||||
} else {
|
||||
// No existing info
|
||||
if (newInfo.trim() !== "") {
|
||||
// New value is not blank
|
||||
editState.changed = true;
|
||||
editState.changes.info = undefined;
|
||||
node.info = newInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function buildDescriptionForm(container,node) {
|
||||
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
|
||||
var toolbarRow = $('<div></div>').appendTo(dialogForm);
|
||||
var row = $('<div class="form-row node-text-editor-row" style="position:relative; padding-top: 4px; height: 100%"></div>').appendTo(dialogForm);
|
||||
var editorId = "node-info-input-info-editor-"+Math.floor(1000*Math.random());
|
||||
$('<div style="height: 100%" class="node-text-editor" id="'+editorId+'" ></div>').appendTo(row);
|
||||
var nodeInfoEditor = RED.editor.createEditor({
|
||||
id: editorId,
|
||||
mode: 'ace/mode/markdown',
|
||||
value: ""
|
||||
});
|
||||
if (node.info) {
|
||||
nodeInfoEditor.getSession().setValue(node.info, -1);
|
||||
}
|
||||
node.infoEditor = nodeInfoEditor;
|
||||
return nodeInfoEditor;
|
||||
}
|
||||
|
||||
})();
|
||||
69
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js
vendored
Normal file
69
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/envVarProperties.js
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
;(function() {
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-envProperties", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.envProperties"),
|
||||
name: RED._("editor-tab.envProperties"),
|
||||
iconClass: "fa fa-list",
|
||||
create: function(container) {
|
||||
var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container);
|
||||
var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form);
|
||||
this.list = $('<ol></ol>').appendTo(listContainer);
|
||||
RED.editor.envVarList.create(this.list, node);
|
||||
},
|
||||
resize: function(size) {
|
||||
this.list.editableList('height',size.height);
|
||||
},
|
||||
close: function() {
|
||||
|
||||
},
|
||||
apply: function(editState) {
|
||||
var old_env = node.env;
|
||||
var new_env = [];
|
||||
if (/^subflow:/.test(node.type)) {
|
||||
new_env = RED.subflow.exportSubflowInstanceEnv(node);
|
||||
}
|
||||
|
||||
// Get the values from the Properties table tab
|
||||
var items = this.list.editableList('items');
|
||||
items.each(function (i,el) {
|
||||
var data = el.data('data');
|
||||
var item;
|
||||
if (data.nameField && data.valueField) {
|
||||
item = {
|
||||
name: data.nameField.val(),
|
||||
value: data.valueField.typedInput("value"),
|
||||
type: data.valueField.typedInput("type")
|
||||
}
|
||||
if (item.name.trim() !== "") {
|
||||
new_env.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (new_env && new_env.length > 0) {
|
||||
new_env.forEach(function(prop) {
|
||||
if (prop.type === "cred") {
|
||||
node.credentials = node.credentials || {_:{}};
|
||||
node.credentials[prop.name] = prop.value;
|
||||
node.credentials['has_'+prop.name] = (prop.value !== "");
|
||||
if (prop.value !== '__PWRD__') {
|
||||
editState.changed = true;
|
||||
}
|
||||
delete prop.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!isSameObj(old_env, new_env)) {
|
||||
node.env = new_env;
|
||||
editState.changes.env = node.env;
|
||||
editState.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
function isSameObj(env0, env1) {
|
||||
return (JSON.stringify(env0) === JSON.stringify(env1));
|
||||
}
|
||||
})();
|
||||
60
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js
vendored
Normal file
60
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/flowProperties.js
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
;(function() {
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-flow-properties", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.properties"),
|
||||
name: RED._("editor-tab.properties"),
|
||||
iconClass: "fa fa-cog",
|
||||
create: function(container) {
|
||||
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(container);
|
||||
$('<div class="form-row">'+
|
||||
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
|
||||
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
|
||||
'</div>').appendTo(dialogForm);
|
||||
|
||||
var row = $('<div class="form-row node-text-editor-row">'+
|
||||
'<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
|
||||
'<div style="min-height:150px;" class="node-text-editor" id="node-input-info"></div>'+
|
||||
'</div>').appendTo(dialogForm);
|
||||
this.tabflowEditor = RED.editor.createEditor({
|
||||
id: 'node-input-info',
|
||||
mode: 'ace/mode/markdown',
|
||||
value: ""
|
||||
});
|
||||
|
||||
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
|
||||
dialogForm.on("submit", function(e) { e.preventDefault();});
|
||||
|
||||
$("#node-input-name").val(node.label);
|
||||
RED.text.bidi.prepareInput($("#node-input-name"));
|
||||
this.tabflowEditor.getSession().setValue(node.info || "", -1);
|
||||
},
|
||||
resize: function(size) {
|
||||
$("#node-input-info").css("height", (size.height-70)+"px");
|
||||
this.tabflowEditor.resize();
|
||||
},
|
||||
close: function() {
|
||||
this.tabflowEditor.destroy();
|
||||
},
|
||||
apply: function(editState) {
|
||||
var label = $( "#node-input-name" ).val();
|
||||
|
||||
if (node.label != label) {
|
||||
editState.changes.label = node.label;
|
||||
editState.changed = true;
|
||||
node.label = label;
|
||||
}
|
||||
|
||||
var info = this.tabflowEditor.getValue();
|
||||
if (node.info !== info) {
|
||||
editState.changes.info = node.info;
|
||||
editState.changed = true;
|
||||
node.info = info;
|
||||
}
|
||||
$("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
|
||||
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
181
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js
vendored
Normal file
181
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/properties.js
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
;(function() {
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-properties", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.properties"),
|
||||
name: RED._("editor-tab.properties"),
|
||||
iconClass: "fa fa-cog",
|
||||
create: function(container) {
|
||||
|
||||
var nodeType = node.type;
|
||||
if (node.type === "subflow") {
|
||||
nodeType = "subflow-template";
|
||||
} else if (node.type.substring(0,8) == "subflow:") {
|
||||
nodeType = "subflow";
|
||||
}
|
||||
|
||||
var i18nNamespace;
|
||||
if (node._def.set.module === "node-red") {
|
||||
i18nNamespace = "node-red";
|
||||
} else {
|
||||
i18nNamespace = node._def.set.id;
|
||||
}
|
||||
|
||||
var formStyle = "dialog-form";
|
||||
this.inputClass = "node-input";
|
||||
if (node._def.category === "config" && nodeType !== "group") {
|
||||
this.inputClass = "node-config-input";
|
||||
formStyle = "node-config-dialog-edit-form";
|
||||
}
|
||||
RED.editor.buildEditForm(container,formStyle,nodeType,i18nNamespace,node);
|
||||
},
|
||||
resize: function(size) {
|
||||
if (node && node._def.oneditresize) {
|
||||
try {
|
||||
node._def.oneditresize.call(node,size);
|
||||
} catch(err) {
|
||||
console.log("oneditresize",node.id,node.type,err.toString());
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
|
||||
},
|
||||
apply: function(editState) {
|
||||
var newValue;
|
||||
var d;
|
||||
if (node._def.defaults) {
|
||||
for (d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d)) {
|
||||
var input = $("#"+this.inputClass+"-"+d);
|
||||
if (input.attr('type') === "checkbox") {
|
||||
newValue = input.prop('checked');
|
||||
} else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
|
||||
// An empty select-multiple box returns null.
|
||||
// Need to treat that as an empty array.
|
||||
newValue = input.val();
|
||||
if (newValue == null) {
|
||||
newValue = [];
|
||||
}
|
||||
} else if ("format" in node._def.defaults[d] && node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
||||
newValue = input.text();
|
||||
} else {
|
||||
newValue = input.val();
|
||||
}
|
||||
if (newValue != null) {
|
||||
if (d === "outputs") {
|
||||
if (newValue.trim() === "") {
|
||||
continue;
|
||||
}
|
||||
if (isNaN(newValue)) {
|
||||
editState.outputMap = JSON.parse(newValue);
|
||||
var outputCount = 0;
|
||||
var outputsChanged = false;
|
||||
var keys = Object.keys(editState.outputMap);
|
||||
keys.forEach(function(p) {
|
||||
if (isNaN(p)) {
|
||||
// New output;
|
||||
outputCount ++;
|
||||
delete editState.outputMap[p];
|
||||
} else {
|
||||
editState.outputMap[p] = editState.outputMap[p]+"";
|
||||
if (editState.outputMap[p] !== "-1") {
|
||||
outputCount++;
|
||||
if (editState.outputMap[p] !== p) {
|
||||
// Output moved
|
||||
outputsChanged = true;
|
||||
} else {
|
||||
delete editState.outputMap[p];
|
||||
}
|
||||
} else {
|
||||
// Output removed
|
||||
outputsChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
newValue = outputCount;
|
||||
if (outputsChanged) {
|
||||
editState.changed = true;
|
||||
}
|
||||
} else {
|
||||
newValue = parseInt(newValue);
|
||||
}
|
||||
}
|
||||
if (node._def.defaults[d].type) {
|
||||
if (newValue == "_ADD_") {
|
||||
newValue = "";
|
||||
}
|
||||
}
|
||||
if (node[d] != newValue) {
|
||||
if (node._def.defaults[d].type) {
|
||||
// Change to a related config node
|
||||
var configNode = RED.nodes.node(node[d]);
|
||||
if (configNode) {
|
||||
var users = configNode.users;
|
||||
users.splice(users.indexOf(node),1);
|
||||
RED.events.emit("nodes:change",configNode);
|
||||
}
|
||||
configNode = RED.nodes.node(newValue);
|
||||
if (configNode) {
|
||||
configNode.users.push(node);
|
||||
RED.events.emit("nodes:change",configNode);
|
||||
}
|
||||
}
|
||||
editState.changes[d] = node[d];
|
||||
node[d] = newValue;
|
||||
editState.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node._def.credentials) {
|
||||
var credDefinition = node._def.credentials;
|
||||
var credsChanged = updateNodeCredentials(node,credDefinition,this.inputClass);
|
||||
editState.changed = editState.changed || credsChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Update the node credentials from the edit form
|
||||
* @param node - the node containing the credentials
|
||||
* @param credDefinition - definition of the credentials
|
||||
* @param prefix - prefix of the input fields
|
||||
* @return {boolean} whether anything has changed
|
||||
*/
|
||||
function updateNodeCredentials(node, credDefinition, prefix) {
|
||||
var changed = false;
|
||||
if (!node.credentials) {
|
||||
node.credentials = {_:{}};
|
||||
} else if (!node.credentials._) {
|
||||
node.credentials._ = {};
|
||||
}
|
||||
|
||||
for (var cred in credDefinition) {
|
||||
if (credDefinition.hasOwnProperty(cred)) {
|
||||
var input = $("#" + prefix + '-' + cred);
|
||||
if (input.length > 0) {
|
||||
var value = input.val();
|
||||
if (credDefinition[cred].type == 'password') {
|
||||
node.credentials['has_' + cred] = (value !== "");
|
||||
if (value == '__PWRD__') {
|
||||
continue;
|
||||
}
|
||||
changed = true;
|
||||
|
||||
}
|
||||
node.credentials[cred] = value;
|
||||
if (value != node.credentials._[cred]) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
179
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js
vendored
Normal file
179
packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/subflowModule.js
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
(function() {
|
||||
var _subflowModulePaneTemplate = '<form class="dialog-form form-horizontal" autocomplete="off">'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-module" data-i18n="[append]editor:subflow.module"><i class="fa fa-cube"></i> </label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-module" data-i18n="[placeholder]common.label.name">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-type" data-i18n="[append]editor:subflow.type"> </label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-type">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-version" data-i18n="[append]editor:subflow.version"></label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-version" data-i18n="[placeholder]editor:subflow.versionPlaceholder">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-desc" data-i18n="[append]editor:subflow.desc"></label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-desc">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-license" data-i18n="[append]editor:subflow.license"></label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-license">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-author" data-i18n="[append]editor:subflow.author"></label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-author" data-i18n="[placeholder]editor:subflow.authorPlaceholder">'+
|
||||
'</div>'+
|
||||
'<div class="form-row">'+
|
||||
'<label for="subflow-input-module-keywords" data-i18n="[append]editor:subflow.keys"></label>'+
|
||||
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-keywords" data-i18n="[placeholder]editor:subflow.keysPlaceholder">'+
|
||||
'</div>'+
|
||||
'</form>';
|
||||
|
||||
RED.editor.registerEditPane("editor-tab-subflow-module", function(node) {
|
||||
return {
|
||||
label: RED._("editor-tab.module"),
|
||||
name: RED._("editor-tab.module"),
|
||||
iconClass: "fa fa-cube",
|
||||
create: function(container) {
|
||||
buildModuleForm(container, node);
|
||||
},
|
||||
resize: function(size) {
|
||||
},
|
||||
close: function() {
|
||||
|
||||
},
|
||||
apply: function(editState) {
|
||||
var newMeta = exportSubflowModuleProperties(node);
|
||||
if (!isSameObj(node.meta,newMeta)) {
|
||||
editState.changes.meta = node.meta;
|
||||
node.meta = newMeta;
|
||||
editState.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function isSameObj(env0, env1) {
|
||||
return (JSON.stringify(env0) === JSON.stringify(env1));
|
||||
}
|
||||
|
||||
function setupInputValidation(input,validator) {
|
||||
var errorTip;
|
||||
var validateTimeout;
|
||||
|
||||
var validateFunction = function() {
|
||||
if (validateTimeout) {
|
||||
return;
|
||||
}
|
||||
validateTimeout = setTimeout(function() {
|
||||
var error = validator(input.val());
|
||||
// if (!error && errorTip) {
|
||||
// errorTip.close();
|
||||
// errorTip = null;
|
||||
// } else if (error && !errorTip) {
|
||||
// errorTip = RED.popover.create({
|
||||
// tooltip: true,
|
||||
// target:input,
|
||||
// size: "small",
|
||||
// direction: "bottom",
|
||||
// content: error,
|
||||
// }).open();
|
||||
// }
|
||||
input.toggleClass("input-error",!!error);
|
||||
validateTimeout = null;
|
||||
})
|
||||
}
|
||||
input.on("change keyup paste", validateFunction);
|
||||
}
|
||||
|
||||
function buildModuleForm(container, node) {
|
||||
$(_subflowModulePaneTemplate).appendTo(container);
|
||||
var moduleProps = node.meta || {};
|
||||
[
|
||||
'module',
|
||||
'type',
|
||||
'version',
|
||||
'author',
|
||||
'desc',
|
||||
'keywords',
|
||||
'license'
|
||||
].forEach(function(property) {
|
||||
$("#subflow-input-module-"+property).val(moduleProps[property]||"")
|
||||
})
|
||||
$("#subflow-input-module-type").attr("placeholder",node.id);
|
||||
|
||||
setupInputValidation($("#subflow-input-module-module"), function(newValue) {
|
||||
newValue = newValue.trim();
|
||||
var isValid = newValue.length < 215;
|
||||
isValid = isValid && !/^[._]/.test(newValue);
|
||||
isValid = isValid && !/[A-Z]/.test(newValue);
|
||||
if (newValue !== encodeURIComponent(newValue)) {
|
||||
var m = /^@([^\/]+)\/([^\/]+)$/.exec(newValue);
|
||||
if (m) {
|
||||
isValid = isValid && (m[1] === encodeURIComponent(m[1]) && m[2] === encodeURIComponent(m[2]))
|
||||
} else {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
return isValid?"":"Invalid module name"
|
||||
})
|
||||
setupInputValidation($("#subflow-input-module-version"), function(newValue) {
|
||||
newValue = newValue.trim();
|
||||
var isValid = newValue === "" ||
|
||||
/^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/.test(newValue);
|
||||
return isValid?"":"Invalid version number"
|
||||
})
|
||||
|
||||
var licenses = ["none", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "GPL-2.0", "GPL-3.0", "MIT", "MPL-2.0", "CDDL-1.0", "EPL-2.0"];
|
||||
var typedLicenses = {
|
||||
types: licenses.map(function(l) {
|
||||
return {
|
||||
value: l,
|
||||
label: l === "none" ? RED._("editor:subflow.licenseNone") : l,
|
||||
hasValue: false
|
||||
};
|
||||
})
|
||||
}
|
||||
typedLicenses.types.push({
|
||||
value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
|
||||
})
|
||||
if (!moduleProps.license) {
|
||||
typedLicenses.default = "none";
|
||||
} else if (licenses.indexOf(moduleProps.license) > -1) {
|
||||
typedLicenses.default = moduleProps.license;
|
||||
} else {
|
||||
typedLicenses.default = "_custom_";
|
||||
}
|
||||
$("#subflow-input-module-license").typedInput(typedLicenses)
|
||||
}
|
||||
function exportSubflowModuleProperties(node) {
|
||||
var value;
|
||||
var moduleProps = {};
|
||||
[
|
||||
'module',
|
||||
'type',
|
||||
'version',
|
||||
'author',
|
||||
'desc',
|
||||
'keywords'
|
||||
].forEach(function(property) {
|
||||
value = $("#subflow-input-module-"+property).val().trim();
|
||||
if (value) {
|
||||
moduleProps[property] = value;
|
||||
}
|
||||
})
|
||||
var selectedLicenseType = $("#subflow-input-module-license").typedInput("type");
|
||||
|
||||
if (selectedLicenseType === '_custom_') {
|
||||
value = $("#subflow-input-module-license").val();
|
||||
if (value) {
|
||||
moduleProps.license = value;
|
||||
}
|
||||
} else if (selectedLicenseType !== "none") {
|
||||
moduleProps.license = selectedLicenseType;
|
||||
}
|
||||
return moduleProps;
|
||||
}
|
||||
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user