Compare commits
783 Commits
v3.0.0
...
fix/embed-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a267e9c411 | ||
|
|
b53ae0cefe | ||
|
|
ebf90207e5 | ||
|
|
4ac8a117ef | ||
|
|
c1bc430ee6 | ||
|
|
9201aa7d7d | ||
|
|
87395a4553 | ||
|
|
6063c633ee | ||
|
|
7481feb366 | ||
|
|
bdfa14fa54 | ||
|
|
0a61ec2bfe | ||
|
|
2bf0106aa2 | ||
|
|
ab7c29d228 | ||
|
|
d9c75ed79e | ||
|
|
6fa722df7b | ||
|
|
18864bfecf | ||
|
|
95754cb2b4 | ||
|
|
ed2a461dc5 | ||
|
|
8d5a456dbd | ||
|
|
2528bbb92f | ||
|
|
259cd48dbb | ||
|
|
b43531f200 | ||
|
|
26da3e18a9 | ||
|
|
bb4b640e58 | ||
|
|
1cc845e17d | ||
|
|
60bfb6fe2c | ||
|
|
144d14ab5b | ||
|
|
8f1ca6e282 | ||
|
|
a93758c6b7 | ||
|
|
1829c088cc | ||
|
|
ee1425d0dd | ||
|
|
24ae090916 | ||
|
|
a3aa9b68fc | ||
|
|
50f475334e | ||
|
|
7b18526f24 | ||
|
|
23afc201a1 | ||
|
|
b1982d74a6 | ||
|
|
e93a37c711 | ||
|
|
8d7509cdea | ||
|
|
e24d0ce605 | ||
|
|
f5d2e4f11f | ||
|
|
de725337d6 | ||
|
|
9d1d369f37 | ||
|
|
2bd925d441 | ||
|
|
bb8dc6f7eb | ||
|
|
be3e5ba7e7 | ||
|
|
663134839f | ||
|
|
736f83a70c | ||
|
|
05d2175f43 | ||
|
|
4caf0053cd | ||
|
|
97bd808431 | ||
|
|
a13c2fd4c1 | ||
|
|
16044b5840 | ||
|
|
93ce86f32d | ||
|
|
4ebf850cb6 | ||
|
|
76af7d5e10 | ||
|
|
507fe69efe | ||
|
|
23e3739718 | ||
|
|
5428a73811 | ||
|
|
4a154e6569 | ||
|
|
0aa5825d8b | ||
|
|
bdb63e99d5 | ||
|
|
6daa043a1b | ||
|
|
8175ec640a | ||
|
|
b5307e4a89 | ||
|
|
19294802be | ||
|
|
cbe3e14b47 | ||
|
|
9dcbc4a126 | ||
|
|
01df1663ad | ||
|
|
a215860782 | ||
|
|
abd5288da8 | ||
|
|
a89bc473f6 | ||
|
|
59b5a50a97 | ||
|
|
57cb59027b | ||
|
|
d1c9c3583f | ||
|
|
2462492c86 | ||
|
|
7a9f0c8756 | ||
|
|
46caf9b198 | ||
|
|
f5db54484c | ||
|
|
8deb6471b9 | ||
|
|
73b3ff8e41 | ||
|
|
016a18d3b2 | ||
|
|
ba31cdabea | ||
|
|
51510566bc | ||
|
|
cabee0ecc8 | ||
|
|
2c2b39a236 | ||
|
|
78450c9316 | ||
|
|
b18fd90b64 | ||
|
|
0188a8d7db | ||
|
|
6c63a8dc28 | ||
|
|
17d6ae15a5 | ||
|
|
40f72278a9 | ||
|
|
f717704731 | ||
|
|
185c225297 | ||
|
|
2694731c36 | ||
|
|
ae89af9978 | ||
|
|
87d617012f | ||
|
|
2420b3fa42 | ||
|
|
175a991ec4 | ||
|
|
0301649aff | ||
|
|
544b045300 | ||
|
|
65884293be | ||
|
|
3cb4861bac | ||
|
|
7beed30815 | ||
|
|
bb380f3751 | ||
|
|
33a7580e46 | ||
|
|
ffb2b5c30a | ||
|
|
7c238fa854 | ||
|
|
185b575e5b | ||
|
|
bcc1147f81 | ||
|
|
f5b130024e | ||
|
|
bb5c333bae | ||
|
|
3684d25848 | ||
|
|
8b0ba3a45e | ||
|
|
e847fb7b77 | ||
|
|
5c78ae4dee | ||
|
|
53ec605963 | ||
|
|
75193a7aa8 | ||
|
|
b269c239d9 | ||
|
|
72b4a1fc4e | ||
|
|
d2d1674d31 | ||
|
|
a6b57777e3 | ||
|
|
65ef4db86f | ||
|
|
7201147b55 | ||
|
|
dd143c95a9 | ||
|
|
005581ee7d | ||
|
|
1431ecc6d7 | ||
|
|
f34d896095 | ||
|
|
e95ebb9226 | ||
|
|
57365eeae0 | ||
|
|
b22bd97818 | ||
|
|
b953b32ff4 | ||
|
|
0eacd6763b | ||
|
|
8499ac7fec | ||
|
|
4adac4af38 | ||
|
|
fd162e242c | ||
|
|
3e83828722 | ||
|
|
f7dc36e3f1 | ||
|
|
a7566dfd86 | ||
|
|
d4d7a20fbd | ||
|
|
dfb281bcf7 | ||
|
|
c62482e81f | ||
|
|
886847ab7b | ||
|
|
a268cab11e | ||
|
|
e9509b9fa1 | ||
|
|
8db452089c | ||
|
|
a1764023f3 | ||
|
|
b08b63dc73 | ||
|
|
a9a4ebf595 | ||
|
|
a8e279db28 | ||
|
|
d09a3e9237 | ||
|
|
efa40cf6ea | ||
|
|
1a3d9f18ab | ||
|
|
653ccd3240 | ||
|
|
c0806cfd07 | ||
|
|
008eb6b77b | ||
|
|
ac60843183 | ||
|
|
3c3fb1e4a9 | ||
|
|
88212e8cfe | ||
|
|
191fa376d2 | ||
|
|
6efae3a395 | ||
|
|
cb8678f07f | ||
|
|
b32b0f9bcb | ||
|
|
5a91fb53b2 | ||
|
|
b0b6edc58e | ||
|
|
8c57d81718 | ||
|
|
10bb68a538 | ||
|
|
d4d1e27ba9 | ||
|
|
d5c887f311 | ||
|
|
ce7adf6da3 | ||
|
|
c626fb9241 | ||
|
|
f21ed30e10 | ||
|
|
b55970cc7a | ||
|
|
74ad2e43a4 | ||
|
|
2d6282cf8b | ||
|
|
e255c46455 | ||
|
|
15c2c7bb5b | ||
|
|
71bcd22444 | ||
|
|
2d104160f2 | ||
|
|
f7c1825de5 | ||
|
|
2c1fd5d711 | ||
|
|
085fbb2a9b | ||
|
|
05f2d8817b | ||
|
|
81fbb22c51 | ||
|
|
01cf59c663 | ||
|
|
5c8ebaff3e | ||
|
|
0e70c28324 | ||
|
|
88f6a4ae26 | ||
|
|
610538ca02 | ||
|
|
8970ff5c68 | ||
|
|
d1a564d5b8 | ||
|
|
8bb1d19c07 | ||
|
|
c1efa381f0 | ||
|
|
29171d1b6f | ||
|
|
e869d49e16 | ||
|
|
6496bea846 | ||
|
|
39842559b5 | ||
|
|
51efb35aa6 | ||
|
|
9402bb9285 | ||
|
|
5a516f7242 | ||
|
|
3b217d78e7 | ||
|
|
8e153b38dc | ||
|
|
6f38bfb148 | ||
|
|
82b6e08d68 | ||
|
|
31fd6567b7 | ||
|
|
25177bd635 | ||
|
|
6928eb7992 | ||
|
|
8300f9a0a2 | ||
|
|
525ba77739 | ||
|
|
6bc748a267 | ||
|
|
5230d2d3b8 | ||
|
|
c3531c9d8b | ||
|
|
b29c04c28d | ||
|
|
b2af353941 | ||
|
|
9dbce74f5e | ||
|
|
db1cf5cc08 | ||
|
|
09360abf81 | ||
|
|
355bd62b8d | ||
|
|
5650de1183 | ||
|
|
2ee8614b93 | ||
|
|
5632334c9a | ||
|
|
780dd8a713 | ||
|
|
7db3c6d290 | ||
|
|
c765270dfe | ||
|
|
03f667c21d | ||
|
|
f79f3078dc | ||
|
|
6e29a2f6d4 | ||
|
|
6304fd50c3 | ||
|
|
2ec29c47ad | ||
|
|
399a238bf4 | ||
|
|
b20ab72298 | ||
|
|
f723e6496a | ||
|
|
8c0aff8863 | ||
|
|
64c5077506 | ||
|
|
2afc87847d | ||
|
|
878ec833ce | ||
|
|
039de8015f | ||
|
|
f67b366b90 | ||
|
|
6f35574d68 | ||
|
|
77e8a36ab0 | ||
|
|
d7cc9f5dbc | ||
|
|
fc3e3aeaec | ||
|
|
4ba135f3b9 | ||
|
|
24894e05dc | ||
|
|
e2b668bee2 | ||
|
|
f112c46bb4 | ||
|
|
331d482b22 | ||
|
|
b07243f131 | ||
|
|
84b0c30d64 | ||
|
|
e3dd9e99a1 | ||
|
|
e3091cb6db | ||
|
|
270f796683 | ||
|
|
24c6bce02d | ||
|
|
2db567589f | ||
|
|
1fe83ebdc8 | ||
|
|
8320d4f222 | ||
|
|
e76c1bc64c | ||
|
|
1f3f8464ea | ||
|
|
81a7e23a12 | ||
|
|
e75391cdf1 | ||
|
|
a213c0c26c | ||
|
|
15424903ed | ||
|
|
1cce117b0a | ||
|
|
abc7b4b6f3 | ||
|
|
05e32ef9e4 | ||
|
|
f0a1fc319c | ||
|
|
385cabc6aa | ||
|
|
397b26a9f3 | ||
|
|
9a40058329 | ||
|
|
7ec2380ed5 | ||
|
|
3d4825305d | ||
|
|
26e564288b | ||
|
|
385a587cfd | ||
|
|
215df02783 | ||
|
|
7c7ed68b20 | ||
|
|
c910a0314a | ||
|
|
ddaec1b9ac | ||
|
|
9dbdef9286 | ||
|
|
e77eef1532 | ||
|
|
1fe0b8861d | ||
|
|
aeb9172144 | ||
|
|
1b413e2f47 | ||
|
|
d6c8400116 | ||
|
|
4a0205e622 | ||
|
|
c2520006ac | ||
|
|
99817fd8bd | ||
|
|
3f35fedd9d | ||
|
|
b7c2d13992 | ||
|
|
a6426587fb | ||
|
|
5f68356278 | ||
|
|
08f61e7408 | ||
|
|
9beda15f00 | ||
|
|
09d1663f81 | ||
|
|
f43b6e7cff | ||
|
|
6581eb4fd1 | ||
|
|
caedfe5c1e | ||
|
|
f6a234aaf9 | ||
|
|
8450fb6596 | ||
|
|
41fa3b5a8c | ||
|
|
522de45a62 | ||
|
|
4acc4b2dda | ||
|
|
c1f4855daf | ||
|
|
3506e96cfd | ||
|
|
b42a94ed77 | ||
|
|
80da790a3c | ||
|
|
d6c706d0f9 | ||
|
|
bd09a6ac45 | ||
|
|
4ada31b20e | ||
|
|
5d8b55e96b | ||
|
|
eab4893aa2 | ||
|
|
4806499040 | ||
|
|
1b1c02ceaa | ||
|
|
a8f0a8a253 | ||
|
|
b68115d3b2 | ||
|
|
c353d60ddc | ||
|
|
5d1337f15d | ||
|
|
eeee8af806 | ||
|
|
61b9aca746 | ||
|
|
c4358b91a2 | ||
|
|
4ce9e67460 | ||
|
|
8e25598a78 | ||
|
|
e88e6a7bcd | ||
|
|
971dfc4c14 | ||
|
|
9d9bf84c3f | ||
|
|
f7e170865d | ||
|
|
134441a6e7 | ||
|
|
80a5d21576 | ||
|
|
45c84beb81 | ||
|
|
4a4ee19ba9 | ||
|
|
f1a812dae2 | ||
|
|
a4781d5882 | ||
|
|
0dba28c388 | ||
|
|
67f7e6a6d2 | ||
|
|
7668be50ae | ||
|
|
100664f77e | ||
|
|
e54f837b83 | ||
|
|
a33337ae0c | ||
|
|
13aa456c3c | ||
|
|
65a194a6d2 | ||
|
|
55e3dd3c18 | ||
|
|
b88f496f4e | ||
|
|
696cf8490b | ||
|
|
ffc08227dd | ||
|
|
6cb3a2de43 | ||
|
|
47543e46f2 | ||
|
|
abd7b4f0f4 | ||
|
|
8caf9f110b | ||
|
|
1370b53726 | ||
|
|
2435436580 | ||
|
|
22aa8ee334 | ||
|
|
6d688ed2bc | ||
|
|
46e204165d | ||
|
|
8590a9a110 | ||
|
|
62058d5dfe | ||
|
|
9bfb965e63 | ||
|
|
1d397af674 | ||
|
|
141a468808 | ||
|
|
47bfef958b | ||
|
|
a24d724e2b | ||
|
|
dd72eacd21 | ||
|
|
e27dc1f7a2 | ||
|
|
ea847d7d32 | ||
|
|
87be0ef073 | ||
|
|
c3c3fc6720 | ||
|
|
8bdb9a657f | ||
|
|
71e1ada641 | ||
|
|
37a3b72025 | ||
|
|
c49573db65 | ||
|
|
97c3e6089d | ||
|
|
8586ced3cc | ||
|
|
2b44ede92b | ||
|
|
86a12e2d28 | ||
|
|
9d7509b4dd | ||
|
|
defece95fc | ||
|
|
7b78d99ac4 | ||
|
|
dbb45e7253 | ||
|
|
7286d3b94f | ||
|
|
cc802b1e9f | ||
|
|
a66a2f5645 | ||
|
|
885c0dc500 | ||
|
|
b826b53cee | ||
|
|
ea93162056 | ||
|
|
39afeab5f8 | ||
|
|
b6950332ad | ||
|
|
ccdce37f88 | ||
|
|
9d6a7f709c | ||
|
|
96a4125f15 | ||
|
|
b16e90c10d | ||
|
|
5164315243 | ||
|
|
3df0492275 | ||
|
|
fa8ca0569d | ||
|
|
f78354a377 | ||
|
|
8b1d8e6a90 | ||
|
|
2244fb0523 | ||
|
|
c611b39f52 | ||
|
|
73a0255ae8 | ||
|
|
e978541bf1 | ||
|
|
ae77c60c53 | ||
|
|
b0d9a934d9 | ||
|
|
1583c86c78 | ||
|
|
a779ba5c0e | ||
|
|
be46ed2686 | ||
|
|
e5002b4ef3 | ||
|
|
1372681b87 | ||
|
|
2179ce6fff | ||
|
|
28dbaf317e | ||
|
|
753db25e4c | ||
|
|
65719b560b | ||
|
|
44402ac6e1 | ||
|
|
7e1b26c6a9 | ||
|
|
8550c92e37 | ||
|
|
7d3b2c064a | ||
|
|
2a715d5348 | ||
|
|
9b76d62753 | ||
|
|
80898407c3 | ||
|
|
40208a13e0 | ||
|
|
ae9b7183b5 | ||
|
|
90569192b7 | ||
|
|
223150550f | ||
|
|
80c6f600db | ||
|
|
a938be3712 | ||
|
|
31c6b0664f | ||
|
|
3fa4052538 | ||
|
|
1780f3858d | ||
|
|
f2de0dc673 | ||
|
|
5eb85fd99c | ||
|
|
3f59597864 | ||
|
|
2ba05a46ee | ||
|
|
292ed87201 | ||
|
|
7e686a8882 | ||
|
|
4ca6e9ec3a | ||
|
|
bd5f95b1c5 | ||
|
|
167dfc3847 | ||
|
|
dcd441f15e | ||
|
|
90c8fbeee4 | ||
|
|
cae1840506 | ||
|
|
1860057a25 | ||
|
|
82c6f6f6bc | ||
|
|
2545262fc2 | ||
|
|
24dd535d9e | ||
|
|
b27fe871c4 | ||
|
|
cb5fff0310 | ||
|
|
b60d45ba76 | ||
|
|
7336a3d9c7 | ||
|
|
c7829201e1 | ||
|
|
63b6c76f51 | ||
|
|
056a5df4e1 | ||
|
|
757d1add5b | ||
|
|
3cf3feb2ae | ||
|
|
46579900cd | ||
|
|
2d4a5a30f7 | ||
|
|
2ed5a045de | ||
|
|
4b42496273 | ||
|
|
c5d8a446ae | ||
|
|
9bee62ada9 | ||
|
|
d15caba4a6 | ||
|
|
536c8128dd | ||
|
|
420359066e | ||
|
|
99918ee0c0 | ||
|
|
480e9ea3ec | ||
|
|
2ee4029e04 | ||
|
|
edd186bdfe | ||
|
|
864d40d934 | ||
|
|
856752db21 | ||
|
|
7fde6db9d1 | ||
|
|
0aac046a0e | ||
|
|
1ad11adb94 | ||
|
|
505adea0ef | ||
|
|
9c64721bf0 | ||
|
|
965fdad8b1 | ||
|
|
a6d6589811 | ||
|
|
a227af05d9 | ||
|
|
3b7a16c439 | ||
|
|
ce0898956d | ||
|
|
cd72851289 | ||
|
|
6711d752e2 | ||
|
|
65472bed54 | ||
|
|
a188ad68ed | ||
|
|
bb01afeb99 | ||
|
|
a1be3a3e77 | ||
|
|
b5e7877912 | ||
|
|
587e7118c9 | ||
|
|
8c5ffb88a3 | ||
|
|
2a00f41ef8 | ||
|
|
f676f94278 | ||
|
|
4ca762344c | ||
|
|
5c5ab5bad5 | ||
|
|
cd6e40f01c | ||
|
|
59a8a22e8a | ||
|
|
2910164d5a | ||
|
|
3afc89db6b | ||
|
|
bfc45993f8 | ||
|
|
b95e2b365a | ||
|
|
a8d50223aa | ||
|
|
73e788b513 | ||
|
|
15d135c11b | ||
|
|
0fcda0be1a | ||
|
|
1bbcd638b8 | ||
|
|
fe73750d66 | ||
|
|
ca4c576f78 | ||
|
|
648637a1a1 | ||
|
|
91adf379da | ||
|
|
08ca57cba2 | ||
|
|
e6fcb1272a | ||
|
|
60a5acdb9d | ||
|
|
2221261ec2 | ||
|
|
6627514e88 | ||
|
|
d7b02da719 | ||
|
|
ebbe015bbc | ||
|
|
e03a92f8d8 | ||
|
|
7d98c1b355 | ||
|
|
e78040a376 | ||
|
|
97eedb568c | ||
|
|
161f1db40e | ||
|
|
b50b97a4d1 | ||
|
|
e8e176ed40 | ||
|
|
bc82e9c7fa | ||
|
|
73ace77305 | ||
|
|
4023dcf09d | ||
|
|
ebf236b387 | ||
|
|
e40d77420c | ||
|
|
27b9f57d7a | ||
|
|
3cd9639f34 | ||
|
|
a5a14f6c76 | ||
|
|
bfac3f8ad0 | ||
|
|
dcadbac4d5 | ||
|
|
626d703d77 | ||
|
|
25b7ef3d2e | ||
|
|
d812e6ab96 | ||
|
|
74e4a77ce6 | ||
|
|
863e1ee113 | ||
|
|
33e4a15830 | ||
|
|
bf09786423 | ||
|
|
f7070dd3f7 | ||
|
|
523c650c9d | ||
|
|
469d408b09 | ||
|
|
cb1b13bdb4 | ||
|
|
480a34c0f7 | ||
|
|
93479320ee | ||
|
|
b2acd5511c | ||
|
|
96ed2f2119 | ||
|
|
606e0120ee | ||
|
|
6da85fd286 | ||
|
|
298b960ef7 | ||
|
|
ee3fbabece | ||
|
|
0bed5cd99a | ||
|
|
bc55af27a7 | ||
|
|
a0006f73ac | ||
|
|
f79070fe60 | ||
|
|
b238f3d060 | ||
|
|
a6ad86bd59 | ||
|
|
60e2ef7cda | ||
|
|
cde0ba11fa | ||
|
|
da9fcd1087 | ||
|
|
8929b37dbe | ||
|
|
509604833e | ||
|
|
08ac9680d7 | ||
|
|
ca5404a93b | ||
|
|
f6f4547af3 | ||
|
|
669f8b0431 | ||
|
|
86aa0251ab | ||
|
|
2252048d2e | ||
|
|
53571a7d72 | ||
|
|
0a469f4ccf | ||
|
|
d10ed664bf | ||
|
|
4aad8d36a9 | ||
|
|
3e9295f313 | ||
|
|
6aa66e99b5 | ||
|
|
c38ad89cd7 | ||
|
|
8fdcc5dd50 | ||
|
|
364381f017 | ||
|
|
9433aa503b | ||
|
|
82dee95cd0 | ||
|
|
813db4a985 | ||
|
|
c63bc28ca0 | ||
|
|
29e74a2c9e | ||
|
|
80fdc6005b | ||
|
|
9e25aa1f9f | ||
|
|
f58d5d28cf | ||
|
|
81cb0d43d7 | ||
|
|
9d7052c626 | ||
|
|
4edd0e0ab7 | ||
|
|
a3d60d393b | ||
|
|
311ab67ebe | ||
|
|
1f581e7b51 | ||
|
|
5fe934110e | ||
|
|
f4df8873be | ||
|
|
6f4c5d7195 | ||
|
|
06f1c2fba2 | ||
|
|
36b32a1813 | ||
|
|
d3a43cb65f | ||
|
|
d98e7b9416 | ||
|
|
fc284fd0a2 | ||
|
|
5841d2eb66 | ||
|
|
0c154be04e | ||
|
|
90bc0483ae | ||
|
|
32765b2d34 | ||
|
|
d9e80ebef9 | ||
|
|
445102226e | ||
|
|
a6ce882511 | ||
|
|
9d20c4c4a9 | ||
|
|
e2d8ea0a70 | ||
|
|
b33d003ba5 | ||
|
|
ee07a90b5e | ||
|
|
55f79507fe | ||
|
|
70d2f1e3d9 | ||
|
|
acafc072db | ||
|
|
51e40581b0 | ||
|
|
a372cf0178 | ||
|
|
d863aa7aa6 | ||
|
|
b31e54b3e5 | ||
|
|
9b5734f2ff | ||
|
|
6b59b9988c | ||
|
|
f9de546d14 | ||
|
|
9e304b947b | ||
|
|
c11a219c62 | ||
|
|
3cc22575cb | ||
|
|
c42b6e2fdb | ||
|
|
1e5dd1cc53 | ||
|
|
877532559e | ||
|
|
cd4750fcce | ||
|
|
fc2be71e1f | ||
|
|
0c9aa2f681 | ||
|
|
1883be95d5 | ||
|
|
f7dadda52a | ||
|
|
2a8fd24504 | ||
|
|
71c70a1b36 | ||
|
|
c34379d936 | ||
|
|
2dde29c628 | ||
|
|
818e71d49c | ||
|
|
6bbeb5ef87 | ||
|
|
7ebed70316 | ||
|
|
130237fc87 | ||
|
|
a28774c2c4 | ||
|
|
b9ade5d2a3 | ||
|
|
b677aa1715 | ||
|
|
7a036883e8 | ||
|
|
e665df21da | ||
|
|
d066b9c913 | ||
|
|
b4290c24b3 | ||
|
|
b66656ad84 | ||
|
|
5c032e84be | ||
|
|
83437ae4ba | ||
|
|
24434cc61a | ||
|
|
53dc40e8c7 | ||
|
|
4affb2bc5b | ||
|
|
3d7b057026 | ||
|
|
d36ab337d7 | ||
|
|
c87690f378 | ||
|
|
9fb9fd4568 | ||
|
|
6bd4fd91ff | ||
|
|
164c2463f5 | ||
|
|
73532e41c5 | ||
|
|
3392b1a1ca | ||
|
|
08cc7114ac | ||
|
|
012f9b5314 | ||
|
|
ba6069324f | ||
|
|
0d26d4cdbd | ||
|
|
4b920feffa | ||
|
|
8e038f6944 | ||
|
|
ce94255a9e | ||
|
|
b4b63f86d9 | ||
|
|
830373efb3 | ||
|
|
c3f18671ec | ||
|
|
2901fb0d72 | ||
|
|
c5466edf71 | ||
|
|
bfc5bfe973 | ||
|
|
4da11955f1 | ||
|
|
d4c775a537 | ||
|
|
ef95a8a305 | ||
|
|
a173d2c808 | ||
|
|
ee002df110 | ||
|
|
06ef17048a | ||
|
|
9487348ba8 | ||
|
|
757060b11f | ||
|
|
ab1f8437ea | ||
|
|
d7afd31572 | ||
|
|
cd3178224a | ||
|
|
9193a1a5d6 | ||
|
|
0d33758ba4 | ||
|
|
e7e8c397ef | ||
|
|
0f3e36a447 | ||
|
|
333dbba393 | ||
|
|
b04b12c7a0 | ||
|
|
1dc804a2b9 | ||
|
|
75219d457a | ||
|
|
a1d69b3210 | ||
|
|
dcbc2f1145 | ||
|
|
36903b338a | ||
|
|
9d8d6832af | ||
|
|
3d004f2322 | ||
|
|
fb827e3586 | ||
|
|
ccca183e08 | ||
|
|
237455ab21 | ||
|
|
6141073137 | ||
|
|
740691417f | ||
|
|
2ed709796a | ||
|
|
75c0350584 | ||
|
|
17d72b9922 | ||
|
|
1796fae3d1 | ||
|
|
1ecd22204d | ||
|
|
356fe4591f | ||
|
|
0230942a3d | ||
|
|
325793eebc | ||
|
|
39d1256f68 | ||
|
|
fd2472d34b | ||
|
|
5e8fbc6552 | ||
|
|
3084a40729 | ||
|
|
0069f51ea4 | ||
|
|
696c612489 | ||
|
|
4a5a4077af | ||
|
|
28bcb899e7 | ||
|
|
4062a7089a | ||
|
|
ad86221c7d | ||
|
|
53938248de | ||
|
|
c018b639ad | ||
|
|
eb2145c7da | ||
|
|
2f4c39d310 | ||
|
|
79ada82223 | ||
|
|
c67463fb3b | ||
|
|
d162471555 | ||
|
|
c99797bcef | ||
|
|
9739cdbbaa | ||
|
|
7f6db561f5 | ||
|
|
9eac00b303 | ||
|
|
b61df04c1b | ||
|
|
b587e21c90 | ||
|
|
02d66ee9fd | ||
|
|
a950e08ef1 | ||
|
|
6e7d28db7b | ||
|
|
a0ea00d0a3 | ||
|
|
beb5606862 | ||
|
|
44f11f93a4 | ||
|
|
7b61f267dd | ||
|
|
971238cedb | ||
|
|
4d19b9249b | ||
|
|
4046b91609 | ||
|
|
e6652109c5 | ||
|
|
e9cfc066a5 | ||
|
|
9ce078c1d3 | ||
|
|
2bcc1675e8 | ||
|
|
a87c2347c9 | ||
|
|
8f2810db30 | ||
|
|
528d0b0429 | ||
|
|
f759014315 | ||
|
|
a568610c28 | ||
|
|
c35a85db12 | ||
|
|
fcd61436c8 | ||
|
|
0798063213 | ||
|
|
80de63323d | ||
|
|
7d0219b11d | ||
|
|
d42434ddc0 | ||
|
|
cbf6d23c24 | ||
|
|
568c05b4b0 | ||
|
|
59ee4babeb | ||
|
|
604ef4d004 | ||
|
|
dc80cc80e6 | ||
|
|
f5a0c3cfca | ||
|
|
f8e9563392 | ||
|
|
3c17a14bd3 | ||
|
|
3140859993 | ||
|
|
3c35bb6091 | ||
|
|
355e37a27d | ||
|
|
ca71ffe3c2 | ||
|
|
4bcc703444 | ||
|
|
f01e0b888d | ||
|
|
5701454c02 | ||
|
|
0584f781c4 | ||
|
|
fd7d096a77 | ||
|
|
6330548cc0 | ||
|
|
048ac5f7a5 | ||
|
|
bac38688f9 | ||
|
|
1f29ff24d7 | ||
|
|
1006617e99 | ||
|
|
99e7e73965 | ||
|
|
a6b91c435c | ||
|
|
938f940f90 | ||
|
|
06a8d62dfe | ||
|
|
44c439d7a9 | ||
|
|
fd11ea8143 | ||
|
|
fb65e0e23d | ||
|
|
1b23c5ea4a | ||
|
|
045dc10a0d |
9
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Hoppscotch",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:18",
|
||||
"forwardPorts": [3000],
|
||||
"features": {
|
||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
|
||||
},
|
||||
"postCreateCommand": "cp .env.example .env && pnpm i"
|
||||
}
|
||||
106
.dockerignore
@@ -1,104 +1,2 @@
|
||||
Dockerfile
|
||||
.vscode
|
||||
.github
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
# Firebase
|
||||
.firebase
|
||||
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# Mac OSX
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
# Build data
|
||||
.hoppscotch
|
||||
|
||||
# File explorer
|
||||
.directory
|
||||
node_modules
|
||||
**/*/node_modules
|
||||
|
||||
64
.env.example
Normal file
@@ -0,0 +1,64 @@
|
||||
#-----------------------Backend Config------------------------------#
|
||||
# Prisma Config
|
||||
DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
|
||||
|
||||
# Auth Tokens Config
|
||||
JWT_SECRET="secret1233"
|
||||
TOKEN_SALT_COMPLEXITY=10
|
||||
MAGIC_LINK_TOKEN_VALIDITY= 3
|
||||
REFRESH_TOKEN_VALIDITY="604800000" # Default validity is 7 days (604800000 ms) in ms
|
||||
ACCESS_TOKEN_VALIDITY="86400000" # Default validity is 1 day (86400000 ms) in ms
|
||||
SESSION_SECRET='add some secret here'
|
||||
|
||||
# Hoppscotch App Domain Config
|
||||
REDIRECT_URL="http://localhost:3000"
|
||||
WHITELISTED_ORIGINS="http://localhost:3170,http://localhost:3000,http://localhost:3100"
|
||||
VITE_ALLOWED_AUTH_PROVIDERS=GOOGLE,GITHUB,MICROSOFT,EMAIL
|
||||
|
||||
# Google Auth Config
|
||||
GOOGLE_CLIENT_ID="************************************************"
|
||||
GOOGLE_CLIENT_SECRET="************************************************"
|
||||
GOOGLE_CALLBACK_URL="http://localhost:3170/v1/auth/google/callback"
|
||||
GOOGLE_SCOPE="email,profile"
|
||||
|
||||
# Github Auth Config
|
||||
GITHUB_CLIENT_ID="************************************************"
|
||||
GITHUB_CLIENT_SECRET="************************************************"
|
||||
GITHUB_CALLBACK_URL="http://localhost:3170/v1/auth/github/callback"
|
||||
GITHUB_SCOPE="user:email"
|
||||
|
||||
# Microsoft Auth Config
|
||||
MICROSOFT_CLIENT_ID="************************************************"
|
||||
MICROSOFT_CLIENT_SECRET="************************************************"
|
||||
MICROSOFT_CALLBACK_URL="http://localhost:3170/v1/auth/microsoft/callback"
|
||||
MICROSOFT_SCOPE="user.read"
|
||||
MICROSOFT_TENANT="common"
|
||||
|
||||
# Mailer config
|
||||
MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com"
|
||||
MAILER_ADDRESS_FROM='"From Name Here" <from@example.com>'
|
||||
|
||||
# Rate Limit Config
|
||||
RATE_LIMIT_TTL=60 # In seconds
|
||||
RATE_LIMIT_MAX=100 # Max requests per IP
|
||||
|
||||
|
||||
#-----------------------Frontend Config------------------------------#
|
||||
|
||||
|
||||
# Base URLs
|
||||
VITE_BASE_URL=http://localhost:3000
|
||||
VITE_SHORTCODE_BASE_URL=http://localhost:3000
|
||||
VITE_ADMIN_URL=http://localhost:3100
|
||||
|
||||
# Backend URLs
|
||||
VITE_BACKEND_GQL_URL=http://localhost:3170/graphql
|
||||
VITE_BACKEND_WS_URL=ws://localhost:3170/graphql
|
||||
VITE_BACKEND_API_URL=http://localhost:3170/v1
|
||||
|
||||
# Terms Of Service And Privacy Policy Links (Optional)
|
||||
VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms
|
||||
VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy
|
||||
|
||||
# Set to `true` for subpath based access
|
||||
ENABLE_SUBPATH_BASED_ACCESS=false
|
||||
2
.github/dependabot.yml
vendored
@@ -5,6 +5,6 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: '00:00'
|
||||
open-pull-requests-limit: 10
|
||||
open-pull-requests-limit: 0
|
||||
reviewers:
|
||||
- liyasthomas
|
||||
|
||||
2
.github/pull_request_template.md
vendored
@@ -23,4 +23,4 @@ Closes # <!-- Issue # here -->
|
||||
- [ ] All the tests have passed
|
||||
|
||||
### Additional Information
|
||||
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->
|
||||
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behaviour, etc. -->
|
||||
|
||||
93
.github/workflows/codeql-analysis.yml
vendored
@@ -1,72 +1,63 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
name: "CodeQL analysis"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '39 7 * * 2'
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# │ │ │ │ │
|
||||
# * * * * *
|
||||
- cron: '30 1 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
# Run extended queries including queries using machine learning
|
||||
queries: security-extended
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
# Run extended queries including queries using machine learning
|
||||
queries: security-extended
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below).
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following
|
||||
# three lines and modify them (or add more) to build your code if your
|
||||
# project uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
35
.github/workflows/deploy-netlify.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Deploy to Netlify
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Push build files to Netlify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup and run pnpm install
|
||||
uses: pnpm/action-setup@v2.2.2
|
||||
with:
|
||||
version: 7
|
||||
run_install: true
|
||||
|
||||
- name: Setup Environment
|
||||
run: mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env
|
||||
|
||||
- name: Build Site
|
||||
env:
|
||||
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: production
|
||||
run: pnpm run generate
|
||||
|
||||
# Deploy the production site with netlify-cli
|
||||
- name: Deploy to Netlify (production)
|
||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-app/dist --prod
|
||||
env:
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_PRODUCTION_SITE_ID }}
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
18
.github/workflows/deploy-prod.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Deploy to Live Channel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy_live_website:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||
with:
|
||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_POSTWOMAN_API }}'
|
||||
channelId: live
|
||||
projectId: postwoman-api
|
||||
45
.github/workflows/deploy-staging-netlify.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Deploy to Staging Netlify
|
||||
|
||||
on:
|
||||
push:
|
||||
# TODO: Migrate to staging branch only
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Push build files to Netlify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup and run pnpm install
|
||||
uses: pnpm/action-setup@v2.2.2
|
||||
with:
|
||||
version: 7
|
||||
run_install: true
|
||||
|
||||
- name: Build Site
|
||||
env:
|
||||
VITE_GA_ID: ${{ secrets.STAGING_GA_ID }}
|
||||
VITE_GTM_ID: ${{ secrets.STAGING_GTM_ID }}
|
||||
VITE_API_KEY: ${{ secrets.STAGING_FB_API_KEY }}
|
||||
VITE_AUTH_DOMAIN: ${{ secrets.STAGING_FB_AUTH_DOMAIN }}
|
||||
VITE_DATABASE_URL: ${{ secrets.STAGING_FB_DATABASE_URL }}
|
||||
VITE_PROJECT_ID: ${{ secrets.STAGING_FB_PROJECT_ID }}
|
||||
VITE_STORAGE_BUCKET: ${{ secrets.STAGING_FB_STORAGE_BUCKET }}
|
||||
VITE_MESSAGING_SENDER_ID: ${{ secrets.STAGING_FB_MESSAGING_SENDER_ID }}
|
||||
VITE_APP_ID: ${{ secrets.STAGING_FB_APP_ID }}
|
||||
VITE_BASE_URL: ${{ secrets.STAGING_BASE_URL }}
|
||||
VITE_BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }}
|
||||
VITE_BACKEND_WS_URL: ${{ secrets.STAGING_BACKEND_WS_URL }}
|
||||
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: staging
|
||||
run: pnpm run generate
|
||||
|
||||
# Deploy the staging site with netlify-cli
|
||||
- name: Deploy to Netlify (staging)
|
||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-app/dist --prod
|
||||
env:
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_STAGING_SITE_ID }}
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
39
.github/workflows/publish-docker.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
with:
|
||||
images: hoppscotch/hoppscotch
|
||||
flavor: |
|
||||
latest=true
|
||||
prefix=
|
||||
suffix=
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
84
.github/workflows/release-push-docker.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: "Push containers to Docker Hub on release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup environment
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push the backend container
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./prod.Dockerfile
|
||||
target: backend
|
||||
push: true
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:latest
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push the frontend container
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./prod.Dockerfile
|
||||
target: app
|
||||
push: true
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:latest
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push the admin dashboard container
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./prod.Dockerfile
|
||||
target: sh_admin
|
||||
push: true
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:latest
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push the AIO container
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./prod.Dockerfile
|
||||
target: aio
|
||||
push: true
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:latest
|
||||
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:${{ github.ref_name }}
|
||||
29
.github/workflows/tests.yml
vendored
@@ -2,12 +2,13 @@ name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, staging, "release/**"]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [main, staging, "release/**"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
@@ -15,17 +16,23 @@ jobs:
|
||||
node-version: ["lts/*"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- name: Setup and run pnpm install
|
||||
uses: pnpm/action-setup@v2.2.2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup environment
|
||||
run: mv .env.example .env
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7
|
||||
version: 8
|
||||
run_install: true
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
|
||||
42
.github/workflows/ui.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Deploy to Netlify (ui)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# run this workflow only if an update is made to the ui package
|
||||
paths:
|
||||
- "packages/hoppscotch-ui/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup environment
|
||||
run: mv .env.example .env
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8
|
||||
run_install: true
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Build site
|
||||
run: pnpm run generate-ui
|
||||
|
||||
# Deploy the ui site with netlify-cli
|
||||
- name: Deploy to Netlify (ui)
|
||||
run: npx netlify-cli@15.11.0 deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
||||
env:
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
85
.gitignore
vendored
@@ -1,15 +1,18 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
# Firebase
|
||||
.firebase
|
||||
|
||||
### Node template
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
@@ -22,6 +25,7 @@ lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
@@ -45,12 +49,27 @@ jspm_packages/
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
@@ -60,26 +79,68 @@ typings/
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# next.js build output
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# nuxt.js build output
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
@@ -107,3 +168,9 @@ tests/*/videos
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# PNPM
|
||||
.pnpm-store
|
||||
|
||||
# GQL SDL generated for the frontends
|
||||
gql-gen/
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
module.exports = {
|
||||
semi: false
|
||||
semi: false,
|
||||
trailingComma: "es5",
|
||||
singleQuote: false,
|
||||
printWidth: 80,
|
||||
useTabs: false,
|
||||
tabWidth: 2
|
||||
}
|
||||
|
||||
13
.vscode/extensions.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"antfu.iconify",
|
||||
"vue.volar",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"csstools.postcss",
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"octref.vetur"
|
||||
]
|
||||
}
|
||||
30
CODEOWNERS
Normal file
@@ -0,0 +1,30 @@
|
||||
# CODEOWNERS is prioritized from bottom to top
|
||||
|
||||
# If none of the below matched
|
||||
* @AndrewBastin @liyasthomas
|
||||
|
||||
# Packages
|
||||
/packages/codemirror-lang-graphql/ @AndrewBastin
|
||||
/packages/hoppscotch-cli/ @AndrewBastin
|
||||
/packages/hoppscotch-common/ @amk-dev @AndrewBastin
|
||||
/packages/hoppscotch-data/ @AndrewBastin
|
||||
/packages/hoppscotch-js-sandbox/ @AndrewBastin
|
||||
/packages/hoppscotch-ui/ @anwarulislam
|
||||
/packages/hoppscotch-web/ @amk-dev
|
||||
/packages/hoppscotch-selfhost-web/ @amk-dev
|
||||
/packages/hoppscotch-sh-admin/ @JoelJacobStephen
|
||||
/packages/hoppscotch-backend/ @ankitsridhar16 @balub
|
||||
|
||||
# Sections within Hoppscotch Common
|
||||
/packages/hoppscotch-common/src/components @anwarulislam
|
||||
/packages/hoppscotch-common/src/components/collections @nivedin @amk-dev
|
||||
/packages/hoppscotch-common/src/components/environments @nivedin @amk-dev
|
||||
/packages/hoppscotch-common/src/composables @amk-dev
|
||||
/packages/hoppscotch-common/src/modules @AndrewBastin @amk-dev
|
||||
/packages/hoppscotch-common/src/pages @AndrewBastin @amk-dev
|
||||
/packages/hoppscotch-common/src/newstore @AndrewBastin @amk-dev
|
||||
|
||||
README.md @liyasthomas
|
||||
|
||||
# The lockfile has no owner
|
||||
pnpm-lock.yaml
|
||||
@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
@@ -22,17 +22,17 @@ community include:
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
@@ -106,23 +106,27 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
||||
29
Dockerfile
@@ -1,29 +0,0 @@
|
||||
FROM node:lts-alpine
|
||||
|
||||
LABEL maintainer="Hoppscotch (support@hoppscotch.io)"
|
||||
|
||||
# Add git as the prebuild target requires it to parse version information
|
||||
RUN apk add --no-cache --virtual .gyp \
|
||||
python3 \
|
||||
make \
|
||||
g++
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
ADD . /app/
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
RUN pnpm i --unsafe-perm=true
|
||||
|
||||
ENV HOST 0.0.0.0
|
||||
EXPOSE 3000
|
||||
|
||||
RUN mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env
|
||||
|
||||
RUN pnpm run generate
|
||||
|
||||
CMD ["pnpm", "run", "start"]
|
||||
228
README.md
@@ -2,23 +2,18 @@
|
||||
<a href="https://hoppscotch.io">
|
||||
<img
|
||||
src="https://avatars.githubusercontent.com/u/56705483"
|
||||
alt="Hoppscotch Logo"
|
||||
alt="Hoppscotch"
|
||||
height="64"
|
||||
/>
|
||||
</a>
|
||||
<br />
|
||||
<p>
|
||||
<h3>
|
||||
<b>
|
||||
Hoppscotch
|
||||
</b>
|
||||
</h3>
|
||||
</p>
|
||||
<p>
|
||||
<h3>
|
||||
<b>
|
||||
Open source API development ecosystem
|
||||
Hoppscotch
|
||||
</b>
|
||||
</p>
|
||||
</h3>
|
||||
<b>
|
||||
Open Source API Development Ecosystem
|
||||
</b>
|
||||
<p>
|
||||
|
||||
[](CODE_OF_CONDUCT.md) [](https://hoppscotch.io) [](https://github.com/hoppscotch/hoppscotch/actions) [](https://twitter.com/share?text=%F0%9F%91%BD%20Hoppscotch%20%E2%80%A2%20Open%20source%20API%20development%20ecosystem%20-%20Helps%20you%20create%20requests%20faster,%20saving%20precious%20time%20on%20development.&url=https://hoppscotch.io&hashtags=hoppscotch&via=hoppscotch_io)
|
||||
@@ -34,23 +29,18 @@
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<a href="https://hoppscotch.io/#gh-light-mode-only" target="_blank">
|
||||
<img
|
||||
src="./packages/hoppscotch-app/static/images/banner-light.png"
|
||||
alt="Hoppscotch"
|
||||
width="100%"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://hoppscotch.io/#gh-dark-mode-only" target="_blank">
|
||||
<img
|
||||
src="./packages/hoppscotch-app/static/images/banner-dark.png"
|
||||
alt="Hoppscotch"
|
||||
width="100%"
|
||||
/>
|
||||
<a href="https://hoppscotch.io">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./packages/hoppscotch-common/public/images/banner-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="./packages/hoppscotch-common/public/images/banner-light.png">
|
||||
<img alt="Hoppscotch" src="./packages/hoppscotch-common/public/images/banner-dark.png">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
_We highly recommend you take a look at the [**Hoppscotch Documentation**](https://docs.hoppscotch.io) to learn more about the app._
|
||||
|
||||
#### **Support**
|
||||
|
||||
[](https://hoppscotch.io/discord) [](https://hoppscotch.io/telegram) [](https://github.com/hoppscotch/hoppscotch/discussions)
|
||||
@@ -59,9 +49,9 @@
|
||||
|
||||
❤️ **Lightweight:** Crafted with minimalistic UI design.
|
||||
|
||||
⚡️ **Fast:** Send requests and get/copy responses in real-time.
|
||||
⚡️ **Fast:** Send requests and get responses in real time.
|
||||
|
||||
**HTTP Methods**
|
||||
🗄️ **HTTP Methods:** Request methods define the type of action you are requesting to be performed.
|
||||
|
||||
- `GET` - Requests retrieve resource information
|
||||
- `POST` - The server creates a new entry in a database
|
||||
@@ -74,17 +64,15 @@
|
||||
- `TRACE` - Performs a message loop-back test along the path to the target resource
|
||||
- `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods.
|
||||
|
||||
🌈 **Make it yours:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings).
|
||||
🌈 **Theming:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings).
|
||||
|
||||
**Theming**
|
||||
|
||||
- Choose a theme: System (default), Light, Dark, and Black
|
||||
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
|
||||
- Choose a theme: System preference, Light, Dark, and Black
|
||||
- Choose accent colors: Green, Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
|
||||
- Distraction-free Zen mode
|
||||
|
||||
_Customized themes are synced with cloud / local session_
|
||||
_Customized themes are synced with your cloud/local session._
|
||||
|
||||
🔥 **PWA:** Install as a [PWA](https://web.dev/what-are-pwas/) on your device.
|
||||
🔥 **PWA:** Install as a [Progressive Web App](https://web.dev/progressive-web-apps) on your device.
|
||||
|
||||
- Instant loading with Service Workers
|
||||
- Offline support
|
||||
@@ -107,7 +95,7 @@ _Customized themes are synced with cloud / local session_
|
||||
|
||||
📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling.
|
||||
|
||||
🌩 **Socket.IO:** Send and Receive data with SocketIO server.
|
||||
🌩 **Socket.IO:** Send and Receive data with the SocketIO server.
|
||||
|
||||
🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker.
|
||||
|
||||
@@ -127,7 +115,7 @@ _Customized themes are synced with cloud / local session_
|
||||
- OAuth 2.0
|
||||
- OIDC Access Token/PKCE
|
||||
|
||||
📢 **Headers:** Describes the format the body of your request is being sent as.
|
||||
📢 **Headers:** Describes the format the body of your request is being sent in.
|
||||
|
||||
📫 **Parameters:** Use request parameters to set varying parts in simulated requests.
|
||||
|
||||
@@ -137,14 +125,14 @@ _Customized themes are synced with cloud / local session_
|
||||
- FormData, JSON, and many more
|
||||
- Toggle between key-value and RAW input parameter list
|
||||
|
||||
👋 **Response:** Contains the status line, headers, and the message/response body.
|
||||
📮 **Response:** Contains the status line, headers, and the message/response body.
|
||||
|
||||
- Copy response to clipboard
|
||||
- Download response as a file
|
||||
- Copy the response to the clipboard
|
||||
- Download the response as a file
|
||||
- View response headers
|
||||
- View raw and preview of HTML, image, JSON, XML responses
|
||||
- View raw and preview HTML, image, JSON, and XML responses
|
||||
|
||||
⏰ **History:** Request entries are synced with cloud / local session storage to restore with a single click.
|
||||
⏰ **History:** Request entries are synced with your cloud/local session storage.
|
||||
|
||||
📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click.
|
||||
|
||||
@@ -152,7 +140,32 @@ _Customized themes are synced with cloud / local session_
|
||||
- Nested folders
|
||||
- Export and import as a file or GitHub gist
|
||||
|
||||
_Collections are synced with cloud / local session storage_
|
||||
_Collections are synced with your cloud/local session storage._
|
||||
|
||||
📜 **Pre-Request Scripts:** Snippets of code associated with a request that is executed before the request is sent.
|
||||
|
||||
- Set environment variables
|
||||
- Include timestamp in the request headers
|
||||
- Send a random alphanumeric string in the URL parameters
|
||||
- Any JavaScript functions
|
||||
|
||||
👨👩👧👦 **Teams:** Helps you collaborate across your teams to design, develop, and test APIs faster.
|
||||
|
||||
- Create unlimited teams
|
||||
- Create unlimited shared collections
|
||||
- Create unlimited team members
|
||||
- Role-based access control
|
||||
- Cloud sync
|
||||
- Multiple devices
|
||||
|
||||
👥 **Workspaces:** Organize your personal and team collections environments into workspaces. Easily switch between workspaces to manage multiple projects.
|
||||
|
||||
- Create unlimited workspaces
|
||||
- Switch between personal and team workspaces
|
||||
|
||||
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
|
||||
|
||||
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/documentation/features/shortcuts)**
|
||||
|
||||
🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs.
|
||||
|
||||
@@ -161,60 +174,31 @@ _Collections are synced with cloud / local session storage_
|
||||
- Access APIs served in non-HTTPS (`http://`) endpoints
|
||||
- Use your Proxy URL
|
||||
|
||||
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/privacy)**_
|
||||
|
||||
📜 **Pre-Request Scripts β:** Snippets of code associated with a request that is executed before the request is sent.
|
||||
|
||||
- Set environment variables
|
||||
- Include timestamp in the request headers
|
||||
- Send a random alphanumeric string in the URL parameters
|
||||
- Any JavaScript functions
|
||||
|
||||
📄 **API Documentation:** Create and share dynamic API documentation easily, quickly.
|
||||
|
||||
1. Add your requests to Collections and Folders
|
||||
2. Export Collections and easily share your APIs with the rest of your team
|
||||
3. Import Collections and Generate Documentation on-the-go
|
||||
|
||||
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
|
||||
|
||||
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/features/shortcuts)**
|
||||
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/support/privacy)**._
|
||||
|
||||
🌎 **i18n:** Experience the app in your language.
|
||||
|
||||
Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
|
||||
Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md) and the process for submitting pull requests to us.
|
||||
|
||||
📦 **Add-ons:** Official add-ons for hoppscotch.
|
||||
☁️ **Auth + Sync:** Sign in and sync your data in real-time across all your devices.
|
||||
|
||||
- **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch
|
||||
- **[CLI β](https://github.com/hoppscotch/hopp-cli)** - A CLI solution for Hoppscotch
|
||||
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that simplifies access to Hoppscotch
|
||||
|
||||
[ **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) | [ **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
|
||||
|
||||
> **Extensions fixes `CORS` issues.**
|
||||
|
||||
- **[Hopp-Doc-Gen](https://github.com/hoppscotch/hopp-doc-gen)** - An API doc generator CLI for Hoppscotch
|
||||
|
||||
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
|
||||
|
||||
☁️ **Auth + Sync:** Sign in and sync your data in real-time.
|
||||
|
||||
**Sign in with**
|
||||
**Sign in with:**
|
||||
|
||||
- GitHub
|
||||
- Google
|
||||
- Microsoft
|
||||
- Email
|
||||
- SSO (Single Sign-On)[^EE]
|
||||
|
||||
**Synchronize your data**
|
||||
**🔄 Synchronize your data:** Handoff to continue tasks on your other devices.
|
||||
|
||||
- Workspaces
|
||||
- History
|
||||
- Collections
|
||||
- Environments
|
||||
- Settings
|
||||
|
||||
✅ **Post-Request Tests β:** Write tests associated with a request that is executed after the request's response.
|
||||
✅ **Post-Request Tests:** Write tests associated with a request that is executed after the request's response.
|
||||
|
||||
- Check the status code as an integer
|
||||
- Filter response headers
|
||||
@@ -222,7 +206,7 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
- Set environment variables
|
||||
- Write JavaScript code
|
||||
|
||||
🌱 **Environments** : Environment variables allow you to store and reuse values in your requests and scripts.
|
||||
🌱 **Environments:** Environment variables allow you to store and reuse values in your requests and scripts.
|
||||
|
||||
- Unlimited environments and variables
|
||||
- Initialize through the pre-request script
|
||||
@@ -241,22 +225,31 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
|
||||
</details>
|
||||
|
||||
👨👩👧👦 **Teams β:** Helps you collaborate across your team to design, develop, and test APIs faster.
|
||||
|
||||
- Unlimited teams
|
||||
- Unlimited shared collections
|
||||
- Unlimited team members
|
||||
- Role-based access control
|
||||
- Cloud sync
|
||||
- Multiple devices
|
||||
|
||||
🚚 **Bulk Edit:** Edit key-value pairs in bulk.
|
||||
|
||||
- Entries are separated by newline
|
||||
- Keys and values are separated by `:`
|
||||
- Prepend `#` to any row you want to add but keep disabled
|
||||
|
||||
**For more features, please read our [documentation](https://docs.hoppscotch.io).**
|
||||
🎛️ **Admin dashboard:** Manage your team and invite members.
|
||||
|
||||
- Insights
|
||||
- Manage users
|
||||
- Manage teams
|
||||
|
||||
📦 **Add-ons:** Official add-ons for hoppscotch.
|
||||
|
||||
- **[Hoppscotch CLI](https://github.com/hoppscotch/hopp-cli)** - Command-line interface for Hoppscotch.
|
||||
- **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch.
|
||||
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that enhance your Hoppscotch experience.
|
||||
|
||||
[ **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) | [ **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
|
||||
|
||||
> **Extensions fix `CORS` issues.**
|
||||
|
||||
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
|
||||
|
||||
**For a complete list of features, please read our [documentation](https://docs.hoppscotch.io).**
|
||||
|
||||
## **Demo**
|
||||
|
||||
@@ -268,56 +261,9 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
2. Click "Send" to simulate the request
|
||||
3. View the response
|
||||
|
||||
## **Built with**
|
||||
|
||||
- [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)
|
||||
- [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS), [SCSS](https://sass-lang.com), [Windi CSS](https://windicss.org)
|
||||
- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
||||
- [TypeScript](https://www.typescriptlang.org)
|
||||
- [Vue](https://vuejs.org)
|
||||
- [Nuxt](https://nuxtjs.org)
|
||||
|
||||
## **Developing**
|
||||
|
||||
0. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/.env.example) file found in `packages/hoppscotch-app` with your own keys and rename it to `.env`.
|
||||
|
||||
_Sample keys only work with the [production build](https://hoppscotch.io)._
|
||||
|
||||
### Browser-based development environment
|
||||
|
||||
- [GitHub codespace](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace)
|
||||
- [Gitpod](https://gitpod.io/#https://github.com/hoppscotch/hoppscotch)
|
||||
|
||||
### Local development environment
|
||||
|
||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||
2. Install pnpm using npm by running `npm install -g pnpm`.
|
||||
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
|
||||
4. Start the development server with `pnpm run dev`.
|
||||
5. Open the development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
|
||||
### Docker compose
|
||||
|
||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||
2. Run `docker-compose up` within the directory that you cloned (probably `hoppscotch`).
|
||||
3. Open the development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
|
||||
## **Docker**
|
||||
|
||||
**Official container** [](https://hub.docker.com/r/hoppscotch/hoppscotch)
|
||||
|
||||
```bash
|
||||
docker run --rm --name hoppscotch -p 3000:3000 hoppscotch/hoppscotch:latest
|
||||
```
|
||||
|
||||
## **Releasing**
|
||||
|
||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||
2. Install pnpm using npm by running `npm install -g pnpm`.
|
||||
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
|
||||
4. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/.env.example) file found in `packages/hoppscotch-app` with your own keys and rename it to `.env`.
|
||||
5. Build the release files with `pnpm run generate`.
|
||||
6. Find the built project in `packages/hoppscotch-app/dist`. Host these files on any [static hosting servers](https://www.pluralsight.com/blog/software-development/where-to-host-your-jamstack-site).
|
||||
Follow our [self-hosting documentation](https://docs.hoppscotch.io/documentation/self-host/getting-started) to get started with the development environment.
|
||||
|
||||
## **Contributing**
|
||||
|
||||
@@ -335,7 +281,7 @@ See the [`CHANGELOG`](CHANGELOG.md) file for details.
|
||||
|
||||
## **Authors**
|
||||
|
||||
This project exists thanks to all the people who contribute — [contribute](CONTRIBUTING.md).
|
||||
This project owes its existence to the collective efforts of all those who contribute — [contribute now](CONTRIBUTING.md).
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors">
|
||||
@@ -347,4 +293,6 @@ This project exists thanks to all the people who contribute — [contribute](CON
|
||||
|
||||
## **License**
|
||||
|
||||
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [`LICENSE`](LICENSE) file for details.
|
||||
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) — see the [`LICENSE`](LICENSE) file for details.
|
||||
|
||||
[^EE]: Enterprise edition feature. [Learn more](https://docs.hoppscotch.io/documentation/self-host/getting-started).
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
This document outlines security procedures and general policies for the Hoppscotch project.
|
||||
|
||||
1. [Reporting a security vulnerability](#reporting-a-security-vulnerability)
|
||||
3. [Incident response process](#incident-response-process)
|
||||
- [Security Policy](#security-policy)
|
||||
- [Reporting a security vulnerability](#reporting-a-security-vulnerability)
|
||||
- [Incident response process](#incident-response-process)
|
||||
|
||||
## Reporting a security vulnerability
|
||||
|
||||
|
||||
@@ -9,26 +9,24 @@ Before you start working on a new language, please look through the [open pull r
|
||||
if there is no existing translation, you can create a new one by following these steps:
|
||||
|
||||
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
|
||||
2. **Checkout the `i18n` branch for latest translations.**
|
||||
3. **Create a new branch for your translation with base branch `i18n`.**
|
||||
4. **Create target language file in the [`locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-app/locales) directory.**
|
||||
5. **Copy the contents of the source file [`locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/locales/en.json) to the target language file.**
|
||||
2. **Checkout the `main` branch for latest translations.**
|
||||
3. **Create a new branch for your translation with base branch `main`.**
|
||||
4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.**
|
||||
5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.**
|
||||
6. **Translate the strings in the target language file.**
|
||||
7. **Add your language entry to [`languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/languages.json).**
|
||||
8. **Save & commit changes.**
|
||||
7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).**
|
||||
8. **Save and commit changes.**
|
||||
9. **Send a pull request.**
|
||||
|
||||
_You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._
|
||||
|
||||
`i18n` branch will be merged into `main` branch once every week.
|
||||
|
||||
Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests.
|
||||
|
||||
## Updating a translation
|
||||
|
||||
### Corrections
|
||||
|
||||
If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, open an issue.
|
||||
If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, [open an issue](https://github.com/hoppscotch/hoppscotch/issues/new/choose).
|
||||
|
||||
### Broken links
|
||||
|
||||
|
||||
19
aio-multiport-setup.Caddyfile
Normal file
@@ -0,0 +1,19 @@
|
||||
:3000 {
|
||||
try_files {path} /
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
}
|
||||
|
||||
:3100 {
|
||||
try_files {path} /
|
||||
root * /site/sh-admin-multiport-setup
|
||||
file_server
|
||||
}
|
||||
|
||||
:3170 {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
|
||||
:80 {
|
||||
respond 404
|
||||
}
|
||||
37
aio-subpath-access.Caddyfile
Normal file
@@ -0,0 +1,37 @@
|
||||
:3000 {
|
||||
respond 404
|
||||
}
|
||||
|
||||
:3100 {
|
||||
respond 404
|
||||
}
|
||||
|
||||
:3170 {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
|
||||
:80 {
|
||||
# Serve the `selfhost-web` SPA by default
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
|
||||
handle_path /admin* {
|
||||
root * /site/sh-admin-subpath-access
|
||||
file_server
|
||||
|
||||
# Ensures any non-existent file in the server is routed to the SPA
|
||||
try_files {path} /
|
||||
}
|
||||
|
||||
# Handle requests under `/backend*` path
|
||||
handle_path /backend* {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
|
||||
# Catch-all route for unknown paths, serves `selfhost-web` SPA
|
||||
handle {
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
try_files {path} /
|
||||
}
|
||||
}
|
||||
73
aio_run.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/local/bin/node
|
||||
// @ts-check
|
||||
|
||||
import { execSync, spawn } from "child_process"
|
||||
import fs from "fs"
|
||||
import process from "process"
|
||||
|
||||
function runChildProcessWithPrefix(command, args, prefix) {
|
||||
const childProcess = spawn(command, args);
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString().trim().split('\n');
|
||||
output.forEach((line) => {
|
||||
console.log(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
const error = data.toString().trim().split('\n');
|
||||
error.forEach((line) => {
|
||||
console.error(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
console.log(`${prefix} Child process exited with code ${code}`);
|
||||
});
|
||||
|
||||
childProcess.on('error', (stuff) => {
|
||||
console.log("error")
|
||||
console.log(stuff)
|
||||
})
|
||||
|
||||
return childProcess
|
||||
}
|
||||
|
||||
const envFileContent = Object.entries(process.env)
|
||||
.filter(([env]) => env.startsWith("VITE_"))
|
||||
.map(([env, val]) => `${env}=${
|
||||
(val.startsWith("\"") && val.endsWith("\""))
|
||||
? val
|
||||
: `"${val}"`
|
||||
}`)
|
||||
.join("\n")
|
||||
|
||||
fs.writeFileSync("build.env", envFileContent)
|
||||
|
||||
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
|
||||
|
||||
fs.rmSync("build.env")
|
||||
|
||||
const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'aio-subpath-access.Caddyfile' : 'aio-multiport-setup.Caddyfile'
|
||||
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
|
||||
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
|
||||
|
||||
caddyProcess.on("exit", (code) => {
|
||||
console.log(`Exiting process because Caddy Server exited with code ${code}`)
|
||||
process.exit(code)
|
||||
})
|
||||
|
||||
backendProcess.on("exit", (code) => {
|
||||
console.log(`Exiting process because Backend Server exited with code ${code}`)
|
||||
process.exit(code)
|
||||
})
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log("SIGINT received, exiting...")
|
||||
|
||||
caddyProcess.kill("SIGINT")
|
||||
backendProcess.kill("SIGINT")
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
@@ -1,23 +1,154 @@
|
||||
# To make it easier to self-host, we have a preset docker compose config that also
|
||||
# has a container with a Postgres instance running.
|
||||
# You can tweak around this file to match your instances
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
web:
|
||||
# This service runs the backend app in the port 3170
|
||||
hoppscotch-backend:
|
||||
container_name: hoppscotch-backend
|
||||
build:
|
||||
dockerfile: prod.Dockerfile
|
||||
context: .
|
||||
target: backend
|
||||
env_file:
|
||||
- ./.env
|
||||
restart: always
|
||||
environment:
|
||||
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
|
||||
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
||||
- PORT=8080
|
||||
volumes:
|
||||
- "./.hoppscotch:/app/.hoppscotch"
|
||||
- "./assets:/app/assets"
|
||||
- "./directives:/app/directives"
|
||||
- "./layouts:/app/layouts"
|
||||
- "./middleware:/app/middleware"
|
||||
- "./pages:/app/pages"
|
||||
- "./plugins:/app/plugins"
|
||||
- "./static:/app/static"
|
||||
- "./store:/app/store"
|
||||
- "./components:/app/components"
|
||||
- "./helpers:/app/helpers"
|
||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||
- /usr/src/app/node_modules/
|
||||
depends_on:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3180:80"
|
||||
- "3170:3170"
|
||||
|
||||
# The main hoppscotch app. This will be hosted at port 3000
|
||||
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
|
||||
# the SH admin dashboard server at packages/hoppscotch-selfhost-web/Caddyfile
|
||||
hoppscotch-app:
|
||||
container_name: hoppscotch-app
|
||||
build:
|
||||
dockerfile: prod.Dockerfile
|
||||
context: .
|
||||
target: app
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
ports:
|
||||
- "3080:80"
|
||||
- "3000:3000"
|
||||
|
||||
# The Self Host dashboard for managing the app. This will be hosted at port 3100
|
||||
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
|
||||
# the SH admin dashboard server at packages/hoppscotch-sh-admin/Caddyfile
|
||||
hoppscotch-sh-admin:
|
||||
container_name: hoppscotch-sh-admin
|
||||
build:
|
||||
dockerfile: prod.Dockerfile
|
||||
context: .
|
||||
target: sh_admin
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
ports:
|
||||
- "3280:80"
|
||||
- "3100:3100"
|
||||
|
||||
# The service that spins up all 3 services at once in one container
|
||||
hoppscotch-aio:
|
||||
container_name: hoppscotch-aio
|
||||
build:
|
||||
dockerfile: prod.Dockerfile
|
||||
context: .
|
||||
target: aio
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3100:3100"
|
||||
- "3170:3170"
|
||||
- "3080:80"
|
||||
|
||||
# The preset DB service, you can delete/comment the below lines if
|
||||
# you are using an external postgres instance
|
||||
# This will be exposed at port 5432
|
||||
hoppscotch-db:
|
||||
image: postgres:15
|
||||
ports:
|
||||
- "5432:5432"
|
||||
user: postgres
|
||||
environment:
|
||||
HOST: 0.0.0.0
|
||||
command: "npm run dev"
|
||||
# The default user defined by the docker image
|
||||
POSTGRES_USER: postgres
|
||||
# NOTE: Please UPDATE THIS PASSWORD!
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_DB: hoppscotch
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# All the services listed below are deprececated
|
||||
hoppscotch-old-backend:
|
||||
container_name: hoppscotch-old-backend
|
||||
build:
|
||||
dockerfile: packages/hoppscotch-backend/Dockerfile
|
||||
context: .
|
||||
target: prod
|
||||
env_file:
|
||||
- ./.env
|
||||
restart: always
|
||||
environment:
|
||||
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
|
||||
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
||||
- PORT=3000
|
||||
volumes:
|
||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||
- /usr/src/app/node_modules/
|
||||
depends_on:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3170:3000"
|
||||
|
||||
hoppscotch-old-app:
|
||||
container_name: hoppscotch-old-app
|
||||
build:
|
||||
dockerfile: packages/hoppscotch-selfhost-web/Dockerfile
|
||||
context: .
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-old-backend
|
||||
ports:
|
||||
- "3000:8080"
|
||||
|
||||
hoppscotch-old-sh-admin:
|
||||
container_name: hoppscotch-old-sh-admin
|
||||
build:
|
||||
dockerfile: packages/hoppscotch-sh-admin/Dockerfile
|
||||
context: .
|
||||
env_file:
|
||||
- ./.env
|
||||
depends_on:
|
||||
- hoppscotch-old-backend
|
||||
ports:
|
||||
- "3100:8080"
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
},
|
||||
"hosting": {
|
||||
"predeploy": [
|
||||
"cd packages/hoppscotch-app && mv .env.example .env && cd ../.. && npm install -g pnpm && pnpm i && pnpm run generate"
|
||||
"mv .env.example .env && npm install -g pnpm && pnpm i && pnpm run generate"
|
||||
],
|
||||
"public": "packages/hoppscotch-app/dist",
|
||||
"public": "packages/hoppscotch-web/dist",
|
||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
||||
"rewrites": [
|
||||
{
|
||||
|
||||
14
healthcheck.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
curlCheck() {
|
||||
if ! curl -s --head "$1" | head -n 1 | grep -q "HTTP/1.[01] [23].."; then
|
||||
echo "URL request failed!"
|
||||
exit 1
|
||||
else
|
||||
echo "URL request succeeded!"
|
||||
fi
|
||||
}
|
||||
|
||||
curlCheck "http://localhost:3000"
|
||||
curlCheck "http://localhost:3100"
|
||||
curlCheck "http://localhost:3170/ping"
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
[build]
|
||||
base = "/"
|
||||
publish = "packages/hoppscotch-app/dist"
|
||||
publish = "packages/hoppscotch-web/dist"
|
||||
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run generate"
|
||||
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-Frame-Options = "SAMEORIGIN"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
|
||||
[[redirects]]
|
||||
|
||||
30
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-app",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Open source API development ecosystem",
|
||||
"author": "Hoppscotch (support@hoppscotch.io)",
|
||||
"private": true,
|
||||
@@ -9,24 +9,38 @@
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky install",
|
||||
"dev": "pnpm -r do-dev",
|
||||
"gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl",
|
||||
"generate": "pnpm -r do-build-prod",
|
||||
"start": "pnpm -r do-prod-start",
|
||||
"start": "http-server packages/hoppscotch-selfhost-web/dist -p 3000",
|
||||
"lint": "pnpm -r do-lint",
|
||||
"typecheck": "pnpm -r do-typecheck",
|
||||
"lintfix": "pnpm -r do-lintfix",
|
||||
"pre-commit": "pnpm -r do-lint && pnpm -r do-typecheck",
|
||||
"test": "pnpm -r do-test"
|
||||
"test": "pnpm -r do-test",
|
||||
"generate-ui": "pnpm -r do-build-ui"
|
||||
},
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@types/node": "^17.0.24"
|
||||
"@types/node": "17.0.27",
|
||||
"cross-env": "^7.0.3",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "12.4.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vue": "3.3.9"
|
||||
},
|
||||
"packageExtensions": {
|
||||
"httpsnippet@^3.0.1": {
|
||||
"peerDependencies": {
|
||||
"ajv": "6.12.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.2.0"
|
||||
"@codemirror/language": "6.9.0",
|
||||
"@lezer/highlight": "1.1.4",
|
||||
"@lezer/lr": "^1.3.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.1.0",
|
||||
"@lezer/generator": "^1.5.1",
|
||||
"mocha": "^9.2.2",
|
||||
"rollup": "^2.70.2",
|
||||
"rollup-plugin-dts": "^4.2.1",
|
||||
"rollup-plugin-ts": "^2.0.7",
|
||||
"typescript": "^4.6.3"
|
||||
"rollup": "^3.29.3",
|
||||
"rollup-plugin-dts": "^6.0.2",
|
||||
"rollup-plugin-ts": "^3.4.5",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
24
packages/dioc/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
141
packages/dioc/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# dioc
|
||||
|
||||
A small and lightweight dependency injection / inversion of control system.
|
||||
|
||||
### About
|
||||
|
||||
`dioc` is a really simple **DI/IOC** system where you write services (which are singletons per container) that can depend on each other and emit events that can be listened upon.
|
||||
|
||||
### Demo
|
||||
|
||||
```ts
|
||||
import { Service, Container } from "dioc"
|
||||
|
||||
// Here is a simple service, which you can define by extending the Service class
|
||||
// and providing an ID static field (of type string)
|
||||
export class PersistenceService extends Service {
|
||||
// This should be unique for each container
|
||||
public static ID = "PERSISTENCE_SERVICE"
|
||||
|
||||
public read(key: string): string | undefined {
|
||||
// ...
|
||||
}
|
||||
|
||||
public write(key: string, value: string) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
type TodoServiceEvent =
|
||||
| { type: "TODO_CREATED"; index: number }
|
||||
| { type: "TODO_DELETED"; index: number }
|
||||
|
||||
// Services have a built in event system
|
||||
// Define the generic argument to say what are the possible emitted values
|
||||
export class TodoService extends Service<TodoServiceEvent> {
|
||||
public static ID = "TODO_SERVICE"
|
||||
|
||||
// Inject persistence service into this service
|
||||
private readonly persistence = this.bind(PersistenceService)
|
||||
|
||||
public todos = []
|
||||
|
||||
// Service constructors cannot have arguments
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.todos = JSON.parse(this.persistence.read("todos") ?? "[]")
|
||||
}
|
||||
|
||||
public addTodo(text: string) {
|
||||
// ...
|
||||
|
||||
// You can access services via the bound fields
|
||||
this.persistence.write("todos", JSON.stringify(this.todos))
|
||||
|
||||
// This is how you emit an event
|
||||
this.emit({
|
||||
type: "TODO_CREATED",
|
||||
index,
|
||||
})
|
||||
}
|
||||
|
||||
public removeTodo(index: number) {
|
||||
// ...
|
||||
|
||||
this.emit({
|
||||
type: "TODO_DELETED",
|
||||
index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Services need a container to run in
|
||||
const container = new Container()
|
||||
|
||||
// You can initialize and get services using Container#bind
|
||||
// It will automatically initialize the service (and its dependencies)
|
||||
const todoService = container.bind(TodoService) // Returns an instance of TodoService
|
||||
```
|
||||
|
||||
### Demo (Unit Test)
|
||||
|
||||
`dioc/testing` contains `TestContainer` which lets you bind mocked services to the container.
|
||||
|
||||
```ts
|
||||
import { TestContainer } from "dioc/testing"
|
||||
import { TodoService, PersistenceService } from "./demo.ts" // The above demo code snippet
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
|
||||
describe("TodoService", () => {
|
||||
it("addTodo writes to persistence", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const writeFn = vi.fn()
|
||||
|
||||
// The first parameter is the service to mock and the second parameter
|
||||
// is the mocked service fields and functions
|
||||
container.bindMock(PersistenceService, {
|
||||
read: () => undefined, // Not really important for this test
|
||||
write: writeFn,
|
||||
})
|
||||
|
||||
// the peristence service bind in TodoService will now use the
|
||||
// above defined mocked implementation
|
||||
const todoService = container.bind(TodoService)
|
||||
|
||||
todoService.addTodo("sup")
|
||||
|
||||
expect(writeFn).toHaveBeenCalledOnce()
|
||||
expect(writeFn).toHaveBeenCalledWith("todos", JSON.stringify(["sup"]))
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Demo (Vue)
|
||||
|
||||
`dioc/vue` contains a Vue Plugin and a `useService` composable that allows Vue components to use the defined services.
|
||||
|
||||
In the app entry point:
|
||||
|
||||
```ts
|
||||
import { createApp } from "vue"
|
||||
import { diocPlugin } from "dioc/vue"
|
||||
|
||||
const app = createApp()
|
||||
|
||||
app.use(diocPlugin, {
|
||||
container: new Container(), // You can pass in the container you want to provide to the components here
|
||||
})
|
||||
```
|
||||
|
||||
In your Vue components:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { TodoService } from "./demo.ts" // The above demo
|
||||
import { useService } from "dioc/vue"
|
||||
|
||||
const todoService = useService(TodoService) // Returns an instance of the TodoService class
|
||||
</script>
|
||||
```
|
||||
2
packages/dioc/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./dist/main.d.ts"
|
||||
export * from "./dist/main.d.ts"
|
||||
147
packages/dioc/lib/container.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { Service } from "./service"
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
|
||||
/**
|
||||
* Stores the current container instance in the current operating context.
|
||||
*
|
||||
* NOTE: This should not be used outside of dioc library code
|
||||
*/
|
||||
export let currentContainer: Container | null = null
|
||||
|
||||
/**
|
||||
* The events emitted by the container
|
||||
*
|
||||
* `SERVICE_BIND` - emitted when a service is bound to the container directly or as a dependency to another service
|
||||
* `SERVICE_INIT` - emitted when a service is initialized
|
||||
*/
|
||||
export type ContainerEvent =
|
||||
| {
|
||||
type: 'SERVICE_BIND';
|
||||
|
||||
/** The Service ID of the service being bounded (the dependency) */
|
||||
boundeeID: string;
|
||||
|
||||
/**
|
||||
* The Service ID of the bounder that is binding the boundee (the dependent)
|
||||
*
|
||||
* NOTE: This will be undefined if the service is bound directly to the container
|
||||
*/
|
||||
bounderID: string | undefined
|
||||
}
|
||||
| {
|
||||
type: 'SERVICE_INIT';
|
||||
|
||||
/** The Service ID of the service being initialized */
|
||||
serviceID: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The dependency injection container, allows for services to be initialized and maintains the dependency trees.
|
||||
*/
|
||||
export class Container {
|
||||
/** Used during the `bind` operation to detect circular dependencies */
|
||||
private bindStack: string[] = []
|
||||
|
||||
/** The map of bound services to their IDs */
|
||||
protected boundMap = new Map<string, Service<unknown>>()
|
||||
|
||||
/** The RxJS observable representing the event stream */
|
||||
protected event$ = new Subject<ContainerEvent>()
|
||||
|
||||
/**
|
||||
* Returns whether a container has the given service bound
|
||||
* @param service The service to check for
|
||||
*/
|
||||
public hasBound<
|
||||
T extends typeof Service<any> & { ID: string }
|
||||
>(service: T): boolean {
|
||||
return this.boundMap.has(service.ID)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the service bound to the container with the given ID or if not found, undefined.
|
||||
*
|
||||
* NOTE: This is an advanced method and should not be used as much as possible.
|
||||
*
|
||||
* @param serviceID The ID of the service to get
|
||||
*/
|
||||
public getBoundServiceWithID(serviceID: string): Service<unknown> | undefined {
|
||||
return this.boundMap.get(serviceID)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a service to the container. This is equivalent to marking a service as a dependency.
|
||||
* @param service The class reference of a service to bind
|
||||
* @param bounder The class reference of the service that is binding the service (if bound directly to the container, this should be undefined)
|
||||
*/
|
||||
public bind<T extends typeof Service<any> & { ID: string }>(
|
||||
service: T,
|
||||
bounder: ((typeof Service<T>) & { ID: string }) | undefined = undefined
|
||||
): InstanceType<T> {
|
||||
// We need to store the current container in a variable so that we can restore it after the bind operation
|
||||
const oldCurrentContainer = currentContainer;
|
||||
currentContainer = this;
|
||||
|
||||
// If the service is already bound, return the existing instance
|
||||
if (this.hasBound(service)) {
|
||||
this.event$.next({
|
||||
type: 'SERVICE_BIND',
|
||||
boundeeID: service.ID,
|
||||
bounderID: bounder?.ID // Return the bounder ID if it is defined, else assume its the container
|
||||
})
|
||||
|
||||
return this.boundMap.get(service.ID) as InstanceType<T> // Casted as InstanceType<T> because service IDs and types are expected to match
|
||||
}
|
||||
|
||||
// Detect circular dependency and throw error
|
||||
if (this.bindStack.findIndex((serviceID) => serviceID === service.ID) !== -1) {
|
||||
const circularServices = `${this.bindStack.join(' -> ')} -> ${service.ID}`
|
||||
|
||||
throw new Error(`Circular dependency detected.\nChain: ${circularServices}`)
|
||||
}
|
||||
|
||||
// Push the service ID onto the bind stack to detect circular dependencies
|
||||
this.bindStack.push(service.ID)
|
||||
|
||||
// Initialize the service and emit events
|
||||
|
||||
// NOTE: We need to cast the service to any as TypeScript thinks that the service is abstract
|
||||
const instance: Service<any> = new (service as any)()
|
||||
|
||||
this.boundMap.set(service.ID, instance)
|
||||
|
||||
this.bindStack.pop()
|
||||
|
||||
this.event$.next({
|
||||
type: 'SERVICE_INIT',
|
||||
serviceID: service.ID,
|
||||
})
|
||||
|
||||
this.event$.next({
|
||||
type: 'SERVICE_BIND',
|
||||
boundeeID: service.ID,
|
||||
bounderID: bounder?.ID
|
||||
})
|
||||
|
||||
|
||||
// Restore the current container
|
||||
currentContainer = oldCurrentContainer;
|
||||
|
||||
// We expect the return type to match the service definition
|
||||
return instance as InstanceType<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of the currently bound service IDs and their instances
|
||||
*/
|
||||
public getBoundServices(): IterableIterator<[string, Service<any>]> {
|
||||
return this.boundMap.entries()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public container event stream
|
||||
*/
|
||||
public getEventStream(): Observable<ContainerEvent> {
|
||||
return this.event$.asObservable()
|
||||
}
|
||||
}
|
||||
2
packages/dioc/lib/main.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./container"
|
||||
export * from "./service"
|
||||
65
packages/dioc/lib/service.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { Container, currentContainer } from './container'
|
||||
|
||||
/**
|
||||
* A Dioc service that can bound to a container and can bind dependency services.
|
||||
*
|
||||
* NOTE: Services cannot have a constructor that takes arguments.
|
||||
*
|
||||
* @template EventDef The type of events that can be emitted by the service. These will be accessible by event streams
|
||||
*/
|
||||
export abstract class Service<EventDef = {}> {
|
||||
|
||||
/**
|
||||
* The internal event stream of the service
|
||||
*/
|
||||
private event$ = new Subject<EventDef>()
|
||||
|
||||
/** The container the service is bound to */
|
||||
#container: Container
|
||||
|
||||
constructor() {
|
||||
if (!currentContainer) {
|
||||
throw new Error(
|
||||
`Tried to initialize service with no container (ID: ${ (this.constructor as any).ID })`
|
||||
)
|
||||
}
|
||||
|
||||
this.#container = currentContainer
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a dependency service into this service.
|
||||
* @param service The class reference of the service to bind
|
||||
*/
|
||||
protected bind<T extends typeof Service<any> & { ID: string }>(service: T): InstanceType<T> {
|
||||
if (!currentContainer) {
|
||||
throw new Error('No currentContainer defined.')
|
||||
}
|
||||
|
||||
return currentContainer.bind(service, this.constructor as typeof Service<any> & { ID: string })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container the service is bound to
|
||||
*/
|
||||
protected getContainer(): Container {
|
||||
return this.#container
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event on the service's event stream
|
||||
* @param event The event to emit
|
||||
*/
|
||||
protected emit(event: EventDef) {
|
||||
this.event$.next(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event stream of the service
|
||||
*/
|
||||
public getEventStream(): Observable<EventDef> {
|
||||
|
||||
return this.event$.asObservable()
|
||||
}
|
||||
}
|
||||
33
packages/dioc/lib/testing.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Container, Service } from "./main";
|
||||
|
||||
/**
|
||||
* A container that can be used for writing tests, contains additional methods
|
||||
* for binding suitable for writing tests. (see `bindMock`).
|
||||
*/
|
||||
export class TestContainer extends Container {
|
||||
|
||||
/**
|
||||
* Binds a mock service to the container.
|
||||
*
|
||||
* @param service
|
||||
* @param mock
|
||||
*/
|
||||
public bindMock<
|
||||
T extends typeof Service<any> & { ID: string },
|
||||
U extends Partial<InstanceType<T>>
|
||||
>(service: T, mock: U): U {
|
||||
if (this.boundMap.has(service.ID)) {
|
||||
throw new Error(`Service '${service.ID}' already bound to container. Did you already call bindMock on this ?`)
|
||||
}
|
||||
|
||||
this.boundMap.set(service.ID, mock as any)
|
||||
|
||||
this.event$.next({
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: service.ID,
|
||||
bounderID: undefined,
|
||||
})
|
||||
|
||||
return mock
|
||||
}
|
||||
}
|
||||
34
packages/dioc/lib/vue.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Plugin, inject } from "vue"
|
||||
import { Container } from "./container"
|
||||
import { Service } from "./service"
|
||||
|
||||
const VUE_CONTAINER_KEY = Symbol()
|
||||
|
||||
// TODO: Some Vue version issue with plugin generics is breaking type checking
|
||||
/**
|
||||
* The Vue Dioc Plugin, this allows the composables to work and access the container
|
||||
*
|
||||
* NOTE: Make sure you add `vue` as dependency to be able to use this plugin (duh)
|
||||
*/
|
||||
export const diocPlugin: Plugin = {
|
||||
install(app, { container }) {
|
||||
app.provide(VUE_CONTAINER_KEY, container)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that binds a service to a Vue Component
|
||||
*
|
||||
* @param service The class reference of the service to bind
|
||||
*/
|
||||
export function useService<
|
||||
T extends typeof Service<any> & { ID: string }
|
||||
>(service: T): InstanceType<T> {
|
||||
const container = inject(VUE_CONTAINER_KEY) as Container | undefined | null
|
||||
|
||||
if (!container) {
|
||||
throw new Error("Container not found, did you forget to install the dioc plugin?")
|
||||
}
|
||||
|
||||
return container.bind(service)
|
||||
}
|
||||
54
packages/dioc/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "dioc",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist",
|
||||
"index.d.ts"
|
||||
],
|
||||
"main": "./dist/counter.umd.cjs",
|
||||
"module": "./dist/counter.js",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/main.d.ts",
|
||||
"require": "./dist/index.cjs",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./vue": {
|
||||
"types": "./dist/vue.d.ts",
|
||||
"require": "./dist/vue.cjs",
|
||||
"import": "./dist/vue.js"
|
||||
},
|
||||
"./testing": {
|
||||
"types": "./dist/testing.d.ts",
|
||||
"require": "./dist/testing.cjs",
|
||||
"import": "./dist/testing.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build && tsc --emitDeclarationOnly",
|
||||
"prepare": "pnpm run build",
|
||||
"test": "vitest run",
|
||||
"do-test": "pnpm run test",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.4",
|
||||
"vitest": "^0.29.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
262
packages/dioc/test/container.spec.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { it, expect, describe, vi } from "vitest"
|
||||
import { Service } from "../lib/service"
|
||||
import { Container, currentContainer, ContainerEvent } from "../lib/container"
|
||||
|
||||
class TestServiceA extends Service {
|
||||
public static ID = "TestServiceA"
|
||||
}
|
||||
|
||||
class TestServiceB extends Service {
|
||||
public static ID = "TestServiceB"
|
||||
|
||||
// Marked public to allow for testing
|
||||
public readonly serviceA = this.bind(TestServiceA)
|
||||
}
|
||||
|
||||
describe("Container", () => {
|
||||
describe("getBoundServiceWithID", () => {
|
||||
it("returns the service instance if it is bound to the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
const service = container.bind(TestServiceA)
|
||||
|
||||
expect(container.getBoundServiceWithID(TestServiceA.ID)).toBe(service)
|
||||
})
|
||||
|
||||
it("returns undefined if the service is not bound to the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
expect(container.getBoundServiceWithID(TestServiceA.ID)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("bind", () => {
|
||||
it("correctly binds the service to it", () => {
|
||||
const container = new Container()
|
||||
|
||||
const service = container.bind(TestServiceA)
|
||||
|
||||
// @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
|
||||
expect(service.getContainer()).toBe(container)
|
||||
})
|
||||
|
||||
it("after bind, the current container is set back to its previous value", () => {
|
||||
const originalValue = currentContainer
|
||||
|
||||
const container = new Container()
|
||||
container.bind(TestServiceA)
|
||||
|
||||
expect(currentContainer).toBe(originalValue)
|
||||
})
|
||||
|
||||
it("dependent services are registered in the same container", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
|
||||
// @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
|
||||
expect(serviceB.serviceA.getContainer()).toBe(container)
|
||||
})
|
||||
|
||||
it("binding an already initialized service returns the initialized instance (services are singletons)", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceA = container.bind(TestServiceA)
|
||||
const serviceA2 = container.bind(TestServiceA)
|
||||
|
||||
expect(serviceA).toBe(serviceA2)
|
||||
})
|
||||
|
||||
it("binding a service which is a dependency of another service returns the same instance created from the dependency resolution (services are singletons)", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
const serviceA = container.bind(TestServiceA)
|
||||
|
||||
expect(serviceB.serviceA).toBe(serviceA)
|
||||
})
|
||||
|
||||
it("binding an initialized service as a dependency returns the same instance", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceA = container.bind(TestServiceA)
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
|
||||
expect(serviceB.serviceA).toBe(serviceA)
|
||||
})
|
||||
|
||||
it("container emits an init event when an uninitialized service is initialized via bind and event only called once", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceFunc = vi.fn<
|
||||
[ContainerEvent & { type: "SERVICE_INIT" }],
|
||||
void
|
||||
>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
if (ev.type === "SERVICE_INIT") {
|
||||
serviceFunc(ev)
|
||||
}
|
||||
})
|
||||
|
||||
const instance = container.bind(TestServiceA)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledOnce()
|
||||
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
|
||||
type: "SERVICE_INIT",
|
||||
serviceID: TestServiceA.ID,
|
||||
})
|
||||
})
|
||||
|
||||
it("the bind event emitted has an undefined bounderID when the service is bound directly to the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceFunc = vi.fn<
|
||||
[ContainerEvent & { type: "SERVICE_BIND" }],
|
||||
void
|
||||
>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
if (ev.type === "SERVICE_BIND") {
|
||||
serviceFunc(ev)
|
||||
}
|
||||
})
|
||||
|
||||
container.bind(TestServiceA)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledOnce()
|
||||
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: TestServiceA.ID,
|
||||
bounderID: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("the bind event emitted has the correct bounderID when the service is bound to another service", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceFunc = vi.fn<
|
||||
[ContainerEvent & { type: "SERVICE_BIND" }],
|
||||
void
|
||||
>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
// We only care about the bind event of TestServiceA
|
||||
if (ev.type === "SERVICE_BIND" && ev.boundeeID === TestServiceA.ID) {
|
||||
serviceFunc(ev)
|
||||
}
|
||||
})
|
||||
|
||||
container.bind(TestServiceB)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledOnce()
|
||||
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: TestServiceA.ID,
|
||||
bounderID: TestServiceB.ID,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasBound", () => {
|
||||
it("returns true if the given service is bound to the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
container.bind(TestServiceA)
|
||||
|
||||
expect(container.hasBound(TestServiceA)).toEqual(true)
|
||||
})
|
||||
|
||||
it("returns false if the given service is not bound to the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
expect(container.hasBound(TestServiceA)).toEqual(false)
|
||||
})
|
||||
|
||||
it("returns true when the service is bound because it is a dependency of another service", () => {
|
||||
const container = new Container()
|
||||
|
||||
container.bind(TestServiceB)
|
||||
|
||||
expect(container.hasBound(TestServiceA)).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getEventStream", () => {
|
||||
it("returns an observable which emits events correctly when services are initialized", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceFunc = vi.fn<
|
||||
[ContainerEvent & { type: "SERVICE_INIT" }],
|
||||
void
|
||||
>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
if (ev.type === "SERVICE_INIT") {
|
||||
serviceFunc(ev)
|
||||
}
|
||||
})
|
||||
|
||||
container.bind(TestServiceB)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledTimes(2)
|
||||
expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
|
||||
type: "SERVICE_INIT",
|
||||
serviceID: TestServiceA.ID,
|
||||
})
|
||||
expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
|
||||
type: "SERVICE_INIT",
|
||||
serviceID: TestServiceB.ID,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns an observable which emits events correctly when services are bound", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceFunc = vi.fn<
|
||||
[ContainerEvent & { type: "SERVICE_BIND" }],
|
||||
void
|
||||
>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
if (ev.type === "SERVICE_BIND") {
|
||||
serviceFunc(ev)
|
||||
}
|
||||
})
|
||||
|
||||
container.bind(TestServiceB)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledTimes(2)
|
||||
expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: TestServiceA.ID,
|
||||
bounderID: TestServiceB.ID,
|
||||
})
|
||||
expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: TestServiceB.ID,
|
||||
bounderID: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getBoundServices", () => {
|
||||
it("returns an iterator over all services bound to the container in the format [service id, service instance]", () => {
|
||||
const container = new Container()
|
||||
|
||||
const instanceB = container.bind(TestServiceB)
|
||||
const instanceA = instanceB.serviceA
|
||||
|
||||
expect(Array.from(container.getBoundServices())).toEqual([
|
||||
[TestServiceA.ID, instanceA],
|
||||
[TestServiceB.ID, instanceB],
|
||||
])
|
||||
})
|
||||
|
||||
it("returns an empty iterator if no services are bound", () => {
|
||||
const container = new Container()
|
||||
|
||||
expect(Array.from(container.getBoundServices())).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
66
packages/dioc/test/service.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { Service, Container } from "../lib/main"
|
||||
|
||||
class TestServiceA extends Service {
|
||||
public static ID = "TestServiceA"
|
||||
}
|
||||
|
||||
class TestServiceB extends Service<"test"> {
|
||||
public static ID = "TestServiceB"
|
||||
|
||||
// Marked public to allow for testing
|
||||
public readonly serviceA = this.bind(TestServiceA)
|
||||
|
||||
public emitTestEvent() {
|
||||
this.emit("test")
|
||||
}
|
||||
}
|
||||
|
||||
describe("Service", () => {
|
||||
describe("constructor", () => {
|
||||
it("throws an error if the service is initialized without a container", () => {
|
||||
expect(() => new TestServiceA()).toThrowError(
|
||||
"Tried to initialize service with no container (ID: TestServiceA)"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("bind", () => {
|
||||
it("correctly binds the dependency service using the container", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceA = container.bind(TestServiceA)
|
||||
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
expect(serviceB.serviceA).toBe(serviceA)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getContainer", () => {
|
||||
it("returns the container the service is bound to", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceA = container.bind(TestServiceA)
|
||||
|
||||
// @ts-expect-error getContainer is a protected member, we are just using it to help with testing
|
||||
expect(serviceA.getContainer()).toBe(container)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getEventStream", () => {
|
||||
it("returns the valid event stream of the service", () => {
|
||||
const container = new Container()
|
||||
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
|
||||
const serviceFunc = vi.fn()
|
||||
|
||||
serviceB.getEventStream().subscribe(serviceFunc)
|
||||
|
||||
serviceB.emitTestEvent()
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledOnce()
|
||||
expect(serviceFunc).toHaveBeenCalledWith("test")
|
||||
})
|
||||
})
|
||||
})
|
||||
92
packages/dioc/test/test-container.spec.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { TestContainer } from "../lib/testing"
|
||||
import { Service } from "../lib/service"
|
||||
import { ContainerEvent } from "../lib/container"
|
||||
|
||||
class TestServiceA extends Service {
|
||||
public static ID = "TestServiceA"
|
||||
|
||||
public test() {
|
||||
return "real"
|
||||
}
|
||||
}
|
||||
|
||||
class TestServiceB extends Service {
|
||||
public static ID = "TestServiceB"
|
||||
|
||||
// declared public to help with testing
|
||||
public readonly serviceA = this.bind(TestServiceA)
|
||||
|
||||
public test() {
|
||||
return this.serviceA.test()
|
||||
}
|
||||
}
|
||||
|
||||
describe("TestContainer", () => {
|
||||
describe("bindMock", () => {
|
||||
it("returns the fake service defined", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const fakeService = {
|
||||
test: () => "fake",
|
||||
}
|
||||
|
||||
const result = container.bindMock(TestServiceA, fakeService)
|
||||
|
||||
expect(result).toBe(fakeService)
|
||||
})
|
||||
|
||||
it("new services bound to the container get the mock service", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const fakeServiceA = {
|
||||
test: () => "fake",
|
||||
}
|
||||
|
||||
container.bindMock(TestServiceA, fakeServiceA)
|
||||
|
||||
const serviceB = container.bind(TestServiceB)
|
||||
|
||||
expect(serviceB.serviceA).toBe(fakeServiceA)
|
||||
})
|
||||
|
||||
it("container emits SERVICE_BIND event", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const fakeServiceA = {
|
||||
test: () => "fake",
|
||||
}
|
||||
|
||||
const serviceFunc = vi.fn<[ContainerEvent, void]>()
|
||||
|
||||
container.getEventStream().subscribe((ev) => {
|
||||
serviceFunc(ev)
|
||||
})
|
||||
|
||||
container.bindMock(TestServiceA, fakeServiceA)
|
||||
|
||||
expect(serviceFunc).toHaveBeenCalledOnce()
|
||||
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
|
||||
type: "SERVICE_BIND",
|
||||
boundeeID: TestServiceA.ID,
|
||||
bounderID: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if service already bound", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const fakeServiceA = {
|
||||
test: () => "fake",
|
||||
}
|
||||
|
||||
container.bindMock(TestServiceA, fakeServiceA)
|
||||
|
||||
expect(() => {
|
||||
container.bindMock(TestServiceA, fakeServiceA)
|
||||
}).toThrowError(
|
||||
"Service 'TestServiceA' already bound to container. Did you already call bindMock on this ?"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
2
packages/dioc/testing.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./dist/testing.d.ts"
|
||||
export * from "./dist/testing.d.ts"
|
||||
21
packages/dioc/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["lib"]
|
||||
}
|
||||
16
packages/dioc/vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: {
|
||||
index: './lib/main.ts',
|
||||
vue: './lib/vue.ts',
|
||||
testing: './lib/testing.ts',
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue'],
|
||||
}
|
||||
},
|
||||
})
|
||||
7
packages/dioc/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
|
||||
}
|
||||
})
|
||||
2
packages/dioc/vue.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./dist/vue.d.ts"
|
||||
export * from "./dist/vue.d.ts"
|
||||
@@ -1,26 +0,0 @@
|
||||
# Google Analytics ID
|
||||
VITE_GA_ID=UA-61422507-4
|
||||
|
||||
# Google Tag Manager ID
|
||||
VITE_GTM_ID=GTM-NMKVBMV
|
||||
|
||||
# Firebase config
|
||||
VITE_API_KEY=AIzaSyCMsFreESs58-hRxTtiqQrIcimh4i1wbsM
|
||||
VITE_AUTH_DOMAIN=postwoman-api.firebaseapp.com
|
||||
VITE_DATABASE_URL=https://postwoman-api.firebaseio.com
|
||||
VITE_PROJECT_ID=postwoman-api
|
||||
VITE_STORAGE_BUCKET=postwoman-api.appspot.com
|
||||
VITE_MESSAGING_SENDER_ID=421993993223
|
||||
VITE_APP_ID=1:421993993223:web:ec0baa8ee8c02ffa1fc6a2
|
||||
VITE_MEASUREMENT_ID=G-BBJ3R80PJT
|
||||
|
||||
# Base URL
|
||||
VITE_BASE_URL=https://hoppscotch.io
|
||||
|
||||
# Backend URLs
|
||||
VITE_BACKEND_GQL_URL=https://api.hoppscotch.io/graphql
|
||||
VITE_BACKEND_WS_URL=wss://api.hoppscotch.io/graphql
|
||||
|
||||
# Sentry (Optional)
|
||||
# VITE_SENTRY_DSN: <Sentry DSN here>
|
||||
# VITE_SENTRY_ENVIRONMENT: <Sentry environment value here>
|
||||
@@ -1,13 +0,0 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77a5.44 5.44 0 00-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 504 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width=".88em" height="1em" viewBox="0 0 21 24" class="iconify iconify--fontisto"><path fill="currentColor" d="M12.731 2.751 17.666 5.6a2.138 2.138 0 1 1 2.07 3.548l-.015.003v5.7a2.14 2.14 0 1 1-2.098 3.502l-.002-.002-4.905 2.832a2.14 2.14 0 1 1-4.079.054l-.004.015-4.941-2.844a2.14 2.14 0 1 1-2.067-3.556l.015-.003V9.15a2.14 2.14 0 1 1 1.58-3.926l-.01-.005c.184.106.342.231.479.376l.001.001 4.938-2.85a2.14 2.14 0 1 1 4.096.021l.004-.015zm-.515.877a.766.766 0 0 1-.057.057l-.001.001 6.461 11.19c.026-.009.056-.016.082-.023V9.146a2.14 2.14 0 0 1-1.555-2.603l-.003.015.019-.072zm-3.015.059-.06-.06-4.946 2.852A2.137 2.137 0 0 1 2.749 9.12l-.015.004-.076.021v5.708l.084.023 6.461-11.19zm2.076.507a2.164 2.164 0 0 1-1.207-.004l.015.004-6.46 11.189c.286.276.496.629.597 1.026l.003.015h12.911c.102-.413.313-.768.599-1.043l.001-.001L11.28 4.194zm.986 16.227 4.917-2.838a1.748 1.748 0 0 1-.038-.142H4.222l-.021.083 4.939 2.852c.39-.403.936-.653 1.54-.653.626 0 1.189.268 1.581.696l.001.002z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,493 +0,0 @@
|
||||
* {
|
||||
@apply backface-hidden;
|
||||
@apply before: backface-hidden;
|
||||
@apply after: backface-hidden;
|
||||
@apply selection: bg-accentDark;
|
||||
@apply selection: text-accentContrast;
|
||||
}
|
||||
|
||||
:root {
|
||||
@apply antialiased;
|
||||
accent-color: var(--accent-color);
|
||||
font-variant-ligatures: common-ligatures;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
@apply border-solid border-l border-dividerLight border-t-0 border-b-0 border-r-0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-divider bg-clip-content;
|
||||
@apply rounded-full;
|
||||
@apply border-solid border-transparent border-4;
|
||||
@apply hover: bg-dividerDark;
|
||||
@apply hover: bg-clip-content;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@apply w-4;
|
||||
@apply h-0;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder,
|
||||
.cm-placeholder {
|
||||
@apply text-secondary;
|
||||
@apply opacity-35;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
@apply text-secondaryDark;
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-primary;
|
||||
@apply text-secondary text-body;
|
||||
@apply font-medium;
|
||||
@apply select-none;
|
||||
@apply overflow-x-hidden;
|
||||
@apply leading-body;
|
||||
animation: fade 300ms forwards;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
100% {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
@apply transition-opacity;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
@apply transition;
|
||||
@apply duration-300;
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
@apply transform;
|
||||
@apply translate-x-full;
|
||||
}
|
||||
|
||||
.svg-icons {
|
||||
@apply flex-shrink-0;
|
||||
@apply overflow-hidden;
|
||||
height: var(--line-height-body);
|
||||
width: var(--line-height-body);
|
||||
}
|
||||
|
||||
a {
|
||||
@apply inline-flex;
|
||||
@apply text-current;
|
||||
@apply no-underline;
|
||||
@apply transition;
|
||||
@apply leading-body;
|
||||
@apply focus: outline-none;
|
||||
|
||||
&.link {
|
||||
@apply items-center;
|
||||
@apply py-0.5 px-1;
|
||||
@apply -my-0.5 -mx-1;
|
||||
@apply text-accent;
|
||||
@apply rounded;
|
||||
@apply hover: text-accentDark;
|
||||
@apply focus-visible: ring;
|
||||
@apply focus-visible: ring-accent;
|
||||
@apply focus-visible: text-accentDark;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tooltip {
|
||||
.tippy-box {
|
||||
@apply fixed;
|
||||
@apply -mt-6;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-box[data-theme="tooltip"] {
|
||||
@apply bg-tooltip;
|
||||
@apply text-primary;
|
||||
@apply font-semibold;
|
||||
@apply py-1 px-2;
|
||||
@apply rounded;
|
||||
@apply truncate;
|
||||
@apply shadow;
|
||||
@apply leading-body;
|
||||
font-size: 86%;
|
||||
|
||||
xmp {
|
||||
@apply hidden;
|
||||
@apply font-sans;
|
||||
@apply bg-gray-500/45;
|
||||
@apply text-primaryLight;
|
||||
@apply rounded-sm;
|
||||
@apply px-1;
|
||||
@apply my-0 ml-1;
|
||||
@apply truncate;
|
||||
@apply sm: inline-flex;
|
||||
|
||||
&.expand-loop {
|
||||
@apply bg-error;
|
||||
@apply text-accentContrast;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-svg-arrow svg {
|
||||
@apply fill-tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-box[data-theme="popover"] {
|
||||
@apply flex flex-col;
|
||||
@apply max-h-46;
|
||||
@apply items-stretch;
|
||||
@apply overflow-y-auto;
|
||||
@apply bg-popover;
|
||||
@apply text-secondary text-body;
|
||||
@apply p-2;
|
||||
@apply shadow-lg;
|
||||
@apply leading-body;
|
||||
@apply border border-dividerDark;
|
||||
@apply focus: outline-none;
|
||||
|
||||
.tippy-svg-arrow svg {
|
||||
@apply fill-popover;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-content {
|
||||
@apply p-0;
|
||||
}
|
||||
|
||||
[data-v-tippy] {
|
||||
@apply flex flex-1;
|
||||
}
|
||||
|
||||
[interactive]>div {
|
||||
@apply flex flex-1;
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-b border-dividerLight;
|
||||
@apply my-2;
|
||||
}
|
||||
|
||||
.heading {
|
||||
@apply font-bold;
|
||||
@apply text-secondaryDark text-lg;
|
||||
@apply tracking-tight;
|
||||
}
|
||||
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
@apply flex;
|
||||
@apply w-full;
|
||||
@apply py-2 px-4;
|
||||
@apply bg-transparent;
|
||||
@apply rounded;
|
||||
@apply text-secondaryDark;
|
||||
@apply border border-divider;
|
||||
@apply focus-visible: border-dividerDark;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
@apply truncate;
|
||||
@apply transition;
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
@apply focus: outline-none;
|
||||
@apply disabled: cursor-not-allowed;
|
||||
}
|
||||
|
||||
.input[type="file"],
|
||||
.input[type="radio"],
|
||||
#installPWA {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.floating-input~label {
|
||||
@apply absolute;
|
||||
@apply px-2 py-0.5;
|
||||
@apply m-2;
|
||||
@apply rounded;
|
||||
@apply transition;
|
||||
@apply origin-top-left;
|
||||
}
|
||||
|
||||
.floating-input:focus-within~label,
|
||||
.floating-input:not(:placeholder-shown)~label {
|
||||
@apply bg-primary;
|
||||
@apply transform;
|
||||
@apply origin-top-left;
|
||||
@apply scale-75;
|
||||
@apply translate-x-1 -translate-y-4;
|
||||
}
|
||||
|
||||
.floating-input:focus-within~label {
|
||||
@apply text-secondaryDark;
|
||||
}
|
||||
|
||||
pre.ace_editor {
|
||||
@apply font-mono;
|
||||
@apply resize-none;
|
||||
@apply z-0;
|
||||
}
|
||||
|
||||
.select {
|
||||
@apply appearance-none;
|
||||
@apply cursor-pointer;
|
||||
|
||||
&::-ms-expand {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
@apply flex flex-1;
|
||||
@apply relative;
|
||||
@apply after: absolute;
|
||||
@apply after: flex;
|
||||
@apply after: inset-y-0;
|
||||
@apply after: items-center;
|
||||
@apply after: justify-center;
|
||||
@apply after: pointer-events-none;
|
||||
@apply after: font-icon;
|
||||
@apply after: text-secondaryLight;
|
||||
@apply after: right-3;
|
||||
@apply after: content-["\e313"];
|
||||
}
|
||||
|
||||
.info-response {
|
||||
@apply text-pink-500;
|
||||
}
|
||||
|
||||
.success-response {
|
||||
@apply text-green-500;
|
||||
}
|
||||
|
||||
.redir-response {
|
||||
@apply text-yellow-500;
|
||||
}
|
||||
|
||||
.cl-error-response {
|
||||
@apply text-red-500;
|
||||
}
|
||||
|
||||
.sv-error-response {
|
||||
@apply text-red-600;
|
||||
}
|
||||
|
||||
.missing-data-response {
|
||||
@apply text-secondaryLight;
|
||||
}
|
||||
|
||||
.toasted-container {
|
||||
@apply max-w-md;
|
||||
|
||||
.toasted {
|
||||
&.toasted-primary {
|
||||
@apply px-4 py-2;
|
||||
@apply bg-tooltip;
|
||||
@apply border-secondaryDark;
|
||||
@apply text-primary text-body;
|
||||
@apply justify-between;
|
||||
@apply shadow-lg;
|
||||
@apply font-semibold;
|
||||
@apply transition;
|
||||
@apply leading-body;
|
||||
@apply sm: rounded;
|
||||
@apply sm: border;
|
||||
|
||||
.action {
|
||||
@apply relative;
|
||||
@apply flex flex-shrink-0;
|
||||
@apply text-body;
|
||||
@apply px-4;
|
||||
@apply my-1;
|
||||
@apply ml-auto;
|
||||
@apply normal-case;
|
||||
@apply font-semibold;
|
||||
@apply leading-body;
|
||||
@apply tracking-normal;
|
||||
@apply last: ml-4;
|
||||
@apply sm: ml-8;
|
||||
@apply before: absolute;
|
||||
@apply before: bg-current;
|
||||
@apply before: opacity-10;
|
||||
@apply before: inset-0;
|
||||
@apply before: transition;
|
||||
@apply before: content-DEFAULT;
|
||||
@apply hover: no-underline;
|
||||
@apply hover: before:opacity-20;
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
@apply bg-accent;
|
||||
@apply text-accentContrast;
|
||||
@apply border-accentDark;
|
||||
}
|
||||
|
||||
&.error {
|
||||
@apply bg-red-200;
|
||||
@apply text-red-800;
|
||||
@apply border-red-400;
|
||||
}
|
||||
|
||||
&.success {
|
||||
@apply bg-green-200;
|
||||
@apply text-green-800;
|
||||
@apply border-green-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.smart-splitter .splitpanes__splitter {
|
||||
@apply relative;
|
||||
@apply bg-primaryLight;
|
||||
@apply before: absolute;
|
||||
@apply before: inset-0;
|
||||
@apply before: bg-accentLight;
|
||||
@apply before: opacity-0;
|
||||
@apply before: z-20;
|
||||
@apply before: transition;
|
||||
@apply before: content-DEFAULT;
|
||||
@apply after: absolute;
|
||||
@apply after: inset-0;
|
||||
@apply after: z-20;
|
||||
@apply after: transition;
|
||||
@apply after: flex;
|
||||
@apply after: items-center;
|
||||
@apply after: justify-center;
|
||||
@apply after: text-dividerDark;
|
||||
@apply after: font-icon;
|
||||
@apply hover: before:opacity-100;
|
||||
@apply hover: after:text-accentDark;
|
||||
}
|
||||
|
||||
.no-splitter .splitpanes__splitter {
|
||||
@apply relative;
|
||||
@apply bg-primaryLight;
|
||||
}
|
||||
|
||||
.smart-splitter.splitpanes--vertical>.splitpanes__splitter {
|
||||
@apply w-1;
|
||||
@apply before: -left-0.5;
|
||||
@apply before: -right-0.5;
|
||||
@apply before: h-full;
|
||||
@apply after: content-["\e5d4"];
|
||||
}
|
||||
|
||||
.smart-splitter.splitpanes--horizontal>.splitpanes__splitter {
|
||||
@apply h-1;
|
||||
@apply before: -top-0.5;
|
||||
@apply before: -bottom-0.5;
|
||||
@apply before: w-full;
|
||||
@apply after: content-["\e5d3"];
|
||||
}
|
||||
|
||||
.no-splitter.splitpanes--vertical>.splitpanes__splitter {
|
||||
@apply w-0.5;
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
.no-splitter.splitpanes--horizontal>.splitpanes__splitter {
|
||||
@apply h-0.5;
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
.cm-focused {
|
||||
@apply select-auto;
|
||||
@apply outline-none;
|
||||
|
||||
.cm-activeLine {
|
||||
@apply bg-primaryLight;
|
||||
}
|
||||
|
||||
.cm-activeLineGutter {
|
||||
@apply bg-primaryDark;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
.cm-line::selection {
|
||||
@apply bg-accentDark #{!important};
|
||||
@apply text-accentContrast #{!important};
|
||||
}
|
||||
|
||||
.cm-line ::selection {
|
||||
@apply bg-accentDark #{!important};
|
||||
@apply text-accentContrast #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-key {
|
||||
@apply inline-flex;
|
||||
@apply font-sans;
|
||||
@apply text-tiny;
|
||||
@apply bg-divider;
|
||||
@apply rounded;
|
||||
@apply ml-2;
|
||||
@apply px-1;
|
||||
@apply transition;
|
||||
}
|
||||
|
||||
.capitalize-first {
|
||||
@apply first-letter: capitalize;
|
||||
}
|
||||
|
||||
details {
|
||||
@apply select-none;
|
||||
}
|
||||
|
||||
details summary::-webkit-details-marker {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
details[open] summary .indicator {
|
||||
@apply transform;
|
||||
@apply rotate-90;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
main {
|
||||
margin-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
.env-highlight {
|
||||
* {
|
||||
@apply text-accentContrast;
|
||||
}
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
@apply bg-accent #{!important};
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
@mixin base-theme {
|
||||
--font-sans: "Inter", sans-serif;
|
||||
--font-mono: "Roboto Mono", monospace;
|
||||
--font-icon: "Material Icons";
|
||||
--font-size-tiny: calc(var(--font-size-body) - 0.062rem);
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
--primary-color: theme("colors.neutral.900");
|
||||
--primary-light-color: theme("colors.dark.600");
|
||||
--primary-dark-color: theme("colors.neutral.800");
|
||||
--primary-contrast-color: #161616;
|
||||
--secondary-color: theme("colors.neutral.400");
|
||||
--secondary-light-color: theme("colors.neutral.500");
|
||||
--secondary-dark-color: theme("colors.neutral.100");
|
||||
--divider-color: theme("colors.neutral.800");
|
||||
--divider-light-color: theme("colors.dark.500");
|
||||
--divider-dark-color: theme("colors.dark.300");
|
||||
--error-color: theme("colors.stone.800");
|
||||
--tooltip-color: theme("colors.neutral.100");
|
||||
--popover-color: theme("colors.dark.700");
|
||||
--editor-theme: "merbivore_soft";
|
||||
}
|
||||
|
||||
@mixin light-theme {
|
||||
--primary-color: theme("colors.white");
|
||||
--primary-light-color: theme("colors.neutral.50");
|
||||
--primary-dark-color: theme("colors.neutral.100");
|
||||
--primary-contrast-color: #fefefe;
|
||||
--secondary-color: theme("colors.neutral.500");
|
||||
--secondary-light-color: theme("colors.neutral.400");
|
||||
--secondary-dark-color: theme("colors.neutral.900");
|
||||
--divider-color: theme("colors.gray.100");
|
||||
--divider-light-color: theme("colors.neutral.100");
|
||||
--divider-dark-color: theme("colors.neutral.300");
|
||||
--error-color: theme("colors.yellow.100");
|
||||
--tooltip-color: theme("colors.neutral.800");
|
||||
--popover-color: theme("colors.white");
|
||||
--editor-theme: "textmate";
|
||||
}
|
||||
|
||||
@mixin black-theme {
|
||||
--primary-color: theme("colors.dark.900");
|
||||
--primary-light-color: theme("colors.neutral.900");
|
||||
--primary-dark-color: theme("colors.dark.800");
|
||||
--primary-contrast-color: #0e0e0e;
|
||||
--secondary-color: theme("colors.neutral.400");
|
||||
--secondary-light-color: theme("colors.neutral.500");
|
||||
--secondary-dark-color: theme("colors.neutral.100");
|
||||
--divider-color: theme("colors.neutral.800");
|
||||
--divider-light-color: theme("colors.dark.800");
|
||||
--divider-dark-color: theme("colors.dark.300");
|
||||
--error-color: theme("colors.stone.900");
|
||||
--tooltip-color: theme("colors.neutral.100");
|
||||
--popover-color: theme("colors.dark.600");
|
||||
--editor-theme: "twilight";
|
||||
}
|
||||
|
||||
@mixin dark-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.blue.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.fuchsia.400");
|
||||
--editor-constant-color: theme("colors.violet.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@mixin light-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.600");
|
||||
--editor-name-color: theme("colors.red.600");
|
||||
--editor-operator-color: theme("colors.indigo.600");
|
||||
--editor-invalid-color: theme("colors.red.600");
|
||||
--editor-separator-color: theme("colors.gray.600");
|
||||
--editor-meta-color: theme("colors.gray.600");
|
||||
--editor-variable-color: theme("colors.green.600");
|
||||
--editor-link-color: theme("colors.cyan.600");
|
||||
--editor-process-color: theme("colors.blue.600");
|
||||
--editor-constant-color: theme("colors.fuchsia.600");
|
||||
--editor-keyword-color: theme("colors.pink.600");
|
||||
}
|
||||
|
||||
@mixin black-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.fuchsia.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.violet.400");
|
||||
--editor-constant-color: theme("colors.blue.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@mixin green-theme {
|
||||
--accent-color: theme("colors.green.500");
|
||||
--accent-light-color: theme("colors.green.400");
|
||||
--accent-dark-color: theme("colors.green.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.green.200");
|
||||
--gradient-via-color: theme("colors.green.400");
|
||||
--gradient-to-color: theme("colors.green.600");
|
||||
}
|
||||
|
||||
@mixin teal-theme {
|
||||
--accent-color: theme("colors.teal.500");
|
||||
--accent-light-color: theme("colors.teal.400");
|
||||
--accent-dark-color: theme("colors.teal.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.teal.200");
|
||||
--gradient-via-color: theme("colors.teal.400");
|
||||
--gradient-to-color: theme("colors.teal.600");
|
||||
}
|
||||
|
||||
@mixin blue-theme {
|
||||
--accent-color: theme("colors.blue.500");
|
||||
--accent-light-color: theme("colors.blue.400");
|
||||
--accent-dark-color: theme("colors.blue.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.blue.200");
|
||||
--gradient-via-color: theme("colors.blue.400");
|
||||
--gradient-to-color: theme("colors.blue.600");
|
||||
}
|
||||
|
||||
@mixin indigo-theme {
|
||||
--accent-color: theme("colors.indigo.500");
|
||||
--accent-light-color: theme("colors.indigo.400");
|
||||
--accent-dark-color: theme("colors.indigo.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.indigo.200");
|
||||
--gradient-via-color: theme("colors.indigo.400");
|
||||
--gradient-to-color: theme("colors.indigo.600");
|
||||
}
|
||||
|
||||
@mixin purple-theme {
|
||||
--accent-color: theme("colors.purple.500");
|
||||
--accent-light-color: theme("colors.purple.400");
|
||||
--accent-dark-color: theme("colors.purple.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.purple.200");
|
||||
--gradient-via-color: theme("colors.purple.400");
|
||||
--gradient-to-color: theme("colors.purple.600");
|
||||
}
|
||||
|
||||
@mixin yellow-theme {
|
||||
--accent-color: theme("colors.yellow.500");
|
||||
--accent-light-color: theme("colors.yellow.400");
|
||||
--accent-dark-color: theme("colors.yellow.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.yellow.200");
|
||||
--gradient-via-color: theme("colors.yellow.400");
|
||||
--gradient-to-color: theme("colors.yellow.600");
|
||||
}
|
||||
|
||||
@mixin orange-theme {
|
||||
--accent-color: theme("colors.orange.500");
|
||||
--accent-light-color: theme("colors.orange.400");
|
||||
--accent-dark-color: theme("colors.orange.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.orange.200");
|
||||
--gradient-via-color: theme("colors.orange.400");
|
||||
--gradient-to-color: theme("colors.orange.600");
|
||||
}
|
||||
|
||||
@mixin red-theme {
|
||||
--accent-color: theme("colors.red.500");
|
||||
--accent-light-color: theme("colors.red.400");
|
||||
--accent-dark-color: theme("colors.red.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.red.200");
|
||||
--gradient-via-color: theme("colors.red.400");
|
||||
--gradient-to-color: theme("colors.red.600");
|
||||
}
|
||||
|
||||
@mixin pink-theme {
|
||||
--accent-color: theme("colors.pink.500");
|
||||
--accent-light-color: theme("colors.pink.400");
|
||||
--accent-dark-color: theme("colors.pink.600");
|
||||
--accent-contrast-color: theme("colors.white");
|
||||
--gradient-from-color: theme("colors.pink.200");
|
||||
--gradient-via-color: theme("colors.pink.400");
|
||||
--gradient-to-color: theme("colors.pink.600");
|
||||
}
|
||||
|
||||
:root {
|
||||
@include base-theme;
|
||||
@include dark-theme;
|
||||
@include green-theme;
|
||||
@include dark-editor-theme;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
@include light-theme;
|
||||
@include light-editor-theme;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
@include dark-theme;
|
||||
@include dark-editor-theme;
|
||||
}
|
||||
|
||||
:root.black {
|
||||
@include black-theme;
|
||||
@include black-editor-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="blue"] {
|
||||
@include blue-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="green"] {
|
||||
@include green-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="teal"] {
|
||||
@include teal-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="indigo"] {
|
||||
@include indigo-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="purple"] {
|
||||
@include purple-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="orange"] {
|
||||
@include orange-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="pink"] {
|
||||
@include pink-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="red"] {
|
||||
@include red-theme;
|
||||
}
|
||||
|
||||
:root[data-accent="yellow"] {
|
||||
@include yellow-theme;
|
||||
}
|
||||
|
||||
@mixin font-small {
|
||||
--font-size-body: 0.75rem;
|
||||
--line-height-body: 1rem;
|
||||
--upper-primary-sticky-fold: 4.125rem;
|
||||
--upper-secondary-sticky-fold: 6.188rem;
|
||||
--upper-tertiary-sticky-fold: 8.25rem;
|
||||
--upper-mobile-primary-sticky-fold: 6.625rem;
|
||||
--upper-mobile-secondary-sticky-fold: 8.688rem;
|
||||
--upper-mobile-sticky-fold: 10.75rem;
|
||||
--upper-mobile-tertiary-sticky-fold: 8.25rem;
|
||||
--lower-primary-sticky-fold: 3rem;
|
||||
--lower-secondary-sticky-fold: 5.063rem;
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
}
|
||||
|
||||
@mixin font-medium {
|
||||
--font-size-body: 0.875rem;
|
||||
--line-height-body: 1.25rem;
|
||||
--upper-primary-sticky-fold: 4.375rem;
|
||||
--upper-secondary-sticky-fold: 6.688rem;
|
||||
--upper-tertiary-sticky-fold: 9rem;
|
||||
--upper-mobile-primary-sticky-fold: 7.125rem;
|
||||
--upper-mobile-secondary-sticky-fold: 9.438rem;
|
||||
--upper-mobile-sticky-fold: 11.75rem;
|
||||
--upper-mobile-tertiary-sticky-fold: 9rem;
|
||||
--lower-primary-sticky-fold: 3.25rem;
|
||||
--lower-secondary-sticky-fold: 5.563rem;
|
||||
--lower-tertiary-sticky-fold: 7.875rem;
|
||||
--sidebar-primary-sticky-fold: 2.25rem;
|
||||
}
|
||||
|
||||
@mixin font-large {
|
||||
--font-size-body: 1rem;
|
||||
--line-height-body: 1.5rem;
|
||||
--upper-primary-sticky-fold: 4.625rem;
|
||||
--upper-secondary-sticky-fold: 7.188rem;
|
||||
--upper-tertiary-sticky-fold: 9.75rem;
|
||||
--upper-mobile-primary-sticky-fold: 7.625rem;
|
||||
--upper-mobile-secondary-sticky-fold: 10.188rem;
|
||||
--upper-mobile-sticky-fold: 12.75rem;
|
||||
--upper-mobile-tertiary-sticky-fold: 9.75rem;
|
||||
--lower-primary-sticky-fold: 3.5rem;
|
||||
--lower-secondary-sticky-fold: 6.063rem;
|
||||
--lower-tertiary-sticky-fold: 8.625rem;
|
||||
--sidebar-primary-sticky-fold: 2.5rem;
|
||||
}
|
||||
|
||||
:root[data-font-size="small"] {
|
||||
@include font-small;
|
||||
}
|
||||
|
||||
:root[data-font-size="medium"] {
|
||||
@include font-medium;
|
||||
}
|
||||
|
||||
:root[data-font-size="large"] {
|
||||
@include font-large;
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Cancel · lar",
|
||||
"choose_file": "Trieu un fitxer",
|
||||
"clear": "Clar",
|
||||
"clear_all": "Esborra-ho tot",
|
||||
"close": "Close",
|
||||
"connect": "Connecteu",
|
||||
"copy": "Copia",
|
||||
"delete": "Suprimeix",
|
||||
"disconnect": "Desconnecta",
|
||||
"dismiss": "Destitueix",
|
||||
"dont_save": "Don't save",
|
||||
"download_file": "Descarrega l'arxiu",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Edita",
|
||||
"filter_response": "Filter response",
|
||||
"go_back": "Torna",
|
||||
"label": "Etiqueta",
|
||||
"learn_more": "Aprèn més",
|
||||
"less": "Less",
|
||||
"more": "Més",
|
||||
"new": "Novetat",
|
||||
"no": "No",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Prettify",
|
||||
"remove": "Elimina",
|
||||
"restore": "Restaura",
|
||||
"save": "Desa",
|
||||
"scroll_to_bottom": "Scroll to bottom",
|
||||
"scroll_to_top": "Scroll to top",
|
||||
"search": "Cerca",
|
||||
"send": "Envia",
|
||||
"start": "Començar",
|
||||
"stop": "Atura",
|
||||
"to_close": "to close",
|
||||
"to_navigate": "to navigate",
|
||||
"to_select": "to select",
|
||||
"turn_off": "Tanca",
|
||||
"turn_on": "Encendre",
|
||||
"undo": "Desfés",
|
||||
"yes": "Sí"
|
||||
},
|
||||
"add": {
|
||||
"new": "Afegir nou",
|
||||
"star": "Afegeix una estrella"
|
||||
},
|
||||
"app": {
|
||||
"chat_with_us": "Xateja amb nosaltres",
|
||||
"contact_us": "Poseu-vos en contacte amb nosaltres",
|
||||
"copy": "Copia",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||
"discord": "Discord",
|
||||
"documentation": "Documentació",
|
||||
"github": "GitHub",
|
||||
"help": "Ajuda, comentaris i documentació",
|
||||
"home": "Inici",
|
||||
"invite": "Convidar",
|
||||
"invite_description": "A Hoppscotch, hem dissenyat una interfície senzilla i intuïtiva per crear i gestionar les vostres API. Hoppscotch és una eina que us ajuda a construir, provar, documentar i compartir les vostres API.",
|
||||
"invite_your_friends": "Convida els teus amics",
|
||||
"join_discord_community": "Uniu-vos a la nostra comunitat Discord",
|
||||
"keyboard_shortcuts": "Dreceres de teclat",
|
||||
"name": "Hoppscotch",
|
||||
"new_version_found": "S'ha trobat una nova versió. Actualitzeu per actualitzar.",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Política de privadesa del servidor intermediari",
|
||||
"reload": "Recarregar",
|
||||
"search": "Cerca",
|
||||
"share": "Compartir",
|
||||
"shortcuts": "Dreceres",
|
||||
"spotlight": "Destac",
|
||||
"status": "Estat",
|
||||
"status_description": "Check the status of the website",
|
||||
"terms_and_privacy": "Condicions i privadesa",
|
||||
"twitter": "Twitter",
|
||||
"type_a_command_search": "Escriviu una ordre o cerqueu ...",
|
||||
"we_use_cookies": "Utilitzem cookies",
|
||||
"whats_new": "Que hi ha de nou?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"auth": {
|
||||
"account_exists": "El compte existeix amb credencials diferents: inicieu la sessió per enllaçar els dos comptes",
|
||||
"all_sign_in_options": "Totes les opcions d'inici de sessió",
|
||||
"continue_with_email": "Continueu amb el correu electrònic",
|
||||
"continue_with_github": "Continueu amb GitHub",
|
||||
"continue_with_google": "Continueu amb Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"email": "Correu electrònic",
|
||||
"logged_out": "Desconnectat",
|
||||
"login": "iniciar Sessió",
|
||||
"login_success": "S'ha iniciat la sessió correctament",
|
||||
"login_to_hoppscotch": "Inicieu la sessió a Hoppscotch",
|
||||
"logout": "Tancar sessió",
|
||||
"re_enter_email": "Re-escriu el teu correu",
|
||||
"send_magic_link": "Envia un enllaç màgic",
|
||||
"sync": "Sincronitzar",
|
||||
"we_sent_magic_link": "Us hem enviat un enllaç màgic!",
|
||||
"we_sent_magic_link_description": "Comproveu la vostra safata d'entrada: hem enviat un correu electrònic a {email}. Conté un enllaç màgic que us permetrà iniciar la sessió."
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Generar testimoni",
|
||||
"include_in_url": "Inclou a l'URL",
|
||||
"learn": "Aprèn com",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Contrasenya",
|
||||
"token": "Testimoni",
|
||||
"type": "Tipus d'autorització",
|
||||
"username": "Nom d'usuari"
|
||||
},
|
||||
"collection": {
|
||||
"created": "S'ha creat la col·lecció",
|
||||
"edit": "Edita la col·lecció",
|
||||
"invalid_name": "Proporcioneu un nom vàlid per a la col·lecció",
|
||||
"my_collections": "Les meves col·leccions",
|
||||
"name": "La meva nova col·lecció",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "Nova col · lecció",
|
||||
"renamed": "S'ha canviat el nom de la col·lecció",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Guardar com",
|
||||
"select": "Seleccioneu una col·lecció",
|
||||
"select_location": "Seleccioneu la ubicació",
|
||||
"select_team": "Selecciona un equip",
|
||||
"team_collections": "Col·leccions per equips"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"logout": "Esteu segur que voleu tancar la sessió?",
|
||||
"remove_collection": "Esteu segur que voleu suprimir permanentment aquesta col·lecció?",
|
||||
"remove_environment": "Esteu segur que voleu suprimir permanentment aquest entorn?",
|
||||
"remove_folder": "Esteu segur que voleu suprimir definitivament aquesta carpeta?",
|
||||
"remove_history": "Esteu segur que voleu suprimir definitivament tot l'historial?",
|
||||
"remove_request": "Esteu segur que voleu suprimir definitivament aquesta sol·licitud?",
|
||||
"remove_team": "Esteu segur que voleu suprimir aquest equip?",
|
||||
"remove_telemetry": "Esteu segur que voleu desactivar Telemetry?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"sync": "Esteu segur que voleu sincronitzar aquest espai de treball?"
|
||||
},
|
||||
"count": {
|
||||
"header": "Capçalera {count}",
|
||||
"message": "{Compte} de missatges",
|
||||
"parameter": "Paràmetre {count}",
|
||||
"protocol": "Protocol {count}",
|
||||
"value": "Valor {count}",
|
||||
"variable": "Variable {count}"
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "Generar documentació",
|
||||
"generate_message": "Importeu qualsevol col·lecció Hoppscotch per generar documentació de l'API sobre la marxa."
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "Aquesta sol·licitud no utilitza cap autorització",
|
||||
"body": "Aquesta sol·licitud no té cap cos",
|
||||
"collection": "La col·lecció està buida",
|
||||
"collections": "Les col·leccions estan buides",
|
||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
||||
"endpoint": "Endpoint cannot be empty",
|
||||
"environments": "Els entorns estan buits",
|
||||
"folder": "La carpeta està buida",
|
||||
"headers": "Aquesta sol·licitud no té cap capçalera",
|
||||
"history": "La història és buida",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "L'equip està buit",
|
||||
"parameters": "Aquesta sol·licitud no té cap paràmetre",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"profile": "Login in to view your profile",
|
||||
"protocols": "Els protocols estan buits",
|
||||
"schema": "Connecteu-vos a un punt final GraphQL",
|
||||
"shortcodes": "Shortcodes are empty",
|
||||
"team_name": "El nom de l'equip és buit",
|
||||
"teams": "Els equips estan buits",
|
||||
"tests": "No hi ha proves per a aquesta sol·licitud"
|
||||
},
|
||||
"environment": {
|
||||
"add_to_global": "Add to Global",
|
||||
"added": "Environment addition",
|
||||
"create_new": "Crea un entorn nou",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Edita l'entorn",
|
||||
"invalid_name": "Proporcioneu un nom vàlid per a l'entorn",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "Nou entorn",
|
||||
"no_environment": "Sense entorn",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"select": "Seleccioneu un entorn",
|
||||
"title": "Entorns",
|
||||
"updated": "Environment updation",
|
||||
"variable_list": "Llista de variables"
|
||||
},
|
||||
"error": {
|
||||
"browser_support_sse": "Sembla que aquest navegador no és compatible amb els esdeveniments enviats per servidor.",
|
||||
"check_console_details": "Consulteu el registre de la consola per obtenir més informació.",
|
||||
"curl_invalid_format": "cURL no està formatat correctament",
|
||||
"empty_req_name": "Nom de la sol·licitud buida",
|
||||
"f12_details": "(F12 per obtenir més informació)",
|
||||
"gql_prettify_invalid_query": "No s'ha pogut definir una consulta no vàlida, resoldre els errors de sintaxi de la consulta i tornar-ho a provar",
|
||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"invalid_link": "Invalid link",
|
||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||
"json_parsing_failed": "Invalid JSON",
|
||||
"json_prettify_invalid_body": "No s'ha pogut personalitzar un cos no vàlid, resoldre errors de sintaxi json i tornar-ho a provar",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "No s'ha pogut enviar la sol·licitud",
|
||||
"no_duration": "Sense durada",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"script_fail": "No s'ha pogut executar l'script de sol·licitud prèvia",
|
||||
"something_went_wrong": "Alguna cosa ha anat malament",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Exporta com a JSON",
|
||||
"create_secret_gist": "Crea un resum secret",
|
||||
"gist_created": "Gist creat",
|
||||
"require_github": "Inicieu la sessió amb GitHub per crear un resum secret",
|
||||
"title": "Export"
|
||||
},
|
||||
"folder": {
|
||||
"created": "S'ha creat la carpeta",
|
||||
"edit": "Edita la carpeta",
|
||||
"invalid_name": "Proporcioneu un nom per a la carpeta",
|
||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
||||
"new": "Carpeta nova",
|
||||
"renamed": "S'ha canviat el nom de la carpeta"
|
||||
},
|
||||
"graphql": {
|
||||
"mutations": "Mutacions",
|
||||
"schema": "Esquema",
|
||||
"subscriptions": "Subscripcions"
|
||||
},
|
||||
"header": {
|
||||
"install_pwa": "Instal·la l'aplicació",
|
||||
"login": "iniciar Sessió",
|
||||
"save_workspace": "Desa el meu espai de treball"
|
||||
},
|
||||
"helpers": {
|
||||
"authorization": "La capçalera de l'autorització es generarà automàticament quan envieu la sol·licitud.",
|
||||
"generate_documentation_first": "Genereu documentació primer",
|
||||
"network_fail": "No es pot arribar al punt final de l'API. Comproveu la connexió de xarxa i torneu-ho a provar.",
|
||||
"offline": "Sembla que estàs fora de línia. És possible que les dades d'aquest espai de treball no estiguin actualitzades.",
|
||||
"offline_short": "Sembla que estàs fora de línia.",
|
||||
"post_request_tests": "Els scripts de prova s'escriuen en JavaScript i s'executen després de rebre la resposta.",
|
||||
"pre_request_script": "Els scripts de sol·licitud prèvia s'escriuen en JavaScript i s'executen abans que s'enviï la sol·licitud.",
|
||||
"script_fail": "Sembla que hi ha un error a l'script de sol·licitud prèvia. Comproveu l'error a continuació i solucioneu l'script en conseqüència.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Escriviu un script de prova per automatitzar la depuració."
|
||||
},
|
||||
"hide": {
|
||||
"collection": "Collapse Collection Panel",
|
||||
"more": "Amaga més",
|
||||
"preview": "Amaga la previsualització",
|
||||
"sidebar": "Amaga la barra lateral"
|
||||
},
|
||||
"import": {
|
||||
"collections": "Importar col·leccions",
|
||||
"curl": "Importa cURL",
|
||||
"failed": "La importació ha fallat",
|
||||
"from_gist": "Importa de Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Importa de Les meves col·leccions",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Introduïu l'URL Gist",
|
||||
"import_from_url_invalid_fetch": "Couldn't get data from the url",
|
||||
"import_from_url_invalid_file_format": "Error while importing collections",
|
||||
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||
"import_from_url_success": "Collections Imported",
|
||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
||||
"title": "Importació"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "Collapse or Expand Collections",
|
||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
||||
"column": "Vertical layout",
|
||||
"name": "Layout",
|
||||
"row": "Horizontal layout",
|
||||
"zen_mode": "Mode Zen"
|
||||
},
|
||||
"modal": {
|
||||
"collections": "Col·leccions",
|
||||
"confirm": "Confirmeu",
|
||||
"edit_request": "Sol·licitud d'edició",
|
||||
"import_export": "Importació-exportació"
|
||||
},
|
||||
"mqtt": {
|
||||
"communication": "Comunicació",
|
||||
"log": "Registre",
|
||||
"message": "Missatge",
|
||||
"publish": "Publica",
|
||||
"subscribe": "Subscriu-te",
|
||||
"topic": "Tema",
|
||||
"topic_name": "Nom del tema",
|
||||
"topic_title": "Publicar / subscriure el tema",
|
||||
"unsubscribe": "Cancel·lar la subscripció",
|
||||
"url": "URL"
|
||||
},
|
||||
"navigation": {
|
||||
"doc": "Documents",
|
||||
"graphql": "GraphQL",
|
||||
"profile": "Profile",
|
||||
"realtime": "Temps real",
|
||||
"rest": "REST",
|
||||
"settings": "Configuració"
|
||||
},
|
||||
"preRequest": {
|
||||
"javascript_code": "Codi JavaScript",
|
||||
"learn": "Llegiu la documentació",
|
||||
"script": "Script de sol·licitud prèvia",
|
||||
"snippets": "Fragments"
|
||||
},
|
||||
"profile": {
|
||||
"app_settings": "App Settings",
|
||||
"editor": "Editor",
|
||||
"editor_description": "Editors can add, edit, and delete requests.",
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Elimina l'estrella"
|
||||
},
|
||||
"request": {
|
||||
"added": "S'ha afegit la sol·licitud",
|
||||
"authorization": "Autorització",
|
||||
"body": "Organisme de sol·licitud",
|
||||
"choose_language": "Tria l'idioma",
|
||||
"content_type": "Tipus de contingut",
|
||||
"content_type_titles": {
|
||||
"others": "Others",
|
||||
"structured": "Structured",
|
||||
"text": "Text"
|
||||
},
|
||||
"copy_link": "Copia l'enllaç",
|
||||
"duration": "Durada",
|
||||
"enter_curl": "Introduïu cURL",
|
||||
"generate_code": "Generar codi",
|
||||
"generated_code": "Codi generat",
|
||||
"header_list": "Llista de capçaleres",
|
||||
"invalid_name": "Proporcioneu un nom per a la sol·licitud",
|
||||
"method": "Mètode",
|
||||
"name": "Sol·licita el nom",
|
||||
"new": "New Request",
|
||||
"override": "Override",
|
||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
||||
"overriden": "Overridden",
|
||||
"parameter_list": "Paràmetres de consulta",
|
||||
"parameters": "Paràmetres",
|
||||
"path": "Camí",
|
||||
"payload": "Càrrega útil",
|
||||
"query": "Consulta",
|
||||
"raw_body": "Cos de sol·licitud en brut",
|
||||
"renamed": "S'ha canviat el nom de la sol·licitud",
|
||||
"run": "Correr",
|
||||
"save": "Desa",
|
||||
"save_as": "Guardar com",
|
||||
"saved": "S'ha desat la sol·licitud",
|
||||
"share": "Compartir",
|
||||
"share_description": "Share Hoppscotch with your friends",
|
||||
"title": "Sol·licitud",
|
||||
"type": "Tipus de sol·licitud",
|
||||
"url": "URL",
|
||||
"variables": "Les variables",
|
||||
"view_my_links": "View my links"
|
||||
},
|
||||
"response": {
|
||||
"body": "Cos de resposta",
|
||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||
"headers": "Capçaleres",
|
||||
"html": "HTML",
|
||||
"image": "Imatge",
|
||||
"json": "JSON",
|
||||
"pdf": "PDF",
|
||||
"preview_html": "Previsualitza HTML",
|
||||
"raw": "Raw",
|
||||
"size": "Mida",
|
||||
"status": "Estat",
|
||||
"time": "Temps",
|
||||
"title": "Resposta",
|
||||
"waiting_for_connection": "esperant la connexió",
|
||||
"xml": "XML"
|
||||
},
|
||||
"settings": {
|
||||
"accent_color": "Color d'accent",
|
||||
"account": "Compte",
|
||||
"account_description": "Personalitzeu la configuració del compte.",
|
||||
"account_email_description": "La vostra adreça de correu electrònic principal.",
|
||||
"account_name_description": "Aquest és el vostre nom visible.",
|
||||
"background": "Antecedents",
|
||||
"black_mode": "Negre",
|
||||
"change_font_size": "Canvia la mida de la lletra",
|
||||
"choose_language": "Tria l'idioma",
|
||||
"dark_mode": "Fosc",
|
||||
"expand_navigation": "Expand navigation",
|
||||
"experiments": "Experiments",
|
||||
"experiments_notice": "Es tracta d'una col·lecció d'experiments en què estem treballant que poden resultar útils, divertits, o ambdós. No són finals i potser no són estables, de manera que si passa alguna cosa massa estrany, no us espanteu. Només cal que apagueu la cosa dang. Bromes a part,",
|
||||
"extension_ver_not_reported": "No s'ha informat",
|
||||
"extension_version": "Versió d'extensió",
|
||||
"extensions": "Extensions",
|
||||
"extensions_use_toggle": "Utilitzeu l'extensió del navegador per enviar sol·licituds (si n'hi ha)",
|
||||
"follow": "Follow Us",
|
||||
"font_size": "Mida de la font",
|
||||
"font_size_large": "Gran",
|
||||
"font_size_medium": "Mitjà",
|
||||
"font_size_small": "Petit",
|
||||
"interceptor": "Interceptor",
|
||||
"interceptor_description": "Middleware entre aplicació i API.",
|
||||
"language": "Llenguatge",
|
||||
"light_mode": "Llum",
|
||||
"official_proxy_hosting": "El servidor intermediari oficial està allotjat per Hoppscotch.",
|
||||
"profile": "Profile",
|
||||
"profile_description": "Update your profile details",
|
||||
"profile_email": "Email address",
|
||||
"profile_name": "Profile name",
|
||||
"proxy": "Servidor intermediari",
|
||||
"proxy_url": "URL del servidor intermediari",
|
||||
"proxy_use_toggle": "Utilitzeu el middleware del servidor intermediari per enviar sol·licituds",
|
||||
"read_the": "Llegir el",
|
||||
"reset_default": "Restableix el valor predeterminat",
|
||||
"short_codes": "Short codes",
|
||||
"short_codes_description": "Short codes which were created by you.",
|
||||
"sidebar_on_left": "Sidebar on left",
|
||||
"sync": "Sincronitza",
|
||||
"sync_collections": "Col·leccions",
|
||||
"sync_description": "Aquesta configuració se sincronitza amb el núvol.",
|
||||
"sync_environments": "Entorns",
|
||||
"sync_history": "Història",
|
||||
"system_mode": "Sistema",
|
||||
"telemetry": "Telemetria",
|
||||
"telemetry_helps_us": "La telemetria ens ajuda a personalitzar les nostres operacions i oferir-vos la millor experiència.",
|
||||
"theme": "Tema",
|
||||
"theme_description": "Personalitzeu el tema de l'aplicació.",
|
||||
"use_experimental_url_bar": "Utilitzeu la barra d'URL experimental amb ressaltat de l'entorn",
|
||||
"user": "Usuari",
|
||||
"verified_email": "Verified email",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shortcodes": {
|
||||
"actions": "Actions",
|
||||
"created_on": "Created on",
|
||||
"deleted": "Shortcode deleted",
|
||||
"method": "Method",
|
||||
"not_found": "Shortcode not found",
|
||||
"short_code": "Short code",
|
||||
"url": "URL"
|
||||
},
|
||||
"shortcut": {
|
||||
"general": {
|
||||
"close_current_menu": "Tanca el menú actual",
|
||||
"command_menu": "Menú de cerca i ordres",
|
||||
"help_menu": "Menú d'ajuda",
|
||||
"show_all": "Dreceres de teclat",
|
||||
"title": "General"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Convidar gent a Hoppscotch",
|
||||
"title": "Divers"
|
||||
},
|
||||
"navigation": {
|
||||
"back": "Torneu a la pàgina anterior",
|
||||
"documentation": "Aneu a la pàgina Documentació",
|
||||
"forward": "Aneu a la pàgina següent",
|
||||
"graphql": "Aneu a la pàgina GraphQL",
|
||||
"profile": "Go to Profile page",
|
||||
"realtime": "Aneu a la pàgina en temps real",
|
||||
"rest": "Aneu a la pàgina REST",
|
||||
"settings": "Aneu a la pàgina Configuració",
|
||||
"title": "Navegació"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "Copia l'enllaç de la sol·licitud",
|
||||
"delete_method": "Seleccioneu el mètode DELETE",
|
||||
"get_method": "Seleccioneu el mètode GET",
|
||||
"head_method": "Seleccioneu el mètode HEAD",
|
||||
"method": "Mètode",
|
||||
"next_method": "Seleccioneu Mètode següent",
|
||||
"post_method": "Seleccioneu el mètode POST",
|
||||
"previous_method": "Seleccioneu el mètode anterior",
|
||||
"put_method": "Seleccioneu el mètode PUT",
|
||||
"reset_request": "Sol·licitud de restabliment",
|
||||
"save_to_collections": "Desa a les col·leccions",
|
||||
"send_request": "Enviar sol.licitud",
|
||||
"title": "Sol·licitud"
|
||||
},
|
||||
"theme": {
|
||||
"black": "Switch theme to black mode",
|
||||
"dark": "Switch theme to dark mode",
|
||||
"light": "Switch theme to light mode",
|
||||
"system": "Switch theme to system mode",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"code": "Mostra el codi",
|
||||
"collection": "Expand Collection Panel",
|
||||
"more": "Mostra més",
|
||||
"sidebar": "Mostra la barra lateral"
|
||||
},
|
||||
"socketio": {
|
||||
"communication": "Comunicació",
|
||||
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||
"event_name": "Nom de l'esdeveniment",
|
||||
"events": "Esdeveniments",
|
||||
"log": "Registre",
|
||||
"url": "URL"
|
||||
},
|
||||
"sse": {
|
||||
"event_type": "Tipus d'esdeveniment",
|
||||
"log": "Registre",
|
||||
"url": "URL"
|
||||
},
|
||||
"state": {
|
||||
"bulk_mode": "Edició massiva",
|
||||
"bulk_mode_placeholder": "Les entrades estan separades per una nova línia\nLes claus i els valors estan separats per:\nPrepara # a qualsevol fila que vulguis afegir, però continua desactivat",
|
||||
"cleared": "Esborrat",
|
||||
"connected": "Connectat",
|
||||
"connected_to": "Connectat a {name}",
|
||||
"connecting_to": "S'està connectant a {name} ...",
|
||||
"connection_error": "Failed to connect",
|
||||
"connection_failed": "Connection failed",
|
||||
"connection_lost": "Connection lost",
|
||||
"copied_to_clipboard": "Copiat al porta-retalls",
|
||||
"deleted": "Suprimit",
|
||||
"deprecated": "DEPRECAT",
|
||||
"disabled": "Desactivat",
|
||||
"disconnected": "Desconnectat",
|
||||
"disconnected_from": "Desconnectat de {name}",
|
||||
"docs_generated": "Documentació generada",
|
||||
"download_started": "S'ha iniciat la baixada",
|
||||
"enabled": "Activat",
|
||||
"file_imported": "Fitxer importat",
|
||||
"finished_in": "Acabat en {duration} ms",
|
||||
"history_deleted": "S'ha suprimit l'historial",
|
||||
"linewrap": "Línies d'embolcall",
|
||||
"loading": "S'està carregant ...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"none": "Cap",
|
||||
"nothing_found": "No s'ha trobat res",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||
"waiting_send_request": "S'està esperant l'enviament de la sol·licitud"
|
||||
},
|
||||
"support": {
|
||||
"changelog": "Llegiu més sobre les darreres versions",
|
||||
"chat": "Tens preguntes? Xateja amb nosaltres!",
|
||||
"community": "Feu preguntes i ajudeu els altres",
|
||||
"documentation": "Llegiu més sobre Hoppscotch",
|
||||
"forum": "Feu preguntes i obteniu respostes",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Navega per l'aplicació més ràpidament",
|
||||
"team": "Poseu-vos en contacte amb l'equip",
|
||||
"title": "Suport",
|
||||
"twitter": "segueix-nos a Twitter"
|
||||
},
|
||||
"tab": {
|
||||
"authorization": "Autorització",
|
||||
"body": "Cos",
|
||||
"collections": "Col·leccions",
|
||||
"documentation": "Documentació",
|
||||
"headers": "Capçaleres",
|
||||
"history": "Història",
|
||||
"mqtt": "MQTT",
|
||||
"parameters": "Paràmetres",
|
||||
"pre_request_script": "Script de sol·licitud prèvia",
|
||||
"queries": "Consultes",
|
||||
"query": "Consulta",
|
||||
"schema": "Schema",
|
||||
"socketio": "Socket.IO",
|
||||
"sse": "SSE",
|
||||
"tests": "Proves",
|
||||
"types": "Tipus",
|
||||
"variables": "Les variables",
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Crea un equip nou",
|
||||
"deleted": "L'equip s'ha suprimit",
|
||||
"edit": "Edita l'equip",
|
||||
"email": "Correu electrònic",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Sortiu de l'equip",
|
||||
"exit_disabled": "L'únic propietari no pot sortir de l'equip",
|
||||
"invalid_email_format": "El format del correu electrònic no és vàlid",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Proporcioneu un permís vàlid al membre de l'equip",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Uniu-vos al programa beta per accedir als equips.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "Vas deixar l'equip",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_removed": "S'ha eliminat l'usuari",
|
||||
"member_role_updated": "Rols d'usuari actualitzats",
|
||||
"members": "Membres",
|
||||
"name_length_insufficient": "El nom de l'equip ha de tenir com a mínim 6 caràcters",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "Nou equip",
|
||||
"new_created": "S'ha creat un nou equip",
|
||||
"new_name": "El meu nou equip",
|
||||
"no_access": "No teniu accés d'edició a aquestes col·leccions",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Permisos",
|
||||
"saved": "L'equip s'ha desat",
|
||||
"select_a_team": "Select a team",
|
||||
"title": "Equips",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
},
|
||||
"test": {
|
||||
"failed": "test failed",
|
||||
"javascript_code": "Codi JavaScript",
|
||||
"learn": "Llegiu la documentació",
|
||||
"passed": "test passed",
|
||||
"report": "Informe de prova",
|
||||
"results": "Resultats de l'exàmen",
|
||||
"script": "Guió",
|
||||
"snippets": "Fragments"
|
||||
},
|
||||
"websocket": {
|
||||
"communication": "Comunicació",
|
||||
"log": "Registre",
|
||||
"message": "Missatge",
|
||||
"protocols": "Protocols",
|
||||
"url": "URL"
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Cancel",
|
||||
"choose_file": "Choose a file",
|
||||
"clear": "Clear",
|
||||
"clear_all": "Clear all",
|
||||
"close": "Close",
|
||||
"connect": "Connect",
|
||||
"copy": "Copy",
|
||||
"delete": "Delete",
|
||||
"disconnect": "Disconnect",
|
||||
"dismiss": "Dismiss",
|
||||
"dont_save": "Don't save",
|
||||
"download_file": "Download file",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Edit",
|
||||
"filter_response": "Filter response",
|
||||
"go_back": "Go back",
|
||||
"label": "Label",
|
||||
"learn_more": "Learn more",
|
||||
"less": "Less",
|
||||
"more": "More",
|
||||
"new": "New",
|
||||
"no": "No",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Prettify",
|
||||
"remove": "Remove",
|
||||
"restore": "Restore",
|
||||
"save": "Save",
|
||||
"scroll_to_bottom": "Scroll to bottom",
|
||||
"scroll_to_top": "Scroll to top",
|
||||
"search": "Search",
|
||||
"send": "Send",
|
||||
"start": "Start",
|
||||
"stop": "Stop",
|
||||
"to_close": "to close",
|
||||
"to_navigate": "to navigate",
|
||||
"to_select": "to select",
|
||||
"turn_off": "Turn off",
|
||||
"turn_on": "Turn on",
|
||||
"undo": "Undo",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"add": {
|
||||
"new": "Add new",
|
||||
"star": "Add star"
|
||||
},
|
||||
"app": {
|
||||
"chat_with_us": "Chat with us",
|
||||
"contact_us": "Contact us",
|
||||
"copy": "Copy",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||
"discord": "Discord",
|
||||
"documentation": "Documentation",
|
||||
"github": "GitHub",
|
||||
"help": "Help & feedback",
|
||||
"home": "Home",
|
||||
"invite": "Invite",
|
||||
"invite_description": "Hoppscotch is an open source API development ecosystem. We designed a simple and intuitive interface for creating and managing your APIs. Hoppscotch is a tool that helps you build, test, document and share your APIs.",
|
||||
"invite_your_friends": "Invite your friends",
|
||||
"join_discord_community": "Join our Discord community",
|
||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"name": "Hoppscotch",
|
||||
"new_version_found": "New version found. Refresh to update.",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Proxy privacy policy",
|
||||
"reload": "Reload",
|
||||
"search": "Search",
|
||||
"share": "Share",
|
||||
"shortcuts": "Shortcuts",
|
||||
"spotlight": "Spotlight",
|
||||
"status": "Status",
|
||||
"status_description": "Check the status of the website",
|
||||
"terms_and_privacy": "Terms and privacy",
|
||||
"twitter": "Twitter",
|
||||
"type_a_command_search": "Type a command or search…",
|
||||
"we_use_cookies": "We use cookies",
|
||||
"whats_new": "What's new?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"auth": {
|
||||
"account_exists": "Account exists with different credential - Login to link both accounts",
|
||||
"all_sign_in_options": "All sign in options",
|
||||
"continue_with_email": "Continue with Email",
|
||||
"continue_with_github": "Continue with GitHub",
|
||||
"continue_with_google": "Continue with Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"email": "Email",
|
||||
"logged_out": "Logged out",
|
||||
"login": "Login",
|
||||
"login_success": "Successfully logged in",
|
||||
"login_to_hoppscotch": "Login to Hoppscotch",
|
||||
"logout": "Logout",
|
||||
"re_enter_email": "Re-enter email",
|
||||
"send_magic_link": "Send a magic link",
|
||||
"sync": "Sync",
|
||||
"we_sent_magic_link": "We sent you a magic link!",
|
||||
"we_sent_magic_link_description": "Check your inbox - we sent an email to {email}. It contains a magic link that will log you in."
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Generate Token",
|
||||
"include_in_url": "Include in URL",
|
||||
"learn": "Learn how",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Password",
|
||||
"token": "Token",
|
||||
"type": "Authorization Type",
|
||||
"username": "Username"
|
||||
},
|
||||
"collection": {
|
||||
"created": "Collection created",
|
||||
"edit": "Edit Collection",
|
||||
"invalid_name": "Please provide a name for the collection",
|
||||
"my_collections": "My Collections",
|
||||
"name": "My New Collection",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "New Collection",
|
||||
"renamed": "Collection renamed",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Save as",
|
||||
"select": "Select a Collection",
|
||||
"select_location": "Select location",
|
||||
"select_team": "Select a team",
|
||||
"team_collections": "Team Collections"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"logout": "Are you sure you want to logout?",
|
||||
"remove_collection": "Are you sure you want to permanently delete this collection?",
|
||||
"remove_environment": "Are you sure you want to permanently delete this environment?",
|
||||
"remove_folder": "Are you sure you want to permanently delete this folder?",
|
||||
"remove_history": "Are you sure you want to permanently delete all history?",
|
||||
"remove_request": "Are you sure you want to permanently delete this request?",
|
||||
"remove_team": "Are you sure you want to delete this team?",
|
||||
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"sync": "Would you like to restore your workspace from cloud? This will discard your local progress."
|
||||
},
|
||||
"count": {
|
||||
"header": "Header {count}",
|
||||
"message": "Message {count}",
|
||||
"parameter": "Parameter {count}",
|
||||
"protocol": "Protocol {count}",
|
||||
"value": "Value {count}",
|
||||
"variable": "Variable {count}"
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "Generate documentation",
|
||||
"generate_message": "Import any Hoppscotch collection to generate API documentation on-the-go."
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "This request does not use any authorization",
|
||||
"body": "This request does not have a body",
|
||||
"collection": "Collection is empty",
|
||||
"collections": "Collections are empty",
|
||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
||||
"endpoint": "Endpoint cannot be empty",
|
||||
"environments": "Environments are empty",
|
||||
"folder": "Folder is empty",
|
||||
"headers": "This request does not have any headers",
|
||||
"history": "History is empty",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Team is empty",
|
||||
"parameters": "This request does not have any parameters",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"profile": "Login in to view your profile",
|
||||
"protocols": "Protocols are empty",
|
||||
"schema": "Connect to a GraphQL endpoint to view schema",
|
||||
"shortcodes": "Shortcodes are empty",
|
||||
"team_name": "Team name empty",
|
||||
"teams": "You don't belong to any teams",
|
||||
"tests": "There are no tests for this request"
|
||||
},
|
||||
"environment": {
|
||||
"add_to_global": "Add to Global",
|
||||
"added": "Environment addition",
|
||||
"create_new": "Create new environment",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Edit Environment",
|
||||
"invalid_name": "Please provide a name for the environment",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "New Environment",
|
||||
"no_environment": "No environment",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"select": "Select environment",
|
||||
"title": "Environments",
|
||||
"updated": "Environment updated",
|
||||
"variable_list": "Variable List"
|
||||
},
|
||||
"error": {
|
||||
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
||||
"check_console_details": "Check console log for details.",
|
||||
"curl_invalid_format": "cURL is not formatted properly",
|
||||
"empty_req_name": "Empty Request Name",
|
||||
"f12_details": "(F12 for details)",
|
||||
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"invalid_link": "Invalid link",
|
||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||
"json_parsing_failed": "Invalid JSON",
|
||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "Could not send request",
|
||||
"no_duration": "No duration",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"script_fail": "Could not execute pre-request script",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Export as JSON",
|
||||
"create_secret_gist": "Create secret Gist",
|
||||
"gist_created": "Gist created",
|
||||
"require_github": "Login with GitHub to create secret gist",
|
||||
"title": "Export"
|
||||
},
|
||||
"folder": {
|
||||
"created": "Folder created",
|
||||
"edit": "Edit Folder",
|
||||
"invalid_name": "Please provide a name for the folder",
|
||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
||||
"new": "New Folder",
|
||||
"renamed": "Folder renamed"
|
||||
},
|
||||
"graphql": {
|
||||
"mutations": "Mutations",
|
||||
"schema": "Schema",
|
||||
"subscriptions": "Subscriptions"
|
||||
},
|
||||
"header": {
|
||||
"install_pwa": "Install app",
|
||||
"login": "Login",
|
||||
"save_workspace": "Save My Workspace"
|
||||
},
|
||||
"helpers": {
|
||||
"authorization": "The authorization header will be automatically generated when you send the request.",
|
||||
"generate_documentation_first": "Generate documentation first",
|
||||
"network_fail": "Unable to reach the API endpoint. Check your network connection or select a different Interceptor and try again.",
|
||||
"offline": "You seem to be offline. Data in this workspace might not be up to date.",
|
||||
"offline_short": "You seem to be offline.",
|
||||
"post_request_tests": "Test scripts are written in JavaScript, and are run after the response is received.",
|
||||
"pre_request_script": "Pre-request scripts are written in JavaScript, and are run before the request is sent.",
|
||||
"script_fail": "It seems there is a glitch in the pre-request script. Check the error below and fix the script accordingly.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Write a test script to automate debugging."
|
||||
},
|
||||
"hide": {
|
||||
"collection": "Collapse Collection Panel",
|
||||
"more": "Hide more",
|
||||
"preview": "Hide Preview",
|
||||
"sidebar": "Collapse sidebar"
|
||||
},
|
||||
"import": {
|
||||
"collections": "Import collections",
|
||||
"curl": "Import cURL",
|
||||
"failed": "Error while importing: format not recognized",
|
||||
"from_gist": "Import from Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Import from My Collections",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Enter Gist URL",
|
||||
"import_from_url_invalid_fetch": "Couldn't get data from the url",
|
||||
"import_from_url_invalid_file_format": "Error while importing collections",
|
||||
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||
"import_from_url_success": "Collections Imported",
|
||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
||||
"title": "Import"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "Collapse or Expand Collections",
|
||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
||||
"column": "Vertical layout",
|
||||
"name": "Layout",
|
||||
"row": "Horizontal layout",
|
||||
"zen_mode": "Zen mode"
|
||||
},
|
||||
"modal": {
|
||||
"collections": "Collections",
|
||||
"confirm": "Confirm",
|
||||
"edit_request": "Edit Request",
|
||||
"import_export": "Import / Export"
|
||||
},
|
||||
"mqtt": {
|
||||
"communication": "Communication",
|
||||
"log": "Log",
|
||||
"message": "Message",
|
||||
"publish": "Publish",
|
||||
"subscribe": "Subscribe",
|
||||
"topic": "Topic",
|
||||
"topic_name": "Topic Name",
|
||||
"topic_title": "Publish / Subscribe topic",
|
||||
"unsubscribe": "Unsubscribe",
|
||||
"url": "URL"
|
||||
},
|
||||
"navigation": {
|
||||
"doc": "Docs",
|
||||
"graphql": "GraphQL",
|
||||
"profile": "Profile",
|
||||
"realtime": "Realtime",
|
||||
"rest": "REST",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"preRequest": {
|
||||
"javascript_code": "JavaScript Code",
|
||||
"learn": "Read documentation",
|
||||
"script": "Pre-Request Script",
|
||||
"snippets": "Snippets"
|
||||
},
|
||||
"profile": {
|
||||
"app_settings": "App Settings",
|
||||
"editor": "Editor",
|
||||
"editor_description": "Editors can add, edit, and delete requests.",
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Remove star"
|
||||
},
|
||||
"request": {
|
||||
"added": "Request added",
|
||||
"authorization": "Authorization",
|
||||
"body": "Request Body",
|
||||
"choose_language": "Choose language",
|
||||
"content_type": "Content Type",
|
||||
"content_type_titles": {
|
||||
"others": "Others",
|
||||
"structured": "Structured",
|
||||
"text": "Text"
|
||||
},
|
||||
"copy_link": "Copy link",
|
||||
"duration": "Duration",
|
||||
"enter_curl": "Enter cURL",
|
||||
"generate_code": "Generate code",
|
||||
"generated_code": "Generated code",
|
||||
"header_list": "Header List",
|
||||
"invalid_name": "Please provide a name for the request",
|
||||
"method": "Method",
|
||||
"name": "Request name",
|
||||
"new": "New Request",
|
||||
"override": "Override",
|
||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
||||
"overriden": "Overridden",
|
||||
"parameter_list": "Query Parameters",
|
||||
"parameters": "Parameters",
|
||||
"path": "Path",
|
||||
"payload": "Payload",
|
||||
"query": "Query",
|
||||
"raw_body": "Raw Request Body",
|
||||
"renamed": "Request renamed",
|
||||
"run": "Run",
|
||||
"save": "Save",
|
||||
"save_as": "Save as",
|
||||
"saved": "Request saved",
|
||||
"share": "Share",
|
||||
"share_description": "Share Hoppscotch with your friends",
|
||||
"title": "Request",
|
||||
"type": "Request type",
|
||||
"url": "URL",
|
||||
"variables": "Variables",
|
||||
"view_my_links": "View my links"
|
||||
},
|
||||
"response": {
|
||||
"body": "Response Body",
|
||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||
"headers": "Headers",
|
||||
"html": "HTML",
|
||||
"image": "Image",
|
||||
"json": "JSON",
|
||||
"pdf": "PDF",
|
||||
"preview_html": "Preview HTML",
|
||||
"raw": "Raw",
|
||||
"size": "Size",
|
||||
"status": "Status",
|
||||
"time": "Time",
|
||||
"title": "Response",
|
||||
"waiting_for_connection": "waiting for connection",
|
||||
"xml": "XML"
|
||||
},
|
||||
"settings": {
|
||||
"accent_color": "Accent color",
|
||||
"account": "アカウント",
|
||||
"account_description": "Customize your account settings.",
|
||||
"account_email_description": "Your primary email address.",
|
||||
"account_name_description": "This is your display name.",
|
||||
"background": "Background",
|
||||
"black_mode": "Black",
|
||||
"change_font_size": "Change font size",
|
||||
"choose_language": "Choose language",
|
||||
"dark_mode": "Dark",
|
||||
"expand_navigation": "Expand navigation",
|
||||
"experiments": "Experiments",
|
||||
"experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ",
|
||||
"extension_ver_not_reported": "Not Reported",
|
||||
"extension_version": "Extension Version",
|
||||
"extensions": "Browser extension",
|
||||
"extensions_use_toggle": "Use the browser extension to send requests (if present)",
|
||||
"follow": "Follow Us",
|
||||
"font_size": "Font size",
|
||||
"font_size_large": "Large",
|
||||
"font_size_medium": "Medium",
|
||||
"font_size_small": "Small",
|
||||
"interceptor": "Interceptor",
|
||||
"interceptor_description": "Middleware between application and APIs.",
|
||||
"language": "Language",
|
||||
"light_mode": "Light",
|
||||
"official_proxy_hosting": "Official Proxy is hosted by Hoppscotch.",
|
||||
"profile": "Profile",
|
||||
"profile_description": "Update your profile details",
|
||||
"profile_email": "Email address",
|
||||
"profile_name": "Profile name",
|
||||
"proxy": "Proxy",
|
||||
"proxy_url": "Proxy URL",
|
||||
"proxy_use_toggle": "Use the proxy middleware to send requests",
|
||||
"read_the": "Read the",
|
||||
"reset_default": "Reset to default",
|
||||
"short_codes": "Short codes",
|
||||
"short_codes_description": "Short codes which were created by you.",
|
||||
"sidebar_on_left": "Sidebar on left",
|
||||
"sync": "Synchronise",
|
||||
"sync_collections": "Collections",
|
||||
"sync_description": "These settings are synced to cloud.",
|
||||
"sync_environments": "Environments",
|
||||
"sync_history": "History",
|
||||
"system_mode": "System",
|
||||
"telemetry": "Telemetry",
|
||||
"telemetry_helps_us": "Telemetry helps us to personalize our operations and deliver the best experience to you.",
|
||||
"theme": "Theme",
|
||||
"theme_description": "Customize your application theme.",
|
||||
"use_experimental_url_bar": "Use experimental URL bar with environment highlighting",
|
||||
"user": "User",
|
||||
"verified_email": "Verified email",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shortcodes": {
|
||||
"actions": "Actions",
|
||||
"created_on": "Created on",
|
||||
"deleted": "Shortcode deleted",
|
||||
"method": "Method",
|
||||
"not_found": "Shortcode not found",
|
||||
"short_code": "Short code",
|
||||
"url": "URL"
|
||||
},
|
||||
"shortcut": {
|
||||
"general": {
|
||||
"close_current_menu": "Close current menu",
|
||||
"command_menu": "Search & command menu",
|
||||
"help_menu": "Help menu",
|
||||
"show_all": "Keyboard shortcuts",
|
||||
"title": "General"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Invite people to Hoppscotch",
|
||||
"title": "Miscellaneous"
|
||||
},
|
||||
"navigation": {
|
||||
"back": "Go back to previous page",
|
||||
"documentation": "Go to Documentation page",
|
||||
"forward": "Go forward to next page",
|
||||
"graphql": "Go to GraphQL page",
|
||||
"profile": "Go to Profile page",
|
||||
"realtime": "Go to Realtime page",
|
||||
"rest": "Go to REST page",
|
||||
"settings": "Go to Settings page",
|
||||
"title": "Navigation"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "Copy Request Link",
|
||||
"delete_method": "Select DELETE method",
|
||||
"get_method": "Select GET method",
|
||||
"head_method": "Select HEAD method",
|
||||
"method": "Method",
|
||||
"next_method": "Select Next method",
|
||||
"post_method": "Select POST method",
|
||||
"previous_method": "Select Previous method",
|
||||
"put_method": "Select PUT method",
|
||||
"reset_request": "Reset Request",
|
||||
"save_to_collections": "Save to Collections",
|
||||
"send_request": "Send Request",
|
||||
"title": "Request"
|
||||
},
|
||||
"theme": {
|
||||
"black": "Switch theme to black mode",
|
||||
"dark": "Switch theme to dark mode",
|
||||
"light": "Switch theme to light mode",
|
||||
"system": "Switch theme to system mode",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"code": "Show code",
|
||||
"collection": "Expand Collection Panel",
|
||||
"more": "Show more",
|
||||
"sidebar": "Expand sidebar"
|
||||
},
|
||||
"socketio": {
|
||||
"communication": "Communication",
|
||||
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||
"event_name": "Event Name",
|
||||
"events": "Events",
|
||||
"log": "Log",
|
||||
"url": "URL"
|
||||
},
|
||||
"sse": {
|
||||
"event_type": "Event type",
|
||||
"log": "Log",
|
||||
"url": "URL"
|
||||
},
|
||||
"state": {
|
||||
"bulk_mode": "Bulk edit",
|
||||
"bulk_mode_placeholder": "Entries are separated by newline\nKeys and values are separated by :\nPrepend # to any row you want to add but keep disabled",
|
||||
"cleared": "Cleared",
|
||||
"connected": "Connected",
|
||||
"connected_to": "Connected to {name}",
|
||||
"connecting_to": "Connecting to {name}...",
|
||||
"connection_error": "Failed to connect",
|
||||
"connection_failed": "Connection failed",
|
||||
"connection_lost": "Connection lost",
|
||||
"copied_to_clipboard": "Copied to clipboard",
|
||||
"deleted": "Deleted",
|
||||
"deprecated": "DEPRECATED",
|
||||
"disabled": "Disabled",
|
||||
"disconnected": "Disconnected",
|
||||
"disconnected_from": "Disconnected from {name}",
|
||||
"docs_generated": "Documentation generated",
|
||||
"download_started": "Download started",
|
||||
"enabled": "Enabled",
|
||||
"file_imported": "File imported",
|
||||
"finished_in": "Finished in {duration} ms",
|
||||
"history_deleted": "History deleted",
|
||||
"linewrap": "Wrap lines",
|
||||
"loading": "Loading...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"none": "None",
|
||||
"nothing_found": "Nothing found for",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||
"waiting_send_request": "Waiting to send request"
|
||||
},
|
||||
"support": {
|
||||
"changelog": "Read more about latest releases",
|
||||
"chat": "Questions? Chat with us!",
|
||||
"community": "Ask questions and help others",
|
||||
"documentation": "Read more about Hoppscotch",
|
||||
"forum": "Ask questions and get answers",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Browse app faster",
|
||||
"team": "Get in touch with the team",
|
||||
"title": "Support",
|
||||
"twitter": "Follow us on Twitter"
|
||||
},
|
||||
"tab": {
|
||||
"authorization": "Authorization",
|
||||
"body": "Body",
|
||||
"collections": "Collections",
|
||||
"documentation": "Documentation",
|
||||
"headers": "Headers",
|
||||
"history": "History",
|
||||
"mqtt": "MQTT",
|
||||
"parameters": "Parameters",
|
||||
"pre_request_script": "Pre-request Script",
|
||||
"queries": "Queries",
|
||||
"query": "Query",
|
||||
"schema": "Schema",
|
||||
"socketio": "Socket.IO",
|
||||
"sse": "SSE",
|
||||
"tests": "Tests",
|
||||
"types": "Types",
|
||||
"variables": "Variables",
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Create new team",
|
||||
"deleted": "Team deleted",
|
||||
"edit": "Edit Team",
|
||||
"email": "E-mail",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Exit Team",
|
||||
"exit_disabled": "Only owner cannot exit the team",
|
||||
"invalid_email_format": "Email format is invalid",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Please provide a valid permission to the team member",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Join the beta program to access teams.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "You left the team",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_removed": "User removed",
|
||||
"member_role_updated": "User roles updated",
|
||||
"members": "Members",
|
||||
"name_length_insufficient": "Team name should be at least 6 characters long",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "New Team",
|
||||
"new_created": "New team created",
|
||||
"new_name": "My New Team",
|
||||
"no_access": "You do not have edit access to these collections",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Permissions",
|
||||
"saved": "Team saved",
|
||||
"select_a_team": "Select a team",
|
||||
"title": "Teams",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
},
|
||||
"test": {
|
||||
"failed": "test failed",
|
||||
"javascript_code": "JavaScript Code",
|
||||
"learn": "Read documentation",
|
||||
"passed": "test passed",
|
||||
"report": "Test Report",
|
||||
"results": "Test Results",
|
||||
"script": "Script",
|
||||
"snippets": "Snippets"
|
||||
},
|
||||
"websocket": {
|
||||
"communication": "Communication",
|
||||
"log": "Log",
|
||||
"message": "Message",
|
||||
"protocols": "Protocols",
|
||||
"url": "URL"
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Anulare",
|
||||
"choose_file": "Alegeți un fișier",
|
||||
"clear": "clar",
|
||||
"clear_all": "Curata tot",
|
||||
"close": "Close",
|
||||
"connect": "Conectați",
|
||||
"copy": "Copie",
|
||||
"delete": "Șterge",
|
||||
"disconnect": "Deconectat",
|
||||
"dismiss": "Renunță",
|
||||
"dont_save": "Don't save",
|
||||
"download_file": "Descărcare fișier",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Editați | ×",
|
||||
"filter_response": "Filter response",
|
||||
"go_back": "Întoarce-te",
|
||||
"label": "Eticheta",
|
||||
"learn_more": "Află mai multe",
|
||||
"less": "Less",
|
||||
"more": "Mai mult",
|
||||
"new": "Nou",
|
||||
"no": "Nu",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Dăruiește",
|
||||
"remove": "Elimina",
|
||||
"restore": "Restabili",
|
||||
"save": "salva",
|
||||
"scroll_to_bottom": "Scroll to bottom",
|
||||
"scroll_to_top": "Scroll to top",
|
||||
"search": "Căutare",
|
||||
"send": "Trimite",
|
||||
"start": "start",
|
||||
"stop": "Stop",
|
||||
"to_close": "to close",
|
||||
"to_navigate": "to navigate",
|
||||
"to_select": "to select",
|
||||
"turn_off": "Opriți",
|
||||
"turn_on": "Aprinde",
|
||||
"undo": "Anula",
|
||||
"yes": "da"
|
||||
},
|
||||
"add": {
|
||||
"new": "Adăuga nou",
|
||||
"star": "Adăugați stea"
|
||||
},
|
||||
"app": {
|
||||
"chat_with_us": "Vorbeste cu noi",
|
||||
"contact_us": "Contactează-ne",
|
||||
"copy": "Copie",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||
"discord": "Discord",
|
||||
"documentation": "Documentație",
|
||||
"github": "GitHub",
|
||||
"help": "Ajutor, feedback și documentație",
|
||||
"home": "Acasă",
|
||||
"invite": "A invita",
|
||||
"invite_description": "În Hoppscotch, am proiectat o interfață simplă și intuitivă pentru crearea și gestionarea API-urilor dvs. Hoppscotch este un instrument care vă ajută să construiți, să testați, să documentați și să partajați API-urile dvs.",
|
||||
"invite_your_friends": "Invita-ti prietenii",
|
||||
"join_discord_community": "Alătură-te comunității noastre Discord",
|
||||
"keyboard_shortcuts": "Comenzi rapide de la tastatură",
|
||||
"name": "Hoppscotch",
|
||||
"new_version_found": "Nouă versiune găsită. Reîmprospătați pentru a actualiza.",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Politica de confidențialitate proxy",
|
||||
"reload": "Reîncarcă",
|
||||
"search": "Căutare",
|
||||
"share": "Acțiune",
|
||||
"shortcuts": "Comenzi rapide",
|
||||
"spotlight": "În centrul atenției",
|
||||
"status": "stare",
|
||||
"status_description": "Check the status of the website",
|
||||
"terms_and_privacy": "Termeni și confidențialitate",
|
||||
"twitter": "Stare de nervozitate",
|
||||
"type_a_command_search": "Tastați o comandă sau căutați ...",
|
||||
"we_use_cookies": "Folosim cookie-uri",
|
||||
"whats_new": "Ce mai e nou?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"auth": {
|
||||
"account_exists": "Contul există cu acreditări diferite - Conectați-vă pentru a conecta ambele conturi",
|
||||
"all_sign_in_options": "Toate opțiunile de conectare",
|
||||
"continue_with_email": "Continuați cu e-mailul",
|
||||
"continue_with_github": "Continuați cu GitHub",
|
||||
"continue_with_google": "Continuați cu Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"email": "E-mail",
|
||||
"logged_out": "Delogat",
|
||||
"login": "Autentificare",
|
||||
"login_success": "Conectat cu succes",
|
||||
"login_to_hoppscotch": "Conectați-vă la Hoppscotch",
|
||||
"logout": "Deconectați-vă",
|
||||
"re_enter_email": "Reintroduceti emailul",
|
||||
"send_magic_link": "Trimiteți un link magic",
|
||||
"sync": "Sincronizare",
|
||||
"we_sent_magic_link": "V-am trimis un link magic!",
|
||||
"we_sent_magic_link_description": "Verificați căsuța de e-mail - am trimis un e-mail la {email}. Conține un link magic care vă va conecta."
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Generați token",
|
||||
"include_in_url": "Includeți în URL",
|
||||
"learn": "Afla cum",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Parola",
|
||||
"token": "Jeton",
|
||||
"type": "Tipul de autorizare",
|
||||
"username": "Nume de utilizator"
|
||||
},
|
||||
"collection": {
|
||||
"created": "Colecție creată",
|
||||
"edit": "Editați colecția",
|
||||
"invalid_name": "Vă rugăm să furnizați un nume valid pentru colecție",
|
||||
"my_collections": "Colecțiile mele",
|
||||
"name": "Noua mea colecție",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "Colecție nouă",
|
||||
"renamed": "Colecția redenumită",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Salvează ca",
|
||||
"select": "Selectați o colecție",
|
||||
"select_location": "Selectați locația",
|
||||
"select_team": "Selectați o echipă",
|
||||
"team_collections": "Colecții de echipă"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"logout": "Sigur doriți să vă deconectați?",
|
||||
"remove_collection": "Sigur doriți să ștergeți definitiv această colecție?",
|
||||
"remove_environment": "Sigur doriți să ștergeți definitiv acest mediu?",
|
||||
"remove_folder": "Sigur doriți să ștergeți definitiv acest folder?",
|
||||
"remove_history": "Sigur doriți să ștergeți definitiv tot istoricul?",
|
||||
"remove_request": "Sigur doriți să ștergeți definitiv această solicitare?",
|
||||
"remove_team": "Sigur doriți să ștergeți această echipă?",
|
||||
"remove_telemetry": "Sigur doriți să renunțați la telemetrie?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"sync": "Sigur doriți să sincronizați acest spațiu de lucru?"
|
||||
},
|
||||
"count": {
|
||||
"header": "Antet {count}",
|
||||
"message": "Număr de mesaje",
|
||||
"parameter": "Parametrul {count}",
|
||||
"protocol": "Protocol {count}",
|
||||
"value": "Valoare {count}",
|
||||
"variable": "Variabila {count}"
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "Generați documentație",
|
||||
"generate_message": "Importați orice colecție Hoppscotch pentru a genera documentație API în mișcare."
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "Această cerere nu folosește nicio autorizație",
|
||||
"body": "Această cerere nu are un corp",
|
||||
"collection": "Colecția este goală",
|
||||
"collections": "Colecțiile sunt goale",
|
||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
||||
"endpoint": "Endpoint cannot be empty",
|
||||
"environments": "Mediile sunt goale",
|
||||
"folder": "Dosarul este gol",
|
||||
"headers": "Această solicitare nu are anteturi",
|
||||
"history": "Istoria este goală",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Echipa este goală",
|
||||
"parameters": "Această solicitare nu are niciun parametru",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"profile": "Login in to view your profile",
|
||||
"protocols": "Protocoalele sunt goale",
|
||||
"schema": "Conectați-vă la un punct final GraphQL",
|
||||
"shortcodes": "Shortcodes are empty",
|
||||
"team_name": "Numele echipei este gol",
|
||||
"teams": "Echipele sunt goale",
|
||||
"tests": "Nu există teste pentru această solicitare"
|
||||
},
|
||||
"environment": {
|
||||
"add_to_global": "Add to Global",
|
||||
"added": "Environment addition",
|
||||
"create_new": "Creați un mediu nou",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Editați mediul",
|
||||
"invalid_name": "Vă rugăm să furnizați un nume valid pentru mediu",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "Noul mediu",
|
||||
"no_environment": "Fără mediu",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"select": "Selectați mediul",
|
||||
"title": "Medii",
|
||||
"updated": "Environment updation",
|
||||
"variable_list": "Lista variabilelor"
|
||||
},
|
||||
"error": {
|
||||
"browser_support_sse": "Acest browser pare să nu aibă suport pentru Server Sent Events.",
|
||||
"check_console_details": "Verificați jurnalul consolei pentru detalii.",
|
||||
"curl_invalid_format": "cURL nu este formatat corect",
|
||||
"empty_req_name": "Nume cerere goală",
|
||||
"f12_details": "(F12 pentru detalii)",
|
||||
"gql_prettify_invalid_query": "Nu am putut preta o interogare nevalidă, rezolvarea erorilor de sintaxă a interogării și încercați din nou",
|
||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"invalid_link": "Invalid link",
|
||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||
"json_parsing_failed": "Invalid JSON",
|
||||
"json_prettify_invalid_body": "Nu s-a putut pregăti un corp nevalid, a rezolva erorile de sintaxă json și a încerca din nou",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "Nu s-a putut trimite solicitarea",
|
||||
"no_duration": "Fără durată",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"script_fail": "Nu s-a putut executa scriptul de pre-cerere",
|
||||
"something_went_wrong": "Ceva n-a mers bine",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Exportați ca JSON",
|
||||
"create_secret_gist": "Creați secțiunea secretă",
|
||||
"gist_created": "Gist creat",
|
||||
"require_github": "Conectați-vă cu GitHub pentru a crea o idee secretă",
|
||||
"title": "Export"
|
||||
},
|
||||
"folder": {
|
||||
"created": "Folder creat",
|
||||
"edit": "Editați folderul",
|
||||
"invalid_name": "Vă rugăm să furnizați un nume pentru dosar",
|
||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
||||
"new": "Dosar nou",
|
||||
"renamed": "Dosar redenumit"
|
||||
},
|
||||
"graphql": {
|
||||
"mutations": "Mutații",
|
||||
"schema": "Schemă",
|
||||
"subscriptions": "Abonamente"
|
||||
},
|
||||
"header": {
|
||||
"install_pwa": "Instalează aplicația",
|
||||
"login": "Autentificare",
|
||||
"save_workspace": "Salvați spațiul meu de lucru"
|
||||
},
|
||||
"helpers": {
|
||||
"authorization": "Antetul autorizației va fi generat automat la trimiterea cererii.",
|
||||
"generate_documentation_first": "Generați mai întâi documentația",
|
||||
"network_fail": "Imposibil de atins punctul final API. Verificați conexiunea la rețea și încercați din nou.",
|
||||
"offline": "Pari să fii offline. Este posibil ca datele din acest spațiu de lucru să nu fie actualizate.",
|
||||
"offline_short": "Pari să fii offline.",
|
||||
"post_request_tests": "Scripturile de testare sunt scrise în JavaScript și se execută după primirea răspunsului.",
|
||||
"pre_request_script": "Scripturile de cerere prealabilă sunt scrise în JavaScript și sunt rulate înainte de trimiterea cererii.",
|
||||
"script_fail": "Se pare că există o eroare în scriptul de pre-cerere. Verificați eroarea de mai jos și remediați scriptul în consecință.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Scrieți un script de test pentru automatizarea depanării."
|
||||
},
|
||||
"hide": {
|
||||
"collection": "Collapse Collection Panel",
|
||||
"more": "Ascunde mai mult",
|
||||
"preview": "Ascundeți previzualizarea",
|
||||
"sidebar": "Ascunde bara laterală"
|
||||
},
|
||||
"import": {
|
||||
"collections": "Import colecții",
|
||||
"curl": "Importați cURL",
|
||||
"failed": "Importul nu a reușit",
|
||||
"from_gist": "Import din Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Import din colecțiile mele",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Introduceți adresa URL Gist",
|
||||
"import_from_url_invalid_fetch": "Couldn't get data from the url",
|
||||
"import_from_url_invalid_file_format": "Error while importing collections",
|
||||
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||
"import_from_url_success": "Collections Imported",
|
||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
||||
"title": "Import"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "Collapse or Expand Collections",
|
||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
||||
"column": "Vertical layout",
|
||||
"name": "Layout",
|
||||
"row": "Horizontal layout",
|
||||
"zen_mode": "Modul Zen"
|
||||
},
|
||||
"modal": {
|
||||
"collections": "Colecții",
|
||||
"confirm": "A confirma",
|
||||
"edit_request": "Solicitare de editare",
|
||||
"import_export": "Import Export"
|
||||
},
|
||||
"mqtt": {
|
||||
"communication": "Comunicare",
|
||||
"log": "Buturuga",
|
||||
"message": "Mesaj",
|
||||
"publish": "Publica",
|
||||
"subscribe": "Abonati-va",
|
||||
"topic": "Subiect",
|
||||
"topic_name": "Nume subiect",
|
||||
"topic_title": "Publică / Abonează subiect",
|
||||
"unsubscribe": "Dezabonează-te",
|
||||
"url": "URL"
|
||||
},
|
||||
"navigation": {
|
||||
"doc": "Documente",
|
||||
"graphql": "GraphQL",
|
||||
"profile": "Profile",
|
||||
"realtime": "Timp real",
|
||||
"rest": "REST",
|
||||
"settings": "Setări"
|
||||
},
|
||||
"preRequest": {
|
||||
"javascript_code": "Cod JavaScript",
|
||||
"learn": "Citiți documentația",
|
||||
"script": "Script de cerere prealabilă",
|
||||
"snippets": "Fragmente"
|
||||
},
|
||||
"profile": {
|
||||
"app_settings": "App Settings",
|
||||
"editor": "Editor",
|
||||
"editor_description": "Editors can add, edit, and delete requests.",
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Eliminați steaua"
|
||||
},
|
||||
"request": {
|
||||
"added": "Cerere adăugată",
|
||||
"authorization": "Autorizare",
|
||||
"body": "Solicitați corpul",
|
||||
"choose_language": "Alege limba",
|
||||
"content_type": "Tipul de conținut",
|
||||
"content_type_titles": {
|
||||
"others": "Others",
|
||||
"structured": "Structured",
|
||||
"text": "Text"
|
||||
},
|
||||
"copy_link": "Copiază legătură",
|
||||
"duration": "Durată",
|
||||
"enter_curl": "Introduceți cURL",
|
||||
"generate_code": "Generați cod",
|
||||
"generated_code": "Cod generat",
|
||||
"header_list": "Lista anteturilor",
|
||||
"invalid_name": "Vă rugăm să furnizați un nume pentru cerere",
|
||||
"method": "Metodă",
|
||||
"name": "Solicitați numele",
|
||||
"new": "New Request",
|
||||
"override": "Override",
|
||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
||||
"overriden": "Overridden",
|
||||
"parameter_list": "Parametrii interogării",
|
||||
"parameters": "Parametrii",
|
||||
"path": "cale",
|
||||
"payload": "Încărcătură utilă",
|
||||
"query": "Interogare",
|
||||
"raw_body": "Corp de solicitare brută",
|
||||
"renamed": "Cererea redenumită",
|
||||
"run": "Alerga",
|
||||
"save": "salva",
|
||||
"save_as": "Salvează ca",
|
||||
"saved": "Cererea a fost salvată",
|
||||
"share": "Acțiune",
|
||||
"share_description": "Share Hoppscotch with your friends",
|
||||
"title": "Cerere",
|
||||
"type": "Tip de solicitare",
|
||||
"url": "URL",
|
||||
"variables": "Variabile",
|
||||
"view_my_links": "View my links"
|
||||
},
|
||||
"response": {
|
||||
"body": "Corpul de răspuns",
|
||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||
"headers": "Anteturi",
|
||||
"html": "HTML",
|
||||
"image": "Imagine",
|
||||
"json": "JSON",
|
||||
"pdf": "PDF",
|
||||
"preview_html": "Previzualizați HTML",
|
||||
"raw": "Brut",
|
||||
"size": "mărimea",
|
||||
"status": "stare",
|
||||
"time": "Timp",
|
||||
"title": "Raspuns",
|
||||
"waiting_for_connection": "așteptând conexiunea",
|
||||
"xml": "XML"
|
||||
},
|
||||
"settings": {
|
||||
"accent_color": "Culoare de accent",
|
||||
"account": "Cont",
|
||||
"account_description": "Personalizați setările contului.",
|
||||
"account_email_description": "Adresa dvs. de e-mail principală.",
|
||||
"account_name_description": "Acesta este numele dvs. afișat.",
|
||||
"background": "fundal",
|
||||
"black_mode": "Negru",
|
||||
"change_font_size": "Schimba marimea fontului",
|
||||
"choose_language": "Alege limba",
|
||||
"dark_mode": "Întuneric",
|
||||
"expand_navigation": "Expand navigation",
|
||||
"experiments": "Experimente",
|
||||
"experiments_notice": "Aceasta este o colecție de experimente la care lucrăm, care s-ar putea dovedi a fi utile, distractive, ambele sau nici una. Nu sunt definitive și s-ar putea să nu fie stabile, așa că, dacă se întâmplă ceva prea ciudat, nu vă panicați. Doar oprește chestia dang. Glume deoparte,",
|
||||
"extension_ver_not_reported": "Nu a fost raportat",
|
||||
"extension_version": "Versiune extensie",
|
||||
"extensions": "Extensii",
|
||||
"extensions_use_toggle": "Utilizați extensia browserului pentru a trimite cereri (dacă există)",
|
||||
"follow": "Follow Us",
|
||||
"font_size": "Marimea fontului",
|
||||
"font_size_large": "Mare",
|
||||
"font_size_medium": "Mediu",
|
||||
"font_size_small": "Mic",
|
||||
"interceptor": "Interceptor",
|
||||
"interceptor_description": "Middleware între aplicație și API-uri.",
|
||||
"language": "Limba",
|
||||
"light_mode": "Ușoară",
|
||||
"official_proxy_hosting": "Proxy oficial este găzduit de Hoppscotch.",
|
||||
"profile": "Profile",
|
||||
"profile_description": "Update your profile details",
|
||||
"profile_email": "Email address",
|
||||
"profile_name": "Profile name",
|
||||
"proxy": "Proxy",
|
||||
"proxy_url": "URL proxy",
|
||||
"proxy_use_toggle": "Utilizați middleware-ul proxy pentru a trimite cereri",
|
||||
"read_the": "Citeste",
|
||||
"reset_default": "Resetare la valorile implicite",
|
||||
"short_codes": "Short codes",
|
||||
"short_codes_description": "Short codes which were created by you.",
|
||||
"sidebar_on_left": "Sidebar on left",
|
||||
"sync": "Sincroniza",
|
||||
"sync_collections": "Colecții",
|
||||
"sync_description": "Aceste setări sunt sincronizate cu cloud.",
|
||||
"sync_environments": "Medii",
|
||||
"sync_history": "Istorie",
|
||||
"system_mode": "Sistem",
|
||||
"telemetry": "Telemetrie",
|
||||
"telemetry_helps_us": "Telemetria ne ajută să ne personalizăm operațiunile și să vă oferim cea mai bună experiență.",
|
||||
"theme": "Temă",
|
||||
"theme_description": "Personalizați tema aplicației.",
|
||||
"use_experimental_url_bar": "Utilizați bara URL experimentală cu evidențierea mediului",
|
||||
"user": "Utilizator",
|
||||
"verified_email": "Verified email",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shortcodes": {
|
||||
"actions": "Actions",
|
||||
"created_on": "Created on",
|
||||
"deleted": "Shortcode deleted",
|
||||
"method": "Method",
|
||||
"not_found": "Shortcode not found",
|
||||
"short_code": "Short code",
|
||||
"url": "URL"
|
||||
},
|
||||
"shortcut": {
|
||||
"general": {
|
||||
"close_current_menu": "Închideți meniul curent",
|
||||
"command_menu": "Căutare și meniu de comandă",
|
||||
"help_menu": "Meniul Ajutor",
|
||||
"show_all": "Comenzi rapide de la tastatură",
|
||||
"title": "General"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Invitați oamenii la Hoppscotch",
|
||||
"title": "Diverse"
|
||||
},
|
||||
"navigation": {
|
||||
"back": "Reveniți la pagina anterioară",
|
||||
"documentation": "Accesați pagina Documentație",
|
||||
"forward": "Mergeți la pagina următoare",
|
||||
"graphql": "Accesați pagina GraphQL",
|
||||
"profile": "Go to Profile page",
|
||||
"realtime": "Accesați pagina în timp real",
|
||||
"rest": "Accesați pagina REST",
|
||||
"settings": "Accesați pagina Setări",
|
||||
"title": "Navigare"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "Copiați legătura de solicitare",
|
||||
"delete_method": "Selectați metoda DELETE",
|
||||
"get_method": "Selectați metoda GET",
|
||||
"head_method": "Selectați metoda HEAD",
|
||||
"method": "Metodă",
|
||||
"next_method": "Selectați Metoda următoare",
|
||||
"post_method": "Selectați metoda POST",
|
||||
"previous_method": "Selectați Metoda anterioară",
|
||||
"put_method": "Selectați metoda PUT",
|
||||
"reset_request": "Cerere de resetare",
|
||||
"save_to_collections": "Salvați în colecții",
|
||||
"send_request": "Trimite cerere",
|
||||
"title": "Cerere"
|
||||
},
|
||||
"theme": {
|
||||
"black": "Switch theme to black mode",
|
||||
"dark": "Switch theme to dark mode",
|
||||
"light": "Switch theme to light mode",
|
||||
"system": "Switch theme to system mode",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"code": "Afișați codul",
|
||||
"collection": "Expand Collection Panel",
|
||||
"more": "Afișați mai multe",
|
||||
"sidebar": "Afișați bara laterală"
|
||||
},
|
||||
"socketio": {
|
||||
"communication": "Comunicare",
|
||||
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||
"event_name": "Numele evenimentului",
|
||||
"events": "Evenimente",
|
||||
"log": "Buturuga",
|
||||
"url": "URL"
|
||||
},
|
||||
"sse": {
|
||||
"event_type": "Tip de eveniment",
|
||||
"log": "Buturuga",
|
||||
"url": "URL"
|
||||
},
|
||||
"state": {
|
||||
"bulk_mode": "Editare în bloc",
|
||||
"bulk_mode_placeholder": "Intrările sunt separate prin linie nouă\nCheile și valorile sunt separate prin:\nPrepend # la orice rând pe care doriți să îl adăugați, dar păstrați-l dezactivat",
|
||||
"cleared": "Eliminat",
|
||||
"connected": "Conectat",
|
||||
"connected_to": "Conectat la {name}",
|
||||
"connecting_to": "Se conectează la {name} ...",
|
||||
"connection_error": "Failed to connect",
|
||||
"connection_failed": "Connection failed",
|
||||
"connection_lost": "Connection lost",
|
||||
"copied_to_clipboard": "Copiat în clipboard",
|
||||
"deleted": "Șters",
|
||||
"deprecated": "DEPRECAT",
|
||||
"disabled": "Dezactivat",
|
||||
"disconnected": "Deconectat",
|
||||
"disconnected_from": "Deconectat de la {name}",
|
||||
"docs_generated": "Documentație generată",
|
||||
"download_started": "Descărcarea a început",
|
||||
"enabled": "Activat",
|
||||
"file_imported": "Fișier importat",
|
||||
"finished_in": "Finalizat în {duration} ms",
|
||||
"history_deleted": "Istoricul a fost șters",
|
||||
"linewrap": "Înfășurați liniile",
|
||||
"loading": "Se încarcă...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"none": "Nici unul",
|
||||
"nothing_found": "Nimic găsit pentru",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||
"waiting_send_request": "Se așteaptă trimiterea cererii"
|
||||
},
|
||||
"support": {
|
||||
"changelog": "Citiți mai multe despre ultimele versiuni",
|
||||
"chat": "Întrebări? Vorbeste cu noi!",
|
||||
"community": "Puneți întrebări și ajutați-i pe ceilalți",
|
||||
"documentation": "Citiți mai multe despre Hoppscotch",
|
||||
"forum": "Puneți întrebări și primiți răspunsuri",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Răsfoiți aplicația mai repede",
|
||||
"team": "Luați legătura cu echipa",
|
||||
"title": "A sustine",
|
||||
"twitter": "Urmăriți-ne pe Twitter"
|
||||
},
|
||||
"tab": {
|
||||
"authorization": "Autorizare",
|
||||
"body": "Corp",
|
||||
"collections": "Colecții",
|
||||
"documentation": "Documentație",
|
||||
"headers": "Anteturi",
|
||||
"history": "Istorie",
|
||||
"mqtt": "MQTT",
|
||||
"parameters": "Parametrii",
|
||||
"pre_request_script": "Script de cerere prealabilă",
|
||||
"queries": "Întrebări",
|
||||
"query": "Interogare",
|
||||
"schema": "Schema",
|
||||
"socketio": "Socket.IO",
|
||||
"sse": "SSE",
|
||||
"tests": "Teste",
|
||||
"types": "Tipuri",
|
||||
"variables": "Variabile",
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Creați o echipă nouă",
|
||||
"deleted": "Echipa a fost ștearsă",
|
||||
"edit": "Editați echipa",
|
||||
"email": "E-mail",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Ieșiți din echipă",
|
||||
"exit_disabled": "Numai proprietarul nu poate ieși din echipă",
|
||||
"invalid_email_format": "Formatul de e-mail nu este valid",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Vă rugăm să oferiți o permisiune validă membrilor echipei",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Alăturați-vă programului beta pentru a accesa echipe.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "Ai părăsit echipa",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_removed": "Utilizatorul a fost eliminat",
|
||||
"member_role_updated": "Rolurile utilizatorului au fost actualizate",
|
||||
"members": "Membri",
|
||||
"name_length_insufficient": "Numele echipei trebuie să aibă cel puțin 6 caractere",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "Echipă nouă",
|
||||
"new_created": "Nouă echipă creată",
|
||||
"new_name": "Noua mea echipă",
|
||||
"no_access": "Nu aveți acces de editare la aceste colecții",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Permisiuni",
|
||||
"saved": "Echipa salvată",
|
||||
"select_a_team": "Select a team",
|
||||
"title": "Echipe",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
},
|
||||
"test": {
|
||||
"failed": "test failed",
|
||||
"javascript_code": "Cod JavaScript",
|
||||
"learn": "Citiți documentația",
|
||||
"passed": "test passed",
|
||||
"report": "Raport de testare",
|
||||
"results": "Rezultatele testului",
|
||||
"script": "Script",
|
||||
"snippets": "Fragmente"
|
||||
},
|
||||
"websocket": {
|
||||
"communication": "Comunicare",
|
||||
"log": "Buturuga",
|
||||
"message": "Mesaj",
|
||||
"protocols": "Protocoale",
|
||||
"url": "URL"
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Скасувати",
|
||||
"choose_file": "Виберіть файл",
|
||||
"clear": "Ясно",
|
||||
"clear_all": "Очистити все",
|
||||
"close": "Close",
|
||||
"connect": "Підключіться",
|
||||
"copy": "Копіювати",
|
||||
"delete": "Видалити",
|
||||
"disconnect": "Відключити",
|
||||
"dismiss": "Відхилити",
|
||||
"dont_save": "Don't save",
|
||||
"download_file": "Завантажити файл",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Редагувати",
|
||||
"filter_response": "Filter response",
|
||||
"go_back": "Повертайся",
|
||||
"label": "Мітка",
|
||||
"learn_more": "Вчи більше",
|
||||
"less": "Less",
|
||||
"more": "Більше",
|
||||
"new": "Новий",
|
||||
"no": "Немає",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Прикрасьте",
|
||||
"remove": "Видалити",
|
||||
"restore": "Відновлювати",
|
||||
"save": "Зберегти",
|
||||
"scroll_to_bottom": "Scroll to bottom",
|
||||
"scroll_to_top": "Scroll to top",
|
||||
"search": "Пошук",
|
||||
"send": "Надіслати",
|
||||
"start": "Почати",
|
||||
"stop": "Стій",
|
||||
"to_close": "to close",
|
||||
"to_navigate": "to navigate",
|
||||
"to_select": "to select",
|
||||
"turn_off": "Вимкнути",
|
||||
"turn_on": "Ввімкнути",
|
||||
"undo": "Скасувати",
|
||||
"yes": "Так"
|
||||
},
|
||||
"add": {
|
||||
"new": "Додати новий",
|
||||
"star": "Додати зірочку"
|
||||
},
|
||||
"app": {
|
||||
"chat_with_us": "Спілкуйтеся з нами",
|
||||
"contact_us": "Зв'яжіться з нами",
|
||||
"copy": "Копіювати",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||
"discord": "Discord",
|
||||
"documentation": "Документація",
|
||||
"github": "GitHub",
|
||||
"help": "Довідка, відгуки та документація",
|
||||
"home": "Додому",
|
||||
"invite": "Запросити",
|
||||
"invite_description": "У Hoppscotch ми розробили простий та інтуїтивно зрозумілий інтерфейс для створення та управління вашими API. Hoppscotch - це інструмент, який допомагає вам створювати, тестувати, документувати та ділитися своїми API.",
|
||||
"invite_your_friends": "Запросіть своїх друзів",
|
||||
"join_discord_community": "Приєднуйтесь до нашої спільноти Discord",
|
||||
"keyboard_shortcuts": "Гарячі клавіши",
|
||||
"name": "Гопскотч",
|
||||
"new_version_found": "Знайдено нову версію. Оновіть, щоб оновити.",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Політика конфіденційності проксі",
|
||||
"reload": "Перезавантажити",
|
||||
"search": "Пошук",
|
||||
"share": "Поділитися",
|
||||
"shortcuts": "Ярлики",
|
||||
"spotlight": "У центрі уваги",
|
||||
"status": "Статус",
|
||||
"status_description": "Check the status of the website",
|
||||
"terms_and_privacy": "Умови та конфіденційність",
|
||||
"twitter": "Twitter",
|
||||
"type_a_command_search": "Введіть команду або виконайте пошук…",
|
||||
"we_use_cookies": "Ми використовуємо файли cookie",
|
||||
"whats_new": "Що нового?",
|
||||
"wiki": "Вікі"
|
||||
},
|
||||
"auth": {
|
||||
"account_exists": "Обліковий запис існує з різними обліковими даними - увійдіть, щоб зв'язати обидва облікові записи",
|
||||
"all_sign_in_options": "Усі параметри входу",
|
||||
"continue_with_email": "Продовжити з електронною поштою",
|
||||
"continue_with_github": "Продовжити з GitHub",
|
||||
"continue_with_google": "Продовжуйте працювати з Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"email": "Електронна пошта",
|
||||
"logged_out": "Вийшли з системи",
|
||||
"login": "Увійти",
|
||||
"login_success": "Вхід успішно здійснено",
|
||||
"login_to_hoppscotch": "Увійдіть до Hoppscotch",
|
||||
"logout": "Вийти",
|
||||
"re_enter_email": "Введіть електронну адресу ще раз",
|
||||
"send_magic_link": "Надішліть чарівне посилання",
|
||||
"sync": "Синхронізувати",
|
||||
"we_sent_magic_link": "Ми надіслали вам чарівне посилання!",
|
||||
"we_sent_magic_link_description": "Перевірте свою поштову скриньку - ми надіслали електронний лист на адресу {email}. Він містить чарівне посилання, яке дозволить вам увійти."
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Створіть маркер",
|
||||
"include_in_url": "Включити в URL",
|
||||
"learn": "Дізнайтесь, як",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Пароль",
|
||||
"token": "Токен",
|
||||
"type": "Тип авторизації",
|
||||
"username": "Ім'я користувача"
|
||||
},
|
||||
"collection": {
|
||||
"created": "Колекція створена",
|
||||
"edit": "Редагувати колекцію",
|
||||
"invalid_name": "Укажіть дійсну назву колекції",
|
||||
"my_collections": "Мої колекції",
|
||||
"name": "Моя нова колекція",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "Нова колекція",
|
||||
"renamed": "Колекція перейменована",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Зберегти як",
|
||||
"select": "Виберіть колекцію",
|
||||
"select_location": "Виберіть місце розташування",
|
||||
"select_team": "Виберіть команду",
|
||||
"team_collections": "Колекції команд"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"logout": "Ви впевнені, що хочете вийти?",
|
||||
"remove_collection": "Ви впевнені, що хочете назавжди видалити цю колекцію?",
|
||||
"remove_environment": "Ви впевнені, що хочете назавжди видалити це середовище?",
|
||||
"remove_folder": "Ви впевнені, що хочете назавжди видалити цю папку?",
|
||||
"remove_history": "Ви впевнені, що хочете назавжди видалити всю історію?",
|
||||
"remove_request": "Ви впевнені, що хочете назавжди видалити цей запит?",
|
||||
"remove_team": "Ви впевнені, що хочете видалити цю команду?",
|
||||
"remove_telemetry": "Ви впевнені, що хочете відмовитися від телеметрії?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"sync": "Ви впевнені, що хочете синхронізувати цю робочу область?"
|
||||
},
|
||||
"count": {
|
||||
"header": "Заголовок {count}",
|
||||
"message": "Повідомлення {count}",
|
||||
"parameter": "Параметр {count}",
|
||||
"protocol": "Протокол {count}",
|
||||
"value": "Значення {count}",
|
||||
"variable": "Змінна {count}"
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "Створення документації",
|
||||
"generate_message": "Імпортуйте будь-яку колекцію Hoppscotch для створення документації API на ходу."
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "У цьому запиті не використовується авторизація",
|
||||
"body": "Цей запит не має тіла",
|
||||
"collection": "Колекція порожня",
|
||||
"collections": "Колекції порожні",
|
||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
||||
"endpoint": "Endpoint cannot be empty",
|
||||
"environments": "Середовища порожні",
|
||||
"folder": "Папка порожня",
|
||||
"headers": "Цей запит не має заголовків",
|
||||
"history": "Історія порожня",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Команда порожня",
|
||||
"parameters": "Цей запит не має жодних параметрів",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"profile": "Login in to view your profile",
|
||||
"protocols": "Протоколи порожні",
|
||||
"schema": "Підключіться до кінцевої точки GraphQL",
|
||||
"shortcodes": "Shortcodes are empty",
|
||||
"team_name": "Назва команди порожня",
|
||||
"teams": "Команди порожні",
|
||||
"tests": "Для цього запиту немає тестів"
|
||||
},
|
||||
"environment": {
|
||||
"add_to_global": "Add to Global",
|
||||
"added": "Environment addition",
|
||||
"create_new": "Створіть нове середовище",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Редагувати середовище",
|
||||
"invalid_name": "Укажіть дійсну назву середовища",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "Нове середовище",
|
||||
"no_environment": "Жодного середовища",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"select": "Виберіть середовище",
|
||||
"title": "Середовища",
|
||||
"updated": "Environment updation",
|
||||
"variable_list": "Список змінних"
|
||||
},
|
||||
"error": {
|
||||
"browser_support_sse": "Схоже, цей браузер не підтримує події, надіслані сервером.",
|
||||
"check_console_details": "Детальніше перевірте журнал консолі.",
|
||||
"curl_invalid_format": "cURL неправильно форматовано",
|
||||
"empty_req_name": "Пуста назва запиту",
|
||||
"f12_details": "(F12 для деталей)",
|
||||
"gql_prettify_invalid_query": "Не вдалося попередньо визначити недійсний запит, вирішити синтаксичні помилки запиту та повторити спробу",
|
||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"invalid_link": "Invalid link",
|
||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||
"json_parsing_failed": "Invalid JSON",
|
||||
"json_prettify_invalid_body": "Не вдалося заздалегідь визначити недійсне тіло, вирішити синтаксичні помилки json і повторити спробу",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "Не вдалося надіслати запит",
|
||||
"no_duration": "Без тривалості",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"script_fail": "Не вдалося виконати сценарій попереднього запиту",
|
||||
"something_went_wrong": "Щось пішло не так",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Експортувати як JSON",
|
||||
"create_secret_gist": "Створіть секретну суть",
|
||||
"gist_created": "Суть створена",
|
||||
"require_github": "Увійдіть за допомогою GitHub, щоб створити секретну історію",
|
||||
"title": "Export"
|
||||
},
|
||||
"folder": {
|
||||
"created": "Папка створена",
|
||||
"edit": "Редагувати папку",
|
||||
"invalid_name": "Укажіть назву папки",
|
||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
||||
"new": "Нова папка",
|
||||
"renamed": "Папка перейменована"
|
||||
},
|
||||
"graphql": {
|
||||
"mutations": "Мутації",
|
||||
"schema": "Схема",
|
||||
"subscriptions": "Підписки"
|
||||
},
|
||||
"header": {
|
||||
"install_pwa": "Встановити додаток",
|
||||
"login": "Увійти",
|
||||
"save_workspace": "Зберегти мою робочу область"
|
||||
},
|
||||
"helpers": {
|
||||
"authorization": "Заголовок авторизації буде автоматично сформований під час надсилання запиту.",
|
||||
"generate_documentation_first": "Спочатку сформуйте документацію",
|
||||
"network_fail": "Не вдається зв'язатися з кінцевою точкою API. Перевірте підключення до мережі та повторіть спробу.",
|
||||
"offline": "Ви, здається, не в мережі. Дані в цій робочій області можуть бути не актуальними.",
|
||||
"offline_short": "Ви, здається, не в мережі.",
|
||||
"post_request_tests": "Тестові сценарії записуються на JavaScript і запускаються після отримання відповіді.",
|
||||
"pre_request_script": "Сценарії попереднього запиту написані на JavaScript і запускаються перед надсиланням запиту.",
|
||||
"script_fail": "Схоже, є збій у сценарії попереднього запиту. Перевірте помилку нижче та виправте відповідним чином сценарій.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Напишіть тестовий сценарій для автоматизації налагодження."
|
||||
},
|
||||
"hide": {
|
||||
"collection": "Collapse Collection Panel",
|
||||
"more": "Приховай більше",
|
||||
"preview": "Приховати попередній перегляд",
|
||||
"sidebar": "Приховати бічну панель"
|
||||
},
|
||||
"import": {
|
||||
"collections": "Імпортувати колекції",
|
||||
"curl": "Імпортувати cURL",
|
||||
"failed": "Не вдалося імпортувати",
|
||||
"from_gist": "Імпорт з Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Імпортувати з Моїх колекцій",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Введіть URL -адресу Gist",
|
||||
"import_from_url_invalid_fetch": "Couldn't get data from the url",
|
||||
"import_from_url_invalid_file_format": "Error while importing collections",
|
||||
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||
"import_from_url_success": "Collections Imported",
|
||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
||||
"title": "Імпорт"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "Collapse or Expand Collections",
|
||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
||||
"column": "Vertical layout",
|
||||
"name": "Layout",
|
||||
"row": "Horizontal layout",
|
||||
"zen_mode": "Режим дзен"
|
||||
},
|
||||
"modal": {
|
||||
"collections": "Колекції",
|
||||
"confirm": "Підтвердити",
|
||||
"edit_request": "Редагувати запит",
|
||||
"import_export": "Імпорт-експорт"
|
||||
},
|
||||
"mqtt": {
|
||||
"communication": "Спілкування",
|
||||
"log": "Журнал",
|
||||
"message": "повідомлення",
|
||||
"publish": "Публікуйте",
|
||||
"subscribe": "Підпишіться",
|
||||
"topic": "Тема",
|
||||
"topic_name": "Назва теми",
|
||||
"topic_title": "Опублікувати / підписатися на тему",
|
||||
"unsubscribe": "Скасувати підписку",
|
||||
"url": "URL"
|
||||
},
|
||||
"navigation": {
|
||||
"doc": "Документи",
|
||||
"graphql": "GraphQL",
|
||||
"profile": "Profile",
|
||||
"realtime": "Реальний час",
|
||||
"rest": "REST",
|
||||
"settings": "Налаштування"
|
||||
},
|
||||
"preRequest": {
|
||||
"javascript_code": "Код JavaScript",
|
||||
"learn": "Прочитайте документацію",
|
||||
"script": "Сценарій попереднього запиту",
|
||||
"snippets": "Фрагменти"
|
||||
},
|
||||
"profile": {
|
||||
"app_settings": "App Settings",
|
||||
"editor": "Editor",
|
||||
"editor_description": "Editors can add, edit, and delete requests.",
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Видалити зірочку"
|
||||
},
|
||||
"request": {
|
||||
"added": "Запит додано",
|
||||
"authorization": "Авторизація",
|
||||
"body": "Орган запиту",
|
||||
"choose_language": "Виберіть мову",
|
||||
"content_type": "Тип вмісту",
|
||||
"content_type_titles": {
|
||||
"others": "Others",
|
||||
"structured": "Structured",
|
||||
"text": "Text"
|
||||
},
|
||||
"copy_link": "Скопіювати посилання",
|
||||
"duration": "Тривалість",
|
||||
"enter_curl": "Введіть cURL",
|
||||
"generate_code": "Сформувати код",
|
||||
"generated_code": "Сформований код",
|
||||
"header_list": "Список заголовків",
|
||||
"invalid_name": "Будь ласка, вкажіть назву запиту",
|
||||
"method": "Метод",
|
||||
"name": "Назва запиту",
|
||||
"new": "New Request",
|
||||
"override": "Override",
|
||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
||||
"overriden": "Overridden",
|
||||
"parameter_list": "Параметри запиту",
|
||||
"parameters": "Параметри",
|
||||
"path": "Шлях",
|
||||
"payload": "Корисне навантаження",
|
||||
"query": "Запит",
|
||||
"raw_body": "Сировина запиту",
|
||||
"renamed": "Запит перейменовано",
|
||||
"run": "Біжи",
|
||||
"save": "Зберегти",
|
||||
"save_as": "Зберегти як",
|
||||
"saved": "Запит збережено",
|
||||
"share": "Поділитися",
|
||||
"share_description": "Share Hoppscotch with your friends",
|
||||
"title": "Запит",
|
||||
"type": "Тип запиту",
|
||||
"url": "URL",
|
||||
"variables": "Змінні",
|
||||
"view_my_links": "View my links"
|
||||
},
|
||||
"response": {
|
||||
"body": "Орган реагування",
|
||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||
"headers": "Заголовки",
|
||||
"html": "HTML",
|
||||
"image": "Зображення",
|
||||
"json": "JSON",
|
||||
"pdf": "PDF",
|
||||
"preview_html": "Попередній перегляд HTML",
|
||||
"raw": "Сирий",
|
||||
"size": "Розмір",
|
||||
"status": "Статус",
|
||||
"time": "Час",
|
||||
"title": "Відповідь",
|
||||
"waiting_for_connection": "очікування підключення",
|
||||
"xml": "XML"
|
||||
},
|
||||
"settings": {
|
||||
"accent_color": "Колір акценту",
|
||||
"account": "Рахунок",
|
||||
"account_description": "Налаштуйте налаштування свого облікового запису.",
|
||||
"account_email_description": "Ваша основна електронна адреса.",
|
||||
"account_name_description": "Це ваше відображуване ім'я.",
|
||||
"background": "Довідка",
|
||||
"black_mode": "Чорний",
|
||||
"change_font_size": "Змінити розмір шрифту",
|
||||
"choose_language": "Виберіть мову",
|
||||
"dark_mode": "Темний",
|
||||
"expand_navigation": "Expand navigation",
|
||||
"experiments": "Експерименти",
|
||||
"experiments_notice": "Це збірка експериментів, над якими ми працюємо, які можуть виявитися корисними, веселими, обома чи ні. Вони не остаточні і можуть бути нестійкими, тому, якщо трапиться щось надто дивне, не панікуйте. Просто вимкніть небезпеку. Жарти в сторону,",
|
||||
"extension_ver_not_reported": "Не повідомляється",
|
||||
"extension_version": "Версія розширення",
|
||||
"extensions": "Розширення",
|
||||
"extensions_use_toggle": "Використовуйте розширення браузера для надсилання запитів (якщо вони є)",
|
||||
"follow": "Follow Us",
|
||||
"font_size": "Розмір шрифту",
|
||||
"font_size_large": "Великий",
|
||||
"font_size_medium": "Середній",
|
||||
"font_size_small": "Маленький",
|
||||
"interceptor": "Перехоплювач",
|
||||
"interceptor_description": "Проміжне програмне забезпечення між додатками та API.",
|
||||
"language": "Мова",
|
||||
"light_mode": "Світло",
|
||||
"official_proxy_hosting": "Офіційний проксі розміщений компанією Hoppscotch.",
|
||||
"profile": "Profile",
|
||||
"profile_description": "Update your profile details",
|
||||
"profile_email": "Email address",
|
||||
"profile_name": "Profile name",
|
||||
"proxy": "Проксі",
|
||||
"proxy_url": "URL проксі",
|
||||
"proxy_use_toggle": "Для надсилання запитів використовуйте проксі -сервер",
|
||||
"read_the": "Читати",
|
||||
"reset_default": "Скинути налаштування за замовчуванням",
|
||||
"short_codes": "Short codes",
|
||||
"short_codes_description": "Short codes which were created by you.",
|
||||
"sidebar_on_left": "Sidebar on left",
|
||||
"sync": "Синхронізувати",
|
||||
"sync_collections": "Колекції",
|
||||
"sync_description": "Ці налаштування синхронізуються з хмарою.",
|
||||
"sync_environments": "Середовища",
|
||||
"sync_history": "Історія",
|
||||
"system_mode": "Система",
|
||||
"telemetry": "Телеметрія",
|
||||
"telemetry_helps_us": "Телеметрія допомагає нам персоналізувати наші операції та забезпечити вам найкращий досвід.",
|
||||
"theme": "Тема",
|
||||
"theme_description": "Налаштуйте тему програми.",
|
||||
"use_experimental_url_bar": "Використовуйте експериментальний рядок URL з виділенням середовища",
|
||||
"user": "Користувач",
|
||||
"verified_email": "Verified email",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shortcodes": {
|
||||
"actions": "Actions",
|
||||
"created_on": "Created on",
|
||||
"deleted": "Shortcode deleted",
|
||||
"method": "Method",
|
||||
"not_found": "Shortcode not found",
|
||||
"short_code": "Short code",
|
||||
"url": "URL"
|
||||
},
|
||||
"shortcut": {
|
||||
"general": {
|
||||
"close_current_menu": "Закрити поточне меню",
|
||||
"command_menu": "Меню пошуку та команд",
|
||||
"help_menu": "Меню довідки",
|
||||
"show_all": "Гарячі клавіши",
|
||||
"title": "Загальні"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Запросіть людей до Hoppscotch",
|
||||
"title": "Різне"
|
||||
},
|
||||
"navigation": {
|
||||
"back": "Повернутися на попередню сторінку",
|
||||
"documentation": "Перейдіть на сторінку Документація",
|
||||
"forward": "Перейти до наступної сторінки",
|
||||
"graphql": "Перейдіть на сторінку GraphQL",
|
||||
"profile": "Go to Profile page",
|
||||
"realtime": "Перейдіть на сторінку реального часу",
|
||||
"rest": "Перейдіть на сторінку REST",
|
||||
"settings": "Перейдіть на сторінку Налаштування",
|
||||
"title": "Навігація"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "Скопіювати посилання на запит",
|
||||
"delete_method": "Виберіть метод ВИДАЛИТИ",
|
||||
"get_method": "Виберіть метод GET",
|
||||
"head_method": "Виберіть метод HEAD",
|
||||
"method": "Метод",
|
||||
"next_method": "Виберіть наступний метод",
|
||||
"post_method": "Виберіть метод POST",
|
||||
"previous_method": "Виберіть Попередній метод",
|
||||
"put_method": "Виберіть метод PUT",
|
||||
"reset_request": "Скинути запит",
|
||||
"save_to_collections": "Зберегти в колекції",
|
||||
"send_request": "Відправляти запит",
|
||||
"title": "Запит"
|
||||
},
|
||||
"theme": {
|
||||
"black": "Switch theme to black mode",
|
||||
"dark": "Switch theme to dark mode",
|
||||
"light": "Switch theme to light mode",
|
||||
"system": "Switch theme to system mode",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"code": "Показати код",
|
||||
"collection": "Expand Collection Panel",
|
||||
"more": "Показати більше",
|
||||
"sidebar": "Показати бічну панель"
|
||||
},
|
||||
"socketio": {
|
||||
"communication": "Спілкування",
|
||||
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||
"event_name": "Назва події",
|
||||
"events": "Події",
|
||||
"log": "Журнал",
|
||||
"url": "URL"
|
||||
},
|
||||
"sse": {
|
||||
"event_type": "Тип події",
|
||||
"log": "Журнал",
|
||||
"url": "URL"
|
||||
},
|
||||
"state": {
|
||||
"bulk_mode": "Масове редагування",
|
||||
"bulk_mode_placeholder": "Записи розділені новим рядком\nКлючі та значення розділені:\nДодати # до будь -якого рядка, який потрібно додати, але залишити вимкненим",
|
||||
"cleared": "Очищено",
|
||||
"connected": "Підключено",
|
||||
"connected_to": "Підключено до {name}",
|
||||
"connecting_to": "Під'єднання до {name} ...",
|
||||
"connection_error": "Failed to connect",
|
||||
"connection_failed": "Connection failed",
|
||||
"connection_lost": "Connection lost",
|
||||
"copied_to_clipboard": "Скопійовано в буфер обміну",
|
||||
"deleted": "Видалено",
|
||||
"deprecated": "ЗНИЖЕНО",
|
||||
"disabled": "Інвалід",
|
||||
"disconnected": "Відключено",
|
||||
"disconnected_from": "Від'єднано від {name}",
|
||||
"docs_generated": "Сформована документація",
|
||||
"download_started": "Завантаження розпочато",
|
||||
"enabled": "Увімкнено",
|
||||
"file_imported": "Файл імпортовано",
|
||||
"finished_in": "Завершено за {duration} мс",
|
||||
"history_deleted": "Історію видалено",
|
||||
"linewrap": "Лінії загортання",
|
||||
"loading": "Завантаження ...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"none": "Жодного",
|
||||
"nothing_found": "Нічого не знайдено",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||
"waiting_send_request": "Очікується надсилання запиту"
|
||||
},
|
||||
"support": {
|
||||
"changelog": "Докладніше про останні випуски",
|
||||
"chat": "Питання? Спілкуйтеся з нами!",
|
||||
"community": "Задавайте питання та допомагайте іншим",
|
||||
"documentation": "Детальніше про Hoppscotch",
|
||||
"forum": "Задавайте питання і отримуйте відповіді",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Швидше переглядайте програми",
|
||||
"team": "Зв'яжіться з командою",
|
||||
"title": "Підтримка",
|
||||
"twitter": "Слідкуйте за нами у Twitter"
|
||||
},
|
||||
"tab": {
|
||||
"authorization": "Авторизація",
|
||||
"body": "Тіло",
|
||||
"collections": "Колекції",
|
||||
"documentation": "Документація",
|
||||
"headers": "Заголовки",
|
||||
"history": "Історія",
|
||||
"mqtt": "MQTT",
|
||||
"parameters": "Параметри",
|
||||
"pre_request_script": "Сценарій попереднього запиту",
|
||||
"queries": "Запити",
|
||||
"query": "Запит",
|
||||
"schema": "Schema",
|
||||
"socketio": "Сокет.IO",
|
||||
"sse": "SSE",
|
||||
"tests": "Тести",
|
||||
"types": "Типи",
|
||||
"variables": "Змінні",
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Створіть нову команду",
|
||||
"deleted": "Команда видалена",
|
||||
"edit": "Редагувати команду",
|
||||
"email": "Електронна пошта",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Вийти з команди",
|
||||
"exit_disabled": "Вийти з команди не може тільки власник",
|
||||
"invalid_email_format": "Формат електронної пошти недійсний",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Надайте дійсний дозвіл члену команди",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Приєднуйтесь до бета -програми, щоб отримати доступ до команд.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "Ви покинули команду",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_removed": "Користувача видалено",
|
||||
"member_role_updated": "Оновлено ролі користувачів",
|
||||
"members": "Члени",
|
||||
"name_length_insufficient": "Назва команди має містити щонайменше 6 символів",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "Нова команда",
|
||||
"new_created": "Створена нова команда",
|
||||
"new_name": "Моя нова команда",
|
||||
"no_access": "Ви не маєте доступу до редагування цих колекцій",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Дозволи",
|
||||
"saved": "Команда збережена",
|
||||
"select_a_team": "Select a team",
|
||||
"title": "Команди",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
},
|
||||
"test": {
|
||||
"failed": "test failed",
|
||||
"javascript_code": "Код JavaScript",
|
||||
"learn": "Прочитайте документацію",
|
||||
"passed": "test passed",
|
||||
"report": "Протокол випробування",
|
||||
"results": "Результати тесту",
|
||||
"script": "Сценарій",
|
||||
"snippets": "Фрагменти"
|
||||
},
|
||||
"websocket": {
|
||||
"communication": "Спілкування",
|
||||
"log": "Журнал",
|
||||
"message": "повідомлення",
|
||||
"protocols": "Протоколи",
|
||||
"url": "URL"
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"autoscroll": "Autoscroll",
|
||||
"cancel": "Hủy bỏ",
|
||||
"choose_file": "Chọn một tệp",
|
||||
"clear": "Thông thoáng",
|
||||
"clear_all": "Quet sạch tât cả",
|
||||
"close": "Close",
|
||||
"connect": "Liên kết",
|
||||
"copy": "Sao chép",
|
||||
"delete": "Xóa bỏ",
|
||||
"disconnect": "Ngắt kết nối",
|
||||
"dismiss": "Miễn nhiệm",
|
||||
"dont_save": "Don't save",
|
||||
"download_file": "Tải tập tin",
|
||||
"drag_to_reorder": "Drag to reorder",
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Chỉnh sửa",
|
||||
"filter_response": "Filter response",
|
||||
"go_back": "Quay lại",
|
||||
"label": "Nhãn",
|
||||
"learn_more": "Tìm hiểu thêm",
|
||||
"less": "Less",
|
||||
"more": "Hơn",
|
||||
"new": "Mới mẻ",
|
||||
"no": "Không",
|
||||
"open_workspace": "Open workspace",
|
||||
"paste": "Paste",
|
||||
"prettify": "Kiểm tra trước",
|
||||
"remove": "Tẩy",
|
||||
"restore": "Khôi phục",
|
||||
"save": "Cứu",
|
||||
"scroll_to_bottom": "Scroll to bottom",
|
||||
"scroll_to_top": "Scroll to top",
|
||||
"search": "Tìm kiếm",
|
||||
"send": "Gửi",
|
||||
"start": "Bắt đầu",
|
||||
"stop": "Ngừng lại",
|
||||
"to_close": "to close",
|
||||
"to_navigate": "to navigate",
|
||||
"to_select": "to select",
|
||||
"turn_off": "Tắt",
|
||||
"turn_on": "Bật",
|
||||
"undo": "Hoàn tác",
|
||||
"yes": "đúng"
|
||||
},
|
||||
"add": {
|
||||
"new": "Thêm mới",
|
||||
"star": "Thêm dấu sao"
|
||||
},
|
||||
"app": {
|
||||
"chat_with_us": "Trò chuyện với chúng tôi",
|
||||
"contact_us": "Liên hệ chúng tôi",
|
||||
"copy": "Sao chép",
|
||||
"copy_user_id": "Copy User Auth Token",
|
||||
"developer_option": "Developer options",
|
||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
||||
"discord": "Discord",
|
||||
"documentation": "Tài liệu",
|
||||
"github": "GitHub",
|
||||
"help": "Tài liệu trợ giúp, phản hồi và",
|
||||
"home": "Nhà",
|
||||
"invite": "Mời gọi",
|
||||
"invite_description": "Trong Hoppscotch, chúng tôi đã thiết kế một giao diện đơn giản và trực quan để tạo và quản lý các API của bạn. Hoppscotch là một công cụ giúp bạn xây dựng, kiểm tra, lập tài liệu và chia sẻ các API của mình.",
|
||||
"invite_your_friends": "Mời bạn bè của bạn",
|
||||
"join_discord_community": "Tham gia cộng đồng Discord của chúng tôi",
|
||||
"keyboard_shortcuts": "Các phím tắt bàn phím",
|
||||
"name": "Nhảy lò cò",
|
||||
"new_version_found": "Phiên bản mới được tìm thấy. Làm mới để cập nhật.",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Chính sách về quyền riêng tư của proxy",
|
||||
"reload": "Nạp lại",
|
||||
"search": "Tìm kiếm",
|
||||
"share": "Chia sẻ",
|
||||
"shortcuts": "Các phím tắt",
|
||||
"spotlight": "Đốm sáng",
|
||||
"status": "Tình trạng",
|
||||
"status_description": "Check the status of the website",
|
||||
"terms_and_privacy": "Điều khoản và quyền riêng tư",
|
||||
"twitter": "Twitter",
|
||||
"type_a_command_search": "Nhập lệnh hoặc tìm kiếm…",
|
||||
"we_use_cookies": "Chúng tôi sử dụng cookie",
|
||||
"whats_new": "Có gì mới?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"auth": {
|
||||
"account_exists": "Tài khoản tồn tại với thông tin đăng nhập khác - Đăng nhập để liên kết cả hai tài khoản",
|
||||
"all_sign_in_options": "Tất cả các tùy chọn đăng nhập",
|
||||
"continue_with_email": "Tiếp tục với Email",
|
||||
"continue_with_github": "Tiếp tục với GitHub",
|
||||
"continue_with_google": "Tiếp tục với Google",
|
||||
"continue_with_microsoft": "Continue with Microsoft",
|
||||
"email": "E-mail",
|
||||
"logged_out": "Đã đăng xuất",
|
||||
"login": "Đăng nhập",
|
||||
"login_success": "Đã đăng nhập thành công",
|
||||
"login_to_hoppscotch": "Đăng nhập Hoppscotch",
|
||||
"logout": "Đăng xuất",
|
||||
"re_enter_email": "Nhập lại địa chỉ e-mail",
|
||||
"send_magic_link": "Gửi một liên kết kỳ diệu",
|
||||
"sync": "Đồng bộ hóa",
|
||||
"we_sent_magic_link": "Chúng tôi đã gửi cho bạn một liên kết kỳ diệu!",
|
||||
"we_sent_magic_link_description": "Kiểm tra hộp thư đến của bạn - chúng tôi đã gửi một email đến {email}. Nó chứa một liên kết kỳ diệu sẽ giúp bạn đăng nhập."
|
||||
},
|
||||
"authorization": {
|
||||
"generate_token": "Tạo mã thông báo",
|
||||
"include_in_url": "Bao gồm trong URL",
|
||||
"learn": "Học cách",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Mật khẩu",
|
||||
"token": "Mã thông báo",
|
||||
"type": "Loại ủy quyền",
|
||||
"username": "tên tài khoản"
|
||||
},
|
||||
"collection": {
|
||||
"created": "Bộ sưu tập đã được tạo",
|
||||
"edit": "Chỉnh sửa bộ sưu tập",
|
||||
"invalid_name": "Vui lòng cung cấp tên hợp lệ cho bộ sưu tập",
|
||||
"my_collections": "Bộ sưu tập của tôi",
|
||||
"name": "Bộ sưu tập mới của tôi",
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "Bộ sưu tập mới",
|
||||
"renamed": "Bộ sưu tập đã được đổi tên",
|
||||
"request_in_use": "Request in use",
|
||||
"save_as": "Lưu thành",
|
||||
"select": "Chọn một Bộ sưu tập",
|
||||
"select_location": "Chọn địa điểm",
|
||||
"select_team": "Chọn một đội",
|
||||
"team_collections": "Bộ sưu tập nhóm"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "Are you sure you want to leave this team?",
|
||||
"logout": "Bạn có chắc chắn bạn muốn thoát?",
|
||||
"remove_collection": "Bạn có chắc chắn muốn xóa vĩnh viễn bộ sưu tập này không?",
|
||||
"remove_environment": "Bạn có chắc chắn muốn xóa vĩnh viễn môi trường này không?",
|
||||
"remove_folder": "Bạn có chắc chắn muốn xóa vĩnh viễn thư mục này không?",
|
||||
"remove_history": "Bạn có chắc chắn muốn xóa vĩnh viễn tất cả lịch sử không?",
|
||||
"remove_request": "Bạn có chắc chắn muốn xóa vĩnh viễn yêu cầu này không?",
|
||||
"remove_team": "Bạn có chắc chắn muốn xóa nhóm này không?",
|
||||
"remove_telemetry": "Bạn có chắc chắn muốn chọn không tham gia Đo lường từ xa không?",
|
||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||
"sync": "Bạn có chắc chắn muốn đồng bộ hóa không gian làm việc này không?"
|
||||
},
|
||||
"count": {
|
||||
"header": "Tiêu đề {count}",
|
||||
"message": "Nhắn tin cho {count}",
|
||||
"parameter": "Tham số {count}",
|
||||
"protocol": "Giao thức {count}",
|
||||
"value": "Giá trị {count}",
|
||||
"variable": "Biến {count}"
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "Tạo tài liệu",
|
||||
"generate_message": "Nhập bất kỳ bộ sưu tập Hoppscotch nào để tạo tài liệu API khi đang di chuyển."
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "Yêu cầu này không sử dụng bất kỳ ủy quyền nào",
|
||||
"body": "Yêu cầu này không có nội dung",
|
||||
"collection": "Bộ sưu tập trống",
|
||||
"collections": "Bộ sưu tập trống",
|
||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
||||
"endpoint": "Endpoint cannot be empty",
|
||||
"environments": "Môi trường trống",
|
||||
"folder": "Tệp này rỗng",
|
||||
"headers": "Yêu cầu này không có bất kỳ tiêu đề nào",
|
||||
"history": "Lịch sử trống",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Đội trống",
|
||||
"parameters": "Yêu cầu này không có bất kỳ thông số nào",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
"profile": "Login in to view your profile",
|
||||
"protocols": "Giao thức trống",
|
||||
"schema": "Kết nối với một điểm cuối GraphQL",
|
||||
"shortcodes": "Shortcodes are empty",
|
||||
"team_name": "Tên đội trống",
|
||||
"teams": "Các đội trống",
|
||||
"tests": "Không có bài kiểm tra nào cho yêu cầu này"
|
||||
},
|
||||
"environment": {
|
||||
"add_to_global": "Add to Global",
|
||||
"added": "Environment addition",
|
||||
"create_new": "Tạo môi trường mới",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Chỉnh sửa môi trường",
|
||||
"invalid_name": "Vui lòng cung cấp tên hợp lệ cho môi trường",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "Môi trường mới",
|
||||
"no_environment": "Không có môi trường",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"select": "Chọn môi trường",
|
||||
"title": "Môi trường",
|
||||
"updated": "Environment updation",
|
||||
"variable_list": "Danh sách biến"
|
||||
},
|
||||
"error": {
|
||||
"browser_support_sse": "Trình duyệt này dường như không có hỗ trợ Sự kiện do Máy chủ gửi.",
|
||||
"check_console_details": "Kiểm tra nhật ký bảng điều khiển để biết chi tiết.",
|
||||
"curl_invalid_format": "cURL không được định dạng đúng",
|
||||
"empty_req_name": "Tên yêu cầu trống",
|
||||
"f12_details": "(F12 để biết chi tiết)",
|
||||
"gql_prettify_invalid_query": "Không thể xác minh trước một truy vấn không hợp lệ, hãy giải quyết các lỗi cú pháp truy vấn và thử lại",
|
||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"invalid_link": "Invalid link",
|
||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||
"json_parsing_failed": "Invalid JSON",
|
||||
"json_prettify_invalid_body": "Không thể kiểm tra nội dung không hợp lệ, hãy giải quyết lỗi cú pháp json và thử lại",
|
||||
"network_error": "There seems to be a network error. Please try again.",
|
||||
"network_fail": "Không thể gửi yêu cầu",
|
||||
"no_duration": "Không có thời lượng",
|
||||
"no_results_found": "No matches found",
|
||||
"page_not_found": "This page could not be found",
|
||||
"script_fail": "Không thể thực thi tập lệnh yêu cầu trước",
|
||||
"something_went_wrong": "Đã xảy ra sự cố",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Xuất dưới dạng JSON",
|
||||
"create_secret_gist": "Tạo Gist bí mật",
|
||||
"gist_created": "Gist đã tạo",
|
||||
"require_github": "Đăng nhập bằng GitHub để tạo ý chính bí mật",
|
||||
"title": "Export"
|
||||
},
|
||||
"folder": {
|
||||
"created": "Thư mục đã được tạo",
|
||||
"edit": "Chỉnh sửa thư mục",
|
||||
"invalid_name": "Vui lòng cung cấp tên cho thư mục",
|
||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
||||
"new": "Thư mục mới",
|
||||
"renamed": "Thư mục đã được đổi tên"
|
||||
},
|
||||
"graphql": {
|
||||
"mutations": "Đột biến",
|
||||
"schema": "Lược đồ",
|
||||
"subscriptions": "Đăng ký"
|
||||
},
|
||||
"header": {
|
||||
"install_pwa": "Cài đặt ứng dụng",
|
||||
"login": "Đăng nhập",
|
||||
"save_workspace": "Lưu không gian làm việc của tôi"
|
||||
},
|
||||
"helpers": {
|
||||
"authorization": "Tiêu đề ủy quyền sẽ được tạo tự động khi bạn gửi yêu cầu.",
|
||||
"generate_documentation_first": "Tạo tài liệu trước tiên",
|
||||
"network_fail": "Không thể truy cập điểm cuối API. Kiểm tra kết nối mạng của bạn và thử lại.",
|
||||
"offline": "Có vẻ như bạn đang ngoại tuyến. Dữ liệu trong không gian làm việc này có thể không được cập nhật.",
|
||||
"offline_short": "Có vẻ như bạn đang ngoại tuyến.",
|
||||
"post_request_tests": "Các tập lệnh kiểm tra được viết bằng JavaScript và được chạy sau khi nhận được phản hồi.",
|
||||
"pre_request_script": "Các tập lệnh yêu cầu trước được viết bằng JavaScript và được chạy trước khi yêu cầu được gửi đi.",
|
||||
"script_fail": "Có vẻ như có trục trặc trong tập lệnh yêu cầu trước. Kiểm tra lỗi bên dưới và sửa tập lệnh cho phù hợp.",
|
||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
||||
"tests": "Viết một kịch bản thử nghiệm để tự động gỡ lỗi."
|
||||
},
|
||||
"hide": {
|
||||
"collection": "Collapse Collection Panel",
|
||||
"more": "Ẩn thêm",
|
||||
"preview": "Ẩn bản xem trước",
|
||||
"sidebar": "Ẩn thanh bên"
|
||||
},
|
||||
"import": {
|
||||
"collections": "Nhập bộ sưu tập",
|
||||
"curl": "Nhập cURL",
|
||||
"failed": "Nhập không thành công",
|
||||
"from_gist": "Nhập từ Gist",
|
||||
"from_gist_description": "Import from Gist URL",
|
||||
"from_insomnia": "Import from Insomnia",
|
||||
"from_insomnia_description": "Import from Insomnia collection",
|
||||
"from_json": "Import from Hoppscotch",
|
||||
"from_json_description": "Import from Hoppscotch collection file",
|
||||
"from_my_collections": "Nhập từ Bộ sưu tập của tôi",
|
||||
"from_my_collections_description": "Import from My Collections file",
|
||||
"from_openapi": "Import from OpenAPI",
|
||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
||||
"from_postman": "Import from Postman",
|
||||
"from_postman_description": "Import from Postman collection",
|
||||
"from_url": "Import from URL",
|
||||
"gist_url": "Nhập URL Gist",
|
||||
"import_from_url_invalid_fetch": "Couldn't get data from the url",
|
||||
"import_from_url_invalid_file_format": "Error while importing collections",
|
||||
"import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'",
|
||||
"import_from_url_success": "Collections Imported",
|
||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
||||
"title": "Nhập khẩu"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "Collapse or Expand Collections",
|
||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
||||
"column": "Vertical layout",
|
||||
"name": "Layout",
|
||||
"row": "Horizontal layout",
|
||||
"zen_mode": "Chế độ Zen"
|
||||
},
|
||||
"modal": {
|
||||
"collections": "Bộ sưu tập",
|
||||
"confirm": "Xác nhận",
|
||||
"edit_request": "Chỉnh sửa Yêu cầu",
|
||||
"import_export": "Nhập khẩu xuất khẩu"
|
||||
},
|
||||
"mqtt": {
|
||||
"communication": "Liên lạc",
|
||||
"log": "Nhật ký",
|
||||
"message": "Thông điệp",
|
||||
"publish": "Công bố",
|
||||
"subscribe": "Đặt mua",
|
||||
"topic": "Chủ đề",
|
||||
"topic_name": "Tên chủ đề",
|
||||
"topic_title": "Xuất bản / Đăng ký chủ đề",
|
||||
"unsubscribe": "Hủy đăng ký",
|
||||
"url": "URL"
|
||||
},
|
||||
"navigation": {
|
||||
"doc": "Docs",
|
||||
"graphql": "GraphQL",
|
||||
"profile": "Profile",
|
||||
"realtime": "Thời gian thực",
|
||||
"rest": "REST",
|
||||
"settings": "Cài đặt"
|
||||
},
|
||||
"preRequest": {
|
||||
"javascript_code": "Mã JavaScript",
|
||||
"learn": "Đọc tài liệu",
|
||||
"script": "Tập lệnh Yêu cầu Trước",
|
||||
"snippets": "Đoạn mã"
|
||||
},
|
||||
"profile": {
|
||||
"app_settings": "App Settings",
|
||||
"editor": "Editor",
|
||||
"editor_description": "Editors can add, edit, and delete requests.",
|
||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
||||
"no_permission": "You do not have permission to perform this action.",
|
||||
"owner": "Owner",
|
||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
||||
"roles": "Roles",
|
||||
"roles_description": "Roles are used to control access to the shared collections.",
|
||||
"updated": "Profile updated",
|
||||
"viewer": "Viewer",
|
||||
"viewer_description": "Viewers can only view and use requests."
|
||||
},
|
||||
"remove": {
|
||||
"star": "Xóa dấu sao"
|
||||
},
|
||||
"request": {
|
||||
"added": "Đã thêm yêu cầu",
|
||||
"authorization": "Ủy quyền",
|
||||
"body": "Nội dung yêu cầu",
|
||||
"choose_language": "Chọn ngôn ngữ",
|
||||
"content_type": "Loại nội dung",
|
||||
"content_type_titles": {
|
||||
"others": "Others",
|
||||
"structured": "Structured",
|
||||
"text": "Text"
|
||||
},
|
||||
"copy_link": "Sao chép đường dẫn",
|
||||
"duration": "Khoảng thời gian",
|
||||
"enter_curl": "Nhập cURL",
|
||||
"generate_code": "Tạo mã",
|
||||
"generated_code": "Mã đã tạo",
|
||||
"header_list": "Danh sách tiêu đề",
|
||||
"invalid_name": "Vui lòng cung cấp tên cho yêu cầu",
|
||||
"method": "Phương pháp",
|
||||
"name": "Yêu cầu tên",
|
||||
"new": "New Request",
|
||||
"override": "Override",
|
||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
||||
"overriden": "Overridden",
|
||||
"parameter_list": "Tham số truy vấn",
|
||||
"parameters": "Thông số",
|
||||
"path": "Đường dẫn",
|
||||
"payload": "Khối hàng",
|
||||
"query": "Truy vấn",
|
||||
"raw_body": "Nội dung yêu cầu thô",
|
||||
"renamed": "Yêu cầu đổi tên",
|
||||
"run": "Chạy",
|
||||
"save": "Cứu",
|
||||
"save_as": "Lưu thành",
|
||||
"saved": "Yêu cầu đã được lưu",
|
||||
"share": "Chia sẻ",
|
||||
"share_description": "Share Hoppscotch with your friends",
|
||||
"title": "Yêu cầu",
|
||||
"type": "Loại yêu cầu",
|
||||
"url": "URL",
|
||||
"variables": "Biến",
|
||||
"view_my_links": "View my links"
|
||||
},
|
||||
"response": {
|
||||
"body": "Cơ quan phản hồi",
|
||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||
"headers": "Tiêu đề",
|
||||
"html": "HTML",
|
||||
"image": "Hình ảnh",
|
||||
"json": "JSON",
|
||||
"pdf": "PDF",
|
||||
"preview_html": "Xem trước HTML",
|
||||
"raw": "Nguyên",
|
||||
"size": "Kích thước",
|
||||
"status": "Tình trạng",
|
||||
"time": "Thời gian",
|
||||
"title": "Phản ứng",
|
||||
"waiting_for_connection": "Đang đợi kết nối",
|
||||
"xml": "XML"
|
||||
},
|
||||
"settings": {
|
||||
"accent_color": "Màu nhấn",
|
||||
"account": "Tài khoản",
|
||||
"account_description": "Tùy chỉnh cài đặt tài khoản của bạn.",
|
||||
"account_email_description": "Địa chỉ email chính của bạn.",
|
||||
"account_name_description": "Đây là tên hiển thị của bạn.",
|
||||
"background": "lai lịch",
|
||||
"black_mode": "Đen",
|
||||
"change_font_size": "Thay đổi kích thước phông chữ",
|
||||
"choose_language": "Chọn ngôn ngữ",
|
||||
"dark_mode": "Tối tăm",
|
||||
"expand_navigation": "Expand navigation",
|
||||
"experiments": "Thí nghiệm",
|
||||
"experiments_notice": "Đây là một bộ sưu tập các thử nghiệm mà chúng tôi đang thực hiện có thể hữu ích, thú vị, cả hai hoặc không. Chúng không phải là cuối cùng và có thể không ổn định, vì vậy nếu có điều gì đó quá kỳ lạ xảy ra, đừng hoảng sợ. Chỉ cần tắt điều này đi. Chuyện cười sang một bên,",
|
||||
"extension_ver_not_reported": "Không được báo cáo",
|
||||
"extension_version": "Phiên bản tiện ích mở rộng",
|
||||
"extensions": "Tiện ích mở rộng",
|
||||
"extensions_use_toggle": "Sử dụng tiện ích mở rộng của trình duyệt để gửi yêu cầu (nếu có)",
|
||||
"follow": "Follow Us",
|
||||
"font_size": "Cỡ chữ",
|
||||
"font_size_large": "Lớn",
|
||||
"font_size_medium": "Vừa phải",
|
||||
"font_size_small": "Bé nhỏ",
|
||||
"interceptor": "Máy đánh chặn",
|
||||
"interceptor_description": "Phần mềm trung gian giữa ứng dụng và các API.",
|
||||
"language": "Ngôn ngữ",
|
||||
"light_mode": "Soi rọi",
|
||||
"official_proxy_hosting": "Proxy chính thức được lưu trữ bởi Hoppscotch.",
|
||||
"profile": "Profile",
|
||||
"profile_description": "Update your profile details",
|
||||
"profile_email": "Email address",
|
||||
"profile_name": "Profile name",
|
||||
"proxy": "Ủy quyền",
|
||||
"proxy_url": "URL proxy",
|
||||
"proxy_use_toggle": "Sử dụng phần mềm trung gian proxy để gửi yêu cầu",
|
||||
"read_the": "Đọc",
|
||||
"reset_default": "Đặt lại về mặc định",
|
||||
"short_codes": "Short codes",
|
||||
"short_codes_description": "Short codes which were created by you.",
|
||||
"sidebar_on_left": "Sidebar on left",
|
||||
"sync": "Làm cho đồng bộ",
|
||||
"sync_collections": "Bộ sưu tập",
|
||||
"sync_description": "Các cài đặt này được đồng bộ hóa với đám mây.",
|
||||
"sync_environments": "Môi trường",
|
||||
"sync_history": "Môn lịch sử",
|
||||
"system_mode": "Hệ thống",
|
||||
"telemetry": "Từ xa",
|
||||
"telemetry_helps_us": "Phép đo từ xa giúp chúng tôi cá nhân hóa các hoạt động của mình và mang đến trải nghiệm tốt nhất cho bạn.",
|
||||
"theme": "Chủ đề",
|
||||
"theme_description": "Tùy chỉnh chủ đề ứng dụng của bạn.",
|
||||
"use_experimental_url_bar": "Sử dụng thanh URL thử nghiệm với đánh dấu môi trường",
|
||||
"user": "Người sử dụng",
|
||||
"verified_email": "Verified email",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shortcodes": {
|
||||
"actions": "Actions",
|
||||
"created_on": "Created on",
|
||||
"deleted": "Shortcode deleted",
|
||||
"method": "Method",
|
||||
"not_found": "Shortcode not found",
|
||||
"short_code": "Short code",
|
||||
"url": "URL"
|
||||
},
|
||||
"shortcut": {
|
||||
"general": {
|
||||
"close_current_menu": "Đóng menu hiện tại",
|
||||
"command_menu": "Menu tìm kiếm và lệnh",
|
||||
"help_menu": "Danh sách trợ giúp",
|
||||
"show_all": "Các phím tắt bàn phím",
|
||||
"title": "Chung"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Mời mọi người nhảy lò cò",
|
||||
"title": "Điều khoản khác"
|
||||
},
|
||||
"navigation": {
|
||||
"back": "Quay lại trang trước",
|
||||
"documentation": "Đi tới trang Tài liệu",
|
||||
"forward": "Chuyển tới trang tiếp theo",
|
||||
"graphql": "Đi tới trang GraphQL",
|
||||
"profile": "Go to Profile page",
|
||||
"realtime": "Chuyển đến trang Thời gian thực",
|
||||
"rest": "Đi tới trang REST",
|
||||
"settings": "Đi tới trang Cài đặt",
|
||||
"title": "dẫn đường"
|
||||
},
|
||||
"request": {
|
||||
"copy_request_link": "Sao chép liên kết yêu cầu",
|
||||
"delete_method": "Chọn phương pháp XÓA",
|
||||
"get_method": "Chọn phương thức GET",
|
||||
"head_method": "Chọn phương pháp HEAD",
|
||||
"method": "Phương pháp",
|
||||
"next_method": "Chọn phương pháp tiếp theo",
|
||||
"post_method": "Chọn phương pháp ĐĂNG",
|
||||
"previous_method": "Chọn phương pháp trước đó",
|
||||
"put_method": "Chọn phương pháp PUT",
|
||||
"reset_request": "Đặt lại yêu cầu",
|
||||
"save_to_collections": "Lưu vào Bộ sưu tập",
|
||||
"send_request": "Gửi yêu cầu",
|
||||
"title": "Yêu cầu"
|
||||
},
|
||||
"theme": {
|
||||
"black": "Switch theme to black mode",
|
||||
"dark": "Switch theme to dark mode",
|
||||
"light": "Switch theme to light mode",
|
||||
"system": "Switch theme to system mode",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"code": "Hiển thị mã",
|
||||
"collection": "Expand Collection Panel",
|
||||
"more": "Cho xem nhiều hơn",
|
||||
"sidebar": "Hiển thị thanh bên"
|
||||
},
|
||||
"socketio": {
|
||||
"communication": "Liên lạc",
|
||||
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||
"event_name": "Tên sự kiện",
|
||||
"events": "Sự kiện",
|
||||
"log": "Nhật ký",
|
||||
"url": "URL"
|
||||
},
|
||||
"sse": {
|
||||
"event_type": "Loại sự kiện",
|
||||
"log": "Nhật ký",
|
||||
"url": "URL"
|
||||
},
|
||||
"state": {
|
||||
"bulk_mode": "Chỉnh sửa hàng loạt",
|
||||
"bulk_mode_placeholder": "Các mục nhập được phân tách bằng dòng mới\nCác khóa và giá trị được phân tách bằng:\nThêm # vào bất kỳ hàng nào bạn muốn thêm nhưng vẫn bị vô hiệu hóa",
|
||||
"cleared": "Đã xóa",
|
||||
"connected": "Đã kết nối",
|
||||
"connected_to": "Đã kết nối với {name}",
|
||||
"connecting_to": "Đang kết nối với {name} ...",
|
||||
"connection_error": "Failed to connect",
|
||||
"connection_failed": "Connection failed",
|
||||
"connection_lost": "Connection lost",
|
||||
"copied_to_clipboard": "Sao chép vào clipboard",
|
||||
"deleted": "Đã xóa",
|
||||
"deprecated": "KHÔNG DÙNG",
|
||||
"disabled": "Tàn tật",
|
||||
"disconnected": "Đã ngắt kết nối",
|
||||
"disconnected_from": "Đã ngắt kết nối khỏi {name}",
|
||||
"docs_generated": "Tài liệu được tạo",
|
||||
"download_started": "Đã bắt đầu tải xuống",
|
||||
"enabled": "Đã bật",
|
||||
"file_imported": "Đã nhập tệp",
|
||||
"finished_in": "Hoàn thành sau {duration} mili giây",
|
||||
"history_deleted": "Lịch sử đã bị xóa",
|
||||
"linewrap": "Quấn dòng",
|
||||
"loading": "Đang tải...",
|
||||
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||
"none": "Không có",
|
||||
"nothing_found": "Không tìm thấy kết quả cho",
|
||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||
"published_message": "Published message: {message} to topic: {topic}",
|
||||
"reconnection_error": "Failed to reconnect",
|
||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||
"waiting_send_request": "Đang chờ gửi yêu cầu"
|
||||
},
|
||||
"support": {
|
||||
"changelog": "Đọc thêm về các bản phát hành mới nhất",
|
||||
"chat": "Câu hỏi? Trò chuyện với chúng tôi!",
|
||||
"community": "Đặt câu hỏi và giúp đỡ người khác",
|
||||
"documentation": "Đọc thêm về Hoppscotch",
|
||||
"forum": "Đặt câu hỏi và nhận câu trả lời",
|
||||
"github": "Follow us on Github",
|
||||
"shortcuts": "Duyệt ứng dụng nhanh hơn",
|
||||
"team": "Liên hệ với nhóm",
|
||||
"title": "Ủng hộ",
|
||||
"twitter": "theo dõi chúng tối trên Twitter"
|
||||
},
|
||||
"tab": {
|
||||
"authorization": "Ủy quyền",
|
||||
"body": "Thân hình",
|
||||
"collections": "Bộ sưu tập",
|
||||
"documentation": "Tài liệu",
|
||||
"headers": "Tiêu đề",
|
||||
"history": "Môn lịch sử",
|
||||
"mqtt": "MQTT",
|
||||
"parameters": "Thông số",
|
||||
"pre_request_script": "Tập lệnh yêu cầu trước",
|
||||
"queries": "Truy vấn",
|
||||
"query": "Truy vấn",
|
||||
"schema": "Schema",
|
||||
"socketio": "Socket.IO",
|
||||
"sse": "SSE",
|
||||
"tests": "Kiểm tra",
|
||||
"types": "Các loại",
|
||||
"variables": "Biến",
|
||||
"websocket": "WebSocket"
|
||||
},
|
||||
"team": {
|
||||
"already_member": "You are already a member of this team. Contact your team owner.",
|
||||
"create_new": "Tạo nhóm mới",
|
||||
"deleted": "Đội đã bị xóa",
|
||||
"edit": "Chỉnh sửa nhóm",
|
||||
"email": "E-mail",
|
||||
"email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
|
||||
"exit": "Nhóm thoát",
|
||||
"exit_disabled": "Chỉ chủ sở hữu không thể thoát khỏi nhóm",
|
||||
"invalid_email_format": "Định dạng email không hợp lệ",
|
||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||
"invalid_invite_link": "Invalid invite link",
|
||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||
"invalid_member_permission": "Vui lòng cung cấp quyền hợp lệ cho thành viên trong nhóm",
|
||||
"invite": "Invite",
|
||||
"invite_more": "Invite more",
|
||||
"invite_tooltip": "Invite people to this workspace",
|
||||
"invited_to_team": "{owner} invited you to join {team}",
|
||||
"join": "Invitation accepted",
|
||||
"join_beta": "Tham gia chương trình beta để truy cập các nhóm.",
|
||||
"join_team": "Join {team}",
|
||||
"joined_team": "You have joined {team}",
|
||||
"joined_team_description": "You are now a member of this team",
|
||||
"left": "Bạn đã rời đội",
|
||||
"login_to_continue": "Login to continue",
|
||||
"login_to_continue_description": "You need to be logged in to join a team.",
|
||||
"logout_and_try_again": "Logout and sign in with another account",
|
||||
"member_has_invite": "This email ID already has an invite. Contact your team owner.",
|
||||
"member_not_found": "Member not found. Contact your team owner.",
|
||||
"member_removed": "Người dùng đã bị xóa",
|
||||
"member_role_updated": "Đã cập nhật vai trò người dùng",
|
||||
"members": "Các thành viên",
|
||||
"name_length_insufficient": "Tên nhóm phải dài ít nhất 6 ký tự",
|
||||
"name_updated": "Team name updated",
|
||||
"new": "Đội mới",
|
||||
"new_created": "Nhóm mới được tạo",
|
||||
"new_name": "Nhóm mới của tôi",
|
||||
"no_access": "Bạn không có quyền truy cập chỉnh sửa vào các bộ sưu tập này",
|
||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||
"not_found": "Team not found. Contact your team owner.",
|
||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||
"pending_invites": "Pending invites",
|
||||
"permissions": "Quyền",
|
||||
"saved": "Đội đã được lưu",
|
||||
"select_a_team": "Select a team",
|
||||
"title": "Đội",
|
||||
"we_sent_invite_link": "We sent an invite link to all invitees!",
|
||||
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
|
||||
},
|
||||
"test": {
|
||||
"failed": "test failed",
|
||||
"javascript_code": "Mã JavaScript",
|
||||
"learn": "Đọc tài liệu",
|
||||
"passed": "test passed",
|
||||
"report": "Báo cáo thử nghiệm",
|
||||
"results": "Kết quả kiểm tra",
|
||||
"script": "Script",
|
||||
"snippets": "Đoạn mã"
|
||||
},
|
||||
"websocket": {
|
||||
"communication": "Liên lạc",
|
||||
"log": "Nhật ký",
|
||||
"message": "Thông điệp",
|
||||
"protocols": "Các giao thức",
|
||||
"url": "URL"
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
{
|
||||
"name": "hoppscotch-app",
|
||||
"private": true,
|
||||
"version": "3.0.0",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"dev:vite": "vite",
|
||||
"dev:gql-codegen": "graphql-codegen --config gql-codegen.yml --watch",
|
||||
"build": "node --max_old_space_size=16384 ./node_modules/vite/bin/vite.js build",
|
||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
||||
"prod-lint": "HOPP_LINT_FOR_PROD=true pnpm run lint",
|
||||
"lintfix": "eslint --fix src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
||||
"generate": "pnpm run build",
|
||||
"preview": "vite preview",
|
||||
"gql-codegen": "graphql-codegen --config gql-codegen.yml",
|
||||
"postinstall": "pnpm run gql-codegen",
|
||||
"do-dev": "pnpm run dev",
|
||||
"do-build-prod": "pnpm run build",
|
||||
"do-lint": "pnpm run prod-lint",
|
||||
"do-typecheck": "pnpm run lint",
|
||||
"do-lintfix": "pnpm run lintfix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@codemirror/autocomplete": "^6.0.3",
|
||||
"@codemirror/commands": "^6.0.1",
|
||||
"@codemirror/lang-javascript": "^6.0.1",
|
||||
"@codemirror/lang-json": "^6.0.0",
|
||||
"@codemirror/lang-xml": "^6.0.0",
|
||||
"@codemirror/language": "^6.2.0",
|
||||
"@codemirror/legacy-modes": "^6.1.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.1.0",
|
||||
"@codemirror/view": "^6.0.2",
|
||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^0.2.0",
|
||||
"@hoppscotch/data": "workspace:^0.4.2",
|
||||
"@hoppscotch/js-sandbox": "workspace:^2.1.0",
|
||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@sentry/tracing": "^7.13.0",
|
||||
"@sentry/vue": "^7.13.0",
|
||||
"@urql/core": "^2.5.0",
|
||||
"@urql/devtools": "^2.0.3",
|
||||
"@urql/exchange-auth": "^0.1.7",
|
||||
"@urql/exchange-graphcache": "^4.4.3",
|
||||
"@vueuse/core": "^8.7.5",
|
||||
"@vueuse/head": "^0.7.9",
|
||||
"acorn-walk": "^8.2.0",
|
||||
"axios": "^0.21.4",
|
||||
"buffer": "^6.0.3",
|
||||
"esprima": "^4.0.1",
|
||||
"events": "^3.3.0",
|
||||
"firebase": "^9.8.4",
|
||||
"fp-ts": "^2.12.1",
|
||||
"fuse.js": "^6.6.2",
|
||||
"globalthis": "^1.0.3",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-language-service-interface": "^2.9.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"httpsnippet": "^2.0.0",
|
||||
"insomnia-importers": "^3.3.0",
|
||||
"io-ts": "^2.2.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath-plus": "^7.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lossless-json": "^1.0.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"paho-mqtt": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
"postman-collection": "^4.1.4",
|
||||
"process": "^0.11.10",
|
||||
"qs": "^6.10.3",
|
||||
"rxjs": "^7.5.5",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||
"socketio-wildcard": "^2.0.0",
|
||||
"splitpanes": "^3.1.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"tern": "^0.24.3",
|
||||
"timers": "^0.1.1",
|
||||
"tippy.js": "^6.3.7",
|
||||
"url": "^0.11.0",
|
||||
"util": "^0.12.4",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.2.25",
|
||||
"vue-github-button": "^3.0.3",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-pdf-embed": "^1.1.4",
|
||||
"vue-router": "^4.0.16",
|
||||
"vue-tippy": "6.0.0-alpha.58",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"wonka": "^4.0.15",
|
||||
"workbox-window": "^6.5.4",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
||||
"@graphql-codegen/add": "^3.2.0",
|
||||
"@graphql-codegen/cli": "^2.8.0",
|
||||
"@graphql-codegen/typed-document-node": "^2.3.1",
|
||||
"@graphql-codegen/typescript": "^2.7.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.1",
|
||||
"@graphql-codegen/typescript-urql-graphcache": "^2.3.1",
|
||||
"@graphql-codegen/urql-introspection": "^2.2.0",
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@iconify-json/lucide": "^1.1.40",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/lossless-json": "^1.0.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/paho-mqtt": "^1.0.6",
|
||||
"@types/postman-collection": "^3.5.7",
|
||||
"@types/splitpanes": "^2.2.1",
|
||||
"@types/yargs-parser": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||
"@typescript-eslint/parser": "^5.19.0",
|
||||
"@vitejs/plugin-vue": "^3.1.0",
|
||||
"@vue/compiler-sfc": "^3.2.39",
|
||||
"@vue/eslint-config-typescript": "^11.0.1",
|
||||
"@vue/runtime-core": "^3.2.39",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"openapi-types": "^12.0.0",
|
||||
"rollup-plugin-polyfill-node": "^0.10.1",
|
||||
"sass": "^1.53.0",
|
||||
"typescript": "^4.5.4",
|
||||
"unplugin-icons": "^0.14.9",
|
||||
"unplugin-vue-components": "^0.21.0",
|
||||
"vite": "^3.1.0",
|
||||
"vite-plugin-checker": "^0.5.1",
|
||||
"vite-plugin-fonts": "^0.6.0",
|
||||
"vite-plugin-html-config": "^1.0.9",
|
||||
"vite-plugin-inspect": "^0.6.1",
|
||||
"vite-plugin-pages": "^0.25.0",
|
||||
"vite-plugin-pages-sitemap": "^1.3.0",
|
||||
"vite-plugin-pwa": "^0.12.2",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
"vue-tsc": "^0.38.2",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 840 KiB |
|
Before Width: | Height: | Size: 831 KiB |
|
Before Width: | Height: | Size: 60 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none"><path fill="#10B981" d="M0 0h512v512H0z"/><circle cx="197.76" cy="157.84" r="10" fill="#fff" fill-opacity=".75"/><circle cx="259.76" cy="161.84" r="12" fill="#fff" fill-opacity=".75"/><circle cx="319.76" cy="177.84" r="10" fill="#fff" fill-opacity=".75"/><path d="M344.963 235.676c2.075-12.698-38.872-29.804-90.967-38.094-52.09-8.296-96.404-4.665-98.48 8.033-.257 1.035 0 1.812.263 2.853-1.298-.521-76.714 211.212-76.714 211.212H364.14s-17.621-181.414-20.211-181.414c.515-.772 1.035-1.549 1.035-2.59Z" fill="url(#a)"/><path d="M314.902 227.386c-1.298 8.033-30.839 9.845-66.343 4.402-35.247-5.7-62.982-16.843-61.684-24.618.521-2.59 3.888-4.665 9.331-5.7-18.141.777-30.062 4.145-31.096 9.845-1.555 10.628 34.726 25.139 81.373 32.657 46.647 7.512 85.782 4.665 87.594-5.7 1.041-6.226-9.33-12.961-26.431-19.439 4.923 2.847 7.513 5.957 7.256 8.553Z" fill="#A7F3D0" fill-opacity=".5"/><path d="M333.557 157.413c-3.104-32.137-27.729-59.351-60.9-64.53-33.172-5.186-64.531 12.954-77.749 42.238 21.251 1.298 44.057 3.631 67.904 7.518 25.396 3.888 49.237 9.074 70.745 14.774Z" fill="url(#b)"/><path d="M74.142 158.002c-2.59 15.808 30.319 35.247 81.894 51.055-.257-1.04-.257-1.818-.257-2.853 2.07-12.698 46.127-16.328 98.48-8.032 52.347 8.29 93.037 25.396 90.961 38.094-.257 1.04-.514 1.818-1.035 2.589 53.645.778 90.968-7.512 93.557-23.32 3.625-24.104-74.638-56.498-174.93-72.306-100.555-15.808-185.045-9.331-188.67 14.773Zm115.586-1.298c.778-4.145 4.665-7.255 8.81-6.477 4.145.777 7.256 4.665 6.478 8.81-.52 4.145-4.665 6.998-8.81 6.478-4.145-.778-7.255-4.666-6.478-8.811Zm59.866 4.145c.777-5.7 6.22-9.587 11.92-8.547 5.7.778 9.588 6.215 8.553 11.921-1.041 5.442-6.478 9.33-11.92 8.553-5.706-.778-9.594-6.221-8.553-11.927Zm62.975 15.294c.778-4.145 4.665-7.255 8.81-6.478 4.145.778 7.255 4.666 6.478 8.811-.515 4.145-4.665 7.255-8.81 6.477-4.145-.777-7.256-4.665-6.478-8.81Z" fill="url(#c)"/><defs><radialGradient id="b" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 32.7063 -69.3245 0 264.232 124.706)"><stop stop-color="#047857"/><stop offset="1" stop-color="#064E3B"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(255.837 186.754) scale(1389.61)"><stop stop-color="#047857"/><stop offset=".115" stop-color="#064E3B"/></radialGradient><linearGradient id="a" x1="224.998" y1="157.606" x2="224.998" y2="403.696" gradientUnits="userSpaceOnUse"><stop stop-color="#86EFAC" stop-opacity=".75"/><stop offset=".635" stop-color="#fff" stop-opacity=".2"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
272
packages/hoppscotch-app/shims-volar.d.ts
vendored
@@ -1,272 +0,0 @@
|
||||
import AppAnnouncement from "./components/app/Announcement.vue";
|
||||
import AppDeveloperOptions from "./components/app/DeveloperOptions.vue";
|
||||
import AppFooter from "./components/app/Footer.vue";
|
||||
import AppFuse from "./components/app/Fuse.vue";
|
||||
import AppGitHubStarButton from "./components/app/GitHubStarButton.vue";
|
||||
import AppHeader from "./components/app/Header.vue";
|
||||
import AppInterceptor from "./components/app/Interceptor.vue";
|
||||
import AppLogo from "./components/app/Logo.vue";
|
||||
import AppOptions from "./components/app/Options.vue";
|
||||
import AppPaneLayout from "./components/app/PaneLayout.vue";
|
||||
import AppPowerSearch from "./components/app/PowerSearch.vue";
|
||||
import AppPowerSearchEntry from "./components/app/PowerSearchEntry.vue";
|
||||
import AppShare from "./components/app/Share.vue";
|
||||
import AppShortcuts from "./components/app/Shortcuts.vue";
|
||||
import AppShortcutsEntry from "./components/app/ShortcutsEntry.vue";
|
||||
import AppSidenav from "./components/app/Sidenav.vue";
|
||||
import AppSlideOver from "./components/app/SlideOver.vue";
|
||||
import AppSupport from "./components/app/Support.vue";
|
||||
import ButtonPrimary from "./components/button/Primary.vue";
|
||||
import ButtonSecondary from "./components/button/Secondary.vue";
|
||||
import CollectionsAdd from "./components/collections/Add.vue";
|
||||
import CollectionsAddFolder from "./components/collections/AddFolder.vue";
|
||||
import CollectionsAddRequest from "./components/collections/AddRequest.vue";
|
||||
import CollectionsChooseType from "./components/collections/ChooseType.vue";
|
||||
import CollectionsEdit from "./components/collections/Edit.vue";
|
||||
import CollectionsEditFolder from "./components/collections/EditFolder.vue";
|
||||
import CollectionsEditRequest from "./components/collections/EditRequest.vue";
|
||||
import CollectionsImportExport from "./components/collections/ImportExport.vue";
|
||||
import CollectionsSaveRequest from "./components/collections/SaveRequest.vue";
|
||||
import CollectionsGraphqlAdd from "./components/collections/graphql/Add.vue";
|
||||
import CollectionsGraphqlAddFolder from "./components/collections/graphql/AddFolder.vue";
|
||||
import CollectionsGraphqlAddRequest from "./components/collections/graphql/AddRequest.vue";
|
||||
import CollectionsGraphqlCollection from "./components/collections/graphql/Collection.vue";
|
||||
import CollectionsGraphqlEdit from "./components/collections/graphql/Edit.vue";
|
||||
import CollectionsGraphqlEditFolder from "./components/collections/graphql/EditFolder.vue";
|
||||
import CollectionsGraphqlEditRequest from "./components/collections/graphql/EditRequest.vue";
|
||||
import CollectionsGraphqlFolder from "./components/collections/graphql/Folder.vue";
|
||||
import CollectionsGraphqlImportExport from "./components/collections/graphql/ImportExport.vue";
|
||||
import CollectionsGraphqlRequest from "./components/collections/graphql/Request.vue";
|
||||
import CollectionsGraphql from "./components/collections/graphql/index.vue";
|
||||
import Collections from "./components/collections/index.vue";
|
||||
import CollectionsMyCollection from "./components/collections/my/Collection.vue";
|
||||
import CollectionsMyFolder from "./components/collections/my/Folder.vue";
|
||||
import CollectionsMyRequest from "./components/collections/my/Request.vue";
|
||||
import CollectionsTeamsCollection from "./components/collections/teams/Collection.vue";
|
||||
import CollectionsTeamsFolder from "./components/collections/teams/Folder.vue";
|
||||
import CollectionsTeamsRequest from "./components/collections/teams/Request.vue";
|
||||
import DocsCollection from "./components/docs/Collection.vue";
|
||||
import DocsFolder from "./components/docs/Folder.vue";
|
||||
import DocsRequest from "./components/docs/Request.vue";
|
||||
import EnvironmentsDetails from "./components/environments/Details.vue";
|
||||
import EnvironmentsEnvironment from "./components/environments/Environment.vue";
|
||||
import EnvironmentsImportExport from "./components/environments/ImportExport.vue";
|
||||
import Environments from "./components/environments/index.vue";
|
||||
import FirebaseLogin from "./components/firebase/Login.vue";
|
||||
import FirebaseLogout from "./components/firebase/Logout.vue";
|
||||
import GraphqlAuthorization from "./components/graphql/Authorization.vue";
|
||||
import GraphqlField from "./components/graphql/Field.vue";
|
||||
import GraphqlRequest from "./components/graphql/Request.vue";
|
||||
import GraphqlRequestOptions from "./components/graphql/RequestOptions.vue";
|
||||
import GraphqlResponse from "./components/graphql/Response.vue";
|
||||
import GraphqlSidebar from "./components/graphql/Sidebar.vue";
|
||||
import GraphqlType from "./components/graphql/Type.vue";
|
||||
import GraphqlTypeLink from "./components/graphql/TypeLink.vue";
|
||||
import HistoryGraphqlCard from "./components/history/graphql/Card.vue";
|
||||
import History from "./components/history/index.vue";
|
||||
import HistoryRestCard from "./components/history/rest/Card.vue";
|
||||
import HttpAuthorization from "./components/http/Authorization.vue";
|
||||
import HttpBody from "./components/http/Body.vue";
|
||||
import HttpBodyParameters from "./components/http/BodyParameters.vue";
|
||||
import HttpCodegenModal from "./components/http/CodegenModal.vue";
|
||||
import HttpHeaders from "./components/http/Headers.vue";
|
||||
import HttpImportCurl from "./components/http/ImportCurl.vue";
|
||||
import HttpOAuth2Authorization from "./components/http/OAuth2Authorization.vue";
|
||||
import HttpParameters from "./components/http/Parameters.vue";
|
||||
import HttpPreRequestScript from "./components/http/PreRequestScript.vue";
|
||||
import HttpRawBody from "./components/http/RawBody.vue";
|
||||
import HttpReqChangeConfirmModal from "./components/http/ReqChangeConfirmModal.vue";
|
||||
import HttpRequest from "./components/http/Request.vue";
|
||||
import HttpRequestOptions from "./components/http/RequestOptions.vue";
|
||||
import HttpResponse from "./components/http/Response.vue";
|
||||
import HttpResponseMeta from "./components/http/ResponseMeta.vue";
|
||||
import HttpSidebar from "./components/http/Sidebar.vue";
|
||||
import HttpTestResult from "./components/http/TestResult.vue";
|
||||
import HttpTestResultEntry from "./components/http/TestResultEntry.vue";
|
||||
import HttpTestResultEnv from "./components/http/TestResultEnv.vue";
|
||||
import HttpTestResultReport from "./components/http/TestResultReport.vue";
|
||||
import HttpTests from "./components/http/Tests.vue";
|
||||
import HttpURLEncodedParams from "./components/http/URLEncodedParams.vue";
|
||||
import LensesHeadersRenderer from "./components/lenses/HeadersRenderer.vue";
|
||||
import LensesHeadersRendererEntry from "./components/lenses/HeadersRendererEntry.vue";
|
||||
import LensesResponseBodyRenderer from "./components/lenses/ResponseBodyRenderer.vue";
|
||||
import LensesRenderersHTMLLensRenderer from "./components/lenses/renderers/HTMLLensRenderer.vue";
|
||||
import LensesRenderersImageLensRenderer from "./components/lenses/renderers/ImageLensRenderer.vue";
|
||||
import LensesRenderersJSONLensRenderer from "./components/lenses/renderers/JSONLensRenderer.vue";
|
||||
import LensesRenderersPDFLensRenderer from "./components/lenses/renderers/PDFLensRenderer.vue";
|
||||
import LensesRenderersRawLensRenderer from "./components/lenses/renderers/RawLensRenderer.vue";
|
||||
import LensesRenderersXMLLensRenderer from "./components/lenses/renderers/XMLLensRenderer.vue";
|
||||
import ProfilePicture from "./components/profile/Picture.vue";
|
||||
import ProfileShortcode from "./components/profile/Shortcode.vue";
|
||||
import RealtimeCommunication from "./components/realtime/Communication.vue";
|
||||
import RealtimeLog from "./components/realtime/Log.vue";
|
||||
import RealtimeLogEntry from "./components/realtime/LogEntry.vue";
|
||||
import SmartAccentModePicker from "./components/smart/AccentModePicker.vue";
|
||||
import SmartAnchor from "./components/smart/Anchor.vue";
|
||||
import SmartAutoComplete from "./components/smart/AutoComplete.vue";
|
||||
import SmartChangeLanguage from "./components/smart/ChangeLanguage.vue";
|
||||
import SmartCheckbox from "./components/smart/Checkbox.vue";
|
||||
import SmartColorModePicker from "./components/smart/ColorModePicker.vue";
|
||||
import SmartConfirmModal from "./components/smart/ConfirmModal.vue";
|
||||
import SmartEnvInput from "./components/smart/EnvInput.vue";
|
||||
import SmartExpand from "./components/smart/Expand.vue";
|
||||
import SmartFileChip from "./components/smart/FileChip.vue";
|
||||
import SmartFontSizePicker from "./components/smart/FontSizePicker.vue";
|
||||
import SmartIcon from "./components/smart/Icon.vue";
|
||||
import SmartIntersection from "./components/smart/Intersection.vue";
|
||||
import SmartItem from "./components/smart/Item.vue";
|
||||
import SmartLoadingIndicator from "./components/smart/LoadingIndicator.vue";
|
||||
import SmartModal from "./components/smart/Modal.vue";
|
||||
import SmartProgressRing from "./components/smart/ProgressRing.vue";
|
||||
import SmartRadio from "./components/smart/Radio.vue";
|
||||
import SmartRadioGroup from "./components/smart/RadioGroup.vue";
|
||||
import SmartSpinner from "./components/smart/Spinner.vue";
|
||||
import SmartTab from "./components/smart/Tab.vue";
|
||||
import SmartTabs from "./components/smart/Tabs.vue";
|
||||
import SmartToggle from "./components/smart/Toggle.vue";
|
||||
import TabPrimary from "./components/tab/Primary.vue";
|
||||
import TabSecondary from "./components/tab/Secondary.vue";
|
||||
import TeamsAdd from "./components/teams/Add.vue";
|
||||
import TeamsEdit from "./components/teams/Edit.vue";
|
||||
import TeamsInvite from "./components/teams/Invite.vue";
|
||||
import TeamsModal from "./components/teams/Modal.vue";
|
||||
import TeamsTeam from "./components/teams/Team.vue";
|
||||
import Teams from "./components/teams/index.vue";
|
||||
declare global {
|
||||
interface __VLS_GlobalComponents {
|
||||
AppAnnouncement: typeof AppAnnouncement;
|
||||
AppDeveloperOptions: typeof AppDeveloperOptions;
|
||||
AppFooter: typeof AppFooter;
|
||||
AppFuse: typeof AppFuse;
|
||||
AppGitHubStarButton: typeof AppGitHubStarButton;
|
||||
AppHeader: typeof AppHeader;
|
||||
AppInterceptor: typeof AppInterceptor;
|
||||
AppLogo: typeof AppLogo;
|
||||
AppOptions: typeof AppOptions;
|
||||
AppPaneLayout: typeof AppPaneLayout;
|
||||
AppPowerSearch: typeof AppPowerSearch;
|
||||
AppPowerSearchEntry: typeof AppPowerSearchEntry;
|
||||
AppShare: typeof AppShare;
|
||||
AppShortcuts: typeof AppShortcuts;
|
||||
AppShortcutsEntry: typeof AppShortcutsEntry;
|
||||
AppSidenav: typeof AppSidenav;
|
||||
AppSlideOver: typeof AppSlideOver;
|
||||
AppSupport: typeof AppSupport;
|
||||
ButtonPrimary: typeof ButtonPrimary;
|
||||
ButtonSecondary: typeof ButtonSecondary;
|
||||
CollectionsAdd: typeof CollectionsAdd;
|
||||
CollectionsAddFolder: typeof CollectionsAddFolder;
|
||||
CollectionsAddRequest: typeof CollectionsAddRequest;
|
||||
CollectionsChooseType: typeof CollectionsChooseType;
|
||||
CollectionsEdit: typeof CollectionsEdit;
|
||||
CollectionsEditFolder: typeof CollectionsEditFolder;
|
||||
CollectionsEditRequest: typeof CollectionsEditRequest;
|
||||
CollectionsImportExport: typeof CollectionsImportExport;
|
||||
CollectionsSaveRequest: typeof CollectionsSaveRequest;
|
||||
CollectionsGraphqlAdd: typeof CollectionsGraphqlAdd;
|
||||
CollectionsGraphqlAddFolder: typeof CollectionsGraphqlAddFolder;
|
||||
CollectionsGraphqlAddRequest: typeof CollectionsGraphqlAddRequest;
|
||||
CollectionsGraphqlCollection: typeof CollectionsGraphqlCollection;
|
||||
CollectionsGraphqlEdit: typeof CollectionsGraphqlEdit;
|
||||
CollectionsGraphqlEditFolder: typeof CollectionsGraphqlEditFolder;
|
||||
CollectionsGraphqlEditRequest: typeof CollectionsGraphqlEditRequest;
|
||||
CollectionsGraphqlFolder: typeof CollectionsGraphqlFolder;
|
||||
CollectionsGraphqlImportExport: typeof CollectionsGraphqlImportExport;
|
||||
CollectionsGraphqlRequest: typeof CollectionsGraphqlRequest;
|
||||
CollectionsGraphql: typeof CollectionsGraphql;
|
||||
Collections: typeof Collections;
|
||||
CollectionsMyCollection: typeof CollectionsMyCollection;
|
||||
CollectionsMyFolder: typeof CollectionsMyFolder;
|
||||
CollectionsMyRequest: typeof CollectionsMyRequest;
|
||||
CollectionsTeamsCollection: typeof CollectionsTeamsCollection;
|
||||
CollectionsTeamsFolder: typeof CollectionsTeamsFolder;
|
||||
CollectionsTeamsRequest: typeof CollectionsTeamsRequest;
|
||||
DocsCollection: typeof DocsCollection;
|
||||
DocsFolder: typeof DocsFolder;
|
||||
DocsRequest: typeof DocsRequest;
|
||||
EnvironmentsDetails: typeof EnvironmentsDetails;
|
||||
EnvironmentsEnvironment: typeof EnvironmentsEnvironment;
|
||||
EnvironmentsImportExport: typeof EnvironmentsImportExport;
|
||||
Environments: typeof Environments;
|
||||
FirebaseLogin: typeof FirebaseLogin;
|
||||
FirebaseLogout: typeof FirebaseLogout;
|
||||
GraphqlAuthorization: typeof GraphqlAuthorization;
|
||||
GraphqlField: typeof GraphqlField;
|
||||
GraphqlRequest: typeof GraphqlRequest;
|
||||
GraphqlRequestOptions: typeof GraphqlRequestOptions;
|
||||
GraphqlResponse: typeof GraphqlResponse;
|
||||
GraphqlSidebar: typeof GraphqlSidebar;
|
||||
GraphqlType: typeof GraphqlType;
|
||||
GraphqlTypeLink: typeof GraphqlTypeLink;
|
||||
HistoryGraphqlCard: typeof HistoryGraphqlCard;
|
||||
History: typeof History;
|
||||
HistoryRestCard: typeof HistoryRestCard;
|
||||
HttpAuthorization: typeof HttpAuthorization;
|
||||
HttpBody: typeof HttpBody;
|
||||
HttpBodyParameters: typeof HttpBodyParameters;
|
||||
HttpCodegenModal: typeof HttpCodegenModal;
|
||||
HttpHeaders: typeof HttpHeaders;
|
||||
HttpImportCurl: typeof HttpImportCurl;
|
||||
HttpOAuth2Authorization: typeof HttpOAuth2Authorization;
|
||||
HttpParameters: typeof HttpParameters;
|
||||
HttpPreRequestScript: typeof HttpPreRequestScript;
|
||||
HttpRawBody: typeof HttpRawBody;
|
||||
HttpReqChangeConfirmModal: typeof HttpReqChangeConfirmModal;
|
||||
HttpRequest: typeof HttpRequest;
|
||||
HttpRequestOptions: typeof HttpRequestOptions;
|
||||
HttpResponse: typeof HttpResponse;
|
||||
HttpResponseMeta: typeof HttpResponseMeta;
|
||||
HttpSidebar: typeof HttpSidebar;
|
||||
HttpTestResult: typeof HttpTestResult;
|
||||
HttpTestResultEntry: typeof HttpTestResultEntry;
|
||||
HttpTestResultEnv: typeof HttpTestResultEnv;
|
||||
HttpTestResultReport: typeof HttpTestResultReport;
|
||||
HttpTests: typeof HttpTests;
|
||||
HttpURLEncodedParams: typeof HttpURLEncodedParams;
|
||||
LensesHeadersRenderer: typeof LensesHeadersRenderer;
|
||||
LensesHeadersRendererEntry: typeof LensesHeadersRendererEntry;
|
||||
LensesResponseBodyRenderer: typeof LensesResponseBodyRenderer;
|
||||
LensesRenderersHTMLLensRenderer: typeof LensesRenderersHTMLLensRenderer;
|
||||
LensesRenderersImageLensRenderer: typeof LensesRenderersImageLensRenderer;
|
||||
LensesRenderersJSONLensRenderer: typeof LensesRenderersJSONLensRenderer;
|
||||
LensesRenderersPDFLensRenderer: typeof LensesRenderersPDFLensRenderer;
|
||||
LensesRenderersRawLensRenderer: typeof LensesRenderersRawLensRenderer;
|
||||
LensesRenderersXMLLensRenderer: typeof LensesRenderersXMLLensRenderer;
|
||||
ProfilePicture: typeof ProfilePicture;
|
||||
ProfileShortcode: typeof ProfileShortcode;
|
||||
RealtimeCommunication: typeof RealtimeCommunication;
|
||||
RealtimeLog: typeof RealtimeLog;
|
||||
RealtimeLogEntry: typeof RealtimeLogEntry;
|
||||
SmartAccentModePicker: typeof SmartAccentModePicker;
|
||||
SmartAnchor: typeof SmartAnchor;
|
||||
SmartAutoComplete: typeof SmartAutoComplete;
|
||||
SmartChangeLanguage: typeof SmartChangeLanguage;
|
||||
SmartCheckbox: typeof SmartCheckbox;
|
||||
SmartColorModePicker: typeof SmartColorModePicker;
|
||||
SmartConfirmModal: typeof SmartConfirmModal;
|
||||
SmartEnvInput: typeof SmartEnvInput;
|
||||
SmartExpand: typeof SmartExpand;
|
||||
SmartFileChip: typeof SmartFileChip;
|
||||
SmartFontSizePicker: typeof SmartFontSizePicker;
|
||||
SmartIcon: typeof SmartIcon;
|
||||
SmartIntersection: typeof SmartIntersection;
|
||||
SmartItem: typeof SmartItem;
|
||||
SmartLoadingIndicator: typeof SmartLoadingIndicator;
|
||||
SmartModal: typeof SmartModal;
|
||||
SmartProgressRing: typeof SmartProgressRing;
|
||||
SmartRadio: typeof SmartRadio;
|
||||
SmartRadioGroup: typeof SmartRadioGroup;
|
||||
SmartSpinner: typeof SmartSpinner;
|
||||
SmartTab: typeof SmartTab;
|
||||
SmartTabs: typeof SmartTabs;
|
||||
SmartToggle: typeof SmartToggle;
|
||||
TabPrimary: typeof TabPrimary;
|
||||
TabSecondary: typeof TabSecondary;
|
||||
TeamsAdd: typeof TeamsAdd;
|
||||
TeamsEdit: typeof TeamsEdit;
|
||||
TeamsInvite: typeof TeamsInvite;
|
||||
TeamsModal: typeof TeamsModal;
|
||||
TeamsTeam: typeof TeamsTeam;
|
||||
Teams: typeof Teams;
|
||||
}
|
||||
}
|
||||
153
packages/hoppscotch-app/src/components.d.ts
vendored
@@ -1,153 +0,0 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
||||
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
|
||||
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
||||
AppFuse: typeof import('./components/app/Fuse.vue')['default']
|
||||
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
||||
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
||||
AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default']
|
||||
AppPowerSearchEntry: typeof import('./components/app/PowerSearchEntry.vue')['default']
|
||||
AppShare: typeof import('./components/app/Share.vue')['default']
|
||||
AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default']
|
||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||
AppSlideOver: typeof import('./components/app/SlideOver.vue')['default']
|
||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
||||
ButtonPrimary: typeof import('./components/button/Primary.vue')['default']
|
||||
ButtonSecondary: typeof import('./components/button/Secondary.vue')['default']
|
||||
Collections: typeof import('./components/collections/index.vue')['default']
|
||||
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
||||
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
|
||||
CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default']
|
||||
CollectionsChooseType: typeof import('./components/collections/ChooseType.vue')['default']
|
||||
CollectionsEdit: typeof import('./components/collections/Edit.vue')['default']
|
||||
CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default']
|
||||
CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default']
|
||||
CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default']
|
||||
CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default']
|
||||
CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default']
|
||||
CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default']
|
||||
CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default']
|
||||
CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default']
|
||||
CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default']
|
||||
CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default']
|
||||
CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default']
|
||||
CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default']
|
||||
CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default']
|
||||
CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default']
|
||||
CollectionsMyCollection: typeof import('./components/collections/my/Collection.vue')['default']
|
||||
CollectionsMyFolder: typeof import('./components/collections/my/Folder.vue')['default']
|
||||
CollectionsMyRequest: typeof import('./components/collections/my/Request.vue')['default']
|
||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||
CollectionsTeamsCollection: typeof import('./components/collections/teams/Collection.vue')['default']
|
||||
CollectionsTeamsFolder: typeof import('./components/collections/teams/Folder.vue')['default']
|
||||
CollectionsTeamsRequest: typeof import('./components/collections/teams/Request.vue')['default']
|
||||
Environments: typeof import('./components/environments/index.vue')['default']
|
||||
EnvironmentsDetails: typeof import('./components/environments/Details.vue')['default']
|
||||
EnvironmentsEnvironment: typeof import('./components/environments/Environment.vue')['default']
|
||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||
FirebaseLogin: typeof import('./components/firebase/Login.vue')['default']
|
||||
FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default']
|
||||
GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default']
|
||||
GraphqlField: typeof import('./components/graphql/Field.vue')['default']
|
||||
GraphqlRequest: typeof import('./components/graphql/Request.vue')['default']
|
||||
GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default']
|
||||
GraphqlResponse: typeof import('./components/graphql/Response.vue')['default']
|
||||
GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default']
|
||||
GraphqlType: typeof import('./components/graphql/Type.vue')['default']
|
||||
GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default']
|
||||
History: typeof import('./components/history/index.vue')['default']
|
||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||
HttpBody: typeof import('./components/http/Body.vue')['default']
|
||||
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
|
||||
HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default']
|
||||
HttpHeaders: typeof import('./components/http/Headers.vue')['default']
|
||||
HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default']
|
||||
HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default']
|
||||
HttpParameters: typeof import('./components/http/Parameters.vue')['default']
|
||||
HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default']
|
||||
HttpRawBody: typeof import('./components/http/RawBody.vue')['default']
|
||||
HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default']
|
||||
HttpRequest: typeof import('./components/http/Request.vue')['default']
|
||||
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
|
||||
HttpResponse: typeof import('./components/http/Response.vue')['default']
|
||||
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
|
||||
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
|
||||
HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
|
||||
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
|
||||
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
|
||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||
IconLucideLoader: typeof import('~icons/lucide/loader')['default']
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
||||
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
|
||||
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
|
||||
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
|
||||
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
|
||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||
ProfilePicture: typeof import('./components/profile/Picture.vue')['default']
|
||||
ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default']
|
||||
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
||||
RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
|
||||
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
||||
SmartAnchor: typeof import('./components/smart/Anchor.vue')['default']
|
||||
SmartAutoComplete: typeof import('./components/smart/AutoComplete.vue')['default']
|
||||
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
||||
SmartCheckbox: typeof import('./components/smart/Checkbox.vue')['default']
|
||||
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
||||
SmartConfirmModal: typeof import('./components/smart/ConfirmModal.vue')['default']
|
||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||
SmartExpand: typeof import('./components/smart/Expand.vue')['default']
|
||||
SmartFileChip: typeof import('./components/smart/FileChip.vue')['default']
|
||||
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
||||
SmartIntersection: typeof import('./components/smart/Intersection.vue')['default']
|
||||
SmartItem: typeof import('./components/smart/Item.vue')['default']
|
||||
SmartLink: typeof import('./components/smart/Link.vue')['default']
|
||||
SmartModal: typeof import('./components/smart/Modal.vue')['default']
|
||||
SmartProgressRing: typeof import('./components/smart/ProgressRing.vue')['default']
|
||||
SmartRadio: typeof import('./components/smart/Radio.vue')['default']
|
||||
SmartRadioGroup: typeof import('./components/smart/RadioGroup.vue')['default']
|
||||
SmartSpinner: typeof import('./components/smart/Spinner.vue')['default']
|
||||
SmartTab: typeof import('./components/smart/Tab.vue')['default']
|
||||
SmartTabs: typeof import('./components/smart/Tabs.vue')['default']
|
||||
SmartToggle: typeof import('./components/smart/Toggle.vue')['default']
|
||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
||||
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
||||
Teams: typeof import('./components/teams/index.vue')['default']
|
||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
|
||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
||||
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
|
||||
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
||||
Tippy: typeof import('vue-tippy')['Tippy']
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex items-center px-4 py-2 transition bg-error text-tiny group"
|
||||
role="alert"
|
||||
>
|
||||
<icon-lucide-info class="mr-2" />
|
||||
<span class="text-secondaryDark">
|
||||
<span class="md:hidden">
|
||||
{{ t("helpers.offline_short") }}
|
||||
</span>
|
||||
<span class="<md:hidden">
|
||||
{{ t("helpers.offline") }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
</script>
|
||||
@@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<div key="outputHash" class="flex flex-col flex-1 overflow-auto">
|
||||
<div class="flex flex-col">
|
||||
<AppPowerSearchEntry
|
||||
v-for="(shortcut, shortcutIndex) in searchResults"
|
||||
:key="`shortcut-${shortcutIndex}`"
|
||||
:active="shortcutIndex === selectedEntry"
|
||||
:shortcut="shortcut.item"
|
||||
@action="emit('action', shortcut.item.action)"
|
||||
@mouseover="selectedEntry = shortcutIndex"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="searchResults.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ search }}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, onMounted } from "vue"
|
||||
import Fuse from "fuse.js"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
import { HoppAction } from "~/helpers/actions"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
input: Record<string, any>[]
|
||||
search: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "action", action: HoppAction): void
|
||||
}>()
|
||||
|
||||
const options = {
|
||||
keys: ["keys", "label", "action", "tags"],
|
||||
}
|
||||
|
||||
const fuse = new Fuse(props.input, options)
|
||||
|
||||
const searchResults = computed(() => fuse.search(props.search))
|
||||
|
||||
const searchResultsItems = computed(() =>
|
||||
searchResults.value.map((searchResult) => searchResult.item)
|
||||
)
|
||||
|
||||
const emitSearchAction = (action: HoppAction) => emit("action", action)
|
||||
|
||||
const { bindArrowKeysListeners, unbindArrowKeysListeners, selectedEntry } =
|
||||
useArrowKeysNavigation(searchResultsItems, {
|
||||
onEnter: emitSearchAction,
|
||||
stopPropagation: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
bindArrowKeysListeners()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unbindArrowKeysListeners()
|
||||
})
|
||||
</script>
|
||||
@@ -1,217 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<header
|
||||
class="flex items-center justify-between flex-1 px-2 py-2 overflow-x-auto overflow-y-hidden space-x-2"
|
||||
>
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<ButtonSecondary
|
||||
class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark uppercase"
|
||||
:label="t('app.name')"
|
||||
to="/"
|
||||
/>
|
||||
<AppGitHubStarButton class="mt-1.5 transition <sm:hidden" />
|
||||
</div>
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<ButtonSecondary
|
||||
v-if="showInstallButton"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('header.install_pwa')"
|
||||
:icon="IconDownload"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click="installPWA()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t('app.search')} <xmp>/</xmp>`"
|
||||
:icon="IconSearch"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click="invokeAction('modals.search.toggle')"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${
|
||||
mdAndLarger ? t('support.title') : t('app.options')
|
||||
} <xmp>?</xmp>`"
|
||||
:icon="IconLifeBuoy"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click="invokeAction('modals.support.toggle')"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="currentUser === null"
|
||||
:icon="IconUploadCloud"
|
||||
:label="t('header.save_workspace')"
|
||||
filled
|
||||
class="hidden md:flex"
|
||||
@click="showLogin = true"
|
||||
/>
|
||||
<ButtonPrimary
|
||||
v-if="currentUser === null"
|
||||
:label="t('header.login')"
|
||||
@click="showLogin = true"
|
||||
/>
|
||||
<div v-else class="inline-flex items-center space-x-2">
|
||||
<ButtonPrimary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('team.invite_tooltip')"
|
||||
:label="t('team.invite')"
|
||||
:icon="IconUserPlus"
|
||||
class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacity-10 !hover:bg-green-400 !hover:text-green-600"
|
||||
@click="showTeamsModal = true"
|
||||
/>
|
||||
<span class="px-2">
|
||||
<tippy
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ProfilePicture
|
||||
v-if="currentUser.photoURL"
|
||||
v-tippy="{
|
||||
theme: 'tooltip',
|
||||
}"
|
||||
:url="currentUser.photoURL"
|
||||
:alt="currentUser.displayName"
|
||||
:title="currentUser.displayName"
|
||||
indicator
|
||||
:indicator-styles="
|
||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||
"
|
||||
/>
|
||||
<ProfilePicture
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="currentUser.displayName"
|
||||
:initial="currentUser.displayName"
|
||||
indicator
|
||||
:indicator-styles="
|
||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||
"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div class="flex flex-col px-2 text-tiny">
|
||||
<span class="inline-flex font-semibold truncate">
|
||||
{{ currentUser.displayName }}
|
||||
</span>
|
||||
<span class="inline-flex truncate text-secondaryLight">
|
||||
{{ currentUser.email }}
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.enter="profile.$el.click()"
|
||||
@keyup.s="settings.$el.click()"
|
||||
@keyup.l="logout.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="profile"
|
||||
to="/profile"
|
||||
:icon="IconUser"
|
||||
:label="t('navigation.profile')"
|
||||
:shortcut="['↩']"
|
||||
@click="hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="settings"
|
||||
to="/settings"
|
||||
:icon="IconSettings"
|
||||
:label="t('navigation.settings')"
|
||||
:shortcut="['S']"
|
||||
@click="hide()"
|
||||
/>
|
||||
<FirebaseLogout
|
||||
ref="logout"
|
||||
:shortcut="['L']"
|
||||
@confirm-logout="hide()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<AppAnnouncement v-if="!network.isOnline" />
|
||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from "vue"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import IconSettings from "~icons/lucide/settings"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconSearch from "~icons/lucide/search"
|
||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
||||
import { probableUser$ } from "@helpers/fb/auth"
|
||||
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { invokeAction } from "@helpers/actions"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
/**
|
||||
* Once the PWA code is initialized, this holds a method
|
||||
* that can be called to show the user the installation
|
||||
* prompt.
|
||||
*/
|
||||
|
||||
const showInstallButton = computed(() => !!pwaDefferedPrompt.value)
|
||||
|
||||
const showLogin = ref(false)
|
||||
const showTeamsModal = ref(false)
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const mdAndLarger = breakpoints.greater("md")
|
||||
|
||||
const network = reactive(useNetwork())
|
||||
|
||||
const currentUser = useReadonlyStream(probableUser$, null)
|
||||
|
||||
onMounted(() => {
|
||||
const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes"
|
||||
if (!cookiesAllowed) {
|
||||
toast.show(`${t("app.we_use_cookies")}`, {
|
||||
duration: 0,
|
||||
action: [
|
||||
{
|
||||
text: `${t("action.learn_more")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setLocalConfig("cookiesAllowed", "yes")
|
||||
toastObject.goAway(0)
|
||||
window.open("https://docs.hoppscotch.io/privacy", "_blank")?.focus()
|
||||
},
|
||||
},
|
||||
{
|
||||
text: `${t("action.dismiss")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setLocalConfig("cookiesAllowed", "yes")
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const profile = ref<any | null>(null)
|
||||
const settings = ref<any | null>(null)
|
||||
const logout = ref<any | null>(null)
|
||||
</script>
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex flex-col px-4 pt-2">
|
||||
<h2 class="inline-flex pb-1 font-semibold text-secondaryDark">
|
||||
{{ t("settings.interceptor") }}
|
||||
</h2>
|
||||
<p class="inline-flex text-tiny">
|
||||
{{ t("settings.interceptor_description") }}
|
||||
</p>
|
||||
</div>
|
||||
<SmartRadioGroup v-model="interceptorSelection" :radios="interceptors" />
|
||||
<div
|
||||
v-if="interceptorSelection == 'EXTENSIONS_ENABLED' && !extensionVersion"
|
||||
class="flex space-x-2"
|
||||
>
|
||||
<ButtonSecondary
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
outline
|
||||
class="!flex-1"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
outline
|
||||
class="!flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconChrome from "~icons/brands/chrome"
|
||||
import IconFirefox from "~icons/brands/firefox"
|
||||
import { computed } from "vue"
|
||||
import { applySetting, toggleSetting } from "~/newstore/settings"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { extensionStatus$ } from "~/newstore/HoppExtension"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const PROXY_ENABLED = useSetting("PROXY_ENABLED")
|
||||
const EXTENSIONS_ENABLED = useSetting("EXTENSIONS_ENABLED")
|
||||
|
||||
const currentExtensionStatus = useReadonlyStream(extensionStatus$, null)
|
||||
|
||||
const extensionVersion = computed(() => {
|
||||
return currentExtensionStatus.value === "available"
|
||||
? window.__POSTWOMAN_EXTENSION_HOOK__?.getVersion() ?? null
|
||||
: null
|
||||
})
|
||||
|
||||
const interceptors = computed(() => [
|
||||
{ value: "BROWSER_ENABLED" as const, label: t("state.none") },
|
||||
{ value: "PROXY_ENABLED" as const, label: t("settings.proxy") },
|
||||
{
|
||||
value: "EXTENSIONS_ENABLED" as const,
|
||||
label:
|
||||
`${t("settings.extensions")}: ` +
|
||||
(extensionVersion.value !== null
|
||||
? `v${extensionVersion.value.major}.${extensionVersion.value.minor}`
|
||||
: t("settings.extension_ver_not_reported")),
|
||||
},
|
||||
])
|
||||
|
||||
type InterceptorMode = typeof interceptors["value"][number]["value"]
|
||||
|
||||
const interceptorSelection = computed<InterceptorMode>({
|
||||
get() {
|
||||
if (PROXY_ENABLED.value) return "PROXY_ENABLED"
|
||||
if (EXTENSIONS_ENABLED.value) return "EXTENSIONS_ENABLED"
|
||||
return "BROWSER_ENABLED"
|
||||
},
|
||||
set(val) {
|
||||
if (val === "EXTENSIONS_ENABLED") {
|
||||
applySetting("EXTENSIONS_ENABLED", true)
|
||||
if (PROXY_ENABLED.value) toggleSetting("PROXY_ENABLED")
|
||||
}
|
||||
if (val === "PROXY_ENABLED") {
|
||||
applySetting("PROXY_ENABLED", true)
|
||||
if (EXTENSIONS_ENABLED.value) toggleSetting("EXTENSIONS_ENABLED")
|
||||
}
|
||||
if (val === "BROWSER_ENABLED") {
|
||||
applySetting("PROXY_ENABLED", false)
|
||||
applySetting("EXTENSIONS_ENABLED", false)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,222 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('app.options')"
|
||||
max-width="sm:max-w-md"
|
||||
class="text-sm"
|
||||
@close="emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<h2 class="p-2 font-semibold font-bold text-secondaryDark">
|
||||
{{ t("layout.name") }}
|
||||
</h2>
|
||||
<SmartItem
|
||||
:icon="IconSidebar"
|
||||
:label="EXPAND_NAVIGATION ? t('hide.sidebar') : t('show.sidebar')"
|
||||
:description="t('layout.collapse_sidebar')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="expandNavigation"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconSidebarOpen"
|
||||
:label="SIDEBAR ? t('hide.collection') : t('show.collection')"
|
||||
:description="t('layout.collapse_collection')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="expandCollection"
|
||||
/>
|
||||
<h2 class="p-2 font-semibold font-bold text-secondaryDark">
|
||||
{{ t("support.title") }}
|
||||
</h2>
|
||||
<SmartItem
|
||||
:icon="IconBook"
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io"
|
||||
:description="t('support.documentation')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
blank
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconGift"
|
||||
:label="t('app.whats_new')"
|
||||
to="https://docs.hoppscotch.io/changelog"
|
||||
:description="t('support.changelog')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
blank
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconActivity"
|
||||
:label="t('app.status')"
|
||||
to="https://status.hoppscotch.io"
|
||||
blank
|
||||
:description="t('app.status_description')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconLock"
|
||||
:label="`${t('app.terms_and_privacy')}`"
|
||||
to="https://docs.hoppscotch.io/privacy"
|
||||
blank
|
||||
:description="t('app.terms_and_privacy')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<h2 class="p-2 font-semibold font-bold text-secondaryDark">
|
||||
{{ t("settings.follow") }}
|
||||
</h2>
|
||||
<SmartItem
|
||||
:icon="IconDiscord"
|
||||
:label="t('app.discord')"
|
||||
to="https://hoppscotch.io/discord"
|
||||
blank
|
||||
:description="t('app.join_discord_community')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconTwitter"
|
||||
:label="t('app.twitter')"
|
||||
to="https://hoppscotch.io/twitter"
|
||||
blank
|
||||
:description="t('support.twitter')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconGithub"
|
||||
:label="`${t('app.github')}`"
|
||||
to="https://github.com/hoppscotch/hoppscotch"
|
||||
blank
|
||||
:description="t('support.github')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconMessageCircle"
|
||||
:label="t('app.chat_with_us')"
|
||||
:description="t('support.chat')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="chatWithUs()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconUserPlus"
|
||||
:label="`${t('app.invite')}`"
|
||||
:description="t('shortcut.miscellaneous.invite')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="expandInvite()"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="navigatorShare"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconShare2"
|
||||
:label="`${t('request.share')}`"
|
||||
:description="t('request.share_description')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="nativeShare()"
|
||||
/>
|
||||
</div>
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import IconSidebar from "~icons/lucide/sidebar"
|
||||
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
||||
import IconBook from "~icons/lucide/book"
|
||||
import IconGift from "~icons/lucide/gift"
|
||||
import IconActivity from "~icons/lucide/activity"
|
||||
import IconLock from "~icons/lucide/lock"
|
||||
import IconDiscord from "~icons/brands/discord"
|
||||
import IconTwitter from "~icons/brands/twitter"
|
||||
import IconGithub from "~icons/hopp/github"
|
||||
import IconMessageCircle from "~icons/lucide/message-circle"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
import IconChevronRight from "~icons/lucide/chevron-right"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { showChat } from "@modules/crisp"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
const navigatorShare = !!navigator.share
|
||||
const showShare = ref(false)
|
||||
|
||||
const ZEN_MODE = useSetting("ZEN_MODE")
|
||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||
const SIDEBAR = useSetting("SIDEBAR")
|
||||
|
||||
watch(
|
||||
() => ZEN_MODE.value,
|
||||
() => {
|
||||
EXPAND_NAVIGATION.value = !ZEN_MODE.value
|
||||
}
|
||||
)
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
defineActionHandler("modals.share.toggle", () => {
|
||||
showShare.value = !showShare.value
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const chatWithUs = () => {
|
||||
showChat()
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const expandNavigation = () => {
|
||||
EXPAND_NAVIGATION.value = !EXPAND_NAVIGATION.value
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const expandCollection = () => {
|
||||
SIDEBAR.value = !SIDEBAR.value
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const expandInvite = () => {
|
||||
showShare.value = true
|
||||
}
|
||||
|
||||
const nativeShare = () => {
|
||||
if (navigator.share) {
|
||||
navigator
|
||||
.share({
|
||||
title: "Hoppscotch",
|
||||
text: "Hoppscotch • Open source API development ecosystem - Helps you create requests faster, saving precious time on development.",
|
||||
url: "https://hoppscotch.io",
|
||||
})
|
||||
.catch(console.error)
|
||||
} else {
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
max-width="sm:max-w-lg"
|
||||
full-width
|
||||
@close="emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col border-b transition border-dividerLight">
|
||||
<input
|
||||
id="command"
|
||||
v-model="search"
|
||||
v-focus
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
name="command"
|
||||
:placeholder="`${t('app.type_a_command_search')}`"
|
||||
class="flex flex-shrink-0 p-6 text-base bg-transparent text-secondaryDark"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-shrink-0 text-tiny text-secondaryLight px-4 pb-4 justify-between whitespace-nowrap overflow-auto <sm:hidden"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<kbd class="shortcut-key">↑</kbd>
|
||||
<kbd class="shortcut-key">↓</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_navigate") }}
|
||||
</span>
|
||||
<kbd class="shortcut-key">↩</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_select") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<kbd class="shortcut-key">ESC</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_close") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AppFuse
|
||||
v-if="search && show"
|
||||
:input="fuse"
|
||||
:search="search"
|
||||
@action="runAction"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col flex-1 overflow-auto space-y-4 divide-y divide-dividerLight"
|
||||
>
|
||||
<div
|
||||
v-for="(map, mapIndex) in mappings"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<h5 class="px-6 py-2 my-2 text-secondaryLight">
|
||||
{{ t(map.section) }}
|
||||
</h5>
|
||||
<AppPowerSearchEntry
|
||||
v-for="(shortcut, shortcutIndex) in map.shortcuts"
|
||||
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
|
||||
:shortcut="shortcut"
|
||||
:active="shortcutsItems.indexOf(shortcut) === selectedEntry"
|
||||
@action="runAction"
|
||||
@mouseover="selectedEntry = shortcutsItems.indexOf(shortcut)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { HoppAction, invokeAction } from "~/helpers/actions"
|
||||
import { spotlight as mappings, fuse } from "@helpers/shortcuts"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const search = ref("")
|
||||
|
||||
const hideModal = () => {
|
||||
search.value = ""
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const runAction = (command: HoppAction) => {
|
||||
invokeAction(command)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const shortcutsItems = computed(() =>
|
||||
mappings.reduce(
|
||||
(shortcuts, section) => [...shortcuts, ...section.shortcuts],
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
const { bindArrowKeysListeners, unbindArrowKeysListeners, selectedEntry } =
|
||||
useArrowKeysNavigation(shortcutsItems, {
|
||||
onEnter: runAction,
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) bindArrowKeysListeners()
|
||||
else unbindArrowKeysListeners()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
class="flex items-center flex-1 px-6 py-3 font-medium cursor-pointer transition search-entry focus:outline-none"
|
||||
:class="{ active: active }"
|
||||
tabindex="-1"
|
||||
@click="emit('action', shortcut.action)"
|
||||
@keydown.enter="emit('action', shortcut.action)"
|
||||
>
|
||||
<component
|
||||
:is="shortcut.icon"
|
||||
class="mr-4 opacity-50 transition svg-icons"
|
||||
:class="{ 'opacity-100 text-secondaryDark': active }"
|
||||
/>
|
||||
<span
|
||||
class="flex flex-1 mr-4 transition"
|
||||
:class="{ 'text-secondaryDark': active }"
|
||||
>
|
||||
{{ t(shortcut.label) }}
|
||||
</span>
|
||||
<kbd
|
||||
v-for="(key, keyIndex) in shortcut.keys"
|
||||
:key="`key-${String(keyIndex)}`"
|
||||
class="shortcut-key"
|
||||
>
|
||||
{{ key }}
|
||||
</kbd>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Component } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
shortcut: {
|
||||
label: string
|
||||
keys: string[]
|
||||
action: string
|
||||
icon: object | Component
|
||||
}
|
||||
active: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "action", action: string): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-entry {
|
||||
@apply relative;
|
||||
|
||||
&::after {
|
||||
@apply absolute;
|
||||
@apply top-0;
|
||||
@apply left-0;
|
||||
@apply bottom-0;
|
||||
@apply bg-transparent;
|
||||
@apply z-2;
|
||||
@apply w-0.5;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-primaryLight;
|
||||
|
||||
&::after {
|
||||
@apply bg-accentLight;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<AppSlideOver :show="show" @close="close()">
|
||||
<template #content>
|
||||
<div class="sticky top-0 z-10 flex flex-col bg-primary">
|
||||
<div
|
||||
class="flex items-center justify-between p-2 border-b border-dividerLight"
|
||||
>
|
||||
<h3 class="ml-4 heading">{{ t("app.shortcuts") }}</h3>
|
||||
<ButtonSecondary :icon="IconX" @click="close()" />
|
||||
</div>
|
||||
<div class="flex flex-col px-6 py-4 border-b border-dividerLight">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex px-4 py-2 border rounded bg-primaryLight border-dividerLight focus-visible:border-divider"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filterText" class="flex flex-col divide-y divide-dividerLight">
|
||||
<div
|
||||
v-for="(map, mapIndex) in searchResults"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="px-6 py-4 space-y-4"
|
||||
>
|
||||
<h1 class="font-semibold text-secondaryDark">
|
||||
{{ t(map.item.section) }}
|
||||
</h1>
|
||||
<AppShortcutsEntry
|
||||
v-for="(shortcut, index) in map.item.shortcuts"
|
||||
:key="`shortcut-${index}`"
|
||||
:shortcut="shortcut"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="searchResults.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col divide-y divide-dividerLight">
|
||||
<div
|
||||
v-for="(map, mapIndex) in mappings"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="px-6 py-4 space-y-4"
|
||||
>
|
||||
<h1 class="font-semibold text-secondaryDark">
|
||||
{{ t(map.section) }}
|
||||
</h1>
|
||||
<AppShortcutsEntry
|
||||
v-for="(shortcut, shortcutIndex) in map.shortcuts"
|
||||
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
|
||||
:shortcut="shortcut"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AppSlideOver>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconX from "~icons/lucide/x"
|
||||
import { computed, ref } from "vue"
|
||||
import Fuse from "fuse.js"
|
||||
import mappings from "~/helpers/shortcuts"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const options = {
|
||||
keys: ["shortcuts.label"],
|
||||
}
|
||||
|
||||
const fuse = new Fuse(mappings, options)
|
||||
|
||||
const filterText = ref("")
|
||||
|
||||
const searchResults = computed(() => fuse.search(filterText.value))
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "close"): void
|
||||
}>()
|
||||
|
||||
const close = () => {
|
||||
filterText.value = ""
|
||||
emit("close")
|
||||
}
|
||||
</script>
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('support.title')"
|
||||
max-width="sm:max-w-md"
|
||||
@close="emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
:icon="IconBook"
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io"
|
||||
:description="t('support.documentation')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
blank
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconZap"
|
||||
:label="t('app.keyboard_shortcuts')"
|
||||
:description="t('support.shortcuts')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="showShortcuts()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconGift"
|
||||
:label="t('app.whats_new')"
|
||||
to="https://docs.hoppscotch.io/changelog"
|
||||
:description="t('support.changelog')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
blank
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconMessageCircle"
|
||||
:label="t('app.chat_with_us')"
|
||||
:description="t('support.chat')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="chatWithUs()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconGitHub"
|
||||
:label="t('app.github')"
|
||||
to="https://hoppscotch.io/github"
|
||||
blank
|
||||
:description="t('support.github')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconDiscord"
|
||||
:label="t('app.join_discord_community')"
|
||||
to="https://hoppscotch.io/discord"
|
||||
blank
|
||||
:description="t('support.community')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconTwitter"
|
||||
:label="t('app.twitter')"
|
||||
to="https://hoppscotch.io/twitter"
|
||||
blank
|
||||
:description="t('support.twitter')"
|
||||
:info-icon="IconChevronRight"
|
||||
active
|
||||
@click="hideModal()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconTwitter from "~icons/brands/twitter"
|
||||
import IconDiscord from "~icons/brands/discord"
|
||||
import IconGitHub from "~icons/hopp/github"
|
||||
import IconMessageCircle from "~icons/lucide/message-circle"
|
||||
import IconGift from "~icons/lucide/gift"
|
||||
import IconZap from "~icons/lucide/zap"
|
||||
import IconBook from "~icons/lucide/book"
|
||||
import IconChevronRight from "~icons/lucide/chevron-right"
|
||||
import { invokeAction } from "@helpers/actions"
|
||||
import { showChat } from "@modules/crisp"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const chatWithUs = () => {
|
||||
showChat()
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const showShortcuts = () => {
|
||||
invokeAction("flyouts.keybinds.toggle")
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
@@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('collection.new')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelAdd"
|
||||
v-model="name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="addNewCollection"
|
||||
/>
|
||||
<label for="selectLabelAdd">
|
||||
{{ t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="addNewCollection"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
loadingState: Boolean,
|
||||
},
|
||||
emits: ["submit", "hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(isShowing: boolean) {
|
||||
if (!isShowing) {
|
||||
this.name = null
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addNewCollection() {
|
||||
if (!this.name) {
|
||||
this.toast.error(this.t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('folder.new')"
|
||||
@close="$emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelAddFolder"
|
||||
v-model="name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="addFolder"
|
||||
/>
|
||||
<label for="selectLabelAddFolder">
|
||||
{{ t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="addFolder"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
folder: { type: Object, default: () => ({}) },
|
||||
folderPath: { type: String, default: null },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
loadingState: Boolean,
|
||||
},
|
||||
emits: ["hide-modal", "add-folder"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(isShowing: boolean) {
|
||||
if (!isShowing) this.name = null
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addFolder() {
|
||||
if (!this.name) {
|
||||
this.toast.error(this.t("folder.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("add-folder", {
|
||||
name: this.name,
|
||||
folder: this.folder,
|
||||
path: this.folderPath || `${this.collectionIndex}`,
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('request.new')"
|
||||
@close="$emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelAddRequest"
|
||||
v-model="name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="addRequest"
|
||||
/>
|
||||
<label for="selectLabelAddRequest">{{ t("action.label") }}</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="addRequest"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { getRESTRequest } from "~/newstore/RESTSession"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
loadingState: boolean
|
||||
folder?: object
|
||||
folderPath?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
(
|
||||
e: "add-request",
|
||||
v: {
|
||||
name: string
|
||||
folder: object | undefined
|
||||
path: string | undefined
|
||||
}
|
||||
): void
|
||||
}>()
|
||||
|
||||
const name = ref("")
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
name.value = getRESTRequest().name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const addRequest = () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("error.empty_req_name")}`)
|
||||
return
|
||||
}
|
||||
emit("add-request", {
|
||||
name: name.value,
|
||||
folder: props.folder,
|
||||
path: props.folderPath,
|
||||
})
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
@@ -1,149 +0,0 @@
|
||||
<template>
|
||||
<div v-show="show">
|
||||
<SmartTabs
|
||||
:id="'collections_tab'"
|
||||
v-model="selectedCollectionTab"
|
||||
render-inactive-tabs
|
||||
>
|
||||
<SmartTab
|
||||
:id="'my-collections'"
|
||||
:label="`${t('collection.my_collections')}`"
|
||||
/>
|
||||
<SmartTab
|
||||
v-if="currentUser"
|
||||
:id="'team-collections'"
|
||||
:label="`${t('collection.team_collections')}`"
|
||||
>
|
||||
<SmartIntersection @intersecting="onTeamSelectIntersect">
|
||||
<tippy
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
placement="bottom"
|
||||
>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${t('collection.select_team')}`"
|
||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-if="collectionsType.selectedTeam"
|
||||
:icon="IconUsers"
|
||||
:label="collectionsType.selectedTeam.name"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
:label="`${t('collection.select_team')}`"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
v-for="(team, index) in myTeams"
|
||||
:key="`team-${index}`"
|
||||
:label="team.name"
|
||||
:info-icon="
|
||||
team.id === collectionsType.selectedTeam?.id
|
||||
? IconDone
|
||||
: null
|
||||
"
|
||||
:active-info-icon="
|
||||
team.id === collectionsType.selectedTeam?.id
|
||||
"
|
||||
:icon="IconUsers"
|
||||
@click="
|
||||
() => {
|
||||
updateSelectedTeam(team)
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</SmartIntersection>
|
||||
</SmartTab>
|
||||
</SmartTabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import { ref, watch } from "vue"
|
||||
import { GetMyTeamsQuery, Team } from "~/helpers/backend/graphql"
|
||||
import { currentUserInfo$ } from "~/helpers/teams/BackendUserInfo"
|
||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { onLoggedIn } from "@composables/auth"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
|
||||
type TeamData = GetMyTeamsQuery["myTeams"][number]
|
||||
|
||||
type CollectionTabs = "my-collections" | "team-collections"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const selectedCollectionTab = ref<CollectionTabs>("my-collections")
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
collectionsType: {
|
||||
type: "my-collections" | "team-collections"
|
||||
selectedTeam: Team | undefined
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update-collection-type", tabID: string): void
|
||||
(e: "update-selected-team", team: TeamData | undefined): void
|
||||
}>()
|
||||
|
||||
const currentUser = useReadonlyStream(currentUserInfo$, null)
|
||||
|
||||
const adapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(adapter.teamList$, null)
|
||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||
let teamListFetched = false
|
||||
|
||||
watch(myTeams, (teams) => {
|
||||
if (teams && !teamListFetched) {
|
||||
teamListFetched = true
|
||||
if (REMEMBERED_TEAM_ID.value && currentUser) {
|
||||
const team = teams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||
if (team) updateSelectedTeam(team)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onLoggedIn(() => {
|
||||
adapter.initialize()
|
||||
})
|
||||
|
||||
const onTeamSelectIntersect = () => {
|
||||
// Load team data as soon as intersection
|
||||
adapter.fetchList()
|
||||
}
|
||||
|
||||
const updateCollectionsType = (tabID: string) => {
|
||||
emit("update-collection-type", tabID)
|
||||
}
|
||||
|
||||
const updateSelectedTeam = (team: TeamData | undefined) => {
|
||||
REMEMBERED_TEAM_ID.value = team?.id
|
||||
emit("update-selected-team", team)
|
||||
}
|
||||
|
||||
watch(selectedCollectionTab, (newValue: string) => {
|
||||
updateCollectionsType(newValue)
|
||||
})
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('collection.edit')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelEdit"
|
||||
v-model="name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="saveCollection"
|
||||
/>
|
||||
<label for="selectLabelEdit">
|
||||
{{ t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="saveCollection"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
editingCollectionName: { type: String, default: null },
|
||||
loadingState: Boolean,
|
||||
},
|
||||
emits: ["submit", "hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingCollectionName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveCollection() {
|
||||
if (!this.name) {
|
||||
this.toast.error(this.t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('folder.edit')"
|
||||
@close="$emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelEditFolder"
|
||||
v-model="name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="editFolder"
|
||||
/>
|
||||
<label for="selectLabelEditFolder">
|
||||
{{ t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="editFolder"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
editingFolderName: { type: String, default: null },
|
||||
loadingState: Boolean,
|
||||
},
|
||||
emits: ["submit", "hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
t: useI18n(),
|
||||
toast: useToast(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingFolderName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editFolder() {
|
||||
if (!this.name) {
|
||||
this.toast.error(this.t("folder.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
},
|
||||
hideModal() {
|
||||
this.name = null
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('modal.edit_request')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="selectLabelEditReq"
|
||||
v-model="requestUpdateData.name"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="saveRequest"
|
||||
/>
|
||||
<label for="selectLabelEditReq">
|
||||
{{ t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="t('action.save')"
|
||||
:loading="loadingState"
|
||||
@click="saveRequest"
|
||||
/>
|
||||
<ButtonSecondary :label="t('action.cancel')" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
editingRequestName: { type: String, default: null },
|
||||
loadingState: Boolean,
|
||||
},
|
||||
emits: ["submit", "hide-modal"],
|
||||
setup() {
|
||||
return {
|
||||
t: useI18n(),
|
||||
toast: useToast(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
requestUpdateData: {
|
||||
name: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingRequestName(val) {
|
||||
this.requestUpdateData.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveRequest() {
|
||||
if (!this.requestUpdateData.name) {
|
||||
this.toast.error(this.t("request.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.requestUpdateData)
|
||||
},
|
||||
hideModal() {
|
||||
this.requestUpdateData = { name: null }
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,431 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('modal.collections')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #actions>
|
||||
<ButtonSecondary
|
||||
v-if="importerType !== null"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.go_back')"
|
||||
:icon="IconArrowLeft"
|
||||
@click="resetImport"
|
||||
/>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="importerType !== null" class="flex flex-col">
|
||||
<div class="flex flex-col pb-6">
|
||||
<div
|
||||
v-for="(step, index) in importerSteps"
|
||||
:key="`step-${index}`"
|
||||
class="flex flex-col space-y-8"
|
||||
>
|
||||
<div v-if="step.name === 'FILE_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasFile,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${step.metadata.caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<input
|
||||
id="inputChooseFileToImportFrom"
|
||||
ref="inputChooseFileToImportFrom"
|
||||
name="inputChooseFileToImportFrom"
|
||||
type="file"
|
||||
class="cursor-pointer transition file:transition file:cursor-pointer text-secondary hover:text-secondaryDark file:mr-2 file:py-2 file:px-4 file:rounded file:border-0 file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||
:accept="step.metadata.acceptedFileTypes"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="step.name === 'URL_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasGist,
|
||||
}"
|
||||
>
|
||||
<icon-lucide-check-circle class="svg-icons" />
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${step.metadata.caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<input
|
||||
v-model="inputChooseGistToImportFrom"
|
||||
type="url"
|
||||
class="input"
|
||||
:placeholder="`${t('import.gist_url')}`"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="step.name === 'TARGET_MY_COLLECTION'"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div class="select-wrapper">
|
||||
<select
|
||||
v-model="mySelectedCollectionID"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="select"
|
||||
autofocus
|
||||
>
|
||||
<option :key="undefined" :value="undefined" disabled selected>
|
||||
{{ t("collection.select") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(collection, collectionIndex) in myCollections"
|
||||
:key="`collection-${collectionIndex}`"
|
||||
:value="collectionIndex"
|
||||
>
|
||||
{{ collection.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonPrimary
|
||||
:label="t('import.title')"
|
||||
:disabled="enableImportButton"
|
||||
:loading="importingMyCollections"
|
||||
@click="finishImport"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex flex-col">
|
||||
<SmartExpand>
|
||||
<template #body>
|
||||
<SmartItem
|
||||
v-for="(importer, index) in importerModules"
|
||||
:key="`importer-${index}`"
|
||||
:icon="importer.icon"
|
||||
:label="t(`${importer.name}`)"
|
||||
@click="importerType = index"
|
||||
/>
|
||||
</template>
|
||||
</SmartExpand>
|
||||
<hr />
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.download_file')"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.as_json')"
|
||||
@click="exportJSON"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
class="flex"
|
||||
>
|
||||
<SmartItem
|
||||
:disabled="
|
||||
!currentUser
|
||||
? true
|
||||
: currentUser.provider !== 'github.com'
|
||||
? true
|
||||
: false
|
||||
"
|
||||
:icon="IconGithub"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click="
|
||||
() => {
|
||||
createCollectionGist()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconGithub from "~icons/lucide/github"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { HoppRESTRequest, HoppCollection } from "@hoppscotch/data"
|
||||
import axios from "axios"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
||||
import { RESTCollectionImporters } from "~/helpers/import-export/import/importers"
|
||||
import { StepReturnValue } from "~/helpers/import-export/steps"
|
||||
import { runGQLQuery, runMutation } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
ExportAsJsonDocument,
|
||||
ImportFromJsonDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
collectionsType:
|
||||
| {
|
||||
type: "team-collections"
|
||||
selectedTeam: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
| { type: "my-collections" }
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
(e: "update-team-collections"): void
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const myCollections = useReadonlyStream(restCollections$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
// Template refs
|
||||
const mode = ref("import_export")
|
||||
const mySelectedCollectionID = ref<undefined | number>(undefined)
|
||||
const collectionJson = ref("")
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement | any>()
|
||||
const inputChooseGistToImportFrom = ref<string>("")
|
||||
|
||||
const getJSONCollection = async () => {
|
||||
if (props.collectionsType.type === "my-collections") {
|
||||
collectionJson.value = JSON.stringify(myCollections.value, null, 2)
|
||||
} else {
|
||||
collectionJson.value = pipe(
|
||||
await runGQLQuery({
|
||||
query: ExportAsJsonDocument,
|
||||
variables: {
|
||||
teamID: props.collectionsType.selectedTeam.id,
|
||||
},
|
||||
}),
|
||||
E.matchW(
|
||||
// TODO: Handle error case gracefully ?
|
||||
() => {
|
||||
throw new Error("Error exporting collection to JSON")
|
||||
},
|
||||
(x) => x.exportCollectionsToJSON
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return collectionJson.value
|
||||
}
|
||||
|
||||
const createCollectionGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await getJSONCollection()
|
||||
|
||||
try {
|
||||
const res = await axios.post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: collectionJson.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
mode.value = "import_export"
|
||||
mySelectedCollectionID.value = undefined
|
||||
resetImport()
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const stepResults = ref<StepReturnValue[]>([])
|
||||
|
||||
watch(mySelectedCollectionID, (newValue) => {
|
||||
if (newValue === undefined) return
|
||||
stepResults.value = []
|
||||
stepResults.value.push(newValue)
|
||||
})
|
||||
|
||||
const importingMyCollections = ref(false)
|
||||
|
||||
const importToTeams = async (content: HoppCollection<HoppRESTRequest>) => {
|
||||
importingMyCollections.value = true
|
||||
if (props.collectionsType.type !== "team-collections") return
|
||||
|
||||
const result = await runMutation(ImportFromJsonDocument, {
|
||||
jsonString: JSON.stringify(content),
|
||||
teamID: props.collectionsType.selectedTeam.id,
|
||||
})()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
console.error(result.left)
|
||||
} else {
|
||||
emit("update-team-collections")
|
||||
}
|
||||
|
||||
importingMyCollections.value = false
|
||||
}
|
||||
|
||||
const exportJSON = async () => {
|
||||
await getJSONCollection()
|
||||
|
||||
const dataToWrite = collectionJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const importerModules = computed(() =>
|
||||
RESTCollectionImporters.filter(
|
||||
(i) => i.applicableTo?.includes(props.collectionsType.type) ?? true
|
||||
)
|
||||
)
|
||||
|
||||
const importerType = ref<number | null>(null)
|
||||
|
||||
const importerModule = computed(() =>
|
||||
importerType.value !== null ? importerModules.value[importerType.value] : null
|
||||
)
|
||||
|
||||
const importerSteps = computed(() => importerModule.value?.steps ?? null)
|
||||
|
||||
const finishImport = async () => {
|
||||
await importerAction(stepResults.value)
|
||||
}
|
||||
|
||||
const importerAction = async (stepResults: any[]) => {
|
||||
if (!importerModule.value) return
|
||||
const result = await importerModule.value?.importer(stepResults as any)()
|
||||
if (E.isLeft(result)) {
|
||||
failedImport()
|
||||
console.error("error", result.left)
|
||||
} else if (E.isRight(result)) {
|
||||
if (props.collectionsType.type === "team-collections") {
|
||||
importToTeams(result.right)
|
||||
fileImported()
|
||||
} else {
|
||||
appendRESTCollections(result.right)
|
||||
fileImported()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasFile = ref(false)
|
||||
const hasGist = ref(false)
|
||||
|
||||
watch(inputChooseGistToImportFrom, (v) => {
|
||||
stepResults.value = []
|
||||
if (v === "") {
|
||||
hasGist.value = false
|
||||
} else {
|
||||
hasGist.value = true
|
||||
stepResults.value.push(inputChooseGistToImportFrom.value)
|
||||
}
|
||||
})
|
||||
|
||||
const onFileChange = () => {
|
||||
stepResults.value = []
|
||||
if (!inputChooseFileToImportFrom.value[0]) {
|
||||
hasFile.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value[0].files ||
|
||||
inputChooseFileToImportFrom.value[0].files.length === 0
|
||||
) {
|
||||
inputChooseFileToImportFrom.value[0].value = ""
|
||||
hasFile.value = false
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
if (!content) {
|
||||
hasFile.value = false
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
stepResults.value.push(content)
|
||||
hasFile.value = !!content?.length
|
||||
}
|
||||
reader.readAsText(inputChooseFileToImportFrom.value[0].files[0])
|
||||
}
|
||||
|
||||
const enableImportButton = computed(
|
||||
() => !(stepResults.value.length === importerSteps.value?.length)
|
||||
)
|
||||
|
||||
const resetImport = () => {
|
||||
importerType.value = null
|
||||
stepResults.value = []
|
||||
inputChooseFileToImportFrom.value = ""
|
||||
hasFile.value = false
|
||||
inputChooseGistToImportFrom.value = ""
|
||||
hasGist.value = false
|
||||
mySelectedCollectionID.value = undefined
|
||||
}
|
||||
</script>
|
||||
@@ -1,391 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('collection.save_as')}`"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col">
|
||||
<div class="relative flex">
|
||||
<input
|
||||
id="selectLabelSaveReq"
|
||||
v-model="requestName"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
@keyup.enter="saveRequestAs"
|
||||
/>
|
||||
<label for="selectLabelSaveReq">
|
||||
{{ t("request.name") }}
|
||||
</label>
|
||||
</div>
|
||||
<label class="p-4">
|
||||
{{ t("collection.select_location") }}
|
||||
</label>
|
||||
<CollectionsGraphql
|
||||
v-if="mode === 'graphql'"
|
||||
:show-coll-actions="false"
|
||||
:picked="picked"
|
||||
:saving-mode="true"
|
||||
@select="onSelect"
|
||||
/>
|
||||
<Collections
|
||||
v-else
|
||||
:picked="picked"
|
||||
:save-request="true"
|
||||
@select="onSelect"
|
||||
@update-collection="updateColl"
|
||||
@update-coll-type="onUpdateCollType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary :label="`${t('action.save')}`" @click="saveRequestAs" />
|
||||
<ButtonSecondary :label="`${t('action.cancel')}`" @click="hideModal" />
|
||||
</span>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { HoppGQLRequest, isHoppRESTRequest } from "@hoppscotch/data"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import {
|
||||
editGraphqlRequest,
|
||||
editRESTRequest,
|
||||
saveGraphqlRequestAs,
|
||||
saveRESTRequestAs,
|
||||
} from "~/newstore/collections"
|
||||
import { getGQLSession, useGQLRequestName } from "~/newstore/GQLSession"
|
||||
import {
|
||||
getRESTRequest,
|
||||
setRESTSaveContext,
|
||||
useRESTRequestName,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
CreateRequestInCollectionDocument,
|
||||
UpdateRequestDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type CollectionType =
|
||||
| {
|
||||
type: "my-collections"
|
||||
}
|
||||
| {
|
||||
type: "team-collections"
|
||||
// TODO: Figure this type out
|
||||
selectedTeam: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
type Picked =
|
||||
| {
|
||||
pickedType: "my-request"
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}
|
||||
| {
|
||||
pickedType: "my-folder"
|
||||
folderPath: string
|
||||
}
|
||||
| {
|
||||
pickedType: "my-collection"
|
||||
collectionIndex: number
|
||||
}
|
||||
| {
|
||||
pickedType: "teams-request"
|
||||
requestID: string
|
||||
}
|
||||
| {
|
||||
pickedType: "teams-folder"
|
||||
folderID: string
|
||||
}
|
||||
| {
|
||||
pickedType: "teams-collection"
|
||||
collectionID: string
|
||||
}
|
||||
| {
|
||||
pickedType: "gql-my-request"
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}
|
||||
| {
|
||||
pickedType: "gql-my-folder"
|
||||
folderPath: string
|
||||
}
|
||||
| {
|
||||
pickedType: "gql-my-collection"
|
||||
collectionIndex: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
mode: "rest" | "graphql"
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
// TODO: Use a better implementation with computed ?
|
||||
// This implementation can't work across updates to mode prop (which won't happen tho)
|
||||
const requestName =
|
||||
props.mode === "rest" ? useRESTRequestName() : useGQLRequestName()
|
||||
|
||||
const requestData = reactive({
|
||||
name: requestName,
|
||||
collectionIndex: undefined as number | undefined,
|
||||
folderName: undefined as number | undefined,
|
||||
requestIndex: undefined as number | undefined,
|
||||
})
|
||||
|
||||
const collectionsType = ref<CollectionType>({
|
||||
type: "my-collections",
|
||||
})
|
||||
|
||||
// TODO: Figure this type out
|
||||
const picked = ref<Picked | null>(null)
|
||||
|
||||
// Resets
|
||||
watch(
|
||||
() => requestData.collectionIndex,
|
||||
() => {
|
||||
requestData.folderName = undefined
|
||||
requestData.requestIndex = undefined
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => requestData.folderName,
|
||||
() => {
|
||||
requestData.requestIndex = undefined
|
||||
}
|
||||
)
|
||||
|
||||
// All the methods
|
||||
const onUpdateCollType = (newCollType: CollectionType) => {
|
||||
collectionsType.value = newCollType
|
||||
}
|
||||
|
||||
const onSelect = ({ picked: pickedVal }: { picked: Picked | null }) => {
|
||||
picked.value = pickedVal
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
picked.value = null
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const saveRequestAs = async () => {
|
||||
if (!requestName.value) {
|
||||
toast.error(`${t("error.empty_req_name")}`)
|
||||
return
|
||||
}
|
||||
if (picked.value === null) {
|
||||
toast.error(`${t("collection.select")}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Clone Deep because objects are shared by reference so updating
|
||||
// just one bit will update other referenced shared instances
|
||||
const requestUpdated =
|
||||
props.mode === "rest"
|
||||
? cloneDeep(getRESTRequest())
|
||||
: cloneDeep(getGQLSession().request)
|
||||
|
||||
// // Filter out all REST file inputs
|
||||
// if (this.mode === "rest" && requestUpdated.bodyParams) {
|
||||
// requestUpdated.bodyParams = requestUpdated.bodyParams.map((param) =>
|
||||
// param?.value?.[0] instanceof File ? { ...param, value: "" } : param
|
||||
// )
|
||||
// }
|
||||
|
||||
if (picked.value.pickedType === "my-request") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
editRESTRequest(
|
||||
picked.value.folderPath,
|
||||
picked.value.requestIndex,
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: picked.value.requestIndex,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "my-folder") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
const insertionIndex = saveRESTRequestAs(
|
||||
picked.value.folderPath,
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: insertionIndex,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "my-collection") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
const insertionIndex = saveRESTRequestAs(
|
||||
`${picked.value.collectionIndex}`,
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: `${picked.value.collectionIndex}`,
|
||||
requestIndex: insertionIndex,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "teams-request") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
if (collectionsType.value.type !== "team-collections")
|
||||
throw new Error("Collections Type mismatch")
|
||||
|
||||
runMutation(UpdateRequestDocument, {
|
||||
requestID: picked.value.requestID,
|
||||
data: {
|
||||
request: JSON.stringify(requestUpdated),
|
||||
title: requestUpdated.name,
|
||||
},
|
||||
})().then((result) => {
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
throw new Error(`${result.left}`)
|
||||
} else {
|
||||
requestSaved()
|
||||
}
|
||||
})
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: picked.value.requestID,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
} else if (picked.value.pickedType === "teams-folder") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
if (collectionsType.value.type !== "team-collections")
|
||||
throw new Error("Collections Type mismatch")
|
||||
|
||||
const result = await runMutation(CreateRequestInCollectionDocument, {
|
||||
collectionID: picked.value.folderID,
|
||||
data: {
|
||||
request: JSON.stringify(requestUpdated),
|
||||
teamID: collectionsType.value.selectedTeam.id,
|
||||
title: requestUpdated.name,
|
||||
},
|
||||
})()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
console.error(result.left)
|
||||
} else {
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: result.right.createRequestInCollection.id,
|
||||
teamID: collectionsType.value.selectedTeam.id,
|
||||
collectionID: picked.value.folderID,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
|
||||
requestSaved()
|
||||
}
|
||||
} else if (picked.value.pickedType === "teams-collection") {
|
||||
if (!isHoppRESTRequest(requestUpdated))
|
||||
throw new Error("requestUpdated is not a REST Request")
|
||||
|
||||
if (collectionsType.value.type !== "team-collections")
|
||||
throw new Error("Collections Type mismatch")
|
||||
|
||||
const result = await runMutation(CreateRequestInCollectionDocument, {
|
||||
collectionID: picked.value.collectionID,
|
||||
data: {
|
||||
title: requestUpdated.name,
|
||||
request: JSON.stringify(requestUpdated),
|
||||
teamID: collectionsType.value.selectedTeam.id,
|
||||
},
|
||||
})()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
console.error(result.left)
|
||||
} else {
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: result.right.createRequestInCollection.id,
|
||||
teamID: collectionsType.value.selectedTeam.id,
|
||||
collectionID: picked.value.collectionID,
|
||||
req: cloneDeep(requestUpdated),
|
||||
})
|
||||
|
||||
requestSaved()
|
||||
}
|
||||
} else if (picked.value.pickedType === "gql-my-request") {
|
||||
// TODO: Check for GQL request ?
|
||||
editGraphqlRequest(
|
||||
picked.value.folderPath,
|
||||
picked.value.requestIndex,
|
||||
requestUpdated as HoppGQLRequest
|
||||
)
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "gql-my-folder") {
|
||||
// TODO: Check for GQL request ?
|
||||
saveGraphqlRequestAs(
|
||||
picked.value.folderPath,
|
||||
requestUpdated as HoppGQLRequest
|
||||
)
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "gql-my-collection") {
|
||||
// TODO: Check for GQL request ?
|
||||
saveGraphqlRequestAs(
|
||||
`${picked.value.collectionIndex}`,
|
||||
requestUpdated as HoppGQLRequest
|
||||
)
|
||||
|
||||
requestSaved()
|
||||
}
|
||||
}
|
||||
|
||||
const requestSaved = () => {
|
||||
toast.success(`${t("request.added")}`)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const updateColl = (ev: CollectionType["type"]) => {
|
||||
collectionsType.value.type = ev
|
||||
}
|
||||
</script>
|
||||
@@ -1,265 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('modal.collections')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #actions>
|
||||
<span>
|
||||
<tippy interactive trigger="click" theme="popover" arrow>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
:icon="IconGithub"
|
||||
:label="t('import.from_gist')"
|
||||
@click="
|
||||
() => {
|
||||
readCollectionGist()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<SmartItem
|
||||
:disabled="
|
||||
!currentUser
|
||||
? true
|
||||
: currentUser.provider !== 'github.com'
|
||||
? true
|
||||
: false
|
||||
"
|
||||
:icon="IconGithub"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click="
|
||||
() => {
|
||||
createCollectionGist()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('import.from_json')"
|
||||
@click="openDialogChooseFileToImportFrom"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToImportFrom"
|
||||
class="input"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
@change="importFromJSON"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.download_file')"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.as_json')"
|
||||
@click="exportJSON"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from "axios"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconGithub from "~icons/lucide/github"
|
||||
import { computed, ref } from "vue"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import {
|
||||
graphqlCollections$,
|
||||
setGraphqlCollections,
|
||||
appendGraphqlCollections,
|
||||
} from "~/newstore/collections"
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const collections = useReadonlyStream(graphqlCollections$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
// Template refs
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement>()
|
||||
|
||||
const collectionJson = computed(() => {
|
||||
return JSON.stringify(collections.value, null, 2)
|
||||
})
|
||||
|
||||
const createCollectionGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: collectionJson.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.data.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
}
|
||||
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const readCollectionGist = async () => {
|
||||
const gist = prompt(t("import.gist_url").toString())
|
||||
if (!gist) return
|
||||
|
||||
try {
|
||||
const { files } = (await axios.get(
|
||||
`https://api.github.com/gists/${gist.split("/").pop()}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)) as {
|
||||
files: {
|
||||
[fileName: string]: {
|
||||
content: any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const collections = JSON.parse(Object.values(files)[0].content)
|
||||
setGraphqlCollections(collections)
|
||||
fileImported()
|
||||
} catch (e) {
|
||||
failedImport()
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const openDialogChooseFileToImportFrom = () => {
|
||||
if (inputChooseFileToImportFrom.value)
|
||||
inputChooseFileToImportFrom.value.click()
|
||||
}
|
||||
|
||||
const importFromJSON = () => {
|
||||
if (!inputChooseFileToImportFrom.value) return
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value.files ||
|
||||
inputChooseFileToImportFrom.value.files.length === 0
|
||||
) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
|
||||
if (!content) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (name === "name" && folders === "folders" && requests === "requests") {
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
failedImport()
|
||||
return
|
||||
}
|
||||
appendGraphqlCollections(collections)
|
||||
fileImported()
|
||||
}
|
||||
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
||||
inputChooseFileToImportFrom.value.value = ""
|
||||
}
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = collectionJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
@@ -1,959 +0,0 @@
|
||||
<template>
|
||||
<div :class="{ 'rounded border border-divider': saveRequest }">
|
||||
<div
|
||||
class="sticky z-10 flex flex-col border-b rounded-t bg-primary border-dividerLight"
|
||||
:style="
|
||||
saveRequest ? 'top: calc(-1.35 * var(--font-size-body))' : 'top: 0'
|
||||
"
|
||||
>
|
||||
<div class="flex flex-col border-b border-dividerLight">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="t('action.search')"
|
||||
class="py-2 pl-4 pr-2 bg-transparent"
|
||||
:disabled="collectionsType.type == 'team-collections'"
|
||||
/>
|
||||
</div>
|
||||
<CollectionsChooseType
|
||||
:collections-type="collectionsType"
|
||||
:show="showTeamCollections"
|
||||
@update-collection-type="updateCollectionType"
|
||||
@update-selected-team="updateSelectedTeam"
|
||||
/>
|
||||
<div class="flex justify-between flex-1">
|
||||
<ButtonSecondary
|
||||
v-if="
|
||||
collectionsType.type == 'team-collections' &&
|
||||
(collectionsType.selectedTeam == undefined ||
|
||||
collectionsType.selectedTeam.myRole == 'VIEWER')
|
||||
"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
disabled
|
||||
class="!rounded-none"
|
||||
:icon="IconPlus"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('action.new')"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
:icon="IconPlus"
|
||||
:label="t('action.new')"
|
||||
class="!rounded-none"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<span class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/collections"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:disabled="
|
||||
collectionsType.type == 'team-collections' &&
|
||||
collectionsType.selectedTeam == undefined
|
||||
"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<component
|
||||
:is="
|
||||
collectionsType.type == 'my-collections'
|
||||
? 'CollectionsMyCollection'
|
||||
: 'CollectionsTeamsCollection'
|
||||
"
|
||||
v-for="(collection, index) in filteredCollections"
|
||||
:key="`collection-${index}`"
|
||||
:collection-index="parseInt(index)"
|
||||
:collection="collection"
|
||||
:is-filtered="filterText.length > 0"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
:loading-collection-i-ds="loadingCollectionIDs"
|
||||
@edit-collection="editCollection(collection, index)"
|
||||
@add-request="addRequest($event)"
|
||||
@add-folder="addFolder($event)"
|
||||
@edit-folder="editFolder($event)"
|
||||
@edit-request="editRequest($event)"
|
||||
@duplicate-request="duplicateRequest($event)"
|
||||
@update-team-collections="updateTeamCollections"
|
||||
@select-collection="$emit('use-collection', collection)"
|
||||
@unselect-collection="$emit('remove-collection', collection)"
|
||||
@select="$emit('select', $event)"
|
||||
@expand-collection="expandCollection"
|
||||
@remove-collection="removeCollection"
|
||||
@remove-request="removeRequest"
|
||||
@remove-folder="removeFolder"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="loadingCollectionIDs.includes('root')"
|
||||
class="flex flex-col items-center justify-center p-4"
|
||||
>
|
||||
<SmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="filteredCollections.length === 0 && filterText.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="t('empty.collections')"
|
||||
/>
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.collections") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
v-if="
|
||||
collectionsType.type == 'team-collections' &&
|
||||
(collectionsType.selectedTeam == undefined ||
|
||||
collectionsType.selectedTeam.myRole == 'VIEWER')
|
||||
"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('team.no_access')"
|
||||
:label="t('add.new')"
|
||||
class="mb-4"
|
||||
filled
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
class="mb-4"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="filterText.length !== 0 && filteredCollections.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
<CollectionsAdd
|
||||
:show="showModalAdd"
|
||||
:loading-state="modalLoadingState"
|
||||
@submit="addNewRootCollection"
|
||||
@hide-modal="displayModalAdd(false)"
|
||||
/>
|
||||
<CollectionsEdit
|
||||
:show="showModalEdit"
|
||||
:editing-collection-name="
|
||||
editingCollection
|
||||
? editingCollection.name || editingCollection.title
|
||||
: ''
|
||||
"
|
||||
:loading-state="modalLoadingState"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
@submit="updateEditingCollection"
|
||||
/>
|
||||
<CollectionsAddRequest
|
||||
:show="showModalAddRequest"
|
||||
:folder="editingFolder"
|
||||
:folder-path="editingFolderPath"
|
||||
:loading-state="modalLoadingState"
|
||||
@add-request="onAddRequest($event)"
|
||||
@hide-modal="displayModalAddRequest(false)"
|
||||
/>
|
||||
<CollectionsAddFolder
|
||||
:show="showModalAddFolder"
|
||||
:folder="editingFolder"
|
||||
:folder-path="editingFolderPath"
|
||||
:loading-state="modalLoadingState"
|
||||
@add-folder="onAddFolder($event)"
|
||||
@hide-modal="displayModalAddFolder(false)"
|
||||
/>
|
||||
<CollectionsEditFolder
|
||||
:show="showModalEditFolder"
|
||||
:editing-folder-name="
|
||||
editingFolder ? editingFolder.name || editingFolder.title : ''
|
||||
"
|
||||
:loading-state="modalLoadingState"
|
||||
@submit="updateEditingFolder"
|
||||
@hide-modal="displayModalEditFolder(false)"
|
||||
/>
|
||||
<CollectionsEditRequest
|
||||
:show="showModalEditRequest"
|
||||
:editing-request-name="editingRequest ? editingRequest.name : ''"
|
||||
:loading-state="modalLoadingState"
|
||||
@submit="updateEditingRequest"
|
||||
@hide-modal="displayModalEditRequest(false)"
|
||||
/>
|
||||
<CollectionsImportExport
|
||||
:show="showModalImportExport"
|
||||
:collections-type="collectionsType"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
@update-team-collections="updateTeamCollections"
|
||||
/>
|
||||
<SmartConfirmModal
|
||||
:show="showConfirmModal"
|
||||
:title="confirmModalTitle"
|
||||
:loading-state="modalLoadingState"
|
||||
@hide-modal="showConfirmModal = false"
|
||||
@resolve="resolveConfirmModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { defineComponent, markRaw } from "vue"
|
||||
import { makeCollection } from "@hoppscotch/data"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import * as E from "fp-ts/Either"
|
||||
import CollectionsMyCollection from "./my/Collection.vue"
|
||||
import CollectionsTeamsCollection from "./teams/Collection.vue"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||
import {
|
||||
restCollections$,
|
||||
addRESTCollection,
|
||||
editRESTCollection,
|
||||
addRESTFolder,
|
||||
removeRESTCollection,
|
||||
removeRESTFolder,
|
||||
editRESTFolder,
|
||||
removeRESTRequest,
|
||||
editRESTRequest,
|
||||
saveRESTRequestAs,
|
||||
} from "~/newstore/collections"
|
||||
import {
|
||||
setRESTRequest,
|
||||
getRESTRequest,
|
||||
getRESTSaveContext,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
CreateChildCollectionDocument,
|
||||
CreateNewRootCollectionDocument,
|
||||
CreateRequestInCollectionDocument,
|
||||
DeleteCollectionDocument,
|
||||
DeleteRequestDocument,
|
||||
RenameCollectionDocument,
|
||||
UpdateRequestDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CollectionsMyCollection,
|
||||
CollectionsTeamsCollection,
|
||||
},
|
||||
props: {
|
||||
saveRequest: Boolean,
|
||||
picked: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: [
|
||||
"update-collection",
|
||||
"update-coll-type",
|
||||
"update-team-collections",
|
||||
"select-request",
|
||||
"select",
|
||||
"use-collection",
|
||||
"remove-collection",
|
||||
],
|
||||
setup() {
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
return {
|
||||
subscribeTo: subscribeToStream,
|
||||
|
||||
collections: useReadonlyStream(restCollections$, [], "deep"),
|
||||
currentUser: useReadonlyStream(currentUser$, null),
|
||||
colorMode: useColorMode(),
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
IconArchive: markRaw(IconArchive),
|
||||
IconHelpCircle: markRaw(IconHelpCircle),
|
||||
IconPlus: markRaw(IconPlus),
|
||||
showModalAdd: false,
|
||||
showModalEdit: false,
|
||||
showModalImportExport: false,
|
||||
showModalAddRequest: false,
|
||||
showModalAddFolder: false,
|
||||
showModalEditFolder: false,
|
||||
showModalEditRequest: false,
|
||||
showConfirmModal: false,
|
||||
modalLoadingState: false,
|
||||
editingCollection: undefined,
|
||||
editingCollectionIndex: undefined,
|
||||
editingCollectionID: undefined,
|
||||
editingFolder: undefined,
|
||||
editingFolderName: undefined,
|
||||
editingFolderIndex: undefined,
|
||||
editingFolderPath: undefined,
|
||||
editingRequest: undefined,
|
||||
editingRequestIndex: undefined,
|
||||
confirmModalTitle: undefined,
|
||||
filterText: "",
|
||||
collectionsType: {
|
||||
type: "my-collections",
|
||||
selectedTeam: undefined,
|
||||
},
|
||||
teamCollectionAdapter: new TeamCollectionAdapter(null),
|
||||
teamCollectionsNew: [],
|
||||
loadingCollectionIDs: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTeamCollections() {
|
||||
if (this.currentUser == null) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
filteredCollections() {
|
||||
const collections =
|
||||
this.collectionsType.type === "my-collections"
|
||||
? this.collections
|
||||
: this.teamCollectionsNew
|
||||
|
||||
if (!this.filterText) {
|
||||
return collections
|
||||
}
|
||||
|
||||
if (this.collectionsType.type === "team-collections") {
|
||||
return []
|
||||
}
|
||||
|
||||
const filterText = this.filterText.toLowerCase()
|
||||
const filteredCollections = []
|
||||
|
||||
for (const collection of collections) {
|
||||
const filteredRequests = []
|
||||
const filteredFolders = []
|
||||
for (const request of collection.requests) {
|
||||
if (request.name.toLowerCase().includes(filterText))
|
||||
filteredRequests.push(request)
|
||||
}
|
||||
for (const folder of this.collectionsType.type === "team-collections"
|
||||
? collection.children
|
||||
: collection.folders) {
|
||||
const filteredFolderRequests = []
|
||||
for (const request of folder.requests) {
|
||||
if (request.name.toLowerCase().includes(filterText))
|
||||
filteredFolderRequests.push(request)
|
||||
}
|
||||
if (filteredFolderRequests.length > 0) {
|
||||
const filteredFolder = Object.assign({}, folder)
|
||||
filteredFolder.requests = filteredFolderRequests
|
||||
filteredFolders.push(filteredFolder)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filteredRequests.length + filteredFolders.length > 0 ||
|
||||
collection.name.toLowerCase().includes(filterText)
|
||||
) {
|
||||
const filteredCollection = Object.assign({}, collection)
|
||||
filteredCollection.requests = filteredRequests
|
||||
filteredCollection.folders = filteredFolders
|
||||
filteredCollections.push(filteredCollection)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCollections
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"collectionsType.type": function emitstuff() {
|
||||
this.$emit("update-collection", this.$data.collectionsType.type)
|
||||
},
|
||||
"collectionsType.selectedTeam"(value) {
|
||||
if (value?.id) this.teamCollectionAdapter.changeTeamID(value.id)
|
||||
},
|
||||
currentUser(newValue) {
|
||||
if (!newValue) this.updateCollectionType("my-collections")
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.teamCollectionAdapter.unsubscribeSubscriptions()
|
||||
},
|
||||
mounted() {
|
||||
this.subscribeTo(this.teamCollectionAdapter.collections$, (colls) => {
|
||||
this.teamCollectionsNew = cloneDeep(colls)
|
||||
})
|
||||
this.subscribeTo(
|
||||
this.teamCollectionAdapter.loadingCollections$,
|
||||
(collectionsIDs) => {
|
||||
this.loadingCollectionIDs = collectionsIDs
|
||||
}
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
updateTeamCollections() {
|
||||
// TODO: Remove this at some point
|
||||
},
|
||||
updateSelectedTeam(newSelectedTeam) {
|
||||
this.collectionsType.selectedTeam = newSelectedTeam
|
||||
this.$emit("update-coll-type", this.collectionsType)
|
||||
},
|
||||
updateCollectionType(newCollectionType) {
|
||||
this.collectionsType.type = newCollectionType
|
||||
this.$emit("update-coll-type", this.collectionsType)
|
||||
},
|
||||
// Intented to be called by the CollectionAdd modal submit event
|
||||
addNewRootCollection(name) {
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
addRESTCollection(
|
||||
makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
})
|
||||
)
|
||||
|
||||
this.displayModalAdd(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
runMutation(CreateNewRootCollectionDocument, {
|
||||
title: name,
|
||||
teamID: this.collectionsType.selectedTeam.id,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
if (E.isLeft(result)) {
|
||||
if (result.left.error === "team_coll/short_title")
|
||||
this.toast.error(this.t("collection.name_length_insufficient"))
|
||||
else this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("collection.created"))
|
||||
this.displayModalAdd(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// Intented to be called by CollectionEdit modal submit event
|
||||
updateEditingCollection(newName) {
|
||||
if (!newName) {
|
||||
this.toast.error(this.t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
const collectionUpdated = {
|
||||
...this.editingCollection,
|
||||
name: newName,
|
||||
}
|
||||
|
||||
editRESTCollection(this.editingCollectionIndex, collectionUpdated)
|
||||
this.displayModalEdit(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
|
||||
runMutation(RenameCollectionDocument, {
|
||||
collectionID: this.editingCollection.id,
|
||||
newTitle: newName,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("collection.renamed"))
|
||||
this.displayModalEdit(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// Intended to be called by CollectionEditFolder modal submit event
|
||||
updateEditingFolder(name) {
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
editRESTFolder(this.editingFolderPath, { ...this.editingFolder, name })
|
||||
this.displayModalEditFolder(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
|
||||
runMutation(RenameCollectionDocument, {
|
||||
collectionID: this.editingFolder.id,
|
||||
newTitle: name,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
if (result.left.error === "team_coll/short_title")
|
||||
this.toast.error(this.t("folder.name_length_insufficient"))
|
||||
else this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("folder.renamed"))
|
||||
this.displayModalEditFolder(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// Intented to by called by CollectionsEditRequest modal submit event
|
||||
updateEditingRequest(requestUpdateData) {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
|
||||
const requestUpdated = {
|
||||
...this.editingRequest,
|
||||
name: requestUpdateData.name || this.editingRequest.name,
|
||||
}
|
||||
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
// Update REST Session with the updated state
|
||||
if (
|
||||
saveCtx &&
|
||||
saveCtx.originLocation === "user-collection" &&
|
||||
saveCtx.requestIndex === this.editingRequestIndex &&
|
||||
saveCtx.folderPath === this.editingFolderPath
|
||||
) {
|
||||
setRESTRequest({
|
||||
...getRESTRequest(),
|
||||
name: requestUpdateData.name,
|
||||
})
|
||||
}
|
||||
|
||||
editRESTRequest(
|
||||
this.editingFolderPath,
|
||||
this.editingRequestIndex,
|
||||
requestUpdated
|
||||
)
|
||||
this.displayModalEditRequest(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
|
||||
const requestName = requestUpdateData.name || this.editingRequest.name
|
||||
|
||||
// Update REST Session with the updated state
|
||||
if (
|
||||
saveCtx &&
|
||||
saveCtx.originLocation === "team-collection" &&
|
||||
saveCtx.requestID === this.editingRequestIndex
|
||||
) {
|
||||
setRESTRequest({
|
||||
...getRESTRequest(),
|
||||
name: requestUpdateData.name,
|
||||
})
|
||||
}
|
||||
|
||||
runMutation(UpdateRequestDocument, {
|
||||
data: {
|
||||
request: JSON.stringify(requestUpdated),
|
||||
title: requestName,
|
||||
},
|
||||
requestID: this.editingRequestIndex,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("request.renamed"))
|
||||
this.$emit("update-team-collections")
|
||||
this.displayModalEditRequest(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
displayModalAdd(shouldDisplay) {
|
||||
this.showModalAdd = shouldDisplay
|
||||
},
|
||||
displayModalEdit(shouldDisplay) {
|
||||
this.showModalEdit = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalImportExport(shouldDisplay) {
|
||||
this.showModalImportExport = shouldDisplay
|
||||
},
|
||||
displayModalAddRequest(shouldDisplay) {
|
||||
this.showModalAddRequest = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalAddFolder(shouldDisplay) {
|
||||
this.showModalAddFolder = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalEditFolder(shouldDisplay) {
|
||||
this.showModalEditFolder = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayModalEditRequest(shouldDisplay) {
|
||||
this.showModalEditRequest = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
displayConfirmModal(shouldDisplay) {
|
||||
this.showConfirmModal = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) this.resetSelectedData()
|
||||
},
|
||||
editCollection(collection, collectionIndex) {
|
||||
this.$data.editingCollection = collection
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.displayModalEdit(true)
|
||||
},
|
||||
onAddFolder({ name, folder, path }) {
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
addRESTFolder(name, path)
|
||||
this.displayModalAddFolder(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
runMutation(CreateChildCollectionDocument, {
|
||||
childTitle: name,
|
||||
collectionID: folder.id,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
if (E.isLeft(result)) {
|
||||
if (result.left.error === "team_coll/short_title")
|
||||
this.toast.error(this.t("folder.name_length_insufficient"))
|
||||
else this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("folder.created"))
|
||||
this.displayModalAddFolder(false)
|
||||
this.$emit("update-team-collections")
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
addFolder(payload) {
|
||||
const { folder, path } = payload
|
||||
this.$data.editingFolder = folder
|
||||
this.$data.editingFolderPath = path
|
||||
this.displayModalAddFolder(true)
|
||||
},
|
||||
editFolder(payload) {
|
||||
const { collectionIndex, folder, folderIndex, folderPath } = payload
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.$data.editingFolder = folder
|
||||
this.$data.editingFolderIndex = folderIndex
|
||||
this.$data.editingFolderPath = folderPath
|
||||
this.$data.collectionsType = this.collectionsType
|
||||
this.displayModalEditFolder(true)
|
||||
},
|
||||
editRequest(payload) {
|
||||
const {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
} = payload
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.$data.editingFolderIndex = folderIndex
|
||||
this.$data.editingFolderName = folderName
|
||||
this.$data.editingRequest = request
|
||||
this.$data.editingRequestIndex = requestIndex
|
||||
this.editingFolderPath = folderPath
|
||||
this.$emit("select-request", requestIndex)
|
||||
this.displayModalEditRequest(true)
|
||||
},
|
||||
resetSelectedData() {
|
||||
this.$data.editingCollection = undefined
|
||||
this.$data.editingCollectionIndex = undefined
|
||||
this.$data.editingCollectionID = undefined
|
||||
this.$data.editingFolder = undefined
|
||||
this.$data.editingFolderPath = undefined
|
||||
this.$data.editingFolderIndex = undefined
|
||||
this.$data.editingRequest = undefined
|
||||
this.$data.editingRequestIndex = undefined
|
||||
|
||||
this.$data.confirmModalTitle = undefined
|
||||
},
|
||||
expandCollection(collectionID) {
|
||||
this.teamCollectionAdapter.expandCollection(collectionID)
|
||||
},
|
||||
removeCollection({ collectionIndex, collectionID }) {
|
||||
this.$data.editingCollectionIndex = collectionIndex
|
||||
this.$data.editingCollectionID = collectionID
|
||||
this.confirmModalTitle = `${this.t("confirm.remove_collection")}`
|
||||
|
||||
this.displayConfirmModal(true)
|
||||
},
|
||||
onRemoveCollection() {
|
||||
const collectionIndex = this.$data.editingCollectionIndex
|
||||
const collectionID = this.$data.editingCollectionID
|
||||
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
// Cancel pick if picked collection is deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-collection" &&
|
||||
this.picked.collectionIndex === collectionIndex
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
|
||||
removeRESTCollection(collectionIndex)
|
||||
|
||||
this.toast.success(this.t("state.deleted"))
|
||||
this.displayConfirmModal(false)
|
||||
} else if (this.collectionsType.type === "team-collections") {
|
||||
this.modalLoadingState = true
|
||||
|
||||
// Cancel pick if picked collection is deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-collection" &&
|
||||
this.picked.collectionID === collectionID
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
|
||||
if (this.collectionsType.selectedTeam.myRole !== "VIEWER") {
|
||||
runMutation(DeleteCollectionDocument, {
|
||||
collectionID,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("state.deleted"))
|
||||
this.displayConfirmModal(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
removeFolder({ collectionID, folder, folderPath }) {
|
||||
this.$data.editingCollectionID = collectionID
|
||||
this.$data.editingFolder = folder
|
||||
this.$data.editingFolderPath = folderPath
|
||||
this.confirmModalTitle = `${this.t("confirm.remove_folder")}`
|
||||
|
||||
this.displayConfirmModal(true)
|
||||
},
|
||||
onRemoveFolder() {
|
||||
const folder = this.$data.editingFolder
|
||||
const folderPath = this.$data.editingFolderPath
|
||||
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
// Cancel pick if picked folder was deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-folder" &&
|
||||
this.picked.folderPath === folderPath
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
removeRESTFolder(folderPath)
|
||||
|
||||
this.toast.success(this.t("state.deleted"))
|
||||
this.displayConfirmModal(false)
|
||||
} else if (this.collectionsType.type === "team-collections") {
|
||||
this.modalLoadingState = true
|
||||
|
||||
// Cancel pick if picked collection folder was deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-folder" &&
|
||||
this.picked.folderID === folder.id
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
|
||||
if (this.collectionsType.selectedTeam.myRole !== "VIEWER") {
|
||||
runMutation(DeleteCollectionDocument, {
|
||||
collectionID: folder.id,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(`${this.t("state.deleted")}`)
|
||||
this.displayConfirmModal(false)
|
||||
|
||||
this.updateTeamCollections()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
removeRequest({ requestIndex, folderPath }) {
|
||||
this.$data.editingRequestIndex = requestIndex
|
||||
this.$data.editingFolderPath = folderPath
|
||||
this.confirmModalTitle = `${this.t("confirm.remove_request")}`
|
||||
|
||||
this.displayConfirmModal(true)
|
||||
},
|
||||
onRemoveRequest() {
|
||||
const requestIndex = this.$data.editingRequestIndex
|
||||
const folderPath = this.$data.editingFolderPath
|
||||
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
// Cancel pick if the picked item is being deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-request" &&
|
||||
this.picked.folderPath === folderPath &&
|
||||
this.picked.requestIndex === requestIndex
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
removeRESTRequest(folderPath, requestIndex)
|
||||
|
||||
this.toast.success(this.t("state.deleted"))
|
||||
this.displayConfirmModal(false)
|
||||
} else if (this.collectionsType.type === "team-collections") {
|
||||
this.modalLoadingState = true
|
||||
// Cancel pick if the picked item is being deleted
|
||||
if (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-request" &&
|
||||
this.picked.requestID === requestIndex
|
||||
) {
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
|
||||
runMutation(DeleteRequestDocument, {
|
||||
requestID: requestIndex,
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
this.toast.success(this.t("state.deleted"))
|
||||
this.displayConfirmModal(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
addRequest(payload) {
|
||||
// TODO: check if the request being worked on
|
||||
// is being overwritten (selected or not)
|
||||
const { folder, path } = payload
|
||||
this.$data.editingFolder = folder
|
||||
this.$data.editingFolderPath = path
|
||||
this.displayModalAddRequest(true)
|
||||
},
|
||||
onAddRequest({ name, folder, path }) {
|
||||
const newRequest = {
|
||||
...cloneDeep(getRESTRequest()),
|
||||
name,
|
||||
}
|
||||
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
// point to it
|
||||
setRESTRequest(newRequest, {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
requestIndex: insertionIndex,
|
||||
})
|
||||
|
||||
this.displayModalAddRequest(false)
|
||||
} else if (
|
||||
this.collectionsType.type === "team-collections" &&
|
||||
this.collectionsType.selectedTeam.myRole !== "VIEWER"
|
||||
) {
|
||||
this.modalLoadingState = true
|
||||
runMutation(CreateRequestInCollectionDocument, {
|
||||
collectionID: folder.id,
|
||||
data: {
|
||||
request: JSON.stringify(newRequest),
|
||||
teamID: this.collectionsType.selectedTeam.id,
|
||||
title: name,
|
||||
},
|
||||
})().then((result) => {
|
||||
this.modalLoadingState = false
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
console.error(result.left.error)
|
||||
} else {
|
||||
const { createRequestInCollection } = result.right
|
||||
// point to it
|
||||
setRESTRequest(newRequest, {
|
||||
originLocation: "team-collection",
|
||||
requestID: createRequestInCollection.id,
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
})
|
||||
this.displayModalAddRequest(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
duplicateRequest({ folderPath, request, collectionID }) {
|
||||
if (this.collectionsType.type === "team-collections") {
|
||||
const newReq = {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.t("action.duplicate")}`,
|
||||
}
|
||||
|
||||
// Error handling ?
|
||||
runMutation(CreateRequestInCollectionDocument, {
|
||||
collectionID,
|
||||
data: {
|
||||
request: JSON.stringify(newReq),
|
||||
teamID: this.collectionsType.selectedTeam.id,
|
||||
title: `${request.name} - ${this.t("action.duplicate")}`,
|
||||
},
|
||||
})()
|
||||
} else if (this.collectionsType.type === "my-collections") {
|
||||
saveRESTRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.t("action.duplicate")}`,
|
||||
})
|
||||
}
|
||||
},
|
||||
resolveConfirmModal(title) {
|
||||
if (title === `${this.t("confirm.remove_collection")}`)
|
||||
this.onRemoveCollection()
|
||||
else if (title === `${this.t("confirm.remove_request")}`)
|
||||
this.onRemoveRequest()
|
||||
else if (title === `${this.t("confirm.remove_folder")}`)
|
||||
this.onRemoveFolder()
|
||||
else {
|
||||
console.error(
|
||||
`Confirm modal title ${title} is not handled by the component`
|
||||
)
|
||||
this.toast.error(this.t("error.something_went_wrong"))
|
||||
this.displayConfirmModal(false)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
// request inside folder is not being deleted, you dumb fuck
|
||||
</script>
|
||||
@@ -1,355 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
:is="getCollectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collection.name }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="
|
||||
$emit('add-request', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.r="requestAction.$el.click()"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.x="exportAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
:shortcut="['R']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-request', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
:shortcut="['X']"
|
||||
@click="
|
||||
() => {
|
||||
exportCollection()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeCollection()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<CollectionsMyFolder
|
||||
v-for="(folder, index) in collection.folders"
|
||||
:key="`folder-${index}`"
|
||||
:folder="folder"
|
||||
:folder-index="index"
|
||||
:folder-path="`${collectionIndex}/${index}`"
|
||||
:collection-index="collectionIndex"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:is-filtered="isFiltered"
|
||||
:picked="picked"
|
||||
@add-request="$emit('add-request', $event)"
|
||||
@add-folder="$emit('add-folder', $event)"
|
||||
@edit-folder="$emit('edit-folder', $event)"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@remove-folder="$emit('remove-folder', $event)"
|
||||
/>
|
||||
<CollectionsMyRequest
|
||||
v-for="(request, index) in collection.requests"
|
||||
:key="`request-${index}`"
|
||||
:request="request"
|
||||
:collection-index="collectionIndex"
|
||||
:folder-index="-1"
|
||||
:folder-name="collection.name"
|
||||
:folder-path="`${collectionIndex}`"
|
||||
:request-index="index"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
(collection.folders == undefined ||
|
||||
collection.folders.length === 0) &&
|
||||
(collection.requests == undefined ||
|
||||
collection.requests.length === 0)
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
||||
:alt="`${t('empty.collection')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ t("empty.collection") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconFilePlus from "~icons/lucide/file-plus"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { defineComponent, ref, markRaw } from "vue"
|
||||
import { moveRESTRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
collectionIndex: { type: Number, default: null },
|
||||
collection: { type: Object, default: () => ({}) },
|
||||
isFiltered: Boolean,
|
||||
saveRequest: Boolean,
|
||||
collectionsType: { type: Object, default: () => ({}) },
|
||||
picked: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: [
|
||||
"select",
|
||||
"expand-collection",
|
||||
"add-collection",
|
||||
"remove-collection",
|
||||
"add-folder",
|
||||
"add-request",
|
||||
"edit-folder",
|
||||
"edit-request",
|
||||
"duplicate-request",
|
||||
"remove-folder",
|
||||
"remove-request",
|
||||
"select-collection",
|
||||
"unselect-collection",
|
||||
"edit-collection",
|
||||
],
|
||||
setup() {
|
||||
return {
|
||||
colorMode: useColorMode(),
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
requestAction: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
exportAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
IconCircle: markRaw(IconCircle),
|
||||
IconCheckCircle: markRaw(IconCheckCircle),
|
||||
IconFilePlus: markRaw(IconFilePlus),
|
||||
IconFolderPlus: markRaw(IconFolderPlus),
|
||||
IconMoreVertical: markRaw(IconMoreVertical),
|
||||
IconEdit: markRaw(IconEdit),
|
||||
IconDownload: markRaw(IconDownload),
|
||||
IconTrash2: markRaw(IconTrash2),
|
||||
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
selectedFolder: {},
|
||||
prevCursor: "",
|
||||
cursor: "",
|
||||
pageNo: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-collection" &&
|
||||
this.picked.collectionIndex === this.collectionIndex
|
||||
)
|
||||
},
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return IconCheckCircle
|
||||
else if (!this.showChildren && !this.isFiltered) return IconFolder
|
||||
else if (this.showChildren || this.isFiltered) return IconFolderOpen
|
||||
else return IconFolder
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
exportCollection() {
|
||||
const collectionJSON = JSON.stringify(this.collection)
|
||||
|
||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
a.download = `${this.collection.name}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.toast.success(this.t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
},
|
||||
toggleShowChildren() {
|
||||
if (this.$props.saveRequest)
|
||||
this.$emit("select", {
|
||||
picked: {
|
||||
pickedType: "my-collection",
|
||||
collectionIndex: this.collectionIndex,
|
||||
},
|
||||
})
|
||||
|
||||
this.$emit("expand-collection", this.collection.id)
|
||||
this.showChildren = !this.showChildren
|
||||
},
|
||||
removeCollection() {
|
||||
this.$emit("remove-collection", {
|
||||
collectionIndex: this.collectionIndex,
|
||||
collectionID: this.collection.id,
|
||||
})
|
||||
},
|
||||
dropEvent({ dataTransfer }: any) {
|
||||
this.dragging = !this.dragging
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
moveRESTRequest(folderPath, requestIndex, `${this.collectionIndex}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,341 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
:is="getCollectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="$emit('add-request', { path: folderPath })"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="$emit('add-folder', { folder, path: folderPath })"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.r="requestAction.$el.click()"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.x="exportAction.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
:shortcut="['R']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-request', { path: folderPath })
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
:shortcut="['X']"
|
||||
@click="
|
||||
() => {
|
||||
exportFolder()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeFolder()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<!-- Referring to this component only (this is recursive) -->
|
||||
<Folder
|
||||
v-for="(subFolder, subFolderIndex) in folder.folders"
|
||||
:key="`subFolder-${subFolderIndex}`"
|
||||
:folder="subFolder"
|
||||
:folder-index="subFolderIndex"
|
||||
:collection-index="collectionIndex"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:folder-path="`${folderPath}/${subFolderIndex}`"
|
||||
:picked="picked"
|
||||
@add-request="$emit('add-request', $event)"
|
||||
@add-folder="$emit('add-folder', $event)"
|
||||
@edit-folder="$emit('edit-folder', $event)"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@update-team-collections="$emit('update-team-collections')"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@remove-folder="$emit('remove-folder', $event)"
|
||||
/>
|
||||
<CollectionsMyRequest
|
||||
v-for="(request, index) in folder.requests"
|
||||
:key="`request-${index}`"
|
||||
:request="request"
|
||||
:collection-index="collectionIndex"
|
||||
:folder-index="folderIndex"
|
||||
:folder-name="folder.name"
|
||||
:folder-path="folderPath"
|
||||
:request-index="index"
|
||||
:picked="picked"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
folder.folders &&
|
||||
folder.folders.length === 0 &&
|
||||
folder.requests &&
|
||||
folder.requests.length === 0
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ t("empty.folder") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import IconFilePlus from "~icons/lucide/file-plus"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import { defineComponent, ref } from "vue"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { moveRESTRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
name: "Folder",
|
||||
props: {
|
||||
folder: { type: Object, default: () => ({}) },
|
||||
folderIndex: { type: Number, default: null },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
folderPath: { type: String, default: null },
|
||||
saveRequest: Boolean,
|
||||
isFiltered: Boolean,
|
||||
collectionsType: { type: Object, default: () => ({}) },
|
||||
picked: { type: Object, default: () => ({}) },
|
||||
},
|
||||
emits: [
|
||||
"add-request",
|
||||
"add-folder",
|
||||
"edit-folder",
|
||||
"update-team",
|
||||
"remove-folder",
|
||||
"edit-request",
|
||||
"duplicate-request",
|
||||
"select",
|
||||
"remove-request",
|
||||
"update-team-collections",
|
||||
],
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
requestAction: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
exportAction: ref<any | null>(null),
|
||||
t,
|
||||
toast: useToast(),
|
||||
colorMode: useColorMode(),
|
||||
IconFilePlus,
|
||||
IconFolderPlus,
|
||||
IconMoreVertical,
|
||||
IconEdit,
|
||||
IconDownload,
|
||||
IconTrash2,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
prevCursor: "",
|
||||
cursor: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-folder" &&
|
||||
this.picked.folderPath === this.folderPath
|
||||
)
|
||||
},
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return IconCheckCircle
|
||||
else if (!this.showChildren && !this.isFiltered) return IconFolder
|
||||
else if (this.showChildren || this.isFiltered) return IconFolderOpen
|
||||
else return IconFolder
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
exportFolder() {
|
||||
const folderJSON = JSON.stringify(this.folder)
|
||||
|
||||
const file = new Blob([folderJSON], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
a.download = `${this.folder.name}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.toast.success(this.t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
},
|
||||
toggleShowChildren() {
|
||||
if (this.$props.saveRequest)
|
||||
this.$emit("select", {
|
||||
picked: {
|
||||
pickedType: "my-folder",
|
||||
collectionIndex: this.collectionIndex,
|
||||
folderName: this.folder.name,
|
||||
folderPath: this.folderPath,
|
||||
},
|
||||
})
|
||||
this.showChildren = !this.showChildren
|
||||
},
|
||||
removeFolder() {
|
||||
this.$emit("remove-folder", {
|
||||
folder: this.folder,
|
||||
folderPath: this.folderPath,
|
||||
})
|
||||
},
|
||||
dropEvent({ dataTransfer }) {
|
||||
this.dragging = !this.dragging
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
moveRESTRequest(folderPath, requestIndex, this.folderPath)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,435 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||
:class="getRequestLabelColor(request.method)"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<component
|
||||
:is="IconCheckCircle"
|
||||
v-if="isSelected"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
<span v-else class="font-semibold truncate text-tiny">
|
||||
{{ request.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconRotateCCW"
|
||||
:title="t('action.restore')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="selectRequest()"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
:icon="IconCopy"
|
||||
:label="t('action.duplicate')"
|
||||
:shortcut="['D']"
|
||||
@click="
|
||||
() => {
|
||||
emit('duplicate-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeRequest()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<HttpReqChangeConfirmModal
|
||||
:show="confirmChange"
|
||||
@hide-modal="confirmChange = false"
|
||||
@save-change="saveRequestChange"
|
||||
@discard-change="discardRequestChange"
|
||||
/>
|
||||
<CollectionsSaveRequest
|
||||
mode="rest"
|
||||
:show="showSaveRequestModal"
|
||||
@hide-modal="showSaveRequestModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import { ref, computed } from "vue"
|
||||
import {
|
||||
HoppRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
translateToNewRequest,
|
||||
isEqualHoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
getRESTRequest,
|
||||
restSaveContext$,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
getRESTSaveContext,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||
import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
||||
|
||||
const props = defineProps<{
|
||||
request: HoppRESTRequest
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName: string
|
||||
requestIndex: number
|
||||
saveRequest: boolean
|
||||
collectionsType: object
|
||||
folderPath: string
|
||||
picked?: {
|
||||
pickedType: string
|
||||
collectionIndex: number
|
||||
folderPath: string
|
||||
folderName: string
|
||||
requestIndex: number
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: "select",
|
||||
data:
|
||||
| {
|
||||
picked: {
|
||||
pickedType: string
|
||||
collectionIndex: number
|
||||
folderPath: string
|
||||
folderName: string
|
||||
requestIndex: number
|
||||
}
|
||||
}
|
||||
| undefined
|
||||
): void
|
||||
|
||||
(
|
||||
e: "remove-request",
|
||||
data: {
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}
|
||||
): void
|
||||
|
||||
(
|
||||
e: "duplicate-request",
|
||||
data: {
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName: string
|
||||
request: HoppRESTRequest
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}
|
||||
): void
|
||||
|
||||
(
|
||||
e: "edit-request",
|
||||
data: {
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName: string
|
||||
request: HoppRESTRequest
|
||||
folderPath: string
|
||||
requestIndex: number
|
||||
}
|
||||
): void
|
||||
}>()
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const dragging = ref(false)
|
||||
const requestMethodLabels = {
|
||||
get: "text-green-500",
|
||||
post: "text-yellow-500",
|
||||
put: "text-blue-500",
|
||||
delete: "text-red-500",
|
||||
default: "text-gray-500",
|
||||
}
|
||||
const confirmChange = ref(false)
|
||||
const showSaveRequestModal = ref(false)
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const options = ref<any | null>(null)
|
||||
const edit = ref<any | null>(null)
|
||||
const duplicate = ref<any | null>(null)
|
||||
const deleteAction = ref<any | null>(null)
|
||||
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
|
||||
const isSelected = computed(
|
||||
() =>
|
||||
props.picked &&
|
||||
props.picked.pickedType === "my-request" &&
|
||||
props.picked.folderPath === props.folderPath &&
|
||||
props.picked.requestIndex === props.requestIndex
|
||||
)
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
active.value &&
|
||||
active.value.originLocation === "user-collection" &&
|
||||
active.value.folderPath === props.folderPath &&
|
||||
active.value.requestIndex === props.requestIndex
|
||||
)
|
||||
|
||||
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||
if (dataTransfer) {
|
||||
dragging.value = !dragging.value
|
||||
dataTransfer.setData("folderPath", props.folderPath)
|
||||
dataTransfer.setData("requestIndex", props.requestIndex.toString())
|
||||
}
|
||||
}
|
||||
|
||||
const removeRequest = () => {
|
||||
emit("remove-request", {
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
})
|
||||
}
|
||||
|
||||
const getRequestLabelColor = (method: string) =>
|
||||
requestMethodLabels[
|
||||
method.toLowerCase() as keyof typeof requestMethodLabels
|
||||
] || requestMethodLabels.default
|
||||
|
||||
const setRestReq = (request: any) => {
|
||||
setRESTRequest(
|
||||
cloneDeep(
|
||||
safelyExtractRESTRequest(
|
||||
translateToNewRequest(request),
|
||||
getDefaultRESTRequest()
|
||||
)
|
||||
),
|
||||
{
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
req: cloneDeep(request),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Loads request from the save once, checks for unsaved changes, but ignores default values */
|
||||
const selectRequest = () => {
|
||||
// Check if this is a save as request popup, if so we don't need to prompt the confirm change popup.
|
||||
if (props.saveRequest) {
|
||||
emit("select", {
|
||||
picked: {
|
||||
pickedType: "my-request",
|
||||
collectionIndex: props.collectionIndex,
|
||||
folderPath: props.folderPath,
|
||||
folderName: props.folderName,
|
||||
requestIndex: props.requestIndex,
|
||||
},
|
||||
})
|
||||
} else if (isEqualHoppRESTRequest(props.request, getDefaultRESTRequest())) {
|
||||
confirmChange.value = false
|
||||
setRestReq(props.request)
|
||||
} else if (!active.value) {
|
||||
// If the current request is the same as the request to be loaded in, there is no data loss
|
||||
const currentReq = getRESTRequest()
|
||||
|
||||
if (isEqualHoppRESTRequest(currentReq, props.request)) {
|
||||
setRestReq(props.request)
|
||||
} else {
|
||||
confirmChange.value = true
|
||||
}
|
||||
} else {
|
||||
const currentReqWithNoChange = active.value.req
|
||||
const currentFullReq = getRESTRequest()
|
||||
|
||||
// Check if whether user clicked the same request or not
|
||||
if (!isActive.value && currentReqWithNoChange !== undefined) {
|
||||
// Check if there is any changes done on the current request
|
||||
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
||||
setRestReq(props.request)
|
||||
} else {
|
||||
confirmChange.value = true
|
||||
}
|
||||
} else {
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Save current request to the collection */
|
||||
const saveRequestChange = () => {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
saveCurrentRequest(saveCtx)
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
/** Discard changes and change the current request and context */
|
||||
const discardRequestChange = () => {
|
||||
setRestReq(props.request)
|
||||
if (!isActive.value) {
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: props.folderPath,
|
||||
requestIndex: props.requestIndex,
|
||||
req: cloneDeep(props.request),
|
||||
})
|
||||
}
|
||||
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
const saveCurrentRequest = (saveCtx: HoppRequestSaveContext | null) => {
|
||||
if (!saveCtx) {
|
||||
showSaveRequestModal.value = true
|
||||
return
|
||||
}
|
||||
if (saveCtx.originLocation === "user-collection") {
|
||||
try {
|
||||
editRESTRequest(
|
||||
saveCtx.folderPath,
|
||||
saveCtx.requestIndex,
|
||||
getRESTRequest()
|
||||
)
|
||||
setRestReq(props.request)
|
||||
toast.success(`${t("request.saved")}`)
|
||||
} catch (e) {
|
||||
setRESTSaveContext(null)
|
||||
saveCurrentRequest(saveCtx)
|
||||
}
|
||||
} else if (saveCtx.originLocation === "team-collection") {
|
||||
const req = getRESTRequest()
|
||||
try {
|
||||
runMutation(UpdateRequestDocument, {
|
||||
requestID: saveCtx.requestID,
|
||||
data: {
|
||||
title: req.name,
|
||||
request: JSON.stringify(req),
|
||||
},
|
||||
})().then((result) => {
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
} else {
|
||||
toast.success(`${t("request.saved")}`)
|
||||
}
|
||||
})
|
||||
setRestReq(props.request)
|
||||
} catch (error) {
|
||||
showSaveRequestModal.value = true
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,407 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
:is="getCollectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collection.title }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="
|
||||
$emit('add-request', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.r="requestAction.$el.click()"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.x="exportAction.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
:shortcut="['R']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-request', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
:shortcut="['X']"
|
||||
:loading="exportLoading"
|
||||
@click="exportCollection"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeCollection()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<CollectionsTeamsFolder
|
||||
v-for="(folder, index) in collection.children"
|
||||
:key="`folder-${index}`"
|
||||
:folder="folder"
|
||||
:folder-index="index"
|
||||
:folder-path="`${collectionIndex}/${index}`"
|
||||
:collection-index="collectionIndex"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:is-filtered="isFiltered"
|
||||
:picked="picked"
|
||||
:loading-collection-i-ds="loadingCollectionIDs"
|
||||
@add-request="$emit('add-request', $event)"
|
||||
@add-folder="$emit('add-folder', $event)"
|
||||
@edit-folder="$emit('edit-folder', $event)"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@expand-collection="expandCollection"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@remove-folder="$emit('remove-folder', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<CollectionsTeamsRequest
|
||||
v-for="(request, index) in collection.requests"
|
||||
:key="`request-${index}`"
|
||||
:request="request.request"
|
||||
:collection-index="collectionIndex"
|
||||
:folder-index="-1"
|
||||
:folder-name="collection.name"
|
||||
:request-index="request.id"
|
||||
:save-request="saveRequest"
|
||||
:collection-i-d="collection.id"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
@edit-request="editRequest($event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="loadingCollectionIDs.includes(collection.id)"
|
||||
class="flex flex-col items-center justify-center p-4"
|
||||
>
|
||||
<SmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
(collection.children == undefined ||
|
||||
collection.children.length === 0) &&
|
||||
(collection.requests == undefined ||
|
||||
collection.requests.length === 0)
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
||||
:alt="`${t('empty.collection')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ t("empty.collection") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconFilePlus from "~icons/lucide/file-plus"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import { defineComponent, ref } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
import {
|
||||
getCompleteCollectionTree,
|
||||
teamCollToHoppRESTColl,
|
||||
} from "~/helpers/backend/helpers"
|
||||
import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
collectionIndex: { type: Number, default: null },
|
||||
collection: { type: Object, default: () => ({}) },
|
||||
isFiltered: Boolean,
|
||||
saveRequest: Boolean,
|
||||
collectionsType: { type: Object, default: () => ({}) },
|
||||
picked: { type: Object, default: () => ({}) },
|
||||
loadingCollectionIDs: { type: Array, default: () => [] },
|
||||
},
|
||||
emits: [
|
||||
"edit-collection",
|
||||
"add-request",
|
||||
"add-folder",
|
||||
"edit-folder",
|
||||
"edit-request",
|
||||
"remove-folder",
|
||||
"select",
|
||||
"remove-request",
|
||||
"duplicate-request",
|
||||
"expand-collection",
|
||||
"remove-collection",
|
||||
],
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
requestAction: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
exportAction: ref<any | null>(null),
|
||||
exportLoading: ref<boolean>(false),
|
||||
t,
|
||||
toast: useToast(),
|
||||
colorMode: useColorMode(),
|
||||
|
||||
IconCheckCircle,
|
||||
IconCircle,
|
||||
IconFilePlus,
|
||||
IconFolderPlus,
|
||||
IconEdit,
|
||||
IconDownload,
|
||||
IconTrash2,
|
||||
IconMoreVertical,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
selectedFolder: {},
|
||||
prevCursor: "",
|
||||
cursor: "",
|
||||
pageNo: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-collection" &&
|
||||
this.picked.collectionID === this.collection.id
|
||||
)
|
||||
},
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return IconCheckCircle
|
||||
else if (!this.showChildren && !this.isFiltered) return IconFolder
|
||||
else if (this.showChildren || this.isFiltered) return IconFolderOpen
|
||||
else return IconFolder
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async exportCollection() {
|
||||
this.exportLoading = true
|
||||
|
||||
const result = await getCompleteCollectionTree(this.collection.id)()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong").toString())
|
||||
console.log(result.left)
|
||||
this.exportLoading = false
|
||||
this.options.tippy().hide()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const hoppColl = teamCollToHoppRESTColl(result.right)
|
||||
|
||||
const collectionJSON = JSON.stringify(hoppColl)
|
||||
|
||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
a.download = `${hoppColl.name}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.toast.success(this.t("state.download_started").toString())
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
|
||||
this.exportLoading = false
|
||||
|
||||
this.options.tippy().hide()
|
||||
},
|
||||
editRequest(event: any) {
|
||||
this.$emit("edit-request", event)
|
||||
if (this.$props.saveRequest)
|
||||
this.$emit("select", {
|
||||
picked: {
|
||||
pickedType: "teams-collection",
|
||||
collectionID: this.collection.id,
|
||||
},
|
||||
})
|
||||
},
|
||||
toggleShowChildren() {
|
||||
if (this.$props.saveRequest)
|
||||
this.$emit("select", {
|
||||
picked: {
|
||||
pickedType: "teams-collection",
|
||||
collectionID: this.collection.id,
|
||||
},
|
||||
})
|
||||
|
||||
this.$emit("expand-collection", this.collection.id)
|
||||
this.showChildren = !this.showChildren
|
||||
},
|
||||
removeCollection() {
|
||||
this.$emit("remove-collection", {
|
||||
collectionIndex: this.collectionIndex,
|
||||
collectionID: this.collection.id,
|
||||
})
|
||||
},
|
||||
expandCollection(collectionID: string) {
|
||||
this.$emit("expand-collection", collectionID)
|
||||
},
|
||||
async dropEvent({ dataTransfer }: any) {
|
||||
this.dragging = !this.dragging
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
const moveRequestResult = await moveRESTTeamRequest(
|
||||
requestIndex,
|
||||
this.collection.id
|
||||
)()
|
||||
if (E.isLeft(moveRequestResult))
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,382 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<component
|
||||
:is="getCollectionIcon"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFilePlus"
|
||||
:title="t('request.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="$emit('add-request', { folder, path: folderPath })"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconFolderPlus"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="$emit('add-folder', { folder, path: folderPath })"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.r="requestAction.$el.click()"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.x="exportAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="requestAction"
|
||||
:icon="IconFilePlus"
|
||||
:label="t('request.new')"
|
||||
:shortcut="['R']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-request', { folder, path: folderPath })
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath: '',
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="exportAction"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.title')"
|
||||
:shortcut="['X']"
|
||||
:loading="exportLoading"
|
||||
@click="exportFolder"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeFolder()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<!-- Referring to this component only (this is recursive) -->
|
||||
<Folder
|
||||
v-for="(subFolder, subFolderIndex) in folder.children"
|
||||
:key="`subFolder-${subFolderIndex}`"
|
||||
:folder="subFolder"
|
||||
:folder-index="subFolderIndex"
|
||||
:collection-index="collectionIndex"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:folder-path="`${folderPath}/${subFolderIndex}`"
|
||||
:picked="picked"
|
||||
:loading-collection-i-ds="loadingCollectionIDs"
|
||||
@add-request="$emit('add-request', $event)"
|
||||
@add-folder="$emit('add-folder', $event)"
|
||||
@edit-folder="$emit('edit-folder', $event)"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@update-team-collections="$emit('update-team-collections')"
|
||||
@select="$emit('select', $event)"
|
||||
@expand-collection="expandCollection"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@remove-folder="$emit('remove-folder', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<CollectionsTeamsRequest
|
||||
v-for="(request, index) in folder.requests"
|
||||
:key="`request-${index}`"
|
||||
:request="request.request"
|
||||
:collection-index="collectionIndex"
|
||||
:folder-index="folderIndex"
|
||||
:folder-name="folder.name"
|
||||
:request-index="request.id"
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
:collection-i-d="folder.id"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="$emit('remove-request', $event)"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="loadingCollectionIDs.includes(folder.id)"
|
||||
class="flex flex-col items-center justify-center p-4"
|
||||
>
|
||||
<SmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
(folder.children == undefined || folder.children.length === 0) &&
|
||||
(folder.requests == undefined || folder.requests.length === 0)
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ t("empty.folder") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconFilePlus from "~icons/lucide/file-plus"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import { defineComponent, ref } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
import {
|
||||
getCompleteCollectionTree,
|
||||
teamCollToHoppRESTColl,
|
||||
} from "~/helpers/backend/helpers"
|
||||
import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
|
||||
export default defineComponent({
|
||||
name: "Folder",
|
||||
props: {
|
||||
folder: { type: Object, default: () => ({}) },
|
||||
folderIndex: { type: Number, default: null },
|
||||
collectionIndex: { type: Number, default: null },
|
||||
folderPath: { type: String, default: null },
|
||||
saveRequest: Boolean,
|
||||
isFiltered: Boolean,
|
||||
collectionsType: { type: Object, default: () => ({}) },
|
||||
picked: { type: Object, default: () => ({}) },
|
||||
loadingCollectionIDs: { type: Array, default: () => [] },
|
||||
},
|
||||
emits: [
|
||||
"add-request",
|
||||
"add-folder",
|
||||
"edit-folder",
|
||||
"update-team-collections",
|
||||
"edit-request",
|
||||
"remove-request",
|
||||
"duplicate-request",
|
||||
"select",
|
||||
"remove-folder",
|
||||
"expand-collection",
|
||||
],
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
requestAction: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
exportAction: ref<any | null>(null),
|
||||
exportLoading: ref<boolean>(false),
|
||||
toast: useToast(),
|
||||
t: useI18n(),
|
||||
colorMode: useColorMode(),
|
||||
IconFilePlus,
|
||||
IconFolderPlus,
|
||||
IconCheckCircle,
|
||||
IconFolder,
|
||||
IconFolderOpen,
|
||||
IconMoreVertical,
|
||||
IconEdit,
|
||||
IconDownload,
|
||||
IconTrash2,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
prevCursor: "",
|
||||
cursor: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-folder" &&
|
||||
this.picked.folderID === this.folder.id
|
||||
)
|
||||
},
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return IconCheckCircle
|
||||
else if (!this.showChildren && !this.isFiltered) return IconFolder
|
||||
else if (this.showChildren || this.isFiltered) return IconFolderOpen
|
||||
else return IconFolder
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async exportFolder() {
|
||||
this.exportLoading = true
|
||||
|
||||
const result = await getCompleteCollectionTree(this.folder.id)()
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
this.toast.error(this.t("error.something_went_wrong").toString())
|
||||
console.log(result.left)
|
||||
this.exportLoading = false
|
||||
this.options.tippy().hide()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const hoppColl = teamCollToHoppRESTColl(result.right)
|
||||
|
||||
const collectionJSON = JSON.stringify(hoppColl)
|
||||
|
||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
a.download = `${hoppColl.name}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.toast.success(this.t("state.download_started").toString())
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
|
||||
this.exportLoading = false
|
||||
|
||||
this.options.tippy().hide()
|
||||
},
|
||||
toggleShowChildren() {
|
||||
if (this.$props.saveRequest)
|
||||
this.$emit("select", {
|
||||
picked: {
|
||||
pickedType: "teams-folder",
|
||||
folderID: this.folder.id,
|
||||
},
|
||||
})
|
||||
|
||||
this.$emit("expand-collection", this.$props.folder.id)
|
||||
this.showChildren = !this.showChildren
|
||||
},
|
||||
removeFolder() {
|
||||
this.$emit("remove-folder", {
|
||||
collectionsType: this.collectionsType,
|
||||
folder: this.folder,
|
||||
})
|
||||
},
|
||||
expandCollection(collectionID: number) {
|
||||
this.$emit("expand-collection", collectionID)
|
||||
},
|
||||
async dropEvent({ dataTransfer }: any) {
|
||||
this.dragging = !this.dragging
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
const moveRequestResult = await moveRESTTeamRequest(
|
||||
requestIndex,
|
||||
this.folder.id
|
||||
)()
|
||||
if (E.isLeft(moveRequestResult))
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,407 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy.show()"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||
:class="getRequestLabelColor(request.method)"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<component
|
||||
:is="IconCheckCircle"
|
||||
v-if="isSelected"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
/>
|
||||
<span v-else class="font-semibold truncate text-tiny">
|
||||
{{ request.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
|
||||
@click="selectRequest()"
|
||||
>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="isActive"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
:title="`${t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-if="!saveRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconRotateCCW"
|
||||
:title="t('action.restore')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click="selectRequest()"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
:icon="IconEdit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
() => {
|
||||
emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
:icon="IconCopy"
|
||||
:label="t('action.duplicate')"
|
||||
:shortcut="['D']"
|
||||
@click="
|
||||
() => {
|
||||
emit('duplicate-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
collectionID,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
:icon="IconTrash2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click="
|
||||
() => {
|
||||
removeRequest()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<HttpReqChangeConfirmModal
|
||||
:show="confirmChange"
|
||||
@hide-modal="confirmChange = false"
|
||||
@save-change="saveRequestChange"
|
||||
@discard-change="discardRequestChange"
|
||||
/>
|
||||
<CollectionsSaveRequest
|
||||
mode="rest"
|
||||
:show="showSaveRequestModal"
|
||||
@hide-modal="showSaveRequestModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { ref, computed } from "vue"
|
||||
import {
|
||||
HoppRESTRequest,
|
||||
isEqualHoppRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
restSaveContext$,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
getRESTSaveContext,
|
||||
getRESTRequest,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import { Team, UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||
import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
||||
|
||||
const props = defineProps<{
|
||||
request: HoppRESTRequest
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName?: string
|
||||
requestIndex: string
|
||||
saveRequest: boolean
|
||||
collectionsType: {
|
||||
type: "my-collections" | "team-collections"
|
||||
selectedTeam: Team | undefined
|
||||
}
|
||||
collectionID: string
|
||||
picked?: {
|
||||
pickedType: string
|
||||
requestID: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: "select",
|
||||
data:
|
||||
| {
|
||||
picked: {
|
||||
pickedType: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
| undefined
|
||||
): void
|
||||
|
||||
(
|
||||
e: "remove-request",
|
||||
data: {
|
||||
folderPath: string | undefined
|
||||
requestIndex: string
|
||||
}
|
||||
): void
|
||||
|
||||
(
|
||||
e: "edit-request",
|
||||
data: {
|
||||
collectionIndex: number
|
||||
folderIndex: number
|
||||
folderName: string | undefined
|
||||
requestIndex: string
|
||||
request: HoppRESTRequest
|
||||
}
|
||||
): void
|
||||
|
||||
(
|
||||
e: "duplicate-request",
|
||||
data: {
|
||||
collectionID: number | string
|
||||
requestIndex: string
|
||||
request: HoppRESTRequest
|
||||
}
|
||||
): void
|
||||
}>()
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const dragging = ref(false)
|
||||
const requestMethodLabels = {
|
||||
get: "text-green-500",
|
||||
post: "text-yellow-500",
|
||||
put: "text-blue-500",
|
||||
delete: "text-red-500",
|
||||
default: "text-gray-500",
|
||||
}
|
||||
const confirmChange = ref(false)
|
||||
const showSaveRequestModal = ref(false)
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const options = ref<any | null>(null)
|
||||
const edit = ref<any | null>(null)
|
||||
const duplicate = ref<any | null>(null)
|
||||
const deleteAction = ref<any | null>(null)
|
||||
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
|
||||
const isSelected = computed(
|
||||
() =>
|
||||
props.picked &&
|
||||
props.picked.pickedType === "teams-request" &&
|
||||
props.picked.requestID === props.requestIndex
|
||||
)
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
active.value &&
|
||||
active.value.originLocation === "team-collection" &&
|
||||
active.value.requestID === props.requestIndex
|
||||
)
|
||||
|
||||
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||
if (dataTransfer) {
|
||||
dragging.value = !dragging.value
|
||||
dataTransfer.setData("requestIndex", props.requestIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const removeRequest = () => {
|
||||
emit("remove-request", {
|
||||
folderPath: props.folderName,
|
||||
requestIndex: props.requestIndex,
|
||||
})
|
||||
}
|
||||
|
||||
const getRequestLabelColor = (method: string): string => {
|
||||
return (
|
||||
(requestMethodLabels as any)[method.toLowerCase()] ||
|
||||
requestMethodLabels.default
|
||||
)
|
||||
}
|
||||
|
||||
const setRestReq = (request: HoppRESTRequest) => {
|
||||
setRESTRequest(
|
||||
safelyExtractRESTRequest(
|
||||
translateToNewRequest(request),
|
||||
getDefaultRESTRequest()
|
||||
),
|
||||
{
|
||||
originLocation: "team-collection",
|
||||
requestID: props.requestIndex,
|
||||
req: request,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const selectRequest = () => {
|
||||
// Check if this is a save as request popup, if so we don't need to prompt the confirm change popup.
|
||||
if (props.saveRequest) {
|
||||
emit("select", {
|
||||
picked: {
|
||||
pickedType: "teams-request",
|
||||
requestID: props.requestIndex,
|
||||
},
|
||||
})
|
||||
} else if (isEqualHoppRESTRequest(props.request, getDefaultRESTRequest())) {
|
||||
confirmChange.value = false
|
||||
setRestReq(props.request)
|
||||
} else if (!active.value) {
|
||||
confirmChange.value = true
|
||||
} else {
|
||||
const currentReqWithNoChange = active.value.req
|
||||
const currentFullReq = getRESTRequest()
|
||||
|
||||
// Check if whether user clicked the same request or not
|
||||
if (!isActive.value && currentReqWithNoChange) {
|
||||
// Check if there is any changes done on the current request
|
||||
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
||||
setRestReq(props.request)
|
||||
} else {
|
||||
confirmChange.value = true
|
||||
}
|
||||
} else {
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Save current request to the collection */
|
||||
const saveRequestChange = () => {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
saveCurrentRequest(saveCtx)
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
/** Discard changes and change the current request and context */
|
||||
const discardRequestChange = () => {
|
||||
setRestReq(props.request)
|
||||
if (!isActive.value) {
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: props.requestIndex,
|
||||
req: props.request,
|
||||
})
|
||||
}
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
const saveCurrentRequest = (saveCtx: HoppRequestSaveContext | null) => {
|
||||
if (!saveCtx) {
|
||||
showSaveRequestModal.value = true
|
||||
return
|
||||
}
|
||||
if (saveCtx.originLocation === "team-collection") {
|
||||
const req = getRESTRequest()
|
||||
try {
|
||||
runMutation(UpdateRequestDocument, {
|
||||
requestID: saveCtx.requestID,
|
||||
data: {
|
||||
title: req.name,
|
||||
request: JSON.stringify(req),
|
||||
},
|
||||
})().then((result) => {
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
} else {
|
||||
toast.success(`${t("request.saved")}`)
|
||||
}
|
||||
})
|
||||
setRestReq(props.request)
|
||||
} catch (error) {
|
||||
showSaveRequestModal.value = true
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(error)
|
||||
}
|
||||
} else if (saveCtx.originLocation === "user-collection") {
|
||||
try {
|
||||
editRESTRequest(
|
||||
saveCtx.folderPath,
|
||||
saveCtx.requestIndex,
|
||||
getRESTRequest()
|
||||
)
|
||||
setRestReq(props.request)
|
||||
toast.success(`${t("request.saved")}`)
|
||||
} catch (e) {
|
||||
setRESTSaveContext(null)
|
||||
saveCurrentRequest(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,288 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('environment.title')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #actions>
|
||||
<span>
|
||||
<tippy ref="options" interactive trigger="click" theme="popover" arrow>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMoreVertical"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
:icon="IconGithub"
|
||||
:label="t('import.from_gist')"
|
||||
@click="
|
||||
() => {
|
||||
readEnvironmentGist()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<SmartItem
|
||||
:disabled="
|
||||
!currentUser
|
||||
? true
|
||||
: currentUser.provider !== 'github.com'
|
||||
? true
|
||||
: false
|
||||
"
|
||||
:icon="IconGithub"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click="
|
||||
() => {
|
||||
createEnvironmentGist()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
:icon="IconFolderPlus"
|
||||
:label="t('import.from_json')"
|
||||
@click="openDialogChooseFileToImportFrom"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToImportFrom"
|
||||
class="input"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
@change="importFromJSON"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.download_file')"
|
||||
:icon="IconDownload"
|
||||
:label="t('export.as_json')"
|
||||
@click="exportJSON"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconGithub from "~icons/lucide/github"
|
||||
import { computed, ref } from "vue"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import axios from "axios"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import {
|
||||
environments$,
|
||||
replaceEnvironments,
|
||||
appendEnvironments,
|
||||
} from "~/newstore/environments"
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const environments = useReadonlyStream(environments$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
// Template refs
|
||||
const options = ref<any>()
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement>()
|
||||
|
||||
const environmentJson = computed(() => {
|
||||
return JSON.stringify(environments.value, null, 2)
|
||||
})
|
||||
|
||||
const createEnvironmentGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-environments.json": {
|
||||
content: environmentJson.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
}
|
||||
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const readEnvironmentGist = async () => {
|
||||
const gist = prompt(t("import.gist_url").toString())
|
||||
if (!gist) return
|
||||
|
||||
try {
|
||||
const { files } = (await axios.get(
|
||||
`https://api.github.com/gists/${gist.split("/").pop()}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)) as {
|
||||
files: {
|
||||
[fileName: string]: {
|
||||
content: any
|
||||
}
|
||||
}
|
||||
}
|
||||
const environments = JSON.parse(Object.values(files)[0].content)
|
||||
replaceEnvironments(environments)
|
||||
fileImported()
|
||||
} catch (e) {
|
||||
failedImport()
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const openDialogChooseFileToImportFrom = () => {
|
||||
if (inputChooseFileToImportFrom.value)
|
||||
inputChooseFileToImportFrom.value.click()
|
||||
}
|
||||
|
||||
const importFromJSON = () => {
|
||||
if (!inputChooseFileToImportFrom.value) return
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value.files ||
|
||||
inputChooseFileToImportFrom.value.files.length === 0
|
||||
) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
|
||||
if (!content) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const environments = JSON.parse(content)
|
||||
if (
|
||||
environments._postman_variable_scope === "environment" ||
|
||||
environments._postman_variable_scope === "globals"
|
||||
) {
|
||||
importFromPostman(environments)
|
||||
} else if (environments[0]) {
|
||||
const [name, variables] = Object.keys(environments[0])
|
||||
if (name === "name" && variables === "variables") {
|
||||
// Do nothing
|
||||
}
|
||||
importFromHoppscotch(environments)
|
||||
} else {
|
||||
failedImport()
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
||||
inputChooseFileToImportFrom.value.value = ""
|
||||
}
|
||||
|
||||
const importFromHoppscotch = (environments: Environment[]) => {
|
||||
appendEnvironments(environments)
|
||||
fileImported()
|
||||
}
|
||||
|
||||
const importFromPostman = ({
|
||||
name,
|
||||
values,
|
||||
}: {
|
||||
name: string
|
||||
values: { key: string; value: string }[]
|
||||
}) => {
|
||||
const environment: Environment = { name, variables: [] }
|
||||
values.forEach(({ key, value }) => environment.variables.push({ key, value }))
|
||||
const environments = [environment]
|
||||
importFromHoppscotch(environments)
|
||||
}
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = environmentJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
@@ -1,191 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="sticky top-0 z-10 flex flex-col rounded-t bg-primary">
|
||||
<tippy ref="options" interactive trigger="click" theme="popover" arrow>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${t('environment.select')}`"
|
||||
class="flex-1 bg-transparent border-b border-dividerLight select-wrapper"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-if="selectedEnvironmentIndex !== -1"
|
||||
:label="environments[selectedEnvironmentIndex].name"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
:label="`${t('environment.select')}`"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
:label="`${t('environment.no_environment')}`"
|
||||
:info-icon="selectedEnvironmentIndex === -1 ? IconDone : null"
|
||||
:active-info-icon="selectedEnvironmentIndex === -1"
|
||||
@click="
|
||||
() => {
|
||||
selectedEnvironmentIndex = -1
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<hr v-if="environments.length > 0" />
|
||||
<SmartItem
|
||||
v-for="(gen, index) in environments"
|
||||
:key="`gen-${index}`"
|
||||
:label="gen.name"
|
||||
:info-icon="index === selectedEnvironmentIndex ? IconDone : null"
|
||||
:active-info-icon="index === selectedEnvironmentIndex"
|
||||
@click="
|
||||
() => {
|
||||
selectedEnvironmentIndex = index
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
<div class="flex justify-between flex-1 border-b border-dividerLight">
|
||||
<ButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="`${t('action.new')}`"
|
||||
class="!rounded-none"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/environments"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconArchive"
|
||||
:title="t('modal.import_export')"
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<EnvironmentsEnvironment
|
||||
environment-index="Global"
|
||||
:environment="globalEnvironment"
|
||||
class="border-b border-dashed border-dividerLight"
|
||||
@edit-environment="editEnvironment('Global')"
|
||||
/>
|
||||
<EnvironmentsEnvironment
|
||||
v-for="(environment, index) in environments"
|
||||
:key="`environment-${index}`"
|
||||
:environment-index="index"
|
||||
:environment="environment"
|
||||
@edit-environment="editEnvironment(index)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="environments.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
/>
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.environments") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
class="mb-4"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
<EnvironmentsDetails
|
||||
:show="showModalDetails"
|
||||
:action="action"
|
||||
:editing-environment-index="editingEnvironmentIndex"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
/>
|
||||
<EnvironmentsImportExport
|
||||
:show="showModalImportExport"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconArchive from "~icons/lucide/archive"
|
||||
import { computed, ref } from "vue"
|
||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import {
|
||||
environments$,
|
||||
setCurrentEnvironment,
|
||||
selectedEnvIndex$,
|
||||
globalEnv$,
|
||||
} from "~/newstore/environments"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const options = ref<any | null>(null)
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const globalEnv = useReadonlyStream(globalEnv$, [])
|
||||
|
||||
const globalEnvironment = computed(() => ({
|
||||
name: "Global",
|
||||
variables: globalEnv.value,
|
||||
}))
|
||||
|
||||
const environments = useReadonlyStream(environments$, [])
|
||||
|
||||
const selectedEnvironmentIndex = useStream(
|
||||
selectedEnvIndex$,
|
||||
-1,
|
||||
setCurrentEnvironment
|
||||
)
|
||||
|
||||
const showModalImportExport = ref(false)
|
||||
const showModalDetails = ref(false)
|
||||
const action = ref<"new" | "edit">("edit")
|
||||
const editingEnvironmentIndex = ref<number | "Global" | null>(null)
|
||||
|
||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
action.value = "new"
|
||||
showModalDetails.value = shouldDisplay
|
||||
}
|
||||
const displayModalEdit = (shouldDisplay: boolean) => {
|
||||
action.value = "edit"
|
||||
showModalDetails.value = shouldDisplay
|
||||
|
||||
if (!shouldDisplay) resetSelectedData()
|
||||
}
|
||||
const displayModalImportExport = (shouldDisplay: boolean) => {
|
||||
showModalImportExport.value = shouldDisplay
|
||||
}
|
||||
const editEnvironment = (environmentIndex: number | "Global") => {
|
||||
editingEnvironmentIndex.value = environmentIndex
|
||||
action.value = "edit"
|
||||
displayModalEdit(true)
|
||||
}
|
||||
const resetSelectedData = () => {
|
||||
editingEnvironmentIndex.value = null
|
||||
}
|
||||
</script>
|
||||
@@ -1,331 +0,0 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('auth.login_to_hoppscotch')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
:loading="signingInWithGitHub"
|
||||
:icon="IconGithub"
|
||||
:label="`${t('auth.continue_with_github')}`"
|
||||
@click="signInWithGithub"
|
||||
/>
|
||||
<SmartItem
|
||||
:loading="signingInWithGoogle"
|
||||
:icon="IconGoogle"
|
||||
:label="`${t('auth.continue_with_google')}`"
|
||||
@click="signInWithGoogle"
|
||||
/>
|
||||
<SmartItem
|
||||
:loading="signingInWithMicrosoft"
|
||||
:icon="IconMicrosoft"
|
||||
:label="`${t('auth.continue_with_microsoft')}`"
|
||||
@click="signInWithMicrosoft"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="IconEmail"
|
||||
:label="`${t('auth.continue_with_email')}`"
|
||||
@click="mode = 'email'"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
v-if="mode === 'email'"
|
||||
class="flex flex-col space-y-2"
|
||||
@submit.prevent="signInWithEmail"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<input
|
||||
id="email"
|
||||
v-model="form.email"
|
||||
v-focus
|
||||
class="input floating-input"
|
||||
placeholder=" "
|
||||
type="email"
|
||||
name="email"
|
||||
autocomplete="off"
|
||||
required
|
||||
spellcheck="false"
|
||||
autofocus
|
||||
/>
|
||||
<label for="email">
|
||||
{{ t("auth.email") }}
|
||||
</label>
|
||||
</div>
|
||||
<ButtonPrimary
|
||||
:loading="signingInWithEmail"
|
||||
type="submit"
|
||||
:label="`${t('auth.send_magic_link')}`"
|
||||
/>
|
||||
</form>
|
||||
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
||||
<div class="flex flex-col items-center justify-center max-w-md">
|
||||
<icon-lucide-inbox class="w-6 h-6 text-accent" />
|
||||
<h3 class="my-2 text-lg text-center">
|
||||
{{ t("auth.we_sent_magic_link") }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
{{
|
||||
t("auth.we_sent_magic_link_description", { email: form.email })
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div v-if="mode === 'sign-in'" class="text-secondaryLight text-tiny">
|
||||
By signing in, you are agreeing to our
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
to="https://docs.hoppscotch.io/terms"
|
||||
blank
|
||||
label="Terms of Service"
|
||||
/>
|
||||
and
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
to="https://docs.hoppscotch.io/privacy"
|
||||
blank
|
||||
label="Privacy Policy"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mode === 'email'">
|
||||
<ButtonSecondary
|
||||
:label="t('auth.all_sign_in_options')"
|
||||
:icon="IconArrowLeft"
|
||||
class="!p-0"
|
||||
@click="mode = 'sign-in'"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="mode === 'email-sent'"
|
||||
class="flex justify-between flex-1 text-secondaryLight"
|
||||
>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="t('auth.re_enter_email')"
|
||||
:icon="IconArrowLeft"
|
||||
@click="mode = 'email'"
|
||||
/>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="`${t('action.dismiss')}`"
|
||||
@click="hideModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue"
|
||||
import {
|
||||
signInUserWithGoogle,
|
||||
signInUserWithGithub,
|
||||
signInUserWithMicrosoft,
|
||||
setProviderInfo,
|
||||
currentUser$,
|
||||
signInWithEmail,
|
||||
linkWithFBCredential,
|
||||
linkWithFBCredentialFromAuthError,
|
||||
getGithubCredentialFromResult,
|
||||
} from "~/helpers/fb/auth"
|
||||
import IconGithub from "~icons/auth/github"
|
||||
import IconGoogle from "~icons/auth/google"
|
||||
import IconEmail from "~icons/auth/email"
|
||||
import IconMicrosoft from "~icons/auth/microsoft"
|
||||
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||
import { setLocalConfig } from "~/newstore/localpersistence"
|
||||
import { useStreamSubscriber } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
emits: ["hide-modal"],
|
||||
setup() {
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
return {
|
||||
subscribeToStream,
|
||||
t: useI18n(),
|
||||
toast: useToast(),
|
||||
IconGithub,
|
||||
IconGoogle,
|
||||
IconEmail,
|
||||
IconMicrosoft,
|
||||
IconArrowLeft,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
email: "",
|
||||
},
|
||||
signingInWithGoogle: false,
|
||||
signingInWithGitHub: false,
|
||||
signingInWithMicrosoft: false,
|
||||
signingInWithEmail: false,
|
||||
mode: "sign-in",
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.subscribeToStream(currentUser$, (user) => {
|
||||
if (user) this.hideModal()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
showLoginSuccess() {
|
||||
this.toast.success(`${this.t("auth.login_success")}`)
|
||||
},
|
||||
async signInWithGoogle() {
|
||||
this.signingInWithGoogle = true
|
||||
|
||||
try {
|
||||
await signInUserWithGoogle()
|
||||
this.showLoginSuccess()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// An error happened.
|
||||
if (
|
||||
(e as any).code === "auth/account-exists-with-different-credential"
|
||||
) {
|
||||
// Step 2.
|
||||
// User's email already exists.
|
||||
// The pending Google credential.
|
||||
const pendingCred = (e as any).credential
|
||||
this.toast.info(`${this.t("auth.account_exists")}`, {
|
||||
duration: 0,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: `${this.t("action.yes")}`,
|
||||
onClick: async (_, toastObject) => {
|
||||
const { user } = await signInUserWithGithub()
|
||||
await linkWithFBCredential(user, pendingCred)
|
||||
|
||||
this.showLoginSuccess()
|
||||
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
}
|
||||
}
|
||||
|
||||
this.signingInWithGoogle = false
|
||||
},
|
||||
async signInWithGithub() {
|
||||
this.signingInWithGitHub = true
|
||||
|
||||
try {
|
||||
const result = await signInUserWithGithub()
|
||||
const credential = getGithubCredentialFromResult(result)!
|
||||
const token = credential.accessToken
|
||||
|
||||
setProviderInfo(result.providerId!, token!)
|
||||
|
||||
this.showLoginSuccess()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// An error happened.
|
||||
if (
|
||||
(e as any).code === "auth/account-exists-with-different-credential"
|
||||
) {
|
||||
// Step 2.
|
||||
// User's email already exists.
|
||||
this.toast.info(`${this.t("auth.account_exists")}`, {
|
||||
duration: 0,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: `${this.t("action.yes")}`,
|
||||
onClick: async (_, toastObject) => {
|
||||
const { user } = await signInUserWithGoogle()
|
||||
await linkWithFBCredentialFromAuthError(user, e)
|
||||
|
||||
this.showLoginSuccess()
|
||||
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
}
|
||||
}
|
||||
|
||||
this.signingInWithGitHub = false
|
||||
},
|
||||
async signInWithMicrosoft() {
|
||||
this.signingInWithMicrosoft = true
|
||||
|
||||
try {
|
||||
await signInUserWithMicrosoft()
|
||||
this.showLoginSuccess()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// An error happened.
|
||||
if (
|
||||
(e as any).code === "auth/account-exists-with-different-credential"
|
||||
) {
|
||||
// Step 2.
|
||||
// User's email already exists.
|
||||
// The pending Microsoft credential.
|
||||
const pendingCred = (e as any).credential
|
||||
this.toast.info(`${this.t("auth.account_exists")}`, {
|
||||
duration: 0,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: `${this.t("action.yes")}`,
|
||||
onClick: async (_, toastObject) => {
|
||||
const { user } = await signInUserWithGithub()
|
||||
await linkWithFBCredential(user, pendingCred)
|
||||
|
||||
this.showLoginSuccess()
|
||||
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.toast.error(`${this.t("error.something_went_wrong")}`)
|
||||
}
|
||||
}
|
||||
|
||||
this.signingInWithMicrosoft = false
|
||||
},
|
||||
async signInWithEmail() {
|
||||
this.signingInWithEmail = true
|
||||
|
||||
const actionCodeSettings = {
|
||||
url: `${import.meta.env.VITE_BASE_URL}/enter`,
|
||||
handleCodeInApp: true,
|
||||
}
|
||||
await signInWithEmail(this.form.email, actionCodeSettings)
|
||||
.then(() => {
|
||||
this.mode = "email-sent"
|
||||
setLocalConfig("emailForSignIn", this.form.email)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
this.toast.error(e.message)
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
.finally(() => {
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.mode = "sign-in"
|
||||
this.toast.clear()
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,306 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("authorization.type") }}
|
||||
</label>
|
||||
<tippy
|
||||
ref="authTypeOptions"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary class="pr-8 ml-2 rounded-none" :label="authName" />
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col space-y-1"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
label="None"
|
||||
:icon="authName === 'None' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'None'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'none'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="Basic Auth"
|
||||
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'Basic Auth'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'basic'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="Bearer Token"
|
||||
:icon="authName === 'Bearer' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'Bearer'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'bearer'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="OAuth 2.0"
|
||||
:icon="authName === 'OAuth 2.0' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'OAuth 2.0'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'oauth-2'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="API key"
|
||||
:icon="authName === 'API key' ? IconCircleDot : IconCircle"
|
||||
:active="authName === 'API key'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'api-key'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<!-- <SmartCheckbox
|
||||
:on="!URLExcludes.auth"
|
||||
@change="setExclude('auth', !$event)"
|
||||
>
|
||||
{{ t("authorization.include_in_url") }}
|
||||
</SmartCheckbox> -->
|
||||
<SmartCheckbox
|
||||
:on="authActive"
|
||||
class="px-2"
|
||||
@change="authActive = !authActive"
|
||||
>
|
||||
{{ t("state.enabled") }}
|
||||
</SmartCheckbox>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/authorization"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear')"
|
||||
:icon="IconTrash2"
|
||||
@click="clearContent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="authType === 'none'"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/login.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.authorization')}`"
|
||||
/>
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.authorization") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
class="mb-4"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div v-if="authType === 'basic'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicUsername"
|
||||
:placeholder="t('authorization.username')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicPassword"
|
||||
:placeholder="t('authorization.password')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="bearerToken" placeholder="Token" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="oauth2Token" placeholder="Token" />
|
||||
</div>
|
||||
<HttpOAuth2Authorization />
|
||||
</div>
|
||||
<div v-if="authType === 'api-key'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiKey" placeholder="Key" />
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiValue" placeholder="Value" />
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
<label class="ml-4 text-secondaryLight">
|
||||
{{ t("authorization.pass_key_by") }}
|
||||
</label>
|
||||
<tippy
|
||||
ref="addToOptions"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary
|
||||
:label="addTo || t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
tabindex="0"
|
||||
role="menu"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<SmartItem
|
||||
:icon="addTo === 'Headers' ? IconCircleDot : IconCircle"
|
||||
:active="addTo === 'Headers'"
|
||||
:label="'Headers'"
|
||||
@click="
|
||||
() => {
|
||||
addTo = 'Headers'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="
|
||||
addTo === 'Query params' ? IconCircleDot : IconCircle
|
||||
"
|
||||
:active="addTo === 'Query params'"
|
||||
:label="'Query params'"
|
||||
@click="
|
||||
() => {
|
||||
addTo = 'Query params'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sticky h-full p-4 overflow-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||
>
|
||||
<div class="pb-2 text-secondaryLight">
|
||||
{{ t("helpers.authorization") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="t('authorization.learn')"
|
||||
:icon="IconExternalLink"
|
||||
to="https://docs.hoppscotch.io/features/authorization"
|
||||
blank
|
||||
reverse
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, Ref } from "vue"
|
||||
import {
|
||||
HoppGQLAuthAPIKey,
|
||||
HoppGQLAuthBasic,
|
||||
HoppGQLAuthBearer,
|
||||
HoppGQLAuthOAuth2,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { gqlAuth$, setGQLAuth } from "~/newstore/GQLSession"
|
||||
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const auth = useStream(
|
||||
gqlAuth$,
|
||||
{ authType: "none", authActive: true },
|
||||
setGQLAuth
|
||||
)
|
||||
const authType = pluckRef(auth, "authType")
|
||||
const authName = computed(() => {
|
||||
if (authType.value === "basic") return "Basic Auth"
|
||||
else if (authType.value === "bearer") return "Bearer"
|
||||
else if (authType.value === "oauth-2") return "OAuth 2.0"
|
||||
else if (authType.value === "api-key") return "API key"
|
||||
else return "None"
|
||||
})
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
const basicUsername = pluckRef(auth as Ref<HoppGQLAuthBasic>, "username")
|
||||
const basicPassword = pluckRef(auth as Ref<HoppGQLAuthBasic>, "password")
|
||||
const bearerToken = pluckRef(auth as Ref<HoppGQLAuthBearer>, "token")
|
||||
const oauth2Token = pluckRef(auth as Ref<HoppGQLAuthOAuth2>, "token")
|
||||
const apiKey = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "key")
|
||||
const apiValue = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "value")
|
||||
const addTo = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "addTo")
|
||||
if (typeof addTo.value === "undefined") {
|
||||
addTo.value = "Headers"
|
||||
apiKey.value = ""
|
||||
apiValue.value = ""
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
auth.value = {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
}
|
||||
}
|
||||
const authTypeOptions = ref<any | null>(null)
|
||||
const addToOptions = ref<any | null>(null)
|
||||
</script>
|
||||
@@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field-title" :class="{ 'field-highlighted': isHighlighted }">
|
||||
{{ fieldName }}
|
||||
<span v-if="fieldArgs.length > 0">
|
||||
(
|
||||
<span v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
{{ field.name }}:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="field.type"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
<span v-if="index !== fieldArgs.length - 1">, </span>
|
||||
</span>
|
||||
) </span
|
||||
>:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="gqlField.type"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.description"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ gqlField.description }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.isDeprecated"
|
||||
class="inline-block px-2 py-1 my-1 text-black bg-yellow-200 rounded field-deprecated"
|
||||
>
|
||||
{{ t("state.deprecated") }}
|
||||
</div>
|
||||
<div v-if="fieldArgs.length > 0">
|
||||
<h5 class="my-2">Arguments:</h5>
|
||||
<div class="pl-4 border-l-2 border-divider">
|
||||
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
<span>
|
||||
{{ field.name }}:
|
||||
<GraphqlTypeLink
|
||||
:gql-type="field.type"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
v-if="field.description"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ field.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// TypeScript + Script Setup this :)
|
||||
import { defineComponent } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
gqlField: { type: Object, default: () => ({}) },
|
||||
jumpTypeCallback: { type: Function, default: () => ({}) },
|
||||
isHighlighted: { type: Boolean, default: false },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
t: useI18n(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fieldName() {
|
||||
return this.gqlField.name
|
||||
},
|
||||
|
||||
fieldArgs() {
|
||||
return this.gqlField.args || []
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.field-highlighted {
|
||||
@apply border-accent border-b-2;
|
||||
}
|
||||
|
||||
.field-title {
|
||||
@apply select-text;
|
||||
}
|
||||
</style>
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary"
|
||||
>
|
||||
<div class="inline-flex flex-1 space-x-2">
|
||||
<input
|
||||
id="url"
|
||||
v-model="url"
|
||||
type="url"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="w-full px-4 py-2 border rounded bg-primaryLight border-divider text-secondaryDark"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
:disabled="connected"
|
||||
@keyup.enter="onConnectClick"
|
||||
/>
|
||||
<ButtonPrimary
|
||||
id="get"
|
||||
name="get"
|
||||
:label="!connected ? t('action.connect') : t('action.disconnect')"
|
||||
class="w-32"
|
||||
@click="onConnectClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
|
||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||
import { getCurrentStrategyID } from "~/helpers/network"
|
||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { gqlHeaders$, gqlURL$, setGQLURL } from "~/newstore/GQLSession"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
conn: GQLConnection
|
||||
}>()
|
||||
|
||||
const connected = useReadonlyStream(props.conn.connected$, false)
|
||||
const headers = useReadonlyStream(gqlHeaders$, [])
|
||||
|
||||
const url = useStream(gqlURL$, "", setGQLURL)
|
||||
|
||||
const onConnectClick = () => {
|
||||
if (!connected.value) {
|
||||
props.conn.connect(url.value, headers.value as any)
|
||||
|
||||
logHoppRequestRunToAnalytics({
|
||||
platform: "graphql-schema",
|
||||
strategy: getCurrentStrategyID(),
|
||||
})
|
||||
} else {
|
||||
props.conn.disconnect()
|
||||
}
|
||||
}
|
||||
</script>
|
||||