Commits vergleichen
1215 Commits
wizard-the
...
main
Autor | SHA1 | Datum | |
---|---|---|---|
5fe63ec70e | |||
6bc33ebd64 | |||
df456abda0 | |||
df30ce32ee | |||
4750829e1a | |||
49535d88bb | |||
|
d5aa616ff8 | ||
|
578c92e90e | ||
|
3d104406fd | ||
|
0eb6fb1ae0 | ||
|
0992e9601c | ||
|
10609f33e2 | ||
|
bb81c5700a | ||
|
03ef41f7f0 | ||
|
bdd290f4e6 | ||
|
2ab15aaf86 | ||
|
de9dccf233 | ||
|
bb3f0c6252 | ||
|
61309fd320 | ||
|
b365b5dd4f | ||
|
de03cbd15a | ||
|
29d7818a4a | ||
|
f2d1437cff | ||
|
9ab4ca21c8 | ||
|
16109b01e7 | ||
|
43cd090b17 | ||
|
5f99dc226a | ||
|
2460685e65 | ||
|
c6fded7113 | ||
|
713c1bcaa5 | ||
|
ffa37e895c | ||
|
22fac139b6 | ||
|
61ea75dcad | ||
|
766d3b5fc9 | ||
|
a786b5956b | ||
|
794b7c9d5c | ||
|
76c359be3b | ||
|
6572b32706 | ||
|
d71d9976b8 | ||
|
27596a1624 | ||
|
3c17ef574e | ||
|
742239b023 | ||
|
33a320021f | ||
|
e6f44b7dfc | ||
|
31f917ec80 | ||
|
6c0d7a671e | ||
|
31b2625a65 | ||
|
bce0acbfd2 | ||
|
b81bc762c7 | ||
|
785bce228e | ||
|
52efc15576 | ||
|
ebc0f706e3 | ||
|
050a38a9d3 | ||
|
8649ab2286 | ||
|
037132fb1a | ||
|
8332818616 | ||
|
36d6f9bc6f | ||
|
121d60330a | ||
|
13a1538eb9 | ||
|
fee56c2d43 | ||
|
da4fe79aea | ||
|
0a450d58f4 | ||
|
b45e01803d | ||
|
b8cfaa7f23 | ||
|
690f12ee3e | ||
|
1f35b80f85 | ||
|
72b1a2aca5 | ||
|
f5129b7ffe | ||
|
a342626267 | ||
|
3fe1a7c7e8 | ||
|
609df33680 | ||
|
ec739d0450 | ||
|
c26a89a78a | ||
|
ec88af0cc0 | ||
|
10d597c495 | ||
|
cc2eb8c742 | ||
|
98fe9ce77f | ||
|
55a4797e4f | ||
|
33da03b63a | ||
|
580889a34e | ||
|
8d32aebf05 | ||
|
b71f627d73 | ||
|
1b93c06188 | ||
|
4449ab7aff | ||
|
1662ff166d | ||
|
270d7926cc | ||
|
a13f7be6a3 | ||
|
f94df66613 | ||
|
5aefa9c544 | ||
|
7861e37224 | ||
|
6a9f93bccb | ||
|
bf2dcdba86 | ||
|
6beccd95f0 | ||
|
603886b394 | ||
|
7fd4d9bd24 | ||
|
0cec743253 | ||
|
ccb7095b00 | ||
|
3189c03fbe | ||
|
c4521b4b92 | ||
|
00a66cd1f9 | ||
|
c495540157 | ||
|
b8965de717 | ||
|
6f4e7015f8 | ||
|
90046f5fe3 | ||
|
ba20a8a195 | ||
|
1f928f0f81 | ||
|
c67d5075cb | ||
|
2737210baf | ||
|
b73cb95e46 | ||
|
a575ffdf76 | ||
|
e51ba49e11 | ||
|
2df920895d | ||
|
39b947d003 | ||
|
10df3208df | ||
|
d74f5cbd89 | ||
|
eadd64bbbc | ||
|
c85be78af5 | ||
|
ebd25f2780 | ||
|
af39c567ea | ||
|
cd6503d425 | ||
|
6e0f10ca0d | ||
|
f8ea421852 | ||
|
f08f9f303f | ||
|
b4d23970bc | ||
|
7561bc8042 | ||
|
d9ace0449f | ||
|
712c15e845 | ||
|
a5179e568b | ||
|
9e65fcb410 | ||
|
29141ab35c | ||
|
a5ebc282cf | ||
|
0602e59d70 | ||
|
78e7ae4e04 | ||
|
c8a19e8c85 | ||
|
298c083624 | ||
|
92b8fdc6d9 | ||
|
61bf199934 | ||
|
86e6945ad4 | ||
|
e7ab0f7143 | ||
|
519e868078 | ||
|
c1f06c6fff | ||
|
42d6e094a2 | ||
|
2fe8501c65 | ||
|
c256a41668 | ||
|
99e747a331 | ||
|
2cb876ac69 | ||
|
acbaebf0aa | ||
|
97db657227 | ||
|
c1ee353470 | ||
|
4bfea70dde | ||
|
12ca60775a | ||
|
4cdfdbf9d5 | ||
|
6dbd8f7c4f | ||
|
49c1848e88 | ||
|
00ba8425e3 | ||
|
705c84dd02 | ||
|
dc23db7a0c | ||
|
929edc5d0e | ||
|
594a226576 | ||
|
b965afa020 | ||
|
92461ad9d3 | ||
|
a4a18115ee | ||
|
dd8b4b8e99 | ||
|
537ffeb883 | ||
|
69dc96562d | ||
|
c83e514de9 | ||
|
887057d0d2 | ||
|
64b1c0264e | ||
|
02a3c426c0 | ||
|
9793ef6453 | ||
|
ed37c13664 | ||
|
e46d681379 | ||
|
2103c4d67e | ||
|
e4b9be9393 | ||
|
1a84acec21 | ||
|
d0459575e0 | ||
|
0c08b5c044 | ||
|
b3266c6d18 | ||
|
7c56b6d4d7 | ||
|
af064dc655 | ||
|
9ff9844420 | ||
|
63d975eba0 | ||
|
6be5511d18 | ||
|
44f078caff | ||
|
42517c094e | ||
|
99f2e8bbd9 | ||
|
3ea19e1086 | ||
|
92fe9db1e0 | ||
|
4c784d98e6 | ||
|
461e617453 | ||
|
8b443e0f08 | ||
|
caab850127 | ||
|
32a8dcf19f | ||
|
06c812a720 | ||
|
15cc33b8cc | ||
|
54ad75bafd | ||
|
cff6796d7c | ||
|
fcb6b2a359 | ||
|
51624ac819 | ||
|
2f6492c2ed | ||
|
93b574beb7 | ||
|
f7cdc77a06 | ||
|
37cef2ccc2 | ||
|
766cae92ba | ||
|
6b2dd5a443 | ||
|
3d65ad032d | ||
|
2423e9afb7 | ||
|
4c8fd63d6f | ||
|
f29f300a6f | ||
|
b035ab9168 | ||
|
80405555b0 | ||
|
454ed607b4 | ||
|
2114b80185 | ||
|
81372b4d3b | ||
|
61ec6123d7 | ||
|
4ceffb7d0f | ||
|
0f59c9092f | ||
|
0b70806b6b | ||
|
1705a2728f | ||
|
6429bff31a | ||
|
2d8b830962 | ||
|
889caf3fa5 | ||
|
d3e4b16610 | ||
|
5da7030c76 | ||
|
fa3e2d0c55 | ||
|
c4e2f2a4a8 | ||
|
6bcc24eabc | ||
|
3fe42c4183 | ||
|
af62ebb547 | ||
|
978aaf89b0 | ||
|
82aae6d34a | ||
|
5ab4147a40 | ||
|
3f0e1b21f0 | ||
|
e7e9c0e8f8 | ||
|
8734cda00b | ||
|
095cfe1eb6 | ||
|
392b6f3d58 | ||
|
e2797ced64 | ||
|
8fdd21601b | ||
|
759358e081 | ||
|
076e1f4966 | ||
|
a146d57f0e | ||
|
78f5af87e1 | ||
|
83d3ca8eb3 | ||
|
014219c038 | ||
|
1254fcfb4e | ||
|
2c52459f29 | ||
|
ebddcb5606 | ||
|
60d50afc54 | ||
|
99c2c2a461 | ||
|
0a5b7411aa | ||
|
c6c4e31ba9 | ||
|
c6dc80f02c | ||
|
7d6456667a | ||
|
33abececd4 | ||
|
ef1a8d1457 | ||
|
5eca78f172 | ||
|
936efe5aff | ||
|
0e5f0fc86d | ||
|
c375c1dcd9 | ||
|
9b8a3589bd | ||
|
345885bdbd | ||
|
e6d7f3d9bc | ||
|
59f00d6278 | ||
|
c475ec65cd | ||
|
a4e49e7bbc | ||
|
c0ed596ff9 | ||
|
20e7b492ea | ||
|
52fe5166cd | ||
|
24ce4da6f2 | ||
|
e489845daf | ||
|
6a4063951e | ||
|
0e70a5a221 | ||
|
030929a414 | ||
|
3a5430c767 | ||
|
091362cb00 | ||
|
631b5fe44c | ||
|
c2c86b12c2 | ||
|
b73437299c | ||
|
a84bf52542 | ||
|
148e188490 | ||
|
a3b665434c | ||
|
9e59b73ebe | ||
|
092947f68b | ||
|
320196b1f0 | ||
|
e422867729 | ||
|
9bb01d29ab | ||
|
0fc2c6b850 | ||
|
762ddfa16e | ||
|
ebea3e666a | ||
|
71277f8448 | ||
|
3e25f43ba0 | ||
|
7068dfd727 | ||
|
ce914c3f57 | ||
|
aef9ed24ae | ||
|
da6e75faca | ||
|
bc81ca89aa | ||
|
dfc23978ae | ||
|
9eb5fc6ff6 | ||
|
643c5ecff0 | ||
|
75d429388e | ||
|
f5d265846d | ||
|
e5d6a20532 | ||
|
e7ee89048a | ||
|
b2b93aad59 | ||
|
7c8f530c86 | ||
|
75e262a526 | ||
|
1e6a5c39b1 | ||
|
4171d81528 | ||
|
edc94b6ea7 | ||
|
a038e72854 | ||
|
202f6ddc06 | ||
|
e82c158c4a | ||
|
a931caffc1 | ||
|
0cb76659e9 | ||
|
7657149e6f | ||
|
1eefd99c6a | ||
|
8f8c6d50c6 | ||
|
735d43e1ee | ||
|
2c84f019bb | ||
|
82d2eee414 | ||
|
ebdc045236 | ||
|
b712b26896 | ||
|
1b3551b13d | ||
|
dfc1540d52 | ||
|
7d2e876584 | ||
|
e21c3fa296 | ||
|
24a8b95e0b | ||
|
146fd30ab5 | ||
|
e88e83dd80 | ||
|
57f591e488 | ||
|
8103a3b9fa | ||
|
be3a479270 | ||
|
8f42268e88 | ||
|
082139cd97 | ||
|
c1007e78f5 | ||
|
794a8efdec | ||
|
19ff97f0b4 | ||
|
34ff9e4136 | ||
|
ded6dacd60 | ||
|
2557d15a0c | ||
|
f0f27769fd | ||
|
828ded7c05 | ||
|
d6b1655a37 | ||
|
fbf7319c36 | ||
|
7c70e8ca75 | ||
|
ac751b269e | ||
|
ff8294b8e9 | ||
|
ced9d768fb | ||
|
2a38aabdca | ||
|
28cf4421d4 | ||
|
c866395495 | ||
|
9a5328a7e9 | ||
|
28bb6f2886 | ||
|
e67cf5001f | ||
|
b7953fb882 | ||
|
9583851501 | ||
|
bd03d62da1 | ||
|
f54025677a | ||
|
0a03817237 | ||
|
039ba8f603 | ||
|
b05cbec277 | ||
|
6026d67b12 | ||
|
a6b6a2c025 | ||
|
06366a1574 | ||
|
8568a1d97f | ||
|
786c5cd6fc | ||
|
9df0fb97d5 | ||
|
0eaf7b7798 | ||
|
fab0095b1f | ||
|
953123fbf8 | ||
|
23fafeaf05 | ||
|
230fe4a51f | ||
|
27186caafc | ||
|
17fe4d732a | ||
|
4fbcc9b849 | ||
|
528585db45 | ||
|
52d308e7b4 | ||
|
09e56499ca | ||
|
71b6a184ca | ||
|
0f9fa5fe94 | ||
|
cc37043195 | ||
|
621360df58 | ||
|
6a6b44a469 | ||
|
405a4e3ffa | ||
|
93ea792972 | ||
|
2828ee9c06 | ||
|
ecbbd5e1ee | ||
|
d0d2921c17 | ||
|
971b41e15a | ||
|
5b923f3340 | ||
|
d6ee1501d1 | ||
|
d68cd4a9fb | ||
|
9064b2fcbb | ||
|
6f13f0e4bf | ||
|
e50ce9e5c0 | ||
|
28e007536a | ||
|
54e6af52e9 | ||
|
5e3b0f6d2a | ||
|
e4de96ed03 | ||
|
da7153fe34 | ||
|
f76ae370b0 | ||
|
345bb310e8 | ||
|
12cd8ac201 | ||
|
b49e878458 | ||
|
bf711a910d | ||
|
1904001098 | ||
|
92b02ed7d9 | ||
|
02255491d0 | ||
|
4ab7c77257 | ||
|
b2a5558de1 | ||
|
71acc4577d | ||
|
0b4c154456 | ||
|
1dfe93ee24 | ||
|
359b165743 | ||
|
eaf0b5f7ba | ||
|
94e1d4988d | ||
|
1f077ce22b | ||
|
c2e759b1c5 | ||
|
60abb6981e | ||
|
072a3ea142 | ||
|
e38b54351f | ||
|
3bacb09f82 | ||
|
8f7fae0e7c | ||
|
919ac702af | ||
|
61e80779ce | ||
|
ebd382cca4 | ||
|
12e5bef372 | ||
|
aa288b19e2 | ||
|
26749bd055 | ||
|
8083f44389 | ||
|
a3db405d93 | ||
|
c79dee3d16 | ||
|
785bd5d956 | ||
|
0be2b98030 | ||
|
bfd4e30d36 | ||
|
b2714b524c | ||
|
e5904846cf | ||
|
47a1a3d730 | ||
|
66808dba08 | ||
|
f8d5e9296d | ||
|
1f67f2ed4f | ||
|
8af3fde41c | ||
|
76de5e60c1 | ||
|
b35781927f | ||
|
b11eea3a06 | ||
|
78a9c97e43 | ||
|
b05ed5093e | ||
|
6c39054853 | ||
|
b3bcaa26f1 | ||
|
d6b4954ae3 | ||
|
9e1dfd9cf9 | ||
|
7386be3ac0 | ||
|
800ec34ed3 | ||
|
6e02c138ef | ||
|
4173404bbd | ||
|
0dd54255ef | ||
|
ff7185f742 | ||
|
f7d074fb05 | ||
|
81680033a7 | ||
|
1f254732da | ||
|
cfe563e515 | ||
|
eae5fbbbb8 | ||
|
e59d12be95 | ||
|
6a1f8c9cb7 | ||
|
5c6b66e730 | ||
|
ef590abce2 | ||
|
e75c9de052 | ||
|
93107236ea | ||
|
209bb077e9 | ||
|
734cc44f62 | ||
|
248e785086 | ||
|
e5a91aaab5 | ||
|
c3c87852b8 | ||
|
5acff01708 | ||
|
1e8b667e3f | ||
|
c8602c13cc | ||
|
5856d95757 | ||
|
f76185c73d | ||
|
2812cd107f | ||
|
2cec01ba2c | ||
|
d4da391a4d | ||
|
1175cbab96 | ||
|
38307c565a | ||
|
2f21358f10 | ||
|
a6109636dd | ||
|
6ca3f9347c | ||
|
bcc243ad27 | ||
|
7669eb8907 | ||
|
bfda41a5ab | ||
|
66cb18701f | ||
|
af77cdbefc | ||
|
0d7a220f1d | ||
|
af9c1ebed5 | ||
|
36bb8c6b8d | ||
|
4967ecef5b | ||
|
8c8a6c1c9f | ||
|
37927bf63c | ||
|
87acb3ba24 | ||
|
875429a701 | ||
|
797f194c15 | ||
|
0248cd9469 | ||
|
97a75c6258 | ||
|
14f29f5366 | ||
|
6d1a8dff8f | ||
|
ca5aaf7f92 | ||
|
65647fc6b7 | ||
|
bd66f380b7 | ||
|
66fe68e88d | ||
|
083e3cf353 | ||
|
9349fca152 | ||
|
4cf90e23b6 | ||
|
18c31dd66a | ||
|
087af5bfad | ||
|
7f9ace13e8 | ||
|
afd77dc409 | ||
|
15cb6d67c5 | ||
|
a592054d1d | ||
|
7b5f39b4b6 | ||
|
59086fefdd | ||
|
b49385774b | ||
|
e34c058f42 | ||
|
6ff14cac23 | ||
|
306fca6779 | ||
|
abc55c1e78 | ||
|
747b36a840 | ||
|
25962ac93d | ||
|
125a11a2b5 | ||
|
b9ec915f7f | ||
|
adf5e91e9f | ||
|
23f3bd2b94 | ||
|
a8d5165fe0 | ||
|
40e6194143 | ||
|
44e6fac586 | ||
|
52b5fd8eee | ||
|
837f2470d1 | ||
|
2b1d9c28bb | ||
|
c4f4a7f4d9 | ||
|
1f04cc50ca | ||
|
dfdf21e03a | ||
|
20cb9f3df7 | ||
|
8988b1d722 | ||
|
f484706d98 | ||
|
967e238f8f | ||
|
4f84bdb87a | ||
|
39bd40cbe1 | ||
|
89a29767c5 | ||
|
7c2ef92672 | ||
|
ef9e80263a | ||
|
5300d03bed | ||
|
ce7200c90f | ||
|
7181d1bbf5 | ||
|
35cd36d395 | ||
|
50a14ffe9c | ||
|
3586079af4 | ||
|
47d150b977 | ||
|
cad81a76cc | ||
|
f53a1d1f22 | ||
|
6d3cf642d9 | ||
|
3e9edbf8c9 | ||
|
9000949a04 | ||
|
6c98daf644 | ||
|
090f6700e8 | ||
|
26c24815c5 | ||
|
6508e99efb | ||
|
7e878a19f1 | ||
|
280c01d149 | ||
|
051aaf4d0e | ||
|
d36e8223b7 | ||
|
39b9124c84 | ||
|
65812b7b4e | ||
|
556a32aa5c | ||
|
3fec341e97 | ||
|
e3efc68dc9 | ||
|
5d55c154c1 | ||
|
d9407b5d7d | ||
|
2414146fab | ||
|
458f2b896f | ||
|
3b270638bf | ||
|
9263a847af | ||
|
e9d78f64a3 | ||
|
796d4c52db | ||
|
1354301f3c | ||
|
292f47f58b | ||
|
6a0d684d50 | ||
|
315aded76d | ||
|
3b8b00c712 | ||
|
415fdba9fe | ||
|
1182c191a0 | ||
|
dfd7e1ad56 | ||
|
b9b3edd565 | ||
|
1f73807376 | ||
|
7239404b41 | ||
|
5cfd322416 | ||
|
0e390e0bd6 | ||
|
bef18c0433 | ||
|
d78ecbb419 | ||
|
96fbdee147 | ||
|
f2f6ebb6e7 | ||
|
2ad58ef79f | ||
|
5aa47e1c43 | ||
|
da91b6df02 | ||
|
f057fe8339 | ||
|
d46899a5df | ||
|
2b447962d1 | ||
|
9f491d2541 | ||
|
5a58607bd7 | ||
|
e94b0dea0e | ||
|
66b2d414e0 | ||
|
cf0b847194 | ||
|
4fa8c92241 | ||
|
ba2f892eb7 | ||
|
3ef92b7859 | ||
|
aa5b364e21 | ||
|
e6a934927c | ||
|
f9b0895b9e | ||
|
f1529294dd | ||
|
0eafe1249d | ||
|
c28eec978e | ||
|
ba9d618df0 | ||
|
8efaa58749 | ||
|
2747611d5a | ||
|
ebd826cc9b | ||
|
01bfbc24d4 | ||
|
048271b136 | ||
|
f9c4de04c4 | ||
|
96b0fa47b6 | ||
|
f2cadf9fc2 | ||
|
29d01227eb | ||
|
4f4a8ed8f3 | ||
|
f36884f2dd | ||
|
41807aec53 | ||
|
6801e2885f | ||
|
d88c1cf2ff | ||
|
706c8e0413 | ||
|
b23952a0d2 | ||
|
f2822c7520 | ||
|
f816073405 | ||
|
cc504baba6 | ||
|
7c3ee8a0e1 | ||
|
0105be82b8 | ||
|
b933d41dbe | ||
|
a4db15adab | ||
|
09d40951da | ||
|
5aeabdd648 | ||
|
83b985ac02 | ||
|
7268050cf7 | ||
|
42b7264fc2 | ||
|
6fce834244 | ||
|
8b2e3e4c20 | ||
|
1cab61b373 | ||
|
72a1f9f2c2 | ||
|
dd74989562 | ||
|
12e1d802a3 | ||
|
1e56477253 | ||
|
b078b9c8f4 | ||
|
ea177b790d | ||
|
6792cfb000 | ||
|
039e478f47 | ||
|
7f233f178c | ||
|
f3aed50927 | ||
|
cbc76e4810 | ||
|
b38f2b947f | ||
|
72fc36dd67 | ||
|
d416cbb20e | ||
|
c6d5778d12 | ||
|
c3bec5a3e9 | ||
|
150afb29e0 | ||
|
60823cd87a | ||
|
11ff38ae90 | ||
|
4b21566673 | ||
|
ac279e4a67 | ||
|
f373e2ad81 | ||
|
c7097d5775 | ||
|
d7271b7288 | ||
|
c107ee2375 | ||
|
0b3c71c621 | ||
|
8f53d25114 | ||
|
d64b6b50dd | ||
|
e761276ed5 | ||
|
5d445ecd86 | ||
|
180fc0b3ea | ||
|
d2c1a30410 | ||
|
85c9629da6 | ||
|
6a5198041b | ||
|
44f5d39e0a | ||
|
7dcc6ea61f | ||
|
b7d332e0e5 | ||
|
b8329f0fa0 | ||
|
8496e15728 | ||
|
f09ab628df | ||
|
ed25847dd8 | ||
|
ce3d2ced55 | ||
|
70329f209a | ||
|
aea2df9b06 | ||
|
7cf99f2391 | ||
|
3136b779b4 | ||
|
47eed48ad5 | ||
|
af93a67bb1 | ||
|
f3c5eeb371 | ||
|
3abb65294c | ||
|
576d96b2ba | ||
|
41cfdfb135 | ||
|
8fd07322d0 | ||
|
39defec897 | ||
|
a2106bf592 | ||
|
a19a1fa3b1 | ||
|
969fff1a3c | ||
|
e88fff7e25 | ||
|
717aedb607 | ||
|
d000bc7665 | ||
|
dec670ac43 | ||
|
cf33fb9792 | ||
|
eb8b289b50 | ||
|
1d7d9d1119 | ||
|
8fd5ae7169 | ||
|
1c9d52bcfc | ||
|
45f52d56c0 | ||
|
45ab9b1b80 | ||
|
3e2faaa264 | ||
|
294c35cf9b | ||
|
2c7addb9ae | ||
|
449b81a93e | ||
|
883dacf347 | ||
|
4ff50b191b | ||
|
8dcdc9c196 | ||
|
04e78c102f | ||
|
5325be0308 | ||
|
0970b49db8 | ||
|
1f320aac93 | ||
|
0313e1be95 | ||
|
7904bb5b3a | ||
|
4348a4ee68 | ||
|
15c0245edf | ||
|
86b0df89d1 | ||
|
0783cac9e0 | ||
|
a87dc19eb3 | ||
|
9b96c02d0f | ||
|
cebc63b6d8 | ||
|
3c0d256e4e | ||
|
b0ea1081a2 | ||
|
1f899935a0 | ||
|
7d941eaf5e | ||
|
8b5b9624d7 | ||
|
305f94e9d9 | ||
|
84d21f78e5 | ||
|
b57bf8e0fe | ||
|
9e8878e174 | ||
|
ef22c1d33d | ||
|
2d6581c60c | ||
|
0b6ba868ce | ||
|
cd43cdd29b | ||
|
61ad74e07b | ||
|
13f5aeda98 | ||
|
37685ea8cf | ||
|
26b0fcffb0 | ||
|
15b72155c5 | ||
|
1296d3bff8 | ||
|
466c7a7a49 | ||
|
a053440466 | ||
|
c56e5dcbf8 | ||
|
04f0d34ef3 | ||
|
92219ace2f | ||
|
f607863510 | ||
|
5edfb4c41e | ||
|
c0b93fc166 | ||
|
591274d38f | ||
|
01d972f6ba | ||
|
7e295dfb14 | ||
|
6cd6b111bd | ||
|
61928ce436 | ||
|
ea52b2779b | ||
|
e7755b106f | ||
|
0c48435672 | ||
|
835600c054 | ||
|
8893e6caf1 | ||
|
d57f260def | ||
|
69fd3d451d | ||
|
cb7bb4e12f | ||
|
72e16ad9c3 | ||
|
684a2a3801 | ||
|
7a56b9d390 | ||
|
8cac2a5960 | ||
|
46c86cda58 | ||
|
f9b35a2541 | ||
|
035e997e35 | ||
|
caf2333326 | ||
|
cb1054bcd6 | ||
|
71af94c80b | ||
|
3169c0803e | ||
|
1a2f8f1b76 | ||
|
c5da68823e | ||
|
dbd7251073 | ||
|
b20b8ce333 | ||
|
038604e284 | ||
|
b62aee8a48 | ||
|
dcefd7c39e | ||
|
6201530606 | ||
|
3b7b0e1a54 | ||
|
dd067768fd | ||
|
ba7e8d7cd2 | ||
|
363ce6a83f | ||
|
294878d376 | ||
|
be684274a3 | ||
|
4c0e95bddb | ||
|
3ccfeced5d | ||
|
a6a87b8471 | ||
|
c1b4971bd9 | ||
|
d71808048d | ||
|
57e3bc7421 | ||
|
61e2817503 | ||
|
bf5ce61ede | ||
|
f1007b1483 | ||
|
649f014466 | ||
|
6c1d2df7e1 | ||
|
90a7512a33 | ||
|
18bba7a92b | ||
|
01a4469af5 | ||
|
fe56e3beff | ||
|
ccc5103e21 | ||
|
ba235bac67 | ||
|
9aefb688a9 | ||
|
2f2072509d | ||
|
56b795af5d | ||
|
3ee4aaf083 | ||
|
7563608b45 | ||
|
9f25125510 | ||
|
b5022cc07b | ||
|
15a653ddd9 | ||
|
56af532903 | ||
|
24752365f7 | ||
|
eb3c190649 | ||
|
14f79bd4c8 | ||
|
462b8fef72 | ||
|
0dc9cdd86a | ||
|
9d95ec8811 | ||
|
12c37bd733 | ||
|
a57d1b5043 | ||
|
c07337de2e | ||
|
3f56dc9134 | ||
|
18b28f5b80 | ||
|
421e64ac46 | ||
|
76bf5631e1 | ||
|
eb9c443484 | ||
|
df9494e461 | ||
|
c57c551129 | ||
|
22b9698f2b | ||
|
1c4422d7e2 | ||
|
15539eb0c3 | ||
|
681ba4e5b4 | ||
|
0f58fe5aab | ||
|
27396538ad | ||
|
24be61e593 | ||
|
f967f792d2 | ||
|
7bea8c1c12 | ||
|
a75fc1f2d4 | ||
|
1b69c1919a | ||
|
3a922f7a93 | ||
|
a0688b81a2 | ||
|
8dc59745e6 | ||
|
1d1ac820f5 | ||
|
69942a8a58 | ||
|
a5fd54a3ee | ||
|
5e231c3e7b | ||
|
3df6fcbf0c | ||
|
ec650a1f67 | ||
|
bed0483e64 | ||
|
5b3550bdde | ||
|
b1d5c888c4 | ||
|
91c9644fb1 | ||
|
71c101290c | ||
|
87f7b22103 | ||
|
47df65292c | ||
|
f0f16ce4dc | ||
|
f0580d2bba | ||
|
676d538da2 | ||
|
8d2dbd565e | ||
|
f92c2cd574 | ||
|
51553bc71d | ||
|
5e5b5e67ee | ||
|
5bbb36e213 | ||
|
f5a35baa1b | ||
|
5d882d69a2 | ||
|
4605b23585 | ||
|
c9e243f3d8 | ||
|
9f0b08a37e | ||
|
02e931ae08 | ||
|
43c4a1e0d6 | ||
|
41b5ba4f31 | ||
|
3ffb0a9c2a | ||
|
b74dfcc3f8 | ||
|
7f065d91a1 | ||
|
517da30a4c | ||
|
b37f7dbdd5 | ||
|
1c31097078 | ||
|
70c3f19334 | ||
|
21b0d52b1b | ||
|
9c98887437 | ||
|
1a08c9b0c0 | ||
|
00682fb4c2 | ||
|
460d4e397a | ||
|
757b382345 | ||
|
b95639b315 | ||
|
1ad9691e49 | ||
|
7af77533ba | ||
|
ce0335b679 | ||
|
c271612fa9 | ||
|
3cb7e1fffb | ||
|
342fe53a4d | ||
|
acd27cd380 | ||
|
ac944dbb3e | ||
|
80d470b25d | ||
|
fac8d821cf | ||
|
e1a746ca28 | ||
|
559d3f4f19 | ||
|
cdafffea2b | ||
|
75d881de06 | ||
|
49538d554d | ||
|
853634be27 | ||
|
98061c14e8 | ||
|
81bb7e56c2 | ||
|
bbd1253891 | ||
|
892914d649 | ||
|
f044ad6101 | ||
|
564985e46e | ||
|
457463f7c7 | ||
|
e37b2a6e85 | ||
|
584ee6d24e | ||
|
c6b8e08e38 | ||
|
9ab441f888 | ||
|
14a337e00c | ||
|
3add43e81b | ||
|
8cbc8745b9 | ||
|
bd5edaffe9 | ||
|
51dbf7ac40 | ||
|
4b0008ee52 | ||
|
b7575a3295 | ||
|
dec5f5b5ce | ||
|
c1481e2ad4 | ||
|
b9b80ecc63 | ||
|
c2864b9790 | ||
|
a3d59caee8 | ||
|
203876e927 | ||
|
60388b7dab | ||
|
31b4663b3f | ||
|
0c9acb9b20 | ||
|
0752f8068a | ||
|
791eab6c47 | ||
|
36257fbdfe | ||
|
9350db5424 | ||
|
7b129debac | ||
|
a806e14c64 | ||
|
5334d12f10 | ||
|
fbab8d89a5 | ||
|
41e0f13b25 | ||
|
50176b400d | ||
|
0b8fced879 | ||
|
30fe73570a | ||
|
a9af086252 | ||
|
ede6752619 | ||
|
5abc0f4a4c | ||
|
f908e5ec9d | ||
|
805e934fdf | ||
|
b4f463778d | ||
|
b616adaf71 | ||
|
487ad3c46d | ||
|
231051d8ea | ||
|
774cf70efc | ||
|
69153a7379 | ||
|
687c3530b3 | ||
|
d9d4c7d8fa | ||
|
deb4460d2c | ||
|
a42e23d352 | ||
|
a13096c8f1 | ||
|
450f7bfc25 | ||
|
e5fbc408f5 | ||
|
3e2907abc2 | ||
|
d4e489456e | ||
|
67cfeb6ed9 | ||
|
14e7e9c0db | ||
|
702ccc868c | ||
|
b475e39ee9 | ||
|
538d618ff3 | ||
|
925c8c009a | ||
|
1f939c1ca0 | ||
|
31e275668c | ||
|
5a424e8e3d | ||
|
6056351b7c | ||
|
d4c25d406b | ||
|
1edfcca78b | ||
|
a2ebd5c463 | ||
|
c9453a0bdd | ||
|
084c6f4a7a | ||
|
2678ee153d | ||
|
270d3bccf5 | ||
|
11192182e7 | ||
|
15668d37dc | ||
|
10fb3ee176 | ||
|
2cd02a15fe | ||
|
7b57e7fcab | ||
|
a8e81150f1 | ||
|
13304f5b6d | ||
|
4a0d176f1a | ||
|
18c43f499e | ||
|
ba897abf8e | ||
|
33824de1d1 | ||
|
0313c773e8 | ||
|
7c9a0ef862 | ||
|
f4d419cac2 | ||
|
23c4b45195 | ||
|
61f7cf64af | ||
|
208cb1229a | ||
|
3383aec37f | ||
|
5063e3eaeb | ||
|
31b9e48c8f | ||
|
f6c3c98d71 | ||
|
8ddff9873d | ||
|
1d70232952 | ||
|
4b20542251 | ||
|
a5b0cf95ba | ||
|
9db8ce908a | ||
|
e07a57e398 | ||
|
2362478c9c | ||
|
481e9e668c | ||
|
8911862320 | ||
|
8dbb3990e4 | ||
|
6ef333a657 | ||
|
65e269c71f | ||
|
c60d1e3338 | ||
|
4abe30464c | ||
|
1e38001942 | ||
|
9fc2092951 | ||
|
450a94e38d | ||
|
7b13605c7b | ||
|
622ab1b2ce | ||
|
d5f8c46f2c | ||
|
e34f2b5076 | ||
|
403f063b0f | ||
|
0e5fc756df | ||
|
001d116298 | ||
|
6b1e7568c1 | ||
|
280d2ffe54 | ||
|
399cc9c220 | ||
|
c394656ed8 | ||
|
35ff172967 | ||
|
b79039c2e2 | ||
|
aa928c319e | ||
|
8eced70f60 | ||
|
0bc151fc7b | ||
|
d74d3d25be | ||
|
466229af0b | ||
|
03fb7b7ada | ||
|
e865516f48 | ||
|
0b20c6ff81 | ||
|
1916335fa9 | ||
|
a810155f91 | ||
|
eadc40bdae | ||
|
eff1d2c7d2 | ||
|
e260241dea | ||
|
3c70b712bf | ||
|
4578cb0a24 | ||
|
61c92ec768 | ||
|
bfa190e98b | ||
|
0d2f3d1cb5 | ||
|
249cda4de6 | ||
|
973cffee26 | ||
|
89f733ae32 | ||
|
e81b773512 | ||
|
dee3691cd0 | ||
|
c1fc2fd0e4 | ||
|
f49f516403 | ||
|
a7904a28af | ||
|
a27c222dc6 | ||
|
ae271ce647 | ||
|
f00d01c50f | ||
|
998757f857 | ||
|
ee9e9a1d94 | ||
|
51038ade8a | ||
|
55b92f4256 | ||
|
911a47db28 | ||
|
543b676f53 | ||
|
5f5adf93e4 | ||
|
bdc4044581 | ||
|
247f7ca466 | ||
|
1a78b44d35 | ||
|
56a1463413 | ||
|
34fee3729c | ||
|
7375c63403 | ||
|
5aef8972ff | ||
|
ca10ae797a | ||
|
d602281a6a | ||
|
dfd382cd8a | ||
|
56f58414b3 | ||
|
20c8ec1202 | ||
|
d88d2b7d97 | ||
|
e4df646022 | ||
|
c9f90fb8ce | ||
|
9293aaeabc | ||
|
d47950733d | ||
|
21ac87cdda | ||
|
c2e51cba3a | ||
|
e235a66b01 | ||
|
d8f6f00e60 | ||
|
dc19b508ab | ||
|
0bd4dbd08b | ||
|
6d1958d772 | ||
|
262f2d5628 | ||
|
8a9f3e1a3d | ||
|
13faa849d8 | ||
|
9ec2da5a9f | ||
|
be86c77a77 | ||
|
28400ee24e | ||
|
f80f40d6b3 | ||
|
7bfa0aff70 | ||
|
5a6856fcff | ||
|
3d9f6aac98 | ||
|
39ee61b422 | ||
|
098e8418fb | ||
|
e441588aa3 | ||
|
d8a24eb940 | ||
|
dd8513a563 | ||
|
582b9d82aa | ||
|
10c7c8bcc0 | ||
|
c490da3145 | ||
|
d3c6733e59 | ||
|
7b9a54590b | ||
|
cc0b5fcf9f | ||
|
dc7e43b6d7 | ||
|
85bb23e70f | ||
|
b2ed2c1dc7 | ||
|
46bb28bb07 | ||
|
33df6f9fbc | ||
|
9452907f0a | ||
|
f742626786 | ||
|
58b3bc13c9 | ||
|
af3e61fe75 | ||
|
f1f13065c8 | ||
|
48fbb61dcc | ||
|
522d4e9489 | ||
|
31a27cbb00 | ||
|
61ff018244 | ||
|
db72617b61 | ||
|
04f008575f | ||
|
c574ad7c32 | ||
|
0c4eb02038 | ||
|
6cb5d62a7a | ||
|
ff14570d64 | ||
|
a1e8070062 | ||
|
eb5e2fe604 | ||
|
a4adeecf5f | ||
|
8c61b9b1f3 | ||
|
ca125a2a8a | ||
|
edbba891f6 | ||
|
580cc55445 | ||
|
4cacd84967 | ||
|
3a3d23a317 | ||
|
5f24882ef6 | ||
|
c254a0c6b8 | ||
|
56268823c5 | ||
|
957a32cf5a | ||
|
743af8edc5 | ||
|
40c0c12719 | ||
|
aca0fab74e | ||
|
cdb9ec6c3e | ||
|
77dd879f80 | ||
|
13eabe0695 | ||
|
4a4c1934a0 | ||
|
887de25833 | ||
|
848874ea1f | ||
|
c463e8b541 | ||
|
8aafb257fd | ||
|
f05d859007 | ||
|
7d38337c84 | ||
|
eb93169f86 | ||
|
be1eab6092 | ||
|
2a8a4367d8 | ||
|
911140fde3 | ||
|
fb8cdab0fd | ||
|
ceef3f4bc9 | ||
|
ec21c8e274 | ||
|
9ded3602eb | ||
|
772392efae | ||
|
b71548427d | ||
|
d7542d58c7 | ||
|
ce222c0c04 | ||
|
bb08f50042 | ||
|
f443840358 | ||
|
a76cda0c02 | ||
|
2788e8026e | ||
|
0205d8ccb5 | ||
|
54c00f1784 | ||
|
c02ae44a82 | ||
|
8171fc730c | ||
|
9c17957b43 | ||
|
cb930a27bb | ||
|
0a23172fa9 | ||
|
f26176b7ab | ||
|
88a0846842 | ||
|
9289439cbd | ||
|
105fc46774 | ||
|
dadac11c1e | ||
|
1bc55c6099 | ||
|
ae918976a3 | ||
|
2fe3aa53b1 | ||
|
369ceb8584 | ||
|
d3e34a5be2 | ||
|
e79458f6ce | ||
|
cf50a7deb3 | ||
|
94a02157af | ||
|
87675c1bfa | ||
|
9d0a061099 | ||
|
873b30ac65 | ||
|
ee613d74c8 | ||
|
c62f1ac96f | ||
|
04b2b87ea2 |
621 geänderte Dateien mit 70873 neuen und 9746 gelöschten Zeilen
|
@ -1 +1,3 @@
|
||||||
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
|
3.1.999: 1f35b80f85e5fd1efb7f4851f0845700432febdc
|
||||||
|
2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b
|
||||||
|
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
|
||||||
|
|
3
.eslintrc
Normale Datei
3
.eslintrc
Normale Datei
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "eslint-config-discourse"
|
||||||
|
}
|
13
.github/workflows/discourse-plugin.yml
gevendort
Normale Datei
13
.github/workflows/discourse-plugin.yml
gevendort
Normale Datei
|
@ -0,0 +1,13 @@
|
||||||
|
name: Discourse Plugin
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
|
44
.github/workflows/plugin-metadata.yml
gevendort
Normale Datei
44
.github/workflows/plugin-metadata.yml
gevendort
Normale Datei
|
@ -0,0 +1,44 @@
|
||||||
|
name: Metadata
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout head repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Store head version
|
||||||
|
run: |
|
||||||
|
sed -n -e 's/^.*version: /head_version=/p' plugin.rb >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout base repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: "${{ github.base_ref }}"
|
||||||
|
|
||||||
|
- name: Store base version
|
||||||
|
run: |
|
||||||
|
sed -n -e 's/^.*version: /base_version=/p' plugin.rb >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
|
||||||
|
- name: Install semver
|
||||||
|
run: npm install --include=dev
|
||||||
|
|
||||||
|
- name: Check version
|
||||||
|
uses: actions/github-script@v5
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const semver = require('semver');
|
||||||
|
const { head_version, base_version } = process.env;
|
||||||
|
|
||||||
|
if (semver.lte(head_version, base_version)) {
|
||||||
|
core.setFailed("Head version is equal to or lower than base version.");
|
||||||
|
}
|
8
.gitignore
gevendort
8
.gitignore
gevendort
|
@ -1,2 +1,8 @@
|
||||||
coverage/*
|
coverage/*
|
||||||
!coverage/.last_run.json
|
!coverage/.last_run.json
|
||||||
|
gems/*
|
||||||
|
.bundle/
|
||||||
|
auto_generated
|
||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
vendor/*
|
||||||
|
|
1
.prettierrc
Normale Datei
1
.prettierrc
Normale Datei
|
@ -0,0 +1 @@
|
||||||
|
{}
|
8
.rubocop.yml
Normale Datei
8
.rubocop.yml
Normale Datei
|
@ -0,0 +1,8 @@
|
||||||
|
inherit_gem:
|
||||||
|
rubocop-discourse: default.yml
|
||||||
|
|
||||||
|
RSpec/ContextWording:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
RSpec/DescribeClass:
|
||||||
|
Enabled: false
|
7
.simplecov
Normale Datei
7
.simplecov
Normale Datei
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
plugin = "discourse-custom-wizard"
|
||||||
|
|
||||||
|
SimpleCov.configure do
|
||||||
|
track_files "plugins/#{plugin}/**/*.rb"
|
||||||
|
add_filter { |src| !(src.filename =~ /(\/#{plugin}\/app\/|\/#{plugin}\/lib\/)/) }
|
||||||
|
end
|
4
.template-lintrc.js
Normale Datei
4
.template-lintrc.js
Normale Datei
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: ["ember-template-lint-plugin-discourse"],
|
||||||
|
extends: "discourse:recommended",
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
All code in this repository is Copyright 2018 by Angus McLeod.
|
All code in this repository is Copyright 2023 by Angus McLeod.
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
7
Gemfile
Normale Datei
7
Gemfile
Normale Datei
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
gem 'rubocop-discourse'
|
||||||
|
end
|
39
Gemfile.lock
Normale Datei
39
Gemfile.lock
Normale Datei
|
@ -0,0 +1,39 @@
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
ast (2.4.2)
|
||||||
|
json (2.6.2)
|
||||||
|
parallel (1.22.1)
|
||||||
|
parser (3.1.2.1)
|
||||||
|
ast (~> 2.4.1)
|
||||||
|
rainbow (3.1.1)
|
||||||
|
regexp_parser (2.6.0)
|
||||||
|
rexml (3.2.5)
|
||||||
|
rubocop (1.36.0)
|
||||||
|
json (~> 2.3)
|
||||||
|
parallel (~> 1.10)
|
||||||
|
parser (>= 3.1.2.1)
|
||||||
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
rubocop-ast (>= 1.20.1, < 2.0)
|
||||||
|
ruby-progressbar (~> 1.7)
|
||||||
|
unicode-display_width (>= 1.4.0, < 3.0)
|
||||||
|
rubocop-ast (1.22.0)
|
||||||
|
parser (>= 3.1.1.0)
|
||||||
|
rubocop-discourse (3.0)
|
||||||
|
rubocop (>= 1.1.0)
|
||||||
|
rubocop-rspec (>= 2.0.0)
|
||||||
|
rubocop-rspec (2.13.2)
|
||||||
|
rubocop (~> 1.33)
|
||||||
|
ruby-progressbar (1.11.0)
|
||||||
|
unicode-display_width (2.3.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
rubocop-discourse
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.2.16
|
28
README.md
28
README.md
|
@ -1,3 +1,27 @@
|
||||||
# discourse-custom-wizard
|
# Discourse Custom Wizard Plugin
|
||||||
|
|
||||||
See further: https://thepavilion.io/c/knowledge/discourse/custom-wizard/118
|
The Custom Wizard Plugin lets you make forms for your Discourse forum. Better user onboarding, structured posting, data enrichment, automated actions and much more for your community.
|
||||||
|
|
||||||
|
<img src="https://camo.githubusercontent.com/593432f1fc9658ffca104065668cc88fa21dffcd3002cb78ffd50c71f33a2523/68747470733a2f2f706176696c696f6e2d6173736574732e6e7963332e63646e2e6469676974616c6f6365616e7370616365732e636f6d2f706c7567696e732f77697a6172642d7265706f7369746f72792d62616e6e65722e706e67" alt="" data-canonical-src="https://pavilion-assets.nyc3.cdn.digitaloceanspaces.com/plugins/wizard-repository-banner.png" style="max-width: 100%;" width="400">
|
||||||
|
|
||||||
|
👋 Looking to report an issue? We're managing issues for this plugin using our [bug report wizard](https://coop.pavilion.tech/w/bug-report).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
If you're not sure how to install a plugin in Discourse, please follow the [plugin installation guide](https://meta.discourse.org/t/install-a-plugin/19157) or contact your Discourse hosting provider.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Read the full documentation here](https://coop.pavilion.tech/c/82), or go directly to the relevant section
|
||||||
|
|
||||||
|
- [Wizard Administration](https://coop.pavilion.tech/t/1602)
|
||||||
|
- [Wizard Settings](https://coop.pavilion.tech/t/1614)
|
||||||
|
- [Step Settings](https://coop.pavilion.tech/t/1735)
|
||||||
|
- [Field Settings](https://coop.pavilion.tech/t/1580)
|
||||||
|
- [Conditional Settings](https://coop.pavilion.tech/t/1673)
|
||||||
|
- [Field Interpolation](https://coop.pavilion.tech/t/1557)
|
||||||
|
- [Handling Dates and Times](https://coop.pavilion.tech/t/1708)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- [Report an issue](https://coop.pavilion.tech/w/bug-report)
|
||||||
|
|
7
SECURITY.md
Normale Datei
7
SECURITY.md
Normale Datei
|
@ -0,0 +1,7 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
The security of Discourse plugins are premised on the security of [Discourse](https://github.com/discourse/discourse). Please first consider whether a security issue is best reported and handled by the Discourse team. You can view the Discourse security policy [here](https://github.com/discourse/discourse/security/policy).
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you find a security vulnerability that is specific to this plugin, please report it to development@pavilion.tech. Security issues always take precedence over all other work. All commits specific to security are prefixed with SECURITY.
|
|
@ -3,6 +3,13 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
||||||
before_action :ensure_admin
|
before_action :ensure_admin
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
subcription = CustomWizard::Subscription.new
|
||||||
|
render_json_dump(
|
||||||
|
subscribed: subcription.subscribed?,
|
||||||
|
subscription_type: subcription.type,
|
||||||
|
subscription_attributes: CustomWizard::Subscription.attributes,
|
||||||
|
subscription_client_installed: CustomWizard::Subscription.client_installed?
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -14,7 +21,7 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_field_list
|
def custom_field_list
|
||||||
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
|
serialize_data(CustomWizard::CustomField.full_list, CustomWizard::CustomFieldSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_error(message)
|
def render_error(message)
|
|
@ -20,6 +20,10 @@ class CustomWizard::AdminApiController < CustomWizard::AdminController
|
||||||
raise Discourse::InvalidParameters, "An API with that name already exists: '#{current.title || current.name}'"
|
raise Discourse::InvalidParameters, "An API with that name already exists: '#{current.title || current.name}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless subscription.includes?(:api, :all)
|
||||||
|
raise Discourse::InvalidParameters, "Your subscription doesn't include API features."
|
||||||
|
end
|
||||||
|
|
||||||
PluginStoreRow.transaction do
|
PluginStoreRow.transaction do
|
||||||
CustomWizard::Api.set(api_params[:name], title: api_params[:title])
|
CustomWizard::Api.set(api_params[:name], title: api_params[:title])
|
||||||
|
|
||||||
|
@ -130,4 +134,8 @@ class CustomWizard::AdminApiController < CustomWizard::AdminController
|
||||||
|
|
||||||
@auth_data ||= auth_data
|
@auth_data ||= auth_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subscription
|
||||||
|
@subscription ||= CustomWizard::Subscription.new
|
||||||
|
end
|
||||||
end
|
end
|
|
@ -1,7 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
||||||
def index
|
def index
|
||||||
render_json_dump(custom_field_list)
|
render_json_dump(
|
||||||
|
custom_fields: custom_field_list
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
44
app/controllers/custom_wizard/admin/logs.rb
Normale Datei
44
app/controllers/custom_wizard/admin/logs.rb
Normale Datei
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::AdminLogsController < CustomWizard::AdminController
|
||||||
|
before_action :find_wizard, except: [:index]
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: ActiveModel::ArraySerializer.new(
|
||||||
|
CustomWizard::Wizard.list(current_user),
|
||||||
|
each_serializer: CustomWizard::BasicWizardSerializer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render_json_dump(
|
||||||
|
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
|
||||||
|
logs: ActiveModel::ArraySerializer.new(
|
||||||
|
log_list.logs,
|
||||||
|
each_serializer: CustomWizard::LogSerializer
|
||||||
|
),
|
||||||
|
total: log_list.total
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def log_list
|
||||||
|
@log_list ||= begin
|
||||||
|
list = CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i, params[:wizard_id])
|
||||||
|
|
||||||
|
if list.logs.any? && (usernames = list.logs.map(&:username)).present?
|
||||||
|
user_map = User.where(username: usernames)
|
||||||
|
.reduce({}) do |result, user|
|
||||||
|
result[user.username] = user
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
list.logs.each do |log_item|
|
||||||
|
log_item.user = user_map[log_item.username]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -41,27 +41,28 @@ class CustomWizard::AdminManagerController < CustomWizard::AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
template_json = JSON.parse file
|
template_json = JSON.parse(file)
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
return render_error(I18n.t('wizard.import.error.invalid_json'))
|
return render_error(I18n.t('wizard.import.error.invalid_json'))
|
||||||
end
|
end
|
||||||
|
|
||||||
imported = []
|
imported = []
|
||||||
failures = []
|
failures = []
|
||||||
|
templates = template_json.is_a?(Array) ? template_json : [template_json]
|
||||||
|
|
||||||
template_json.each do |json|
|
templates.each do |raw_template|
|
||||||
template = CustomWizard::Template.new(json)
|
template = CustomWizard::Template.new(raw_template)
|
||||||
template.save(skip_jobs: true, create: true)
|
template.save(skip_jobs: true, create: true)
|
||||||
|
|
||||||
if template.errors.any?
|
if template.errors.any?
|
||||||
failures.push(
|
failures.push(
|
||||||
id: json['id'],
|
id: template.data['id'],
|
||||||
messages: template.errors.full_messages.join(', ')
|
messages: template.errors.full_messages.join(', ')
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
imported.push(
|
imported.push(
|
||||||
id: json['id'],
|
id: template.data['id'],
|
||||||
name: json['name']
|
name: template.data['name']
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -13,34 +13,29 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
|
||||||
def show
|
def show
|
||||||
render_json_dump(
|
render_json_dump(
|
||||||
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
|
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
|
||||||
submissions: build_submissions.as_json
|
submissions: ActiveModel::ArraySerializer.new(
|
||||||
|
submission_list.submissions,
|
||||||
|
each_serializer: CustomWizard::SubmissionSerializer
|
||||||
|
),
|
||||||
|
total: submission_list.total
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def download
|
def download
|
||||||
send_data build_submissions.to_json,
|
content = ActiveModel::ArraySerializer.new(
|
||||||
|
CustomWizard::Submission.list(@wizard).submissions,
|
||||||
|
each_serializer: CustomWizard::SubmissionSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
send_data content.to_json,
|
||||||
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
|
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
|
||||||
content_type: "application/json",
|
content_type: "application/json",
|
||||||
disposition: "attachment"
|
disposition: "attachment"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
protected
|
||||||
|
|
||||||
def build_submissions
|
def submission_list
|
||||||
PluginStoreRow.where(plugin_name: "#{@wizard.id}_submissions")
|
CustomWizard::Submission.list(@wizard, page: params[:page].to_i)
|
||||||
.order('id DESC')
|
|
||||||
.map do |row|
|
|
||||||
value = ::JSON.parse(row.value)
|
|
||||||
|
|
||||||
if user = User.find_by(id: row.key)
|
|
||||||
username = user.username
|
|
||||||
else
|
|
||||||
username = I18n.t('admin.wizard.submissions.no_user', id: row.key)
|
|
||||||
end
|
|
||||||
|
|
||||||
value.map do |v|
|
|
||||||
{ username: username }.merge!(v.except("redirect_to"))
|
|
||||||
end
|
|
||||||
end.flatten
|
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -37,7 +37,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
wizard_id = template.save(create: params[:create])
|
wizard_id = template.save(create: params[:create])
|
||||||
|
|
||||||
if template.errors.any?
|
if template.errors.any?
|
||||||
render json: failed_json.merge(errors: result.errors.full_messages)
|
render json: failed_json.merge(backend_validation_error: template.errors.full_messages.join("\n\n"))
|
||||||
else
|
else
|
||||||
render json: success_json.merge(wizard_id: wizard_id)
|
render json: success_json.merge(wizard_id: wizard_id)
|
||||||
end
|
end
|
||||||
|
@ -79,21 +79,28 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
:required,
|
:required,
|
||||||
:prompt_completion,
|
:prompt_completion,
|
||||||
:restart_on_revisit,
|
:restart_on_revisit,
|
||||||
|
:resume_on_revisit,
|
||||||
:theme_id,
|
:theme_id,
|
||||||
permitted: mapped_params,
|
permitted: mapped_params,
|
||||||
steps: [
|
steps: [
|
||||||
:id,
|
:id,
|
||||||
|
:index,
|
||||||
:title,
|
:title,
|
||||||
:key,
|
:key,
|
||||||
:banner,
|
:banner,
|
||||||
|
:banner_upload_id,
|
||||||
:raw_description,
|
:raw_description,
|
||||||
:required_data_message,
|
:required_data_message,
|
||||||
|
:force_final,
|
||||||
required_data: mapped_params,
|
required_data: mapped_params,
|
||||||
permitted_params: mapped_params,
|
permitted_params: mapped_params,
|
||||||
|
condition: mapped_params,
|
||||||
fields: [
|
fields: [
|
||||||
:id,
|
:id,
|
||||||
|
:index,
|
||||||
:label,
|
:label,
|
||||||
:image,
|
:image,
|
||||||
|
:image_upload_id,
|
||||||
:description,
|
:description,
|
||||||
:required,
|
:required,
|
||||||
:key,
|
:key,
|
||||||
|
@ -105,9 +112,15 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
:format,
|
:format,
|
||||||
:limit,
|
:limit,
|
||||||
:property,
|
:property,
|
||||||
|
:preview_template,
|
||||||
|
:placeholder,
|
||||||
|
:can_create_tag,
|
||||||
prefill: mapped_params,
|
prefill: mapped_params,
|
||||||
content: mapped_params,
|
content: mapped_params,
|
||||||
|
condition: mapped_params,
|
||||||
|
index: mapped_params,
|
||||||
validations: {},
|
validations: {},
|
||||||
|
tag_groups: [],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -151,7 +164,9 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
mentionable_level: mapped_params,
|
mentionable_level: mapped_params,
|
||||||
messageable_level: mapped_params,
|
messageable_level: mapped_params,
|
||||||
visibility_level: mapped_params,
|
visibility_level: mapped_params,
|
||||||
members_visibility_level: mapped_params
|
members_visibility_level: mapped_params,
|
||||||
|
add_event: mapped_params,
|
||||||
|
add_location: mapped_params
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
end
|
end
|
113
app/controllers/custom_wizard/steps.rb
Normale Datei
113
app/controllers/custom_wizard/steps.rb
Normale Datei
|
@ -0,0 +1,113 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
|
||||||
|
before_action :ensure_can_update
|
||||||
|
|
||||||
|
def update
|
||||||
|
update = update_params.to_h
|
||||||
|
|
||||||
|
update[:fields] = {}
|
||||||
|
if params[:fields]
|
||||||
|
field_ids = @builder.wizard.field_ids
|
||||||
|
params[:fields].each do |k, v|
|
||||||
|
update[:fields][k] = v if field_ids.include? k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@builder.build
|
||||||
|
|
||||||
|
updater = @builder.wizard.create_updater(update[:step_id], update[:fields])
|
||||||
|
updater.update
|
||||||
|
@result = updater.result
|
||||||
|
|
||||||
|
if updater.success?
|
||||||
|
wizard_id = update_params[:wizard_id]
|
||||||
|
builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
|
||||||
|
@wizard = builder.build(force: true)
|
||||||
|
|
||||||
|
current_step = @wizard.find_step(update[:step_id])
|
||||||
|
current_submission = @wizard.current_submission
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
if current_step.conditional_final_step && !current_step.last_step
|
||||||
|
current_step.force_final = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if current_step.final?
|
||||||
|
builder.template.actions.each do |action_template|
|
||||||
|
if action_template['run_after'] === 'wizard_completion'
|
||||||
|
action_result = CustomWizard::Action.new(
|
||||||
|
action: action_template,
|
||||||
|
wizard: @wizard,
|
||||||
|
submission: current_submission
|
||||||
|
).perform
|
||||||
|
|
||||||
|
if action_result.success?
|
||||||
|
current_submission = action_result.submission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
current_submission.save
|
||||||
|
|
||||||
|
if redirect = get_redirect
|
||||||
|
updater.result[:redirect_on_complete] = redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
@wizard.cleanup_on_complete!
|
||||||
|
|
||||||
|
result[:final] = true
|
||||||
|
else
|
||||||
|
current_submission.save
|
||||||
|
|
||||||
|
result[:final] = false
|
||||||
|
result[:next_step_id] = current_step.next.id
|
||||||
|
end
|
||||||
|
|
||||||
|
result.merge!(updater.result) if updater.result.present?
|
||||||
|
result[:refresh_required] = true if updater.refresh_required?
|
||||||
|
result[:wizard] = ::CustomWizard::WizardSerializer.new(
|
||||||
|
@wizard,
|
||||||
|
scope: Guardian.new(current_user),
|
||||||
|
root: false
|
||||||
|
).as_json
|
||||||
|
|
||||||
|
render json: result
|
||||||
|
else
|
||||||
|
errors = []
|
||||||
|
updater.errors.messages.each do |field, msg|
|
||||||
|
errors << { field: field, description: msg.join(',') }
|
||||||
|
end
|
||||||
|
render json: { errors: errors }, status: 422
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_can_update
|
||||||
|
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
|
||||||
|
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
|
||||||
|
|
||||||
|
@step_template = @builder.template.steps.select do |s|
|
||||||
|
s['id'] == update_params[:step_id]
|
||||||
|
end.first
|
||||||
|
raise Discourse::InvalidParameters.new(:step_id) if !@step_template
|
||||||
|
raise Discourse::InvalidAccess.new if !@builder.check_condition(@step_template)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_params
|
||||||
|
@update_params || begin
|
||||||
|
params.require(:step_id)
|
||||||
|
params.require(:wizard_id)
|
||||||
|
params.permit(:wizard_id, :step_id).transform_values { |v| v.underscore }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_redirect
|
||||||
|
return @result[:redirect_on_next] if @result[:redirect_on_next].present?
|
||||||
|
|
||||||
|
submission = @wizard.current_submission
|
||||||
|
return nil unless submission.present?
|
||||||
|
## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry
|
||||||
|
submission.route_to || submission.redirect_on_complete || submission.redirect_to
|
||||||
|
end
|
||||||
|
end
|
39
app/controllers/custom_wizard/wizard.rb
Normale Datei
39
app/controllers/custom_wizard/wizard.rb
Normale Datei
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::WizardController < ::CustomWizard::WizardClientController
|
||||||
|
def show
|
||||||
|
if wizard.present?
|
||||||
|
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
|
||||||
|
else
|
||||||
|
render json: { error: I18n.t('wizard.none') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip
|
||||||
|
params.require(:wizard_id)
|
||||||
|
|
||||||
|
if wizard.required && !wizard.completed? && wizard.permitted?
|
||||||
|
return render json: { error: I18n.t('wizard.no_skip') }
|
||||||
|
end
|
||||||
|
|
||||||
|
result = { success: 'OK' }
|
||||||
|
|
||||||
|
if current_user && wizard.can_access?
|
||||||
|
if redirect_to = wizard.current_submission&.redirect_to
|
||||||
|
result.merge!(redirect_to: redirect_to)
|
||||||
|
end
|
||||||
|
|
||||||
|
wizard.cleanup_on_skip!
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: result
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def wizard
|
||||||
|
@wizard ||= begin
|
||||||
|
return nil unless @builder.present?
|
||||||
|
@builder.build({ reset: params[:reset] }, params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::WizardClientController < ::ApplicationController
|
||||||
|
before_action :ensure_plugin_enabled
|
||||||
|
before_action :set_builder
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_plugin_enabled
|
||||||
|
unless SiteSetting.custom_wizard_enabled
|
||||||
|
redirect_to path("/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def guest_id
|
||||||
|
return nil if current_user.present?
|
||||||
|
cookies[:custom_wizard_guest_id] ||= CustomWizard::Wizard.generate_guest_id
|
||||||
|
cookies[:custom_wizard_guest_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_builder
|
||||||
|
@builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user, guest_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,11 +9,13 @@ module Jobs
|
||||||
user_ids = []
|
user_ids = []
|
||||||
|
|
||||||
User.human_users.each do |user|
|
User.human_users.each do |user|
|
||||||
if CustomWizard::Wizard.set_wizard_redirect(wizard.id, user)
|
if CustomWizard::Wizard.set_user_redirect(wizard.id, user)
|
||||||
user_ids.push(user.id)
|
user_ids.push(user.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CustomWizard::Template.clear_cache_keys
|
||||||
|
|
||||||
MessageBus.publish "/redirect_to_wizard", wizard.id, user_ids: user_ids
|
MessageBus.publish "/redirect_to_wizard", wizard.id, user_ids: user_ids
|
||||||
end
|
end
|
||||||
end
|
end
|
10
app/serializers/custom_wizard/log_serializer.rb
Normale Datei
10
app/serializers/custom_wizard/log_serializer.rb
Normale Datei
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomWizard::LogSerializer < ApplicationSerializer
|
||||||
|
attributes :date,
|
||||||
|
:action,
|
||||||
|
:username,
|
||||||
|
:message
|
||||||
|
|
||||||
|
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
||||||
|
end
|
35
app/serializers/custom_wizard/submission_serializer.rb
Normale Datei
35
app/serializers/custom_wizard/submission_serializer.rb
Normale Datei
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::SubmissionSerializer < ApplicationSerializer
|
||||||
|
attributes :id,
|
||||||
|
:fields,
|
||||||
|
:submitted_at,
|
||||||
|
:user
|
||||||
|
|
||||||
|
def include_user?
|
||||||
|
object.wizard.user.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def user
|
||||||
|
::BasicUserSerializer.new(object.wizard.user, root: false).as_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def fields
|
||||||
|
@fields ||= begin
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
object.wizard.template['steps'].each do |step|
|
||||||
|
step['fields'].each do |field|
|
||||||
|
if value = object.fields[field['id']]
|
||||||
|
result[field['id']] = {
|
||||||
|
value: value,
|
||||||
|
type: field['type'],
|
||||||
|
label: field['label']
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
138
app/serializers/custom_wizard/wizard_field_serializer.rb
Normale Datei
138
app/serializers/custom_wizard/wizard_field_serializer.rb
Normale Datei
|
@ -0,0 +1,138 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomWizard::FieldSerializer < ::ApplicationSerializer
|
||||||
|
|
||||||
|
attributes :id,
|
||||||
|
:index,
|
||||||
|
:type,
|
||||||
|
:required,
|
||||||
|
:value,
|
||||||
|
:label,
|
||||||
|
:placeholder,
|
||||||
|
:description,
|
||||||
|
:image,
|
||||||
|
:file_types,
|
||||||
|
:format,
|
||||||
|
:limit,
|
||||||
|
:property,
|
||||||
|
:content,
|
||||||
|
:tag_groups,
|
||||||
|
:can_create_tag,
|
||||||
|
:validations,
|
||||||
|
:max_length,
|
||||||
|
:char_counter,
|
||||||
|
:preview_template
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
object.index
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
object.type
|
||||||
|
end
|
||||||
|
|
||||||
|
def required
|
||||||
|
object.required
|
||||||
|
end
|
||||||
|
|
||||||
|
def value
|
||||||
|
object.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def label
|
||||||
|
I18n.t("#{i18n_key}.label", default: object.label, base_url: Discourse.base_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_label?
|
||||||
|
label.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_description?
|
||||||
|
description.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def placeholder
|
||||||
|
I18n.t("#{i18n_key}.placeholder", default: object.placeholder)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_placeholder?
|
||||||
|
placeholder.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def image
|
||||||
|
object.image
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_image?
|
||||||
|
object.image.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_types
|
||||||
|
object.file_types
|
||||||
|
end
|
||||||
|
|
||||||
|
def format
|
||||||
|
object.format
|
||||||
|
end
|
||||||
|
|
||||||
|
def limit
|
||||||
|
object.limit
|
||||||
|
end
|
||||||
|
|
||||||
|
def property
|
||||||
|
object.property
|
||||||
|
end
|
||||||
|
|
||||||
|
def content
|
||||||
|
object.content
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_groups
|
||||||
|
object.tag_groups
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_create_tag
|
||||||
|
object.can_create_tag
|
||||||
|
end
|
||||||
|
|
||||||
|
def validations
|
||||||
|
validations = {}
|
||||||
|
object.validations&.each do |type, props|
|
||||||
|
next unless props["status"]
|
||||||
|
validations[props["position"]] ||= {}
|
||||||
|
validations[props["position"]][type] = props.merge CustomWizard::RealtimeValidation.types[type.to_sym]
|
||||||
|
end
|
||||||
|
|
||||||
|
validations
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_length
|
||||||
|
object.max_length
|
||||||
|
end
|
||||||
|
|
||||||
|
def char_counter
|
||||||
|
object.char_counter
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview_template
|
||||||
|
object.preview_template
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def i18n_key
|
||||||
|
@i18n_key ||= "#{object.step.wizard.id}.#{object.step.id}.#{object.id}".underscore
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribed?
|
||||||
|
@subscribed ||= CustomWizard::Subscription.subscribed?
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,16 +4,15 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
|
||||||
|
|
||||||
attributes :start,
|
attributes :start,
|
||||||
:background,
|
:background,
|
||||||
|
:submission_last_updated_at,
|
||||||
:theme_id,
|
:theme_id,
|
||||||
:completed,
|
:completed,
|
||||||
:required,
|
:required,
|
||||||
:permitted,
|
:permitted,
|
||||||
:uncategorized_category_id
|
:resume_on_revisit
|
||||||
|
|
||||||
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
|
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
|
||||||
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
||||||
has_many :categories, serializer: ::BasicCategorySerializer, embed: :objects
|
|
||||||
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
|
|
||||||
|
|
||||||
def completed
|
def completed
|
||||||
object.completed?
|
object.completed?
|
||||||
|
@ -30,30 +29,18 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
object.start.id
|
object.start
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_start?
|
def include_start?
|
||||||
include_steps? && object.start.present?
|
include_steps? && object.start.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submission_last_updated_at
|
||||||
|
object.current_submission.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
def include_steps?
|
def include_steps?
|
||||||
!include_completed?
|
!include_completed?
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_categories?
|
|
||||||
object.needs_categories
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_groups?
|
|
||||||
object.needs_groups
|
|
||||||
end
|
|
||||||
|
|
||||||
def uncategorized_category_id
|
|
||||||
SiteSetting.uncategorized_category_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_uncategorized_category_id?
|
|
||||||
object.needs_categories
|
|
||||||
end
|
|
||||||
end
|
end
|
83
app/serializers/custom_wizard/wizard_step_serializer.rb
Normale Datei
83
app/serializers/custom_wizard/wizard_step_serializer.rb
Normale Datei
|
@ -0,0 +1,83 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomWizard::StepSerializer < ::ApplicationSerializer
|
||||||
|
|
||||||
|
attributes :id,
|
||||||
|
:index,
|
||||||
|
:next,
|
||||||
|
:previous,
|
||||||
|
:description,
|
||||||
|
:title,
|
||||||
|
:banner,
|
||||||
|
:permitted,
|
||||||
|
:permitted_message,
|
||||||
|
:final
|
||||||
|
|
||||||
|
has_many :fields, serializer: ::CustomWizard::FieldSerializer, embed: :objects
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
object.index
|
||||||
|
end
|
||||||
|
|
||||||
|
def next
|
||||||
|
object.next.id if object.next.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_next?
|
||||||
|
object.next.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous
|
||||||
|
object.previous.id if object.previous.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_previous?
|
||||||
|
object.previous.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
I18n.t("#{i18n_key}.title", default: object.title, base_url: Discourse.base_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_title?
|
||||||
|
title.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_description?
|
||||||
|
description.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def banner
|
||||||
|
object.banner
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_banner?
|
||||||
|
object.banner.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted
|
||||||
|
object.permitted
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_message
|
||||||
|
object.permitted_message
|
||||||
|
end
|
||||||
|
|
||||||
|
def final
|
||||||
|
object.final?
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def i18n_key
|
||||||
|
@i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore
|
||||||
|
end
|
||||||
|
end
|
28
app/views/layouts/qunit.html.erb
Normale Datei
28
app/views/layouts/qunit.html.erb
Normale Datei
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Custom Wizard QUnit Test Runner</title>
|
||||||
|
<%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %>
|
||||||
|
<%= discourse_stylesheet_link_tag :wizard, theme_id: nil %>
|
||||||
|
<%= discourse_stylesheet_link_tag :wizard_custom %>
|
||||||
|
<%= preload_script "locales/en" %>
|
||||||
|
<%= preload_script "ember_jquery" %>
|
||||||
|
<%= preload_script "wizard-vendor" %>
|
||||||
|
<%= preload_script "wizard-custom" %>
|
||||||
|
<%= preload_script "wizard-raw-templates" %>
|
||||||
|
<%= preload_script "wizard-plugin" %>
|
||||||
|
<%= preload_script "pretty-text-bundle" %>
|
||||||
|
<%= preload_script "wizard-qunit" %>
|
||||||
|
<%= csrf_meta_tags %>
|
||||||
|
|
||||||
|
<script src="<%= ExtraLocalesController.url("wizard") %>"></script>
|
||||||
|
|
||||||
|
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
|
||||||
|
<meta name="discourse_theme_id" content="">
|
||||||
|
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
|
||||||
|
</head>
|
||||||
|
<body class="custom-wizard">
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,119 +1,126 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
import { or, alias } from "@ember/object/computed";
|
import { alias, equal, or } from "@ember/object/computed";
|
||||||
|
import I18n from "I18n";
|
||||||
const generateContent = function(array, type) {
|
|
||||||
return array.map(key => ({
|
|
||||||
id: key,
|
|
||||||
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: 'tr',
|
tagName: "tr",
|
||||||
topicSerializers: ['topic_view', 'topic_list_item'],
|
topicSerializers: ["topic_view", "topic_list_item"],
|
||||||
postSerializers: ['post'],
|
postSerializers: ["post"],
|
||||||
groupSerializers: ['basic_group'],
|
groupSerializers: ["basic_group"],
|
||||||
categorySerializers: ['basic_category'],
|
categorySerializers: ["basic_category"],
|
||||||
klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'),
|
showInputs: or("field.new", "field.edit"),
|
||||||
typeContent: generateContent(['string', 'boolean', 'integer', 'json'], 'type'),
|
classNames: ["custom-field-input"],
|
||||||
showInputs: or('field.new', 'field.edit'),
|
loading: or("saving", "destroying"),
|
||||||
classNames: ['custom-field-input'],
|
destroyDisabled: alias("loading"),
|
||||||
loading: or('saving', 'destroying'),
|
closeDisabled: alias("loading"),
|
||||||
destroyDisabled: alias('loading'),
|
isExternal: equal("field.id", "external"),
|
||||||
closeDisabled: alias('loading'),
|
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this.set('originalField', JSON.parse(JSON.stringify(this.field)));
|
this.set("originalField", JSON.parse(JSON.stringify(this.field)));
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('field.klass')
|
@discourseComputed("field.klass")
|
||||||
serializerContent(klass, p2) {
|
serializerContent(klass) {
|
||||||
const serializers = this.get(`${klass}Serializers`);
|
const serializers = this.get(`${klass}Serializers`);
|
||||||
|
|
||||||
if (serializers) {
|
if (serializers) {
|
||||||
return generateContent(serializers, 'serializers');
|
return serializers.reduce((result, key) => {
|
||||||
} else {
|
result.push({
|
||||||
return [];
|
id: key,
|
||||||
|
name: I18n.t(`admin.wizard.custom_field.serializers.${key}`),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes('field.klass')
|
@observes("field.klass")
|
||||||
clearSerializersWhenClassChanges() {
|
clearSerializersWhenClassChanges() {
|
||||||
this.set('field.serializers', null);
|
this.set("field.serializers", null);
|
||||||
},
|
},
|
||||||
|
|
||||||
compareArrays(array1, array2) {
|
compareArrays(array1, array2) {
|
||||||
return array1.length === array2.length && array1.every((value, index) => {
|
return (
|
||||||
return value === array2[index];
|
array1.length === array2.length &&
|
||||||
});
|
array1.every((value, index) => {
|
||||||
|
return value === array2[index];
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
'saving',
|
"saving",
|
||||||
'field.name',
|
"isExternal",
|
||||||
'field.klass',
|
"field.name",
|
||||||
'field.type',
|
"field.klass",
|
||||||
'field.serializers'
|
"field.type",
|
||||||
|
"field.serializers"
|
||||||
)
|
)
|
||||||
saveDisabled(saving) {
|
saveDisabled(saving, isExternal) {
|
||||||
if (saving) return true;
|
if (saving || isExternal) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const originalField = this.originalField;
|
const originalField = this.originalField;
|
||||||
if (!originalField) return false;
|
if (!originalField) {
|
||||||
|
return false;
|
||||||
return ['name', 'klass', 'type', 'serializers'].every(attr => {
|
}
|
||||||
|
|
||||||
|
return ["name", "klass", "type", "serializers"].every((attr) => {
|
||||||
let current = this.get(attr);
|
let current = this.get(attr);
|
||||||
let original = originalField[attr];
|
let original = originalField[attr];
|
||||||
|
|
||||||
if (!current) return false;
|
if (!current) {
|
||||||
|
return false;
|
||||||
if (attr == 'serializers') {
|
}
|
||||||
|
|
||||||
|
if (attr === "serializers") {
|
||||||
return this.compareArrays(current, original);
|
return this.compareArrays(current, original);
|
||||||
} else {
|
} else {
|
||||||
return current == original;
|
return current === original;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
edit() {
|
edit() {
|
||||||
this.set('field.edit', true);
|
this.set("field.edit", true);
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
if (this.field.edit) {
|
if (this.field.edit) {
|
||||||
this.set('field.edit', false);
|
this.set("field.edit", false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.set('destroying', true);
|
this.set("destroying", true);
|
||||||
this.removeField(this.field);
|
this.removeField(this.field);
|
||||||
},
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.set('saving', true);
|
this.set("saving", true);
|
||||||
|
|
||||||
const field = this.field;
|
const field = this.field;
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
id: field.id,
|
id: field.id,
|
||||||
klass: field.klass,
|
klass: field.klass,
|
||||||
type: field.type,
|
type: field.type,
|
||||||
serializers: field.serializers,
|
serializers: field.serializers,
|
||||||
name: field.name
|
name: field.name,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.saveField(data).then((result) => {
|
this.saveField(data).then((result) => {
|
||||||
this.set('saving', false);
|
this.set("saving", false);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.set('field.edit', false);
|
this.set("field.edit", false);
|
||||||
} else {
|
} else {
|
||||||
this.set('saveIcon', 'times');
|
this.set("saveIcon", "times");
|
||||||
}
|
}
|
||||||
setTimeout(() => this.set('saveIcon', null), 10000);
|
setTimeout(() => this.set("saveIcon", null), 10000);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
146
assets/javascripts/discourse/components/custom-user-selector.js.es6
Normale Datei
146
assets/javascripts/discourse/components/custom-user-selector.js.es6
Normale Datei
|
@ -0,0 +1,146 @@
|
||||||
|
import {
|
||||||
|
default as computed,
|
||||||
|
observes,
|
||||||
|
} from "discourse-common/utils/decorators";
|
||||||
|
import { renderAvatar } from "discourse/helpers/user-avatar";
|
||||||
|
import userSearch from "discourse/lib/user-search";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import Handlebars from "handlebars";
|
||||||
|
import { isEmpty } from "@ember/utils";
|
||||||
|
import TextField from "discourse/components/text-field";
|
||||||
|
|
||||||
|
const template = function (params) {
|
||||||
|
const options = params.options;
|
||||||
|
let html = "<div class='autocomplete'>";
|
||||||
|
|
||||||
|
if (options.users) {
|
||||||
|
html += "<ul>";
|
||||||
|
options.users.forEach((u) => {
|
||||||
|
html += `<li><a href title="${u.name}">`;
|
||||||
|
html += renderAvatar(u, { imageSize: "tiny" });
|
||||||
|
html += `<span class='username'>${u.username}</span>`;
|
||||||
|
if (u.name) {
|
||||||
|
html += `<span class='name'>${u.name}</span>`;
|
||||||
|
}
|
||||||
|
html += `</a></li>`;
|
||||||
|
});
|
||||||
|
html += "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</div>";
|
||||||
|
|
||||||
|
return new Handlebars.SafeString(html).string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextField.extend({
|
||||||
|
attributeBindings: ["autofocus", "maxLength"],
|
||||||
|
autocorrect: false,
|
||||||
|
autocapitalize: false,
|
||||||
|
name: "user-selector",
|
||||||
|
id: "custom-member-selector",
|
||||||
|
|
||||||
|
@computed("placeholderKey")
|
||||||
|
placeholder(placeholderKey) {
|
||||||
|
return placeholderKey ? I18n.t(placeholderKey) : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("usernames")
|
||||||
|
_update() {
|
||||||
|
if (this.get("canReceiveUpdates") === "true") {
|
||||||
|
this.didInsertElement({ updateData: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement(opts) {
|
||||||
|
this._super();
|
||||||
|
let self = this,
|
||||||
|
selected = [],
|
||||||
|
groups = [],
|
||||||
|
includeMentionableGroups =
|
||||||
|
this.get("includeMentionableGroups") === "true",
|
||||||
|
includeMessageableGroups =
|
||||||
|
this.get("includeMessageableGroups") === "true",
|
||||||
|
includeGroups = this.get("includeGroups") === "true",
|
||||||
|
allowedUsers = this.get("allowedUsers") === "true";
|
||||||
|
|
||||||
|
function excludedUsernames() {
|
||||||
|
// hack works around some issues with allowAny eventing
|
||||||
|
const usernames = self.get("single") ? [] : selected;
|
||||||
|
return usernames;
|
||||||
|
}
|
||||||
|
$(this.element)
|
||||||
|
.val(this.get("usernames"))
|
||||||
|
.autocomplete({
|
||||||
|
template,
|
||||||
|
disabled: this.get("disabled"),
|
||||||
|
single: this.get("single"),
|
||||||
|
allowAny: this.get("allowAny"),
|
||||||
|
updateData: opts && opts.updateData ? opts.updateData : false,
|
||||||
|
|
||||||
|
dataSource(term) {
|
||||||
|
const termRegex = /[^a-zA-Z0-9_\-\.@\+]/;
|
||||||
|
let results = userSearch({
|
||||||
|
term: term.replace(termRegex, ""),
|
||||||
|
topicId: self.get("topicId"),
|
||||||
|
exclude: excludedUsernames(),
|
||||||
|
includeGroups,
|
||||||
|
allowedUsers,
|
||||||
|
includeMentionableGroups,
|
||||||
|
includeMessageableGroups,
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
|
||||||
|
transformComplete(v) {
|
||||||
|
if (v.username || v.name) {
|
||||||
|
if (!v.username) {
|
||||||
|
groups.push(v.name);
|
||||||
|
}
|
||||||
|
return v.username || v.name;
|
||||||
|
} else {
|
||||||
|
let excludes = excludedUsernames();
|
||||||
|
return v.usernames.filter(function (item) {
|
||||||
|
return excludes.indexOf(item) === -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeItems(items) {
|
||||||
|
let hasGroups = false;
|
||||||
|
items = items.map(function (i) {
|
||||||
|
if (groups.indexOf(i) > -1) {
|
||||||
|
hasGroups = true;
|
||||||
|
}
|
||||||
|
return i.username ? i.username : i;
|
||||||
|
});
|
||||||
|
self.set("usernames", items.join(","));
|
||||||
|
self.set("hasGroups", hasGroups);
|
||||||
|
|
||||||
|
selected = items;
|
||||||
|
if (self.get("onChangeCallback")) {
|
||||||
|
self.sendAction("onChangeCallback");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reverseTransform(i) {
|
||||||
|
return { username: i };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super();
|
||||||
|
$(this.element).autocomplete("destroy");
|
||||||
|
},
|
||||||
|
|
||||||
|
// THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT
|
||||||
|
@observes("usernames")
|
||||||
|
_clearInput: function () {
|
||||||
|
if (arguments.length > 1) {
|
||||||
|
if (isEmpty(this.get("usernames"))) {
|
||||||
|
$(this.element).parent().find("a").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
import CategorySelector from "select-kit/components/category-selector";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import { makeArray } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
|
export default CategorySelector.extend({
|
||||||
|
classNames: ["category-selector", "wizard-category-selector"],
|
||||||
|
content: computed(
|
||||||
|
"categories.[]",
|
||||||
|
"blacklist.[]",
|
||||||
|
"whitelist.[]",
|
||||||
|
function () {
|
||||||
|
return this._super().filter((category) => {
|
||||||
|
const whitelist = makeArray(this.whitelist);
|
||||||
|
return !whitelist.length || whitelist.indexOf(category.id) > -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
|
@ -0,0 +1,213 @@
|
||||||
|
import ComposerEditor from "discourse/components/composer-editor";
|
||||||
|
import {
|
||||||
|
bind,
|
||||||
|
default as discourseComputed,
|
||||||
|
on,
|
||||||
|
} from "discourse-common/utils/decorators";
|
||||||
|
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||||
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
|
import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
|
||||||
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
|
import { alias } from "@ember/object/computed";
|
||||||
|
import Site from "discourse/models/site";
|
||||||
|
import { uploadIcon } from "discourse/lib/uploads";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
|
const IMAGE_MARKDOWN_REGEX =
|
||||||
|
/!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
||||||
|
|
||||||
|
export default ComposerEditor.extend({
|
||||||
|
modal: service(),
|
||||||
|
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
allowUpload: true,
|
||||||
|
showLink: false,
|
||||||
|
topic: null,
|
||||||
|
showToolbar: true,
|
||||||
|
focusTarget: "reply",
|
||||||
|
canWhisper: false,
|
||||||
|
lastValidatedAt: "lastValidatedAt",
|
||||||
|
popupMenuOptions: [],
|
||||||
|
draftStatus: "null",
|
||||||
|
replyPlaceholder: alias("field.translatedPlaceholder"),
|
||||||
|
wizardEventFieldId: null,
|
||||||
|
composerEventPrefix: "wizard-editor",
|
||||||
|
|
||||||
|
@on("didInsertElement")
|
||||||
|
_composerEditorInit() {
|
||||||
|
const $input = $(this.element.querySelector(".d-editor-input"));
|
||||||
|
|
||||||
|
if (this.siteSettings.enable_mentions) {
|
||||||
|
$input.autocomplete({
|
||||||
|
template: findRawTemplate("user-selector-autocomplete"),
|
||||||
|
dataSource: (term) => this._userSearchTerm.call(this, term),
|
||||||
|
key: "@",
|
||||||
|
transformComplete: (v) => v.username || v.name,
|
||||||
|
afterComplete: (value) => {
|
||||||
|
this.composer.set("reply", value);
|
||||||
|
scheduleOnce("afterRender", () => $input.blur().focus());
|
||||||
|
},
|
||||||
|
triggerRule: (textarea) =>
|
||||||
|
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteSettings = this.siteSettings;
|
||||||
|
if (siteSettings.mentionables_enabled) {
|
||||||
|
Site.currentProp("mentionable_items", this.wizard.mentionable_items);
|
||||||
|
const { SEPARATOR } = requirejs(
|
||||||
|
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
|
||||||
|
);
|
||||||
|
const { searchMentionableItem } = requirejs(
|
||||||
|
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-item-search"
|
||||||
|
);
|
||||||
|
|
||||||
|
$input.autocomplete({
|
||||||
|
template: findRawTemplate("javascripts/mentionable-item-autocomplete"),
|
||||||
|
key: SEPARATOR,
|
||||||
|
afterComplete: (value) => {
|
||||||
|
this.composer.set("reply", value);
|
||||||
|
scheduleOnce("afterRender", () => $input.blur().focus());
|
||||||
|
},
|
||||||
|
transformComplete: (item) => item.model.slug,
|
||||||
|
dataSource: (term) =>
|
||||||
|
term.match(/\s/) ? null : searchMentionableItem(term, siteSettings),
|
||||||
|
triggerRule: (textarea) =>
|
||||||
|
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
|
||||||
|
this._bindUploadTarget();
|
||||||
|
|
||||||
|
const field = this.field;
|
||||||
|
this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
|
||||||
|
field.id
|
||||||
|
)} .d-editor-input`;
|
||||||
|
|
||||||
|
this._uppyInstance.on("file-added", () => {
|
||||||
|
this.session.set("wizardEventFieldId", field.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("field.id")
|
||||||
|
fileUploadElementId(fieldId) {
|
||||||
|
return `file-uploader-${dasherize(fieldId)}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
allowedFileTypes() {
|
||||||
|
return this.siteSettings.authorized_extensions
|
||||||
|
.split("|")
|
||||||
|
.map((ext) => "." + ext)
|
||||||
|
.join(",");
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed()
|
||||||
|
uploadIcon() {
|
||||||
|
return uploadIcon(false, this.siteSettings);
|
||||||
|
},
|
||||||
|
|
||||||
|
@bind
|
||||||
|
_handleImageDeleteButtonClick(event) {
|
||||||
|
if (!event.target.classList.contains("delete-image-button")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = parseInt(
|
||||||
|
event.target.closest(".button-wrapper").dataset.imageIndex,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
const matchingPlaceholder =
|
||||||
|
this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX);
|
||||||
|
|
||||||
|
this.session.set("wizardEventFieldId", this.field.id);
|
||||||
|
this.appEvents.trigger(
|
||||||
|
"composer:replace-text",
|
||||||
|
matchingPlaceholder[index],
|
||||||
|
"",
|
||||||
|
{ regex: IMAGE_MARKDOWN_REGEX, index }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
extraButtons(toolbar) {
|
||||||
|
const component = this;
|
||||||
|
|
||||||
|
if (this.allowUpload && this.uploadIcon) {
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "upload",
|
||||||
|
group: "insertions",
|
||||||
|
icon: this.uploadIcon,
|
||||||
|
title: "upload",
|
||||||
|
sendAction: (event) => component.send("showUploadModal", event),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "link",
|
||||||
|
group: "insertions",
|
||||||
|
shortcut: "K",
|
||||||
|
trimLeading: true,
|
||||||
|
unshift: true,
|
||||||
|
sendAction: (event) => component.send("showLinkModal", event),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.siteSettings.mentionables_enabled) {
|
||||||
|
const { SEPARATOR } = requirejs(
|
||||||
|
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
|
||||||
|
);
|
||||||
|
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "insert-mentionable",
|
||||||
|
group: "extras",
|
||||||
|
icon: this.siteSettings.mentionables_composer_button_icon,
|
||||||
|
title: "mentionables.composer.insert.title",
|
||||||
|
perform: () => {
|
||||||
|
this.appEvents.trigger("wizard-editor:insert-text", {
|
||||||
|
fieldId: this.field.id,
|
||||||
|
text: SEPARATOR,
|
||||||
|
});
|
||||||
|
const $textarea = $(
|
||||||
|
document.querySelector(
|
||||||
|
`.composer-field.${this.field.id} textarea.d-editor-input`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$textarea.trigger("keyup.autocomplete");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
previewUpdated(preview) {
|
||||||
|
highlightSyntax(preview, this.siteSettings, this.session);
|
||||||
|
|
||||||
|
if (this.siteSettings.mentionables_enabled) {
|
||||||
|
const { linkSeenMentionableItems } = requirejs(
|
||||||
|
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-items-preview-styling"
|
||||||
|
);
|
||||||
|
linkSeenMentionableItems(preview, this.siteSettings);
|
||||||
|
}
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
showLinkModal(toolbarEvent) {
|
||||||
|
let linkText = "";
|
||||||
|
this._lastSel = toolbarEvent.selected;
|
||||||
|
|
||||||
|
if (this._lastSel) {
|
||||||
|
linkText = this._lastSel.value;
|
||||||
|
}
|
||||||
|
this.modal.show(InsertHyperlink, {
|
||||||
|
model: { linkText, toolbarEvent },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showUploadModal() {
|
||||||
|
this.session.set("wizardEventFieldId", this.field.id);
|
||||||
|
document.getElementById(this.fileUploadElementId).click();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import DateInput from "discourse/components/date-input";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default DateInput.extend({
|
||||||
|
useNativePicker: false,
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
|
@discourseComputed()
|
||||||
|
placeholder() {
|
||||||
|
return this.format;
|
||||||
|
},
|
||||||
|
_opts() {
|
||||||
|
return {
|
||||||
|
format: this.format || "LL",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,14 +1,16 @@
|
||||||
import DateTimeInput from "discourse/components/date-time-input";
|
import DateTimeInput from "discourse/components/date-time-input";
|
||||||
import discourseComputed from 'discourse-common/utils/decorators';
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default DateTimeInput.extend({
|
export default DateTimeInput.extend({
|
||||||
@discourseComputed('timeFirst', 'tabindex')
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
|
@discourseComputed("timeFirst", "tabindex")
|
||||||
timeTabindex(timeFirst, tabindex) {
|
timeTabindex(timeFirst, tabindex) {
|
||||||
return timeFirst ? tabindex : tabindex + 1;
|
return timeFirst ? tabindex : tabindex + 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('timeFirst', 'tabindex')
|
@discourseComputed("timeFirst", "tabindex")
|
||||||
dateTabindex(timeFirst, tabindex) {
|
dateTabindex(timeFirst, tabindex) {
|
||||||
return timeFirst ? tabindex + 1 : tabindex;
|
return timeFirst ? tabindex + 1 : tabindex;
|
||||||
}
|
},
|
||||||
});
|
});
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
import Category from "discourse/models/category";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
didInsertElement() {
|
||||||
|
const property = this.field.property || "id";
|
||||||
|
const value = this.field.value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
this.set(
|
||||||
|
"categories",
|
||||||
|
[...value].reduce((result, v) => {
|
||||||
|
let val =
|
||||||
|
property === "id" ? Category.findById(v) : Category.findBySlug(v);
|
||||||
|
if (val) {
|
||||||
|
result.push(val);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("categories")
|
||||||
|
setValue() {
|
||||||
|
const categories = (this.categories || []).filter((c) => !!c);
|
||||||
|
const property = this.field.property || "id";
|
||||||
|
|
||||||
|
if (categories.length) {
|
||||||
|
this.set(
|
||||||
|
"field.value",
|
||||||
|
categories.reduce((result, c) => {
|
||||||
|
if (c && c[property]) {
|
||||||
|
result.push(c[property]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({});
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||||
|
import { schedule } from "@ember/runloop";
|
||||||
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
|
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { on } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
@on("init")
|
||||||
|
updatePreview() {
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule("afterRender", () => {
|
||||||
|
if (this._state !== "inDOM" || !this.element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $preview = $(this.element);
|
||||||
|
|
||||||
|
if ($preview.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previewUpdated($preview);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
previewUpdated($preview) {
|
||||||
|
// Paint oneboxes
|
||||||
|
const paintFunc = () => {
|
||||||
|
loadOneboxes(
|
||||||
|
$preview[0],
|
||||||
|
ajax,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
this.siteSettings.max_oneboxes_per_post,
|
||||||
|
true // refresh on every load
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
discourseDebounce(this, paintFunc, 450);
|
||||||
|
|
||||||
|
// Short upload urls need resolution
|
||||||
|
resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {
|
||||||
|
default as computed,
|
||||||
|
observes,
|
||||||
|
} from "discourse-common/utils/decorators";
|
||||||
|
import EmberObject from "@ember/object";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
showPreview: false,
|
||||||
|
classNameBindings: [
|
||||||
|
":wizard-field-composer",
|
||||||
|
"showPreview:show-preview:hide-preview",
|
||||||
|
],
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this.set(
|
||||||
|
"composer",
|
||||||
|
EmberObject.create({
|
||||||
|
loading: false,
|
||||||
|
reply: this.get("field.value") || "",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("composer.reply")
|
||||||
|
setField() {
|
||||||
|
this.set("field.value", this.get("composer.reply"));
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("showPreview")
|
||||||
|
togglePreviewLabel(showPreview) {
|
||||||
|
return showPreview
|
||||||
|
? "wizard_composer.hide_preview"
|
||||||
|
: "wizard_composer.show_preview";
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
togglePreview() {
|
||||||
|
this.toggleProperty("showPreview");
|
||||||
|
},
|
||||||
|
|
||||||
|
groupsMentioned() {},
|
||||||
|
afterRefresh() {},
|
||||||
|
cannotSeeMention() {},
|
||||||
|
importQuote() {},
|
||||||
|
showUploadSelector() {},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
@observes("dateTime")
|
||||||
|
setValue() {
|
||||||
|
this.set("field.value", this.dateTime.format(this.field.format));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onChange(value) {
|
||||||
|
this.set("dateTime", moment(value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
@observes("date")
|
||||||
|
setValue() {
|
||||||
|
this.set("field.value", this.date.format(this.field.format));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onChange(value) {
|
||||||
|
this.set("date", moment(value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
keyPress(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onChangeValue(value) {
|
||||||
|
this.set("field.value", value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
keyPress(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
keyPress(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
|
@observes("time")
|
||||||
|
setValue() {
|
||||||
|
this.set("field.value", this.time.format(this.field.format));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onChange(value) {
|
||||||
|
this.set(
|
||||||
|
"time",
|
||||||
|
moment({
|
||||||
|
hours: value.hours,
|
||||||
|
minutes: value.minutes,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
import UppyUploadMixin from "discourse/mixins/uppy-upload";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
|
||||||
|
export default Component.extend(UppyUploadMixin, {
|
||||||
|
classNames: ["wizard-field-upload"],
|
||||||
|
classNameBindings: ["isImage", "fieldClass"],
|
||||||
|
uploading: false,
|
||||||
|
type: computed(function () {
|
||||||
|
return `wizard_${this.field.id}`;
|
||||||
|
}),
|
||||||
|
id: computed(function () {
|
||||||
|
return `wizard_field_upload_${this.field.id}`;
|
||||||
|
}),
|
||||||
|
isImage: computed("field.value.extension", function () {
|
||||||
|
return (
|
||||||
|
this.field.value &&
|
||||||
|
this.siteSettings.wizard_recognised_image_upload_formats
|
||||||
|
.split("|")
|
||||||
|
.includes(this.field.value.extension)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
uploadDone(upload) {
|
||||||
|
this.set("field.value", upload);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({});
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
});
|
41
assets/javascripts/discourse/components/custom-wizard-field.js.es6
Normale Datei
41
assets/javascripts/discourse/components/custom-wizard-field.js.es6
Normale Datei
|
@ -0,0 +1,41 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import { cookAsync } from "discourse/lib/text";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: [
|
||||||
|
":wizard-field",
|
||||||
|
"typeClasses",
|
||||||
|
"field.invalid",
|
||||||
|
"field.id",
|
||||||
|
],
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
cookAsync(this.field.translatedDescription).then((cookedDescription) => {
|
||||||
|
this.set("cookedDescription", cookedDescription);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("field.type", "field.id")
|
||||||
|
typeClasses: (type, id) =>
|
||||||
|
`${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`,
|
||||||
|
|
||||||
|
@discourseComputed("field.id")
|
||||||
|
fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`,
|
||||||
|
|
||||||
|
@discourseComputed("field.type", "field.id")
|
||||||
|
inputComponentName(type, id) {
|
||||||
|
if (["text_only"].includes(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return dasherize(type === "component" ? id : `custom-wizard-field-${type}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("field.type")
|
||||||
|
textType(fieldType) {
|
||||||
|
return ["text", "textarea"].includes(fieldType);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
import ComboBox from "select-kit/components/combo-box";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import { makeArray } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
|
export default ComboBox.extend({
|
||||||
|
content: computed("groups.[]", "field.content.[]", function () {
|
||||||
|
const whitelist = makeArray(this.field.content);
|
||||||
|
return this.groups
|
||||||
|
.filter((group) => {
|
||||||
|
return !whitelist.length || whitelist.indexOf(group.id) > -1;
|
||||||
|
})
|
||||||
|
.map((g) => {
|
||||||
|
return {
|
||||||
|
id: g.id,
|
||||||
|
name: g.full_name ? g.full_name : g.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
import CustomWizard from "../models/custom-wizard";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
import getURL from "discourse-common/lib/get-url";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: [":wizard-no-access", "reasonClass"],
|
||||||
|
|
||||||
|
@discourseComputed("reason")
|
||||||
|
reasonClass(reason) {
|
||||||
|
return dasherize(reason);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
siteName() {
|
||||||
|
return this.siteSettings.title || "";
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
skip() {
|
||||||
|
if (this.currentUser) {
|
||||||
|
CustomWizard.skip(this.get("wizardId"));
|
||||||
|
} else {
|
||||||
|
window.location = getURL("/");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -2,10 +2,10 @@ import Component from "@ember/component";
|
||||||
import { bind } from "@ember/runloop";
|
import { bind } from "@ember/runloop";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: ['wizard-similar-topics'],
|
classNames: ["wizard-similar-topics"],
|
||||||
showTopics: true,
|
showTopics: true,
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
$(document).on("click", bind(this, this.documentClick));
|
$(document).on("click", bind(this, this.documentClick));
|
||||||
},
|
},
|
||||||
|
@ -15,22 +15,24 @@ export default Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
documentClick(e) {
|
documentClick(e) {
|
||||||
if (this._state == "destroying") return;
|
if (this._state === "destroying") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let $target = $(e.target);
|
let $target = $(e.target);
|
||||||
|
|
||||||
if (!$target.hasClass('show-topics')) {
|
if (!$target.hasClass("show-topics")) {
|
||||||
this.set('showTopics', false);
|
this.set("showTopics", false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes('topics')
|
@observes("topics")
|
||||||
toggleShowWhenTopicsChange() {
|
toggleShowWhenTopicsChange() {
|
||||||
this.set('showTopics', true);
|
this.set("showTopics", true);
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleShowTopics() {
|
toggleShowTopics() {
|
||||||
this.set('showTopics', true);
|
this.set("showTopics", true);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: [":wizard-step-form", "customStepClass"],
|
||||||
|
|
||||||
|
@discourseComputed("step.id")
|
||||||
|
customStepClass: (stepId) => `wizard-step-${stepId}`,
|
||||||
|
});
|
227
assets/javascripts/discourse/components/custom-wizard-step.js.es6
Normale Datei
227
assets/javascripts/discourse/components/custom-wizard-step.js.es6
Normale Datei
|
@ -0,0 +1,227 @@
|
||||||
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import getUrl from "discourse-common/lib/get-url";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { schedule } from "@ember/runloop";
|
||||||
|
import { cookAsync } from "discourse/lib/text";
|
||||||
|
import CustomWizard, {
|
||||||
|
updateCachedWizard,
|
||||||
|
} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard";
|
||||||
|
import { alias, not } from "@ember/object/computed";
|
||||||
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
|
||||||
|
const alreadyWarned = {};
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: [":wizard-step", "step.id"],
|
||||||
|
saving: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.set("stylingDropdown", {});
|
||||||
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
cookAsync(this.step.translatedTitle).then((cookedTitle) => {
|
||||||
|
this.set("cookedTitle", cookedTitle);
|
||||||
|
});
|
||||||
|
cookAsync(this.step.translatedDescription).then((cookedDescription) => {
|
||||||
|
this.set("cookedDescription", cookedDescription);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.autoFocus();
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.index", "wizard.required")
|
||||||
|
showQuitButton: (index, required) => index === 0 && !required,
|
||||||
|
|
||||||
|
showNextButton: not("step.final"),
|
||||||
|
showDoneButton: alias("step.final"),
|
||||||
|
|
||||||
|
@discourseComputed(
|
||||||
|
"step.index",
|
||||||
|
"step.displayIndex",
|
||||||
|
"wizard.totalSteps",
|
||||||
|
"wizard.completed"
|
||||||
|
)
|
||||||
|
showFinishButton: (index, displayIndex, total, completed) => {
|
||||||
|
return index !== 0 && displayIndex !== total && completed;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.index")
|
||||||
|
showBackButton: (index) => index > 0,
|
||||||
|
|
||||||
|
@discourseComputed("step.banner")
|
||||||
|
bannerImage(src) {
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return getUrl(src);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.id")
|
||||||
|
bannerAndDescriptionClass(id) {
|
||||||
|
return `wizard-banner-and-description wizard-banner-and-description-${id}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.fields.[]")
|
||||||
|
primaryButtonIndex(fields) {
|
||||||
|
return fields.length + 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.fields.[]")
|
||||||
|
secondaryButtonIndex(fields) {
|
||||||
|
return fields.length + 2;
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("step.id")
|
||||||
|
_stepChanged() {
|
||||||
|
this.set("saving", false);
|
||||||
|
this.autoFocus();
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("step.message")
|
||||||
|
_handleMessage: function () {
|
||||||
|
const message = this.get("step.message");
|
||||||
|
this.showMessage(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.index", "wizard.totalSteps")
|
||||||
|
barStyle(displayIndex, totalSteps) {
|
||||||
|
let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1);
|
||||||
|
if (ratio < 0) {
|
||||||
|
ratio = 0;
|
||||||
|
}
|
||||||
|
if (ratio > 1) {
|
||||||
|
ratio = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlSafe(`width: ${ratio * 200}px`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.fields")
|
||||||
|
includeSidebar(fields) {
|
||||||
|
return !!fields.findBy("show_in_sidebar");
|
||||||
|
},
|
||||||
|
|
||||||
|
autoFocus() {
|
||||||
|
discourseLater(() => {
|
||||||
|
schedule("afterRender", () => {
|
||||||
|
if ($(".invalid .wizard-focusable").length) {
|
||||||
|
this.animateInvalidFields();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
animateInvalidFields() {
|
||||||
|
schedule("afterRender", () => {
|
||||||
|
let $invalid = $(".invalid .wizard-focusable");
|
||||||
|
if ($invalid.length) {
|
||||||
|
$([document.documentElement, document.body]).animate(
|
||||||
|
{
|
||||||
|
scrollTop: $invalid.offset().top - 200,
|
||||||
|
},
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
advance() {
|
||||||
|
this.set("saving", true);
|
||||||
|
this.get("step")
|
||||||
|
.save()
|
||||||
|
.then((response) => {
|
||||||
|
updateCachedWizard(CustomWizard.build(response["wizard"]));
|
||||||
|
|
||||||
|
if (response["final"]) {
|
||||||
|
CustomWizard.finished(response);
|
||||||
|
} else {
|
||||||
|
this.goNext(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => this.animateInvalidFields())
|
||||||
|
.finally(() => this.set("saving", false));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
quit() {
|
||||||
|
this.get("wizard").skip();
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
this.send("nextStep");
|
||||||
|
},
|
||||||
|
|
||||||
|
showMessage(message) {
|
||||||
|
this.sendAction(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
stylingDropdownChanged(id, value) {
|
||||||
|
this.set("stylingDropdown", { id, value });
|
||||||
|
},
|
||||||
|
|
||||||
|
exitEarly() {
|
||||||
|
const step = this.step;
|
||||||
|
step.validate();
|
||||||
|
|
||||||
|
if (step.get("valid")) {
|
||||||
|
this.set("saving", true);
|
||||||
|
|
||||||
|
step
|
||||||
|
.save()
|
||||||
|
.then(() => this.send("quit"))
|
||||||
|
.finally(() => this.set("saving", false));
|
||||||
|
} else {
|
||||||
|
this.autoFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
backStep() {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.goBack();
|
||||||
|
},
|
||||||
|
|
||||||
|
nextStep() {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = this.step;
|
||||||
|
const result = step.validate();
|
||||||
|
|
||||||
|
if (result.warnings.length) {
|
||||||
|
const unwarned = result.warnings.filter((w) => !alreadyWarned[w]);
|
||||||
|
if (unwarned.length) {
|
||||||
|
unwarned.forEach((w) => (alreadyWarned[w] = true));
|
||||||
|
return window.bootbox.confirm(
|
||||||
|
unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"),
|
||||||
|
I18n.t("no_value"),
|
||||||
|
I18n.t("yes_value"),
|
||||||
|
(confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.get("valid")) {
|
||||||
|
this.advance();
|
||||||
|
} else {
|
||||||
|
this.autoFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import TagChooser from "select-kit/components/tag-chooser";
|
||||||
|
|
||||||
|
export default TagChooser.extend({
|
||||||
|
searchTags(url, data, callback) {
|
||||||
|
if (this.tagGroups) {
|
||||||
|
let tagGroupsString = this.tagGroups.join(",");
|
||||||
|
data.filterForInput = {
|
||||||
|
name: "custom-wizard-tag-chooser",
|
||||||
|
groups: tagGroupsString,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._super(url, data, callback);
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import TagChooser from 'select-kit/components/tag-chooser';
|
import TagChooser from "select-kit/components/tag-chooser";
|
||||||
import { makeArray } from "discourse-common/lib/helpers";
|
import { makeArray } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
export default TagChooser.extend({
|
export default TagChooser.extend({
|
||||||
|
@ -7,5 +7,5 @@ export default TagChooser.extend({
|
||||||
const whitelist = makeArray(context.whitelist);
|
const whitelist = makeArray(context.whitelist);
|
||||||
return !whitelist.length || whitelist.indexOf(tag.id) > 1;
|
return !whitelist.length || whitelist.indexOf(tag.id) > 1;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
import computed from "discourse-common/utils/decorators";
|
||||||
|
import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import TextField from "discourse/components/text-field";
|
||||||
|
|
||||||
|
export default TextField.extend({
|
||||||
|
attributeBindings: [
|
||||||
|
"autocorrect",
|
||||||
|
"autocapitalize",
|
||||||
|
"autofocus",
|
||||||
|
"maxLength",
|
||||||
|
"dir",
|
||||||
|
],
|
||||||
|
|
||||||
|
@computed
|
||||||
|
dir() {
|
||||||
|
if (this.siteSettings.support_mixed_text_direction) {
|
||||||
|
let val = this.value;
|
||||||
|
if (val) {
|
||||||
|
return isRTL(val) ? "rtl" : "ltr";
|
||||||
|
} else {
|
||||||
|
return siteDir();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keyUp() {
|
||||||
|
if (this.siteSettings.support_mixed_text_direction) {
|
||||||
|
let val = this.value;
|
||||||
|
if (isRTL(val)) {
|
||||||
|
this.set("dir", "rtl");
|
||||||
|
} else if (isLTR(val)) {
|
||||||
|
this.set("dir", "ltr");
|
||||||
|
} else {
|
||||||
|
this.set("dir", siteDir());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed("placeholderKey")
|
||||||
|
placeholder(placeholderKey) {
|
||||||
|
return placeholderKey ? I18n.t(placeholderKey) : "";
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,3 +1,3 @@
|
||||||
import TimeInput from "discourse/components/time-input";
|
import TimeInput from "discourse/components/time-input";
|
||||||
|
|
||||||
export default TimeInput.extend();
|
export default TimeInput.extend({});
|
|
@ -1,5 +1,5 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
actions: {
|
actions: {
|
||||||
perform() {
|
perform() {
|
|
@ -0,0 +1,34 @@
|
||||||
|
<DModal @closeModal={{@closeModal}} @title={{this.title}}>
|
||||||
|
{{#if loading}}
|
||||||
|
<LoadingSpinner size="large" />
|
||||||
|
{{else}}
|
||||||
|
<div class="edit-directory-columns-container">
|
||||||
|
{{#each @model.columns as |column|}}
|
||||||
|
<div class="edit-directory-column">
|
||||||
|
<div class="left-content">
|
||||||
|
<label class="column-name">
|
||||||
|
<Input @type="checkbox" @checked={{column.enabled}} />
|
||||||
|
{{directory-table-header-title
|
||||||
|
field=column.label
|
||||||
|
translated=true
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<DButton
|
||||||
|
class="btn-primary"
|
||||||
|
@label="directory.edit_columns.save"
|
||||||
|
@action={{action "save"}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
class="btn-secondary reset-to-default"
|
||||||
|
@label="directory.edit_columns.reset_to_default"
|
||||||
|
@action={{action "resetToDefault"}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DModal>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class AdminWizardsColumnComponent extends Component {
|
||||||
|
title = I18n.t("admin.wizard.edit_columns");
|
||||||
|
|
||||||
|
@action save() {
|
||||||
|
this.args.closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action resetToDefault() {
|
||||||
|
this.args.model.reset();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<DModal
|
||||||
|
@closeModal={{@closeModal}}
|
||||||
|
class="next-session-time-modal"
|
||||||
|
@title={{this.title}}
|
||||||
|
>
|
||||||
|
<DateTimeInput
|
||||||
|
@date={{this.bufferedDateTime}}
|
||||||
|
@onChange={{action "dateTimeChanged"}}
|
||||||
|
@showTime="true"
|
||||||
|
@clearable="true"
|
||||||
|
/>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<DButton
|
||||||
|
@action={{action "submit"}}
|
||||||
|
class="btn-primary"
|
||||||
|
@label="admin.wizard.after_time_modal.done"
|
||||||
|
@disabled={{this.submitDisabled}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DModal>
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class NextSessionScheduledComponent extends Component {
|
||||||
|
@tracked bufferedDateTime;
|
||||||
|
title = I18n.t("admin.wizard.after_time_modal.title");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.bufferedDateTime = this.args.model.dateTime
|
||||||
|
? moment(this.args.model.dateTime)
|
||||||
|
: moment(Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
get submitDisabled() {
|
||||||
|
return moment().isAfter(this.bufferedDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action submit() {
|
||||||
|
const dateTime = this.bufferedDateTime;
|
||||||
|
this.args.model.update(moment(dateTime).utc().toISOString());
|
||||||
|
this.args.closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action dateTimeChanged(dateTime) {
|
||||||
|
this.bufferedDateTime = dateTime;
|
||||||
|
}
|
||||||
|
}
|
160
assets/javascripts/discourse/components/similar-topics-validator.js.es6
Normale Datei
160
assets/javascripts/discourse/components/similar-topics-validator.js.es6
Normale Datei
|
@ -0,0 +1,160 @@
|
||||||
|
import WizardFieldValidator from "discourse/plugins/discourse-custom-wizard/discourse/components/validator";
|
||||||
|
import { deepMerge } from "discourse-common/lib/object";
|
||||||
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
|
import { cancel, later } from "@ember/runloop";
|
||||||
|
import { A } from "@ember/array";
|
||||||
|
import EmberObject, { computed } from "@ember/object";
|
||||||
|
import { and, equal, notEmpty } from "@ember/object/computed";
|
||||||
|
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||||
|
import { dasherize } from "@ember/string";
|
||||||
|
|
||||||
|
export default WizardFieldValidator.extend({
|
||||||
|
classNames: ["similar-topics-validator"],
|
||||||
|
similarTopics: null,
|
||||||
|
hasInput: notEmpty("field.value"),
|
||||||
|
hasSimilarTopics: notEmpty("similarTopics"),
|
||||||
|
hasNotSearched: equal("similarTopics", null),
|
||||||
|
noSimilarTopics: computed("similarTopics", function () {
|
||||||
|
return this.similarTopics !== null && this.similarTopics.length === 0;
|
||||||
|
}),
|
||||||
|
showSimilarTopics: computed("typing", "hasSimilarTopics", function () {
|
||||||
|
return this.hasSimilarTopics && !this.typing;
|
||||||
|
}),
|
||||||
|
showNoSimilarTopics: computed("typing", "noSimilarTopics", function () {
|
||||||
|
return this.noSimilarTopics && !this.typing;
|
||||||
|
}),
|
||||||
|
hasValidationCategories: notEmpty("validationCategories"),
|
||||||
|
insufficientCharacters: computed("typing", "field.value", function () {
|
||||||
|
return this.hasInput && this.field.value.length < 5 && !this.typing;
|
||||||
|
}),
|
||||||
|
insufficientCharactersCategories: and(
|
||||||
|
"insufficientCharacters",
|
||||||
|
"hasValidationCategories"
|
||||||
|
),
|
||||||
|
|
||||||
|
@discourseComputed("validation.categories")
|
||||||
|
validationCategories(categoryIds) {
|
||||||
|
if (categoryIds) {
|
||||||
|
return categoryIds.map((id) => this.site.categoriesById[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return A();
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("validationCategories")
|
||||||
|
catLinks(categories) {
|
||||||
|
return categories.map((category) => categoryBadgeHTML(category)).join("");
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed(
|
||||||
|
"loading",
|
||||||
|
"showSimilarTopics",
|
||||||
|
"showNoSimilarTopics",
|
||||||
|
"insufficientCharacters",
|
||||||
|
"insufficientCharactersCategories"
|
||||||
|
)
|
||||||
|
currentState(
|
||||||
|
loading,
|
||||||
|
showSimilarTopics,
|
||||||
|
showNoSimilarTopics,
|
||||||
|
insufficientCharacters,
|
||||||
|
insufficientCharactersCategories
|
||||||
|
) {
|
||||||
|
switch (true) {
|
||||||
|
case loading:
|
||||||
|
return "loading";
|
||||||
|
case showSimilarTopics:
|
||||||
|
return "results";
|
||||||
|
case showNoSimilarTopics:
|
||||||
|
return "no_results";
|
||||||
|
case insufficientCharactersCategories:
|
||||||
|
return "insufficient_characters_categories";
|
||||||
|
case insufficientCharacters:
|
||||||
|
return "insufficient_characters";
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("currentState")
|
||||||
|
currentStateClass(currentState) {
|
||||||
|
if (currentState) {
|
||||||
|
return `similar-topics-${dasherize(currentState)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "similar-topics";
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("currentState")
|
||||||
|
currentStateKey(currentState) {
|
||||||
|
if (currentState) {
|
||||||
|
return `realtime_validations.similar_topics.${currentState}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
validate() {},
|
||||||
|
|
||||||
|
@observes("field.value")
|
||||||
|
customValidate() {
|
||||||
|
const field = this.field;
|
||||||
|
|
||||||
|
if (!field.value) {
|
||||||
|
this.set("similarTopics", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = field.value;
|
||||||
|
|
||||||
|
this.set("typing", true);
|
||||||
|
|
||||||
|
const lastKeyUp = new Date();
|
||||||
|
this._lastKeyUp = lastKeyUp;
|
||||||
|
|
||||||
|
// One second from now, check to see if the last key was hit when
|
||||||
|
// we recorded it. If it was, the user paused typing.
|
||||||
|
cancel(this._lastKeyTimeout);
|
||||||
|
this._lastKeyTimeout = later(() => {
|
||||||
|
if (lastKeyUp !== this._lastKeyUp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set("typing", false);
|
||||||
|
|
||||||
|
if (value && value.length < 5) {
|
||||||
|
this.set("similarTopics", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSimilarTopics();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSimilarTopics() {
|
||||||
|
this.set("similarTopics", null);
|
||||||
|
this.set("updating", true);
|
||||||
|
|
||||||
|
this.backendValidate({
|
||||||
|
title: this.get("field.value"),
|
||||||
|
categories: this.get("validation.categories"),
|
||||||
|
time_unit: this.get("validation.time_unit"),
|
||||||
|
time_n_value: this.get("validation.time_n_value"),
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
const similarTopics = A(
|
||||||
|
deepMerge(result["topics"], result["similar_topics"])
|
||||||
|
);
|
||||||
|
similarTopics.forEach(function (topic, index) {
|
||||||
|
similarTopics[index] = EmberObject.create(topic);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("similarTopics", similarTopics);
|
||||||
|
})
|
||||||
|
.finally(() => this.set("updating", false));
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
closeMessage() {
|
||||||
|
this.set("showMessage", false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,17 +1,15 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { equal } from "@ember/object/computed";
|
import { equal } from "@ember/object/computed";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax, getToken } from "discourse/lib/ajax";
|
||||||
import { getToken } from "wizard/lib/ajax";
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: ['validator'],
|
classNames: ["validator"],
|
||||||
classNameBindings: ["isValid", "isInvalid"],
|
classNameBindings: ["isValid", "isInvalid"],
|
||||||
validMessageKey: null,
|
validMessageKey: null,
|
||||||
invalidMessageKey: null,
|
invalidMessageKey: null,
|
||||||
isValid: null,
|
isValid: null,
|
||||||
isInvalid: equal("isValid", false),
|
isInvalid: equal("isValid", false),
|
||||||
layoutName: "components/validator", // useful for sharing the template with extending components
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
|
||||||
import Component from '@ember/component';
|
|
||||||
|
|
||||||
export default Component.extend({
|
|
||||||
classNames: 'wizard-advanced-toggle',
|
|
||||||
|
|
||||||
@discourseComputed('showAdvanced')
|
|
||||||
toggleClass(showAdvanced) {
|
|
||||||
let classes = 'btn'
|
|
||||||
if (showAdvanced) classes += ' btn-primary';
|
|
||||||
return classes;
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
toggleAdvanced() {
|
|
||||||
this.toggleProperty('showAdvanced');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,89 +1,104 @@
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed } from "discourse-common/utils/decorators";
|
||||||
import { equal, empty, or, and } from "@ember/object/computed";
|
import { empty, equal, or } from "@ember/object/computed";
|
||||||
import { generateName, selectKitContent } from '../lib/wizard';
|
import { notificationLevels, selectKitContent } from "../lib/wizard";
|
||||||
import { computed } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import wizardSchema from '../lib/wizard-schema';
|
import UndoChanges from "../mixins/undo-changes";
|
||||||
import UndoChanges from '../mixins/undo-changes';
|
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { notificationLevels } from '../lib/wizard';
|
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
export default Component.extend(UndoChanges, {
|
export default Component.extend(UndoChanges, {
|
||||||
componentType: 'action',
|
componentType: "action",
|
||||||
classNameBindings: [':wizard-custom-action', 'visible'],
|
classNameBindings: [":wizard-custom-action", "visible"],
|
||||||
visible: computed('currentActionId', function() { return this.action.id === this.currentActionId }),
|
visible: computed("currentActionId", function () {
|
||||||
createTopic: equal('action.type', 'create_topic'),
|
return this.action.id === this.currentActionId;
|
||||||
updateProfile: equal('action.type', 'update_profile'),
|
}),
|
||||||
watchCategories: equal('action.type', 'watch_categories'),
|
createTopic: equal("action.type", "create_topic"),
|
||||||
sendMessage: equal('action.type', 'send_message'),
|
updateProfile: equal("action.type", "update_profile"),
|
||||||
openComposer: equal('action.type', 'open_composer'),
|
watchCategories: equal("action.type", "watch_categories"),
|
||||||
sendToApi: equal('action.type', 'send_to_api'),
|
watchTags: equal("action.type", "watch_tags"),
|
||||||
addToGroup: equal('action.type', 'add_to_group'),
|
sendMessage: equal("action.type", "send_message"),
|
||||||
routeTo: equal('action.type', 'route_to'),
|
openComposer: equal("action.type", "open_composer"),
|
||||||
createCategory: equal('action.type', 'create_category'),
|
sendToApi: equal("action.type", "send_to_api"),
|
||||||
createGroup: equal('action.type', 'create_group'),
|
addToGroup: equal("action.type", "add_to_group"),
|
||||||
apiEmpty: empty('action.api'),
|
routeTo: equal("action.type", "route_to"),
|
||||||
groupPropertyTypes: selectKitContent(['id', 'name']),
|
createCategory: equal("action.type", "create_category"),
|
||||||
hasAdvanced: or('hasCustomFields', 'routeTo'),
|
createGroup: equal("action.type", "create_group"),
|
||||||
showAdvanced: and('hasAdvanced', 'action.type'),
|
apiEmpty: empty("action.api"),
|
||||||
hasCustomFields: or('basicTopicFields', 'updateProfile', 'createGroup', 'createCategory'),
|
groupPropertyTypes: selectKitContent(["id", "name"]),
|
||||||
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
hasCustomFields: or(
|
||||||
publicTopicFields: or('createTopic', 'openComposer'),
|
"basicTopicFields",
|
||||||
showPostAdvanced: or('createTopic', 'sendMessage'),
|
"updateProfile",
|
||||||
actionTypes: Object.keys(wizardSchema.action.types).map(type => {
|
"createGroup",
|
||||||
|
"createCategory"
|
||||||
|
),
|
||||||
|
basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
|
||||||
|
publicTopicFields: or("createTopic", "openComposer"),
|
||||||
|
showPostAdvanced: or("createTopic", "sendMessage"),
|
||||||
|
availableNotificationLevels: notificationLevels.map((type) => {
|
||||||
return {
|
return {
|
||||||
id: type,
|
id: type,
|
||||||
name: I18n.t(`admin.wizard.action.${type}.label`)
|
name: I18n.t(`admin.wizard.action.watch_x.notification_level.${type}`),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
availableNotificationLevels: notificationLevels.map((type, index) => {
|
|
||||||
return {
|
messageUrl: "https://discourse.pluginmanager.org/t/action-settings",
|
||||||
id: type,
|
|
||||||
name: I18n.t(`admin.wizard.action.watch_categories.notification_level.${type}`)
|
@discourseComputed("action.type")
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
messageUrl: 'https://thepavilion.io/t/2810',
|
|
||||||
|
|
||||||
@discourseComputed('action.type')
|
|
||||||
messageKey(type) {
|
messageKey(type) {
|
||||||
let key = 'type';
|
let key = "type";
|
||||||
if (type) {
|
if (type) {
|
||||||
key = 'edit';
|
key = "edit";
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('wizard.steps')
|
@discourseComputed("action.type")
|
||||||
|
customFieldsContext(type) {
|
||||||
|
return `action.${type}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("wizard.steps")
|
||||||
runAfterContent(steps) {
|
runAfterContent(steps) {
|
||||||
let content = steps.map(function(step) {
|
let content = steps.map(function (step) {
|
||||||
return {
|
return {
|
||||||
id: step.id,
|
id: step.id,
|
||||||
name: step.title || step.id
|
name: step.title || step.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
content.unshift({
|
content.unshift({
|
||||||
id: 'wizard_completion',
|
id: "wizard_completion",
|
||||||
name: I18n.t('admin.wizard.action.run_after.wizard_completion')
|
name: I18n.t("admin.wizard.action.run_after.wizard_completion"),
|
||||||
});
|
});
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('apis')
|
@discourseComputed("apis")
|
||||||
availableApis(apis) {
|
availableApis(apis) {
|
||||||
return apis.map(a => {
|
return apis.map((a) => {
|
||||||
return {
|
return {
|
||||||
id: a.name,
|
id: a.name,
|
||||||
name: a.title
|
name: a.title,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('apis', 'action.api')
|
@discourseComputed("apis", "action.api")
|
||||||
availableEndpoints(apis, api) {
|
availableEndpoints(apis, api) {
|
||||||
if (!api) return [];
|
if (!api) {
|
||||||
return apis.find(a => a.name === api).endpoints;
|
return [];
|
||||||
}
|
}
|
||||||
|
return apis.find((a) => a.name === api).endpoints;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("fieldTypes")
|
||||||
|
hasEventField(fieldTypes) {
|
||||||
|
return fieldTypes.map((ft) => ft.id).includes("event");
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("fieldTypes")
|
||||||
|
hasLocationField(fieldTypes) {
|
||||||
|
return fieldTypes.map((ft) => ft.id).includes("location");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,117 +1,159 @@
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed } from "discourse-common/utils/decorators";
|
||||||
import { equal, or, alias } from "@ember/object/computed";
|
import { equal, or } from "@ember/object/computed";
|
||||||
import { computed } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import { selectKitContent } from '../lib/wizard';
|
import { selectKitContent } from "../lib/wizard";
|
||||||
import UndoChanges from '../mixins/undo-changes';
|
import UndoChanges from "../mixins/undo-changes";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import wizardSchema from '../lib/wizard-schema';
|
import wizardSchema from "../lib/wizard-schema";
|
||||||
|
|
||||||
export default Component.extend(UndoChanges, {
|
export default Component.extend(UndoChanges, {
|
||||||
componentType: 'field',
|
componentType: "field",
|
||||||
classNameBindings: [':wizard-custom-field', 'visible'],
|
classNameBindings: [":wizard-custom-field", "visible"],
|
||||||
visible: computed('currentFieldId', function() { return this.field.id === this.currentFieldId }),
|
visible: computed("currentFieldId", function () {
|
||||||
isDropdown: equal('field.type', 'dropdown'),
|
return this.field.id === this.currentFieldId;
|
||||||
isUpload: equal('field.type', 'upload'),
|
}),
|
||||||
isCategory: equal('field.type', 'category'),
|
isDropdown: equal("field.type", "dropdown"),
|
||||||
isGroup: equal('field.type', 'group'),
|
isUpload: equal("field.type", "upload"),
|
||||||
isTag: equal('field.type', 'tag'),
|
isCategory: equal("field.type", "category"),
|
||||||
isText: equal('field.type', 'text'),
|
isGroup: equal("field.type", "group"),
|
||||||
isTextarea: equal('field.type', 'textarea'),
|
isTag: equal("field.type", "tag"),
|
||||||
isUrl: equal('field.type', 'url'),
|
isText: equal("field.type", "text"),
|
||||||
isComposer: equal('field.type', 'composer'),
|
isTextarea: equal("field.type", "textarea"),
|
||||||
showPrefill: or('isText', 'isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
isUrl: equal("field.type", "url"),
|
||||||
showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
isComposer: equal("field.type", "composer"),
|
||||||
showLimit: or('isCategory', 'isTag'),
|
showPrefill: or("isText", "isCategory", "isTag", "isGroup", "isDropdown"),
|
||||||
isTextType: or('isText', 'isTextarea', 'isComposer'),
|
showContent: or("isCategory", "isTag", "isGroup", "isDropdown"),
|
||||||
categoryPropertyTypes: selectKitContent(['id', 'slug']),
|
showLimit: or("isCategory", "isTag"),
|
||||||
showAdvanced: alias('field.type'),
|
isTextType: or("isText", "isTextarea", "isComposer"),
|
||||||
messageUrl: 'https://thepavilion.io/t/2809',
|
isComposerPreview: equal("field.type", "composer_preview"),
|
||||||
|
categoryPropertyTypes: selectKitContent(["id", "slug"]),
|
||||||
@discourseComputed('field.type')
|
messageUrl: "https://discourse.pluginmanager.org/t/field-settings",
|
||||||
|
|
||||||
|
@discourseComputed("field.type")
|
||||||
validations(type) {
|
validations(type) {
|
||||||
const applicableToField = [];
|
const applicableToField = [];
|
||||||
|
|
||||||
for(let validation in wizardSchema.field.validations) {
|
for (let validation in wizardSchema.field.validations) {
|
||||||
if ((wizardSchema.field.validations[validation]["types"]).includes(type)) {
|
if (wizardSchema.field.validations[validation]["types"].includes(type)) {
|
||||||
applicableToField.push(validation)
|
applicableToField.push(validation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return applicableToField;
|
return applicableToField;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('field.type')
|
@discourseComputed("field.type")
|
||||||
isDateTime(type) {
|
isDateTime(type) {
|
||||||
return ['date_time', 'date', 'time'].indexOf(type) > -1;
|
return ["date_time", "date", "time"].indexOf(type) > -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('field.type')
|
@discourseComputed("field.type")
|
||||||
messageKey(type) {
|
messageKey(type) {
|
||||||
let key = 'type';
|
let key = "type";
|
||||||
if (type) {
|
if (type) {
|
||||||
key = 'edit';
|
key = "edit";
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
|
|
||||||
setupTypeOutput(fieldType, options) {
|
setupTypeOutput(fieldType, options) {
|
||||||
const selectionType = {
|
const selectionType = {
|
||||||
category: 'category',
|
category: "category",
|
||||||
tag: 'tag',
|
tag: "tag",
|
||||||
group: 'group'
|
group: "group",
|
||||||
}[fieldType];
|
}[fieldType];
|
||||||
|
|
||||||
if (selectionType) {
|
if (selectionType) {
|
||||||
options[`${selectionType}Selection`] = 'output';
|
options[`${selectionType}Selection`] = "output";
|
||||||
options.outputDefaultSelection = selectionType;
|
options.outputDefaultSelection = selectionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('field.type')
|
@discourseComputed("field.type")
|
||||||
contentOptions(fieldType) {
|
contentOptions(fieldType) {
|
||||||
let options = {
|
let options = {
|
||||||
wizardFieldSelection: true,
|
wizardFieldSelection: true,
|
||||||
textSelection: 'key,value',
|
textSelection: "key,value",
|
||||||
userFieldSelection: 'key,value',
|
userFieldSelection: "key,value",
|
||||||
context: 'field'
|
context: "field",
|
||||||
}
|
};
|
||||||
|
|
||||||
options = this.setupTypeOutput(fieldType, options);
|
options = this.setupTypeOutput(fieldType, options);
|
||||||
|
|
||||||
if (this.isDropdown) {
|
if (this.isDropdown) {
|
||||||
options.wizardFieldSelection = 'key,value';
|
options.wizardFieldSelection = "key,value";
|
||||||
options.userFieldOptionsSelection = 'output';
|
options.userFieldOptionsSelection = "output";
|
||||||
options.textSelection = 'key,value,output';
|
options.textSelection = "key,value";
|
||||||
options.inputTypes = 'conditional,association,assignment';
|
options.inputTypes = "association,conditional,assignment";
|
||||||
options.pairConnector = 'association';
|
options.pairConnector = "association";
|
||||||
options.keyPlaceholder = 'admin.wizard.key';
|
options.keyPlaceholder = "admin.wizard.key";
|
||||||
options.valuePlaceholder = 'admin.wizard.value';
|
options.valuePlaceholder = "admin.wizard.value";
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('field.type')
|
@discourseComputed("field.type")
|
||||||
prefillOptions(fieldType) {
|
prefillOptions(fieldType) {
|
||||||
let options = {
|
let options = {
|
||||||
wizardFieldSelection: true,
|
wizardFieldSelection: true,
|
||||||
textSelection: true,
|
textSelection: true,
|
||||||
userFieldSelection: 'key,value',
|
userFieldSelection: "key,value",
|
||||||
context: 'field'
|
context: "field",
|
||||||
}
|
};
|
||||||
|
|
||||||
return this.setupTypeOutput(fieldType, options);
|
return this.setupTypeOutput(fieldType, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
@discourseComputed("step.index")
|
||||||
imageUploadDone(upload) {
|
fieldConditionOptions(stepIndex) {
|
||||||
this.set("field.image", upload.url);
|
const options = {
|
||||||
},
|
inputTypes: "validation",
|
||||||
|
context: "field",
|
||||||
imageUploadDeleted() {
|
textSelection: "value",
|
||||||
this.set("field.image", null);
|
userFieldSelection: true,
|
||||||
|
groupSelection: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stepIndex > 0) {
|
||||||
|
options.wizardFieldSelection = true;
|
||||||
|
options.wizardActionSelection = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("step.index")
|
||||||
|
fieldIndexOptions(stepIndex) {
|
||||||
|
const options = {
|
||||||
|
context: "field",
|
||||||
|
userFieldSelection: true,
|
||||||
|
groupSelection: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stepIndex > 0) {
|
||||||
|
options.wizardFieldSelection = true;
|
||||||
|
options.wizardActionSelection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
imageUploadDone(upload) {
|
||||||
|
this.setProperties({
|
||||||
|
"field.image": upload.url,
|
||||||
|
"field.image_upload_id": upload.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
imageUploadDeleted() {
|
||||||
|
this.setProperties({
|
||||||
|
"field.image": null,
|
||||||
|
"field.image_upload_id": null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,40 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: 'wizard-custom-step',
|
classNames: "wizard-custom-step",
|
||||||
|
|
||||||
|
@discourseComputed("step.index")
|
||||||
|
stepConditionOptions(stepIndex) {
|
||||||
|
const options = {
|
||||||
|
inputTypes: "validation",
|
||||||
|
context: "step",
|
||||||
|
textSelection: "value",
|
||||||
|
userFieldSelection: true,
|
||||||
|
groupSelection: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stepIndex > 0) {
|
||||||
|
options["wizardFieldSelection"] = true;
|
||||||
|
options["wizardActionSelection"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
bannerUploadDone(upload) {
|
bannerUploadDone(upload) {
|
||||||
this.set("step.banner", upload.url);
|
this.setProperties({
|
||||||
|
"step.banner": upload.url,
|
||||||
|
"step.banner_upload_id": upload.id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
bannerUploadDeleted() {
|
bannerUploadDeleted() {
|
||||||
this.set("step.banner", null);
|
this.setProperties({
|
||||||
}
|
"step.banner": null,
|
||||||
}
|
"step.banner_upload_id": null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,136 +1,150 @@
|
||||||
import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators';
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { generateName } from '../lib/wizard';
|
import { generateName } from "../lib/wizard";
|
||||||
import { default as wizardSchema, setWizardDefaults } from '../lib/wizard-schema';
|
import {
|
||||||
|
setWizardDefaults,
|
||||||
|
default as wizardSchema,
|
||||||
|
} from "../lib/wizard-schema";
|
||||||
import { notEmpty } from "@ember/object/computed";
|
import { notEmpty } from "@ember/object/computed";
|
||||||
import { scheduleOnce, bind } from "@ember/runloop";
|
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { A } from "@ember/array";
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNameBindings: [':wizard-links', 'itemType'],
|
classNameBindings: [":wizard-links", "itemType"],
|
||||||
items: A(),
|
items: A(),
|
||||||
anyLinks: notEmpty('links'),
|
anyLinks: notEmpty("links"),
|
||||||
|
|
||||||
@on('didInsertElement')
|
|
||||||
@observes('links.[]')
|
|
||||||
setupSortable() {
|
|
||||||
scheduleOnce('afterRender', () => (this.applySortable()));
|
|
||||||
},
|
|
||||||
|
|
||||||
applySortable() {
|
|
||||||
$(this.element).find(".link-list")
|
|
||||||
.sortable({ tolerance: 'pointer' })
|
|
||||||
.on('sortupdate', (e, ui) => {
|
|
||||||
this.updateItemOrder(ui.item.data('id'), ui.item.index());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateItemOrder(itemId, newIndex) {
|
updateItemOrder(itemId, newIndex) {
|
||||||
const items = this.items;
|
const items = this.items;
|
||||||
const item = items.findBy('id', itemId);
|
const item = items.findBy("id", itemId);
|
||||||
items.removeObject(item);
|
items.removeObject(item);
|
||||||
|
item.set("index", newIndex);
|
||||||
items.insertAt(newIndex, item);
|
items.insertAt(newIndex, item);
|
||||||
scheduleOnce('afterRender', this, () => this.applySortable());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('itemType')
|
@discourseComputed("itemType")
|
||||||
header: (itemType) => `admin.wizard.${itemType}.header`,
|
header: (itemType) => `admin.wizard.${itemType}.header`,
|
||||||
|
|
||||||
@discourseComputed('current', 'items.@each.id', 'items.@each.type', 'items.@each.label', 'items.@each.title')
|
@discourseComputed(
|
||||||
|
"current",
|
||||||
|
"items.@each.id",
|
||||||
|
"items.@each.type",
|
||||||
|
"items.@each.label",
|
||||||
|
"items.@each.title"
|
||||||
|
)
|
||||||
links(current, items) {
|
links(current, items) {
|
||||||
if (!items) return;
|
if (!items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return items.map((item) => {
|
return items.map((item, index) => {
|
||||||
if (item) {
|
if (item) {
|
||||||
let link = {
|
let link = {
|
||||||
id: item.id
|
id: item.id,
|
||||||
}
|
};
|
||||||
|
|
||||||
let label = item.label || item.title || item.id;
|
let label = item.label || item.title || item.id;
|
||||||
if (this.generateLabels && item.type) {
|
if (this.generateLabels && item.type) {
|
||||||
label = generateName(item.type);
|
label = generateName(item.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
link.label = `${label} (${item.id})`;
|
link.label = `${label} (${item.id})`;
|
||||||
|
|
||||||
let classes = 'btn';
|
let classes = "btn";
|
||||||
if (current && item.id === current.id) {
|
if (current && item.id === current.id) {
|
||||||
classes += ' btn-primary';
|
classes += " btn-primary";
|
||||||
};
|
}
|
||||||
|
|
||||||
link.classes = classes;
|
link.classes = classes;
|
||||||
|
link.index = index;
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
link.first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === items.length - 1) {
|
||||||
|
link.last = true;
|
||||||
|
}
|
||||||
|
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getNextIndex() {
|
||||||
|
const items = this.items;
|
||||||
|
if (!items || items.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const numbers = items
|
||||||
|
.map((i) => Number(i.id.split("_").pop()))
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
return numbers[numbers.length - 1];
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
add() {
|
add() {
|
||||||
const items = this.get('items');
|
const items = this.get("items");
|
||||||
const itemType = this.itemType;
|
const itemType = this.itemType;
|
||||||
let params = setWizardDefaults({}, itemType);
|
let params = setWizardDefaults({}, itemType);
|
||||||
|
|
||||||
params.isNew = true;
|
params.isNew = true;
|
||||||
|
params.index = this.getNextIndex();
|
||||||
let next = 1;
|
|
||||||
|
let id = `${itemType}_${params.index + 1}`;
|
||||||
if (items.length) {
|
if (itemType === "field") {
|
||||||
next = Math.max.apply(Math, items.map((i) => {
|
|
||||||
let parts = i.id.split('_');
|
|
||||||
let lastPart = parts[parts.length - 1];
|
|
||||||
return isNaN(lastPart) ? 0 : lastPart;
|
|
||||||
})) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = `${itemType}_${next}`;
|
|
||||||
|
|
||||||
if (itemType === 'field') {
|
|
||||||
id = `${this.parentId}_${id}`;
|
id = `${this.parentId}_${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
params.id = id;
|
params.id = id;
|
||||||
|
|
||||||
let objectArrays = wizardSchema[itemType].objectArrays;
|
let objectArrays = wizardSchema[itemType].objectArrays;
|
||||||
if (objectArrays) {
|
if (objectArrays) {
|
||||||
Object.keys(objectArrays).forEach(objectType => {
|
Object.keys(objectArrays).forEach((objectType) => {
|
||||||
params[objectArrays[objectType].property] = A();
|
params[objectArrays[objectType].property] = A();
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const newItem = EmberObject.create(params);
|
const newItem = EmberObject.create(params);
|
||||||
items.pushObject(newItem);
|
items.pushObject(newItem);
|
||||||
|
|
||||||
this.set('current', newItem);
|
this.set("current", newItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
back(item) {
|
||||||
|
this.updateItemOrder(item.id, item.index - 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
forward(item) {
|
||||||
|
this.updateItemOrder(item.id, item.index + 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
change(itemId) {
|
change(itemId) {
|
||||||
this.set('current', this.items.findBy('id', itemId));
|
this.set("current", this.items.findBy("id", itemId));
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(itemId) {
|
remove(itemId) {
|
||||||
const items = this.items;
|
const items = this.items;
|
||||||
let item;
|
let item;
|
||||||
let index;
|
let index;
|
||||||
|
|
||||||
items.forEach((it, ind) => {
|
items.forEach((it, ind) => {
|
||||||
if (it.id === itemId) {
|
if (it.id === itemId) {
|
||||||
item = it;
|
item = it;
|
||||||
index = ind;
|
index = ind;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let nextIndex;
|
let nextIndex;
|
||||||
if (this.current.id === itemId) {
|
if (this.current.id === itemId) {
|
||||||
nextIndex = index < (items.length-2) ? (index+1) : (index-1);
|
nextIndex = index < items.length - 2 ? index + 1 : index - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.removeObject(item);
|
items.removeObject(item);
|
||||||
|
|
||||||
if (nextIndex) {
|
if (nextIndex) {
|
||||||
this.set('current', items[nextIndex]);
|
this.set("current", items[nextIndex]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,35 +1,38 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { gt } from '@ember/object/computed';
|
import { gt } from "@ember/object/computed";
|
||||||
import { computed } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import { defaultConnector } from '../lib/wizard-mapper';
|
import { defaultConnector } from "../lib/wizard-mapper";
|
||||||
import { later } from "@ember/runloop";
|
import { later } from "@ember/runloop";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'],
|
classNameBindings: [
|
||||||
hasMultiple: gt('connectors.length', 1),
|
":mapper-connector",
|
||||||
connectorLabel: computed(function() {
|
":mapper-block",
|
||||||
|
"hasMultiple::single",
|
||||||
|
],
|
||||||
|
hasMultiple: gt("connectors.length", 1),
|
||||||
|
connectorLabel: computed(function () {
|
||||||
let key = this.connector;
|
let key = this.connector;
|
||||||
let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`;
|
let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`;
|
||||||
return I18n.t(`admin.wizard.${path}`);
|
return I18n.t(`admin.wizard.${path}`);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
if (!this.connector) {
|
if (!this.connector) {
|
||||||
later(() => {
|
later(() => {
|
||||||
this.set(
|
this.set(
|
||||||
'connector',
|
"connector",
|
||||||
defaultConnector(this.connectorType, this.inputType, this.options)
|
defaultConnector(this.connectorType, this.inputType, this.options)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
changeConnector(value) {
|
changeConnector(value) {
|
||||||
this.set('connector', value);
|
this.set("connector", value);
|
||||||
this.onUpdate('connector', this.connectorType);
|
this.onUpdate("connector", this.connectorType);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,69 +1,78 @@
|
||||||
import { computed, set } from "@ember/object";
|
import { computed, set } from "@ember/object";
|
||||||
import { alias, equal, or, not } from "@ember/object/computed";
|
import { alias, equal, not, or } from "@ember/object/computed";
|
||||||
import { newPair, connectorContent, inputTypesContent, defaultSelectionType, defaultConnector } from '../lib/wizard-mapper';
|
import {
|
||||||
|
connectorContent,
|
||||||
|
defaultConnector,
|
||||||
|
defaultSelectionType,
|
||||||
|
inputTypesContent,
|
||||||
|
newPair,
|
||||||
|
} from "../lib/wizard-mapper";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
import { A } from "@ember/array";
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNameBindings: [':mapper-input', 'inputType'],
|
classNameBindings: [":mapper-input", "inputType"],
|
||||||
inputType: alias('input.type'),
|
inputType: alias("input.type"),
|
||||||
isConditional: equal('inputType', 'conditional'),
|
isConditional: equal("inputType", "conditional"),
|
||||||
isAssignment: equal('inputType', 'assignment'),
|
isAssignment: equal("inputType", "assignment"),
|
||||||
isAssociation: equal('inputType', 'association'),
|
isAssociation: equal("inputType", "association"),
|
||||||
isValidation: equal('inputType', 'validation'),
|
isValidation: equal("inputType", "validation"),
|
||||||
hasOutput: or('isConditional', 'isAssignment'),
|
hasOutput: or("isConditional", "isAssignment"),
|
||||||
hasPairs: or('isConditional', 'isAssociation', 'isValidation'),
|
hasPairs: or("isConditional", "isAssociation", "isValidation"),
|
||||||
canAddPair: not('isAssignment'),
|
canAddPair: not("isAssignment"),
|
||||||
connectors: computed(function() { return connectorContent('output', this.input.type, this.options) }),
|
connectors: computed(function () {
|
||||||
inputTypes: computed(function() { return inputTypesContent(this.options) }),
|
return connectorContent("output", this.input.type, this.options);
|
||||||
|
}),
|
||||||
@observes('input.type')
|
inputTypes: computed(function () {
|
||||||
|
return inputTypesContent(this.options);
|
||||||
|
}),
|
||||||
|
|
||||||
|
@observes("input.type")
|
||||||
setupType() {
|
setupType() {
|
||||||
if (this.hasPairs && (!this.input.pairs || this.input.pairs.length < 1)) {
|
if (this.hasPairs && (!this.input.pairs || this.input.pairs.length < 1)) {
|
||||||
this.send('addPair');
|
this.send("addPair");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasOutput) {
|
if (this.hasOutput) {
|
||||||
this.set('input.output', null);
|
this.set("input.output", null);
|
||||||
|
|
||||||
if (!this.input.outputConnector) {
|
if (!this.input.outputConnector) {
|
||||||
const options = this.options;
|
const options = this.options;
|
||||||
this.set('input.output_type', defaultSelectionType('output', options));
|
this.set("input.output_type", defaultSelectionType("output", options));
|
||||||
this.set('input.output_connector', defaultConnector('output', this.inputType, options));
|
this.set(
|
||||||
|
"input.output_connector",
|
||||||
|
defaultConnector("output", this.inputType, options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
addPair() {
|
addPair() {
|
||||||
if (!this.input.pairs) {
|
if (!this.input.pairs) {
|
||||||
this.set('input.pairs', A());
|
this.set("input.pairs", A());
|
||||||
}
|
}
|
||||||
|
|
||||||
const pairs = this.input.pairs;
|
const pairs = this.input.pairs;
|
||||||
const pairCount = pairs.length + 1;
|
const pairCount = pairs.length + 1;
|
||||||
|
|
||||||
pairs.forEach(p => (set(p, 'pairCount', pairCount)));
|
pairs.forEach((p) => set(p, "pairCount", pairCount));
|
||||||
|
|
||||||
pairs.pushObject(
|
pairs.pushObject(
|
||||||
newPair(
|
newPair(
|
||||||
this.input.type,
|
this.input.type,
|
||||||
Object.assign(
|
Object.assign({}, this.options, { index: pairs.length, pairCount })
|
||||||
{},
|
|
||||||
this.options,
|
|
||||||
{ index: pairs.length, pairCount }
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
removePair(pair) {
|
removePair(pair) {
|
||||||
const pairs = this.input.pairs;
|
const pairs = this.input.pairs;
|
||||||
const pairCount = pairs.length - 1;
|
const pairCount = pairs.length - 1;
|
||||||
|
|
||||||
pairs.forEach(p => (set(p, 'pairCount', pairCount)));
|
pairs.forEach((p) => set(p, "pairCount", pairCount));
|
||||||
pairs.removeObject(pair);
|
pairs.removeObject(pair);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import { connectorContent } from '../lib/wizard-mapper';
|
import { connectorContent } from "../lib/wizard-mapper";
|
||||||
import { gt, or, alias } from "@ember/object/computed";
|
import { alias, gt } from "@ember/object/computed";
|
||||||
import { computed, observes } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNameBindings: [':mapper-pair', 'hasConnector::no-connector'],
|
classNameBindings: [":mapper-pair", "hasConnector::no-connector"],
|
||||||
firstPair: gt('pair.index', 0),
|
firstPair: gt("pair.index", 0),
|
||||||
showRemove: alias('firstPair'),
|
showRemove: alias("firstPair"),
|
||||||
showJoin: computed('pair.pairCount', function() { return this.pair.index < (this.pair.pairCount - 1) }),
|
showJoin: computed("pair.pairCount", function () {
|
||||||
connectors: computed(function() { return connectorContent('pair', this.inputType, this.options) })
|
return this.pair.index < this.pair.pairCount - 1;
|
||||||
});
|
}),
|
||||||
|
connectors: computed(function () {
|
||||||
|
return connectorContent("pair", this.inputType, this.options);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import discourseComputed from 'discourse-common/utils/decorators';
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: 'a',
|
tagName: "a",
|
||||||
classNameBindings: ['active'],
|
classNameBindings: ["active"],
|
||||||
|
|
||||||
@discourseComputed('item.type', 'activeType')
|
@discourseComputed("item.type", "activeType")
|
||||||
active(type, activeType) { return type === activeType },
|
active(type, activeType) {
|
||||||
|
return type === activeType;
|
||||||
|
},
|
||||||
|
|
||||||
click() {
|
click() {
|
||||||
this.toggle(this.item.type)
|
this.toggle(this.item.type);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,51 +1,179 @@
|
||||||
import { alias, or, gt } from "@ember/object/computed";
|
import { alias, gt, or } from "@ember/object/computed";
|
||||||
import { computed } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators";
|
import {
|
||||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
default as discourseComputed,
|
||||||
import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
|
observes,
|
||||||
import { snakeCase, generateName, userProperties } from '../lib/wizard';
|
} from "discourse-common/utils/decorators";
|
||||||
|
import { getOwner } from "discourse-common/lib/get-owner";
|
||||||
|
import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper";
|
||||||
|
import {
|
||||||
|
generateName,
|
||||||
|
sentenceCase,
|
||||||
|
snakeCase,
|
||||||
|
userProperties,
|
||||||
|
} from "../lib/wizard";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { bind, later } from "@ember/runloop";
|
import { bind, later } from "@ember/runloop";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
|
||||||
export default Component.extend({
|
const customFieldActionMap = {
|
||||||
classNameBindings: [':mapper-selector', 'activeType'],
|
topic: ["create_topic", "send_message"],
|
||||||
|
post: ["create_topic", "send_message"],
|
||||||
showText: computed('activeType', function() { return this.showInput('text') }),
|
category: ["create_category"],
|
||||||
showWizardField: computed('activeType', function() { return this.showInput('wizardField') }),
|
group: ["create_group"],
|
||||||
showWizardAction: computed('activeType', function() { return this.showInput('wizardAction') }),
|
user: ["update_profile"],
|
||||||
showUserField: computed('activeType', function() { return this.showInput('userField') }),
|
};
|
||||||
showUserFieldOptions: computed('activeType', function() { return this.showInput('userFieldOptions') }),
|
|
||||||
showCategory: computed('activeType', function() { return this.showInput('category') }),
|
const values = ["present", "true", "false"];
|
||||||
showTag: computed('activeType', function() { return this.showInput('tag') }),
|
|
||||||
showGroup: computed('activeType', function() { return this.showInput('group') }),
|
export default Component.extend(Subscription, {
|
||||||
showUser: computed('activeType', function() { return this.showInput('user') }),
|
classNameBindings: [":mapper-selector", "activeType"],
|
||||||
showList: computed('activeType', function() { return this.showInput('list') }),
|
|
||||||
showCustomField: computed('activeType', function() { return this.showInput('customField') }),
|
showText: computed("activeType", function () {
|
||||||
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
|
return this.showInput("text");
|
||||||
wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
|
}),
|
||||||
wizardActionEnabled: computed('options.wizardActionSelection', 'inputType', function() { return this.optionEnabled('wizardActionSelection') }),
|
showWizardField: computed("activeType", function () {
|
||||||
customFieldEnabled: computed('options.customFieldSelection', 'inputType', function() { return this.optionEnabled('customFieldSelection') }),
|
return this.showInput("wizardField");
|
||||||
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
|
}),
|
||||||
userFieldOptionsEnabled: computed('options.userFieldOptionsSelection', 'inputType', function() { return this.optionEnabled('userFieldOptionsSelection') }),
|
showWizardAction: computed("activeType", function () {
|
||||||
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
|
return this.showInput("wizardAction");
|
||||||
tagEnabled: computed('options.tagSelection', 'inputType', function() { return this.optionEnabled('tagSelection') }),
|
}),
|
||||||
groupEnabled: computed('options.groupSelection', 'inputType', function() { return this.optionEnabled('groupSelection') }),
|
showUserField: computed("activeType", function () {
|
||||||
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
|
return this.showInput("userField");
|
||||||
listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }),
|
}),
|
||||||
|
showUserFieldOptions: computed("activeType", function () {
|
||||||
groups: alias('site.groups'),
|
return this.showInput("userFieldOptions");
|
||||||
categories: alias('site.categories'),
|
}),
|
||||||
showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions', 'showCustomField'),
|
showCategory: computed("activeType", function () {
|
||||||
showMultiSelect: or('showCategory', 'showGroup'),
|
return this.showInput("category");
|
||||||
hasTypes: gt('selectorTypes.length', 1),
|
}),
|
||||||
|
showTag: computed("activeType", function () {
|
||||||
|
return this.showInput("tag");
|
||||||
|
}),
|
||||||
|
showGroup: computed("activeType", function () {
|
||||||
|
return this.showInput("group");
|
||||||
|
}),
|
||||||
|
showUser: computed("activeType", function () {
|
||||||
|
return this.showInput("user");
|
||||||
|
}),
|
||||||
|
showList: computed("activeType", function () {
|
||||||
|
return this.showInput("list");
|
||||||
|
}),
|
||||||
|
showCustomField: computed("activeType", function () {
|
||||||
|
return this.showInput("customField");
|
||||||
|
}),
|
||||||
|
showValue: computed("activeType", function () {
|
||||||
|
return this.showInput("value");
|
||||||
|
}),
|
||||||
|
textEnabled: computed("options.textSelection", "inputType", function () {
|
||||||
|
return this.optionEnabled("textSelection");
|
||||||
|
}),
|
||||||
|
wizardFieldEnabled: computed(
|
||||||
|
"options.wizardFieldSelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("wizardFieldSelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
wizardActionEnabled: computed(
|
||||||
|
"options.wizardActionSelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("wizardActionSelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
customFieldEnabled: computed(
|
||||||
|
"options.customFieldSelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("customFieldSelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
userFieldEnabled: computed(
|
||||||
|
"options.userFieldSelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("userFieldSelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
userFieldOptionsEnabled: computed(
|
||||||
|
"options.userFieldOptionsSelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("userFieldOptionsSelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
categoryEnabled: computed(
|
||||||
|
"options.categorySelection",
|
||||||
|
"inputType",
|
||||||
|
function () {
|
||||||
|
return this.optionEnabled("categorySelection");
|
||||||
|
}
|
||||||
|
),
|
||||||
|
tagEnabled: computed("options.tagSelection", "inputType", function () {
|
||||||
|
return this.optionEnabled("tagSelection");
|
||||||
|
}),
|
||||||
|
groupEnabled: computed("options.groupSelection", "inputType", function () {
|
||||||
|
return this.optionEnabled("groupSelection");
|
||||||
|
}),
|
||||||
|
guestGroup: computed("options.guestGroup", "inputType", function () {
|
||||||
|
return this.optionEnabled("guestGroup");
|
||||||
|
}),
|
||||||
|
userEnabled: computed("options.userSelection", "inputType", function () {
|
||||||
|
return this.optionEnabled("userSelection");
|
||||||
|
}),
|
||||||
|
listEnabled: computed("options.listSelection", "inputType", function () {
|
||||||
|
return this.optionEnabled("listSelection");
|
||||||
|
}),
|
||||||
|
valueEnabled: computed("connector", function () {
|
||||||
|
return this.connector === "is";
|
||||||
|
}),
|
||||||
|
|
||||||
|
@discourseComputed("site.groups", "guestGroup", "subscriptionType")
|
||||||
|
groups(groups, guestGroup, subscriptionType) {
|
||||||
|
let result = groups;
|
||||||
|
if (!guestGroup) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["standard", "business"].includes(subscriptionType)) {
|
||||||
|
let guestIndex;
|
||||||
|
result.forEach((r, index) => {
|
||||||
|
if (r.id === 0) {
|
||||||
|
r.name = I18n.t("admin.wizard.selector.label.users");
|
||||||
|
guestIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.splice(guestIndex, 0, {
|
||||||
|
id: -1,
|
||||||
|
name: I18n.t("admin.wizard.selector.label.guests"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
categories: alias("site.categories"),
|
||||||
|
showComboBox: or(
|
||||||
|
"showWizardField",
|
||||||
|
"showWizardAction",
|
||||||
|
"showUserField",
|
||||||
|
"showUserFieldOptions",
|
||||||
|
"showCustomField",
|
||||||
|
"showValue"
|
||||||
|
),
|
||||||
|
showMultiSelect: or("showCategory", "showGroup"),
|
||||||
|
hasTypes: gt("selectorTypes.length", 1),
|
||||||
showTypes: false,
|
showTypes: false,
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
if (!this.activeType || (this.activeType && !this[`${this.activeType}Enabled`])) {
|
if (
|
||||||
|
!this.activeType ||
|
||||||
|
(this.activeType && !this[`${this.activeType}Enabled`])
|
||||||
|
) {
|
||||||
later(() => this.resetActiveType());
|
later(() => this.resetActiveType());
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).on("click", bind(this, this.documentClick));
|
$(document).on("click", bind(this, this.documentClick));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -54,44 +182,49 @@ export default Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
documentClick(e) {
|
documentClick(e) {
|
||||||
if (this._state == "destroying") return;
|
if (this._state === "destroying") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let $target = $(e.target);
|
let $target = $(e.target);
|
||||||
|
|
||||||
if (!$target.parents('.type-selector').length && this.showTypes) {
|
if (!$target.parents(".type-selector").length && this.showTypes) {
|
||||||
this.set('showTypes', false);
|
this.set("showTypes", false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed("connector")
|
||||||
selectorTypes() {
|
selectorTypes() {
|
||||||
return selectionTypes.filter(type => (this[`${type}Enabled`]))
|
return selectionTypes
|
||||||
.map(type => ({ type, label: this.typeLabel(type) }));
|
.filter((type) => this[`${type}Enabled`])
|
||||||
|
.map((type) => ({ type, label: this.typeLabel(type) }));
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('activeType')
|
@discourseComputed("activeType")
|
||||||
activeTypeLabel(activeType) {
|
activeTypeLabel(activeType) {
|
||||||
return this.typeLabel(activeType);
|
return this.typeLabel(activeType);
|
||||||
},
|
},
|
||||||
|
|
||||||
typeLabel(type) {
|
typeLabel(type) {
|
||||||
return type ? I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`) : null;
|
return type
|
||||||
|
? I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`)
|
||||||
|
: null;
|
||||||
},
|
},
|
||||||
|
|
||||||
comboBoxAllowAny: or('showWizardField', 'showWizardAction'),
|
comboBoxAllowAny: or("showWizardField", "showWizardAction"),
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
showController() {
|
showController() {
|
||||||
return getOwner(this).lookup('controller:admin-wizards-wizard-show');
|
return getOwner(this).lookup("controller:admin-wizards-wizard-show");
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
'activeType',
|
"activeType",
|
||||||
'showController.wizardFields.[]',
|
"showController.wizardFields.[]",
|
||||||
'showController.wizard.actions.[]',
|
"showController.wizard.actions.[]",
|
||||||
'showController.userFields.[]',
|
"showController.userFields.[]",
|
||||||
'showController.currentField.id',
|
"showController.currentField.id",
|
||||||
'showController.currentAction.id',
|
"showController.currentAction.id",
|
||||||
'showController.customFields'
|
"showController.customFields"
|
||||||
)
|
)
|
||||||
comboBoxContent(
|
comboBoxContent(
|
||||||
activeType,
|
activeType,
|
||||||
|
@ -103,133 +236,175 @@ export default Component.extend({
|
||||||
customFields
|
customFields
|
||||||
) {
|
) {
|
||||||
let content;
|
let content;
|
||||||
|
let context;
|
||||||
if (activeType === 'wizardField') {
|
let contextType;
|
||||||
|
|
||||||
|
if (this.options.context) {
|
||||||
|
let contextAttrs = this.options.context.split(".");
|
||||||
|
context = contextAttrs[0];
|
||||||
|
contextType = contextAttrs[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeType === "wizardField") {
|
||||||
content = wizardFields;
|
content = wizardFields;
|
||||||
|
|
||||||
if (this.options.context === 'field') {
|
if (context === "field") {
|
||||||
content = content.filter(field => field.id !== currentFieldId);
|
content = content.filter((field) => field.id !== currentFieldId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeType === 'wizardAction') {
|
if (activeType === "wizardAction") {
|
||||||
content = wizardActions.map(a => ({
|
content = wizardActions.map((a) => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
label: `${generateName(a.type)} (${a.id})`,
|
label: `${generateName(a.type)} (${a.id})`,
|
||||||
type: a.type
|
type: a.type,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (this.options.context === 'action') {
|
if (context === "action") {
|
||||||
content = content.filter(a => a.id !== currentActionId);
|
content = content.filter((a) => a.id !== currentActionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeType === 'userField') {
|
if (activeType === "userField") {
|
||||||
content = userProperties.map((f) => ({
|
content = userProperties
|
||||||
id: f,
|
.map((f) => ({
|
||||||
name: generateName(f)
|
id: f,
|
||||||
})).concat((userFields || []));
|
name: generateName(f),
|
||||||
|
}))
|
||||||
if (this.options.context === 'action' &&
|
.concat(userFields || []);
|
||||||
this.inputType === 'association' &&
|
|
||||||
this.selectorType === 'key') {
|
if (
|
||||||
|
context === "action" &&
|
||||||
const excludedFields = ['username','email', 'trust_level'];
|
this.inputType === "association" &&
|
||||||
content = content.filter(userField => excludedFields.indexOf(userField.id) === -1);
|
this.selectorType === "key"
|
||||||
|
) {
|
||||||
|
const excludedFields = ["username", "email", "trust_level"];
|
||||||
|
content = content.filter(
|
||||||
|
(userField) => excludedFields.indexOf(userField.id) === -1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeType === 'userFieldOptions') {
|
if (activeType === "userFieldOptions") {
|
||||||
content = userFields;
|
content = userFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeType === 'customField') {
|
if (activeType === "customField") {
|
||||||
content = customFields;
|
content = customFields
|
||||||
|
.filter((f) => {
|
||||||
|
return (
|
||||||
|
f.type !== "json" &&
|
||||||
|
customFieldActionMap[f.klass].includes(contextType)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((f) => ({
|
||||||
|
id: f.name,
|
||||||
|
name: `${sentenceCase(f.klass)} ${f.name} (${f.type})`,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeType === "value") {
|
||||||
|
content = values.map((value) => ({
|
||||||
|
id: value,
|
||||||
|
name: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('activeType')
|
@discourseComputed("activeType")
|
||||||
multiSelectContent(activeType) {
|
multiSelectContent(activeType) {
|
||||||
return {
|
return {
|
||||||
category: this.categories,
|
category: this.categories,
|
||||||
group: this.groups,
|
group: this.groups,
|
||||||
list: ''
|
list: "",
|
||||||
}[activeType];
|
}[activeType];
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('activeType', 'inputType')
|
@discourseComputed("activeType", "inputType")
|
||||||
placeholderKey(activeType, inputType) {
|
placeholderKey(activeType) {
|
||||||
if (activeType === 'text' && this.options[`${this.selectorType}Placeholder`]) {
|
if (
|
||||||
|
activeType === "text" &&
|
||||||
|
this.options[`${this.selectorType}Placeholder`]
|
||||||
|
) {
|
||||||
return this.options[`${this.selectorType}Placeholder`];
|
return this.options[`${this.selectorType}Placeholder`];
|
||||||
} else {
|
} else {
|
||||||
return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`;
|
return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('activeType')
|
@discourseComputed("activeType")
|
||||||
multiSelectOptions(activeType) {
|
multiSelectOptions(activeType) {
|
||||||
let result = {
|
let result = {
|
||||||
none: this.placeholderKey
|
none: this.placeholderKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (activeType === 'list') {
|
if (activeType === "list") {
|
||||||
result.allowAny = true;
|
result.allowAny = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
optionEnabled(type) {
|
optionEnabled(type) {
|
||||||
const options = this.options;
|
const options = this.options;
|
||||||
if (!options) return false;
|
if (!options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const option = options[type];
|
const option = options[type];
|
||||||
if (option === true) return true;
|
if (option === true) {
|
||||||
if (typeof option !== 'string') return false;
|
return true;
|
||||||
|
}
|
||||||
return option.split(',').filter(option => {
|
if (typeof option !== "string") {
|
||||||
return [this.selectorType, this.inputType].indexOf(option) !== -1;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.split(",").filter((o) => {
|
||||||
|
return [this.selectorType, this.inputType].indexOf(o) !== -1;
|
||||||
}).length;
|
}).length;
|
||||||
},
|
},
|
||||||
|
|
||||||
showInput(type) {
|
showInput(type) {
|
||||||
return this.activeType === type && this[`${type}Enabled`];
|
return this.activeType === type && this[`${type}Enabled`];
|
||||||
},
|
},
|
||||||
|
|
||||||
changeValue(value) {
|
changeValue(value) {
|
||||||
this.set('value', value);
|
this.set("value", value);
|
||||||
this.onUpdate('selector', this.activeType);
|
this.onUpdate("selector", this.activeType);
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes('inputType')
|
@observes("inputType")
|
||||||
resetActiveType() {
|
resetActiveType() {
|
||||||
this.set('activeType', defaultSelectionType(this.selectorType, this.options));
|
this.set(
|
||||||
|
"activeType",
|
||||||
|
defaultSelectionType(this.selectorType, this.options, this.connector)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleType(type) {
|
toggleType(type) {
|
||||||
this.set('activeType', type);
|
this.set("activeType", type);
|
||||||
this.set('showTypes', false);
|
this.set("showTypes", false);
|
||||||
this.set('value', null);
|
this.set("value", null);
|
||||||
this.onUpdate('selector');
|
this.onUpdate("selector");
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleTypes() {
|
toggleTypes() {
|
||||||
this.toggleProperty('showTypes');
|
this.toggleProperty("showTypes");
|
||||||
},
|
},
|
||||||
|
|
||||||
changeValue(value) {
|
changeValue(value) {
|
||||||
this.changeValue(value);
|
this.changeValue(value);
|
||||||
},
|
},
|
||||||
|
|
||||||
changeInputValue(event) {
|
changeInputValue(event) {
|
||||||
this.changeValue(event.target.value);
|
this.changeValue(event.target.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
changeUserValue(previousValue, value) {
|
changeUserValue(value) {
|
||||||
this.changeValue(value);
|
this.changeValue(value);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,84 +1,86 @@
|
||||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
import { newInput, selectionTypes } from "../lib/wizard-mapper";
|
||||||
import { newInput, selectionTypes } from '../lib/wizard-mapper';
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
|
||||||
import { later } from "@ember/runloop";
|
import { later } from "@ember/runloop";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { A } from "@ember/array";
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: 'wizard-mapper',
|
classNames: "wizard-mapper",
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
if (this.inputs && this.inputs.constructor !== Array) {
|
if (this.inputs && this.inputs.constructor !== Array) {
|
||||||
later(() => this.set('inputs', null));
|
later(() => this.set("inputs", null));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('inputs.@each.type')
|
@discourseComputed("inputs.@each.type")
|
||||||
canAdd(inputs) {
|
canAdd(inputs) {
|
||||||
return !inputs ||
|
return (
|
||||||
inputs.constructor !== Array ||
|
!inputs ||
|
||||||
inputs.every(i => {
|
inputs.constructor !== Array ||
|
||||||
return ['assignment','association'].indexOf(i.type) === -1;
|
inputs.every((i) => {
|
||||||
});
|
return ["assignment", "association"].indexOf(i.type) === -1;
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('options.@each.inputType')
|
@discourseComputed("options.@each.inputType")
|
||||||
inputOptions(options) {
|
inputOptions(options) {
|
||||||
let result = {
|
let result = {
|
||||||
inputTypes: options.inputTypes || 'assignment,conditional',
|
inputTypes: options.inputTypes || "assignment,conditional",
|
||||||
inputConnector: options.inputConnector || 'or',
|
inputConnector: options.inputConnector || "or",
|
||||||
pairConnector: options.pairConnector || null,
|
pairConnector: options.pairConnector || null,
|
||||||
outputConnector: options.outputConnector || null,
|
outputConnector: options.outputConnector || null,
|
||||||
context: options.context || null
|
context: options.context || null,
|
||||||
}
|
guestGroup: options.guestGroup || false,
|
||||||
|
};
|
||||||
let inputTypes = ['key', 'value', 'output'];
|
|
||||||
inputTypes.forEach(type => {
|
let inputTypes = ["key", "value", "output"];
|
||||||
|
inputTypes.forEach((type) => {
|
||||||
result[`${type}Placeholder`] = options[`${type}Placeholder`] || null;
|
result[`${type}Placeholder`] = options[`${type}Placeholder`] || null;
|
||||||
result[`${type}DefaultSelection`] = options[`${type}DefaultSelection`] || null;
|
result[`${type}DefaultSelection`] =
|
||||||
|
options[`${type}DefaultSelection`] || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
selectionTypes.forEach(type => {
|
selectionTypes.forEach((type) => {
|
||||||
if (options[`${type}Selection`] !== undefined) {
|
if (options[`${type}Selection`] !== undefined) {
|
||||||
result[`${type}Selection`] = options[`${type}Selection`]
|
result[`${type}Selection`] = options[`${type}Selection`];
|
||||||
} else {
|
} else {
|
||||||
result[`${type}Selection`] = type === 'text' ? true : false;
|
result[`${type}Selection`] = type === "text" ? true : false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdate() {
|
onUpdate() {},
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
add() {
|
add() {
|
||||||
if (!this.get('inputs')) {
|
if (!this.get("inputs")) {
|
||||||
this.set('inputs', A());
|
this.set("inputs", A());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.get('inputs').pushObject(
|
this.get("inputs").pushObject(
|
||||||
newInput(this.inputOptions, this.inputs.length)
|
newInput(this.inputOptions, this.inputs.length)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.onUpdate(this.property, 'input');
|
this.onUpdate(this.property, "input");
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(input) {
|
remove(input) {
|
||||||
const inputs = this.inputs;
|
const inputs = this.inputs;
|
||||||
inputs.removeObject(input);
|
inputs.removeObject(input);
|
||||||
|
|
||||||
if (inputs.length) {
|
if (inputs.length) {
|
||||||
inputs[0].set('connector', null);
|
inputs[0].set("connector", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onUpdate(this.property, 'input');
|
this.onUpdate(this.property, "input");
|
||||||
},
|
},
|
||||||
|
|
||||||
inputUpdated(component, type) {
|
inputUpdated(component, type) {
|
||||||
this.onUpdate(this.property, component, type);
|
this.onUpdate(this.property, component, type);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed } from "discourse-common/utils/decorators";
|
||||||
import { not, notEmpty } from "@ember/object/computed";
|
import { not, notEmpty } from "@ember/object/computed";
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
error: 'times-circle',
|
error: "times-circle",
|
||||||
success: 'check-circle',
|
success: "check-circle",
|
||||||
info: 'info-circle'
|
warn: "exclamation-circle",
|
||||||
}
|
info: "info-circle",
|
||||||
|
};
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNameBindings: [':wizard-message', 'type', 'loading'],
|
classNameBindings: [":wizard-message", "type", "loading"],
|
||||||
showDocumentation: not('loading'),
|
showDocumentation: not("loading"),
|
||||||
showIcon: not('loading'),
|
showIcon: not("loading"),
|
||||||
hasItems: notEmpty('items'),
|
hasItems: notEmpty("items"),
|
||||||
|
|
||||||
@discourseComputed('type')
|
@discourseComputed("type")
|
||||||
icon(type) {
|
icon(type) {
|
||||||
return icons[type] || 'info-circle';
|
return icons[type] || "info-circle";
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('key', 'component', 'opts')
|
@discourseComputed("key", "component", "opts")
|
||||||
message(key, component, opts) {
|
message(key, component, opts) {
|
||||||
return I18n.t(`admin.wizard.message.${component}.${key}`, opts || {});
|
return I18n.t(`admin.wizard.message.${component}.${key}`, opts || {});
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed('component')
|
@discourseComputed("component")
|
||||||
documentation(component) {
|
documentation(component) {
|
||||||
return I18n.t(`admin.wizard.message.${component}.documentation`);
|
return I18n.t(`admin.wizard.message.${component}.documentation`);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
@ -6,29 +6,27 @@ import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: ["realtime-validations"],
|
classNames: ["realtime-validations", "setting", "full", "subscription"],
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
timeUnits() {
|
timeUnits() {
|
||||||
return [
|
return ["days", "weeks", "months", "years"].map((unit) => {
|
||||||
"days",
|
return {
|
||||||
"weeks",
|
id: unit,
|
||||||
"months",
|
name: I18n.t(`admin.wizard.field.validations.time_units.${unit}`),
|
||||||
"years"
|
};
|
||||||
].map((unit) => {
|
});
|
||||||
return {
|
|
||||||
id: unit,
|
|
||||||
name: I18n.t(`admin.wizard.field.validations.time_units.${unit}`)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
if (!this.validations) return;
|
if (!this.validations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.field.validations) {
|
if (!this.field.validations) {
|
||||||
const validations = {};
|
const validations = {};
|
||||||
|
|
||||||
this.validations.forEach((validation) => {
|
this.validations.forEach((validation) => {
|
||||||
validations[validation] = {};
|
validations[validation] = {};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default Component.extend(Subscription, {
|
||||||
|
tagName: "a",
|
||||||
|
classNameBindings: [":wizard-subscription-badge", "subscriptionType"],
|
||||||
|
attributeBindings: ["title"],
|
||||||
|
|
||||||
|
@discourseComputed("subscriptionType")
|
||||||
|
i18nKey(type) {
|
||||||
|
return `admin.wizard.subscription.type.${type ? type : "none"}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("i18nKey")
|
||||||
|
title(i18nKey) {
|
||||||
|
return I18n.t(`${i18nKey}.title`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("i18nKey")
|
||||||
|
label(i18nKey) {
|
||||||
|
return I18n.t(`${i18nKey}.label`);
|
||||||
|
},
|
||||||
|
|
||||||
|
click() {
|
||||||
|
DiscourseURL.routeTo(this.subscriptionLink);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
|
||||||
|
export default Component.extend(Subscription, {
|
||||||
|
classNameBindings: [":wizard-subscription-container", "subscribed"],
|
||||||
|
|
||||||
|
@discourseComputed("subscribed")
|
||||||
|
subscribedIcon(subscribed) {
|
||||||
|
return subscribed ? "check" : "times";
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("subscribed")
|
||||||
|
subscribedLabel(subscribed) {
|
||||||
|
return `admin.wizard.subscription.${
|
||||||
|
subscribed ? "subscribed" : "not_subscribed"
|
||||||
|
}.label`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("subscribed")
|
||||||
|
subscribedTitle(subscribed) {
|
||||||
|
return `admin.wizard.subscription.${
|
||||||
|
subscribed ? "subscribed" : "not_subscribed"
|
||||||
|
}.title`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default Component.extend(Subscription, {
|
||||||
|
tagName: "a",
|
||||||
|
classNameBindings: [":btn", ":btn-pavilion-support", "subscriptionType"],
|
||||||
|
attributeBindings: ["title"],
|
||||||
|
|
||||||
|
@discourseComputed("subscribed")
|
||||||
|
i18nKey(subscribed) {
|
||||||
|
return `admin.wizard.subscription.cta.${
|
||||||
|
subscribed ? "subscribed" : "none"
|
||||||
|
}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("subscribed")
|
||||||
|
icon(subscribed) {
|
||||||
|
return subscribed ? "far-life-ring" : "external-link-alt";
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("i18nKey")
|
||||||
|
title(i18nKey) {
|
||||||
|
return I18n.t(`${i18nKey}.title`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("i18nKey")
|
||||||
|
label(i18nKey) {
|
||||||
|
return I18n.t(`${i18nKey}.label`);
|
||||||
|
},
|
||||||
|
|
||||||
|
click() {
|
||||||
|
window.open(this.subscriptionCtaLink, "_blank").focus();
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,96 @@
|
||||||
|
import SingleSelectComponent from "select-kit/components/single-select";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
const nameKey = function (feature, attribute, value) {
|
||||||
|
if (feature === "action") {
|
||||||
|
return `admin.wizard.action.${value}.label`;
|
||||||
|
} else {
|
||||||
|
return `admin.wizard.${feature}.${attribute}.${value}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SingleSelectComponent.extend(Subscription, {
|
||||||
|
classNames: ["combo-box", "wizard-subscription-selector"],
|
||||||
|
|
||||||
|
selectKitOptions: {
|
||||||
|
autoFilterable: false,
|
||||||
|
filterable: false,
|
||||||
|
showFullTitle: true,
|
||||||
|
headerComponent:
|
||||||
|
"wizard-subscription-selector/wizard-subscription-selector-header",
|
||||||
|
caretUpIcon: "caret-up",
|
||||||
|
caretDownIcon: "caret-down",
|
||||||
|
},
|
||||||
|
|
||||||
|
allowedSubscriptionTypes(feature, attribute, value) {
|
||||||
|
let attributes = this.subscriptionAttributes[feature];
|
||||||
|
if (!attributes || !attributes[attribute]) {
|
||||||
|
return ["none"];
|
||||||
|
}
|
||||||
|
let allowedTypes = [];
|
||||||
|
Object.keys(attributes[attribute]).forEach((subscriptionType) => {
|
||||||
|
let values = attributes[attribute][subscriptionType];
|
||||||
|
if (values[0] === "*" || values.includes(value)) {
|
||||||
|
allowedTypes.push(subscriptionType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return allowedTypes;
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("feature", "attribute", "wizard.allowGuests")
|
||||||
|
content(feature, attribute) {
|
||||||
|
return filterValues(this.wizard, feature, attribute)
|
||||||
|
.map((value) => {
|
||||||
|
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
|
||||||
|
feature,
|
||||||
|
attribute,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
|
||||||
|
let subscriptionRequired =
|
||||||
|
allowedSubscriptionTypes.length &&
|
||||||
|
!allowedSubscriptionTypes.includes("none");
|
||||||
|
|
||||||
|
let attrs = {
|
||||||
|
id: value,
|
||||||
|
name: I18n.t(nameKey(feature, attribute, value)),
|
||||||
|
subscriptionRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (subscriptionRequired) {
|
||||||
|
let subscribed = allowedSubscriptionTypes.includes(
|
||||||
|
this.subscriptionType
|
||||||
|
);
|
||||||
|
let selectorKey = subscribed ? "subscribed" : "not_subscribed";
|
||||||
|
let selectorLabel = `admin.wizard.subscription.${selectorKey}.selector`;
|
||||||
|
|
||||||
|
attrs.disabled = !subscribed;
|
||||||
|
attrs.selectorLabel = selectorLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
})
|
||||||
|
.sort(function (a, b) {
|
||||||
|
if (a.subscriptionType && !b.subscriptionType) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!a.subscriptionType && b.subscriptionType) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.subscriptionType === b.subscriptionType) {
|
||||||
|
return a.subscriptionType
|
||||||
|
? a.subscriptionType.localeCompare(b.subscriptionType)
|
||||||
|
: 0;
|
||||||
|
} else {
|
||||||
|
return a.subscriptionType === "standard" ? -1 : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
modifyComponentForRow() {
|
||||||
|
return "wizard-subscription-selector/wizard-subscription-selector-row";
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import { reads } from "@ember/object/computed";
|
||||||
|
|
||||||
|
export default SingleSelectHeaderComponent.extend({
|
||||||
|
classNames: ["combo-box-header", "wizard-subscription-selector-header"],
|
||||||
|
caretUpIcon: reads("selectKit.options.caretUpIcon"),
|
||||||
|
caretDownIcon: reads("selectKit.options.caretDownIcon"),
|
||||||
|
caretIcon: computed(
|
||||||
|
"selectKit.isExpanded",
|
||||||
|
"caretUpIcon",
|
||||||
|
"caretDownIcon",
|
||||||
|
function () {
|
||||||
|
return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
|
||||||
|
import { default as discourseComputed } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default SelectKitRowComponent.extend({
|
||||||
|
classNameBindings: ["isDisabled:disabled"],
|
||||||
|
|
||||||
|
@discourseComputed("item")
|
||||||
|
isDisabled() {
|
||||||
|
return this.item.disabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
click(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!this.item.disabled) {
|
||||||
|
this.selectKit.select(this.rowValue, this.item);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden …
In neuem Issue referenzieren