Compare commits
398 Commits
refactor/t
...
bug/withDe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
407dad8c7f | ||
|
|
9f944506e0 | ||
|
|
114c37645a | ||
|
|
8f9bb621b8 | ||
|
|
48a6c87d9d | ||
|
|
f28b55dd4d | ||
|
|
8a8b4b0245 | ||
|
|
0afbc57012 | ||
|
|
c651f2440f | ||
|
|
8c05084994 | ||
|
|
6813be47f0 | ||
|
|
30327e8d27 | ||
|
|
8096ed300d | ||
|
|
4a8efbf426 | ||
|
|
7a6d117a76 | ||
|
|
73568043f1 | ||
|
|
76a3b35e9e | ||
|
|
feb1991da3 | ||
|
|
2bee4342b8 | ||
|
|
7e9fc486f2 | ||
|
|
1d99b79926 | ||
|
|
eb8347f942 | ||
|
|
d383b48916 | ||
|
|
e88c40db0a | ||
|
|
f228f37bb8 | ||
|
|
503a54fc5e | ||
|
|
48b21aa0bf | ||
|
|
ca40cc5271 | ||
|
|
1c641c6d11 | ||
|
|
32b362f9cc | ||
|
|
103ef8ee0d | ||
|
|
4a6239e017 | ||
|
|
1f637edd36 | ||
|
|
25878b9bb1 | ||
|
|
521a96bffb | ||
|
|
ead1f3954f | ||
|
|
0ac84b58e3 | ||
|
|
a2f1e37ad2 | ||
|
|
373343fea1 | ||
|
|
2ef99026e5 | ||
|
|
29aff9accc | ||
|
|
7d7f628f6e | ||
|
|
8f6cf07e82 | ||
|
|
245b8a6e3c | ||
|
|
a967100be8 | ||
|
|
a9bca8e1f8 | ||
|
|
7de8e6be5e | ||
|
|
6b70a39f02 | ||
|
|
d538d722d7 | ||
|
|
13bd831c5f | ||
|
|
9b297ba882 | ||
|
|
be6c802745 | ||
|
|
564cce2462 | ||
|
|
8f166b8b3f | ||
|
|
fe7192ae61 | ||
|
|
6d54f21c1e | ||
|
|
87f8f61163 | ||
|
|
510ba376e5 | ||
|
|
40c88b3e35 | ||
|
|
7c65da4cf3 | ||
|
|
0f07c47e9f | ||
|
|
17c45fee11 | ||
|
|
a63c0817cc | ||
|
|
68aa54bdb7 | ||
|
|
41cb6eb190 | ||
|
|
61e5a48b02 | ||
|
|
9e4d7df7d0 | ||
|
|
e83dbc2e5c | ||
|
|
03ab6a208d | ||
|
|
dbd39ba0d8 | ||
|
|
136b1ff63b | ||
|
|
69a6207a4d | ||
|
|
9e74a8c2e7 | ||
|
|
ad76d100ee | ||
|
|
235968073a | ||
|
|
ad7b8da37e | ||
|
|
7d3e1a700f | ||
|
|
d3a1898dad | ||
|
|
45e508fc36 | ||
|
|
8edad7ded7 | ||
|
|
75e1adb7b3 | ||
|
|
22ac13f2f0 | ||
|
|
7f0246eb47 | ||
|
|
5f0800760f | ||
|
|
6db99c9e37 | ||
|
|
3b2cabd3f3 | ||
|
|
b0dd6b0bd6 | ||
|
|
874b846e60 | ||
|
|
dbe2525c6f | ||
|
|
afd414fa3f | ||
|
|
94763dcb31 | ||
|
|
6314740f46 | ||
|
|
d7332120e3 | ||
|
|
8c74fe9925 | ||
|
|
c74ddeb530 | ||
|
|
e31c0a9d02 | ||
|
|
d9e5d4aec5 | ||
|
|
c1ee8f5dd0 | ||
|
|
dd59de3de0 | ||
|
|
511a3c55f3 | ||
|
|
c9021ab3ca | ||
|
|
a765c4a7cc | ||
|
|
ea99732474 | ||
|
|
6c64ffe833 | ||
|
|
5fa6c6cdb3 | ||
|
|
d94759870e | ||
|
|
f0a6fc641a | ||
|
|
7ba00bee0b | ||
|
|
dc2bdf81b9 | ||
|
|
187a30abac | ||
|
|
5b824ccb17 | ||
|
|
3bdf2baf97 | ||
|
|
9af8a24a89 | ||
|
|
57c4759bdb | ||
|
|
d9d7261bc5 | ||
|
|
a12315d81a | ||
|
|
9f0956556f | ||
|
|
748318d44e | ||
|
|
ff3062cdfc | ||
|
|
48d67fe7e1 | ||
|
|
2c9918f9a7 | ||
|
|
ee03952201 | ||
|
|
43dcd3c443 | ||
|
|
8f8c42a92a | ||
|
|
6496aded25 | ||
|
|
eacf8113af | ||
|
|
c4a1527153 | ||
|
|
4a89a6aafc | ||
|
|
52539b084d | ||
|
|
8d5bd051a1 | ||
|
|
3809e9853e | ||
|
|
5f795acd61 | ||
|
|
17c550404f | ||
|
|
a840079119 | ||
|
|
2761894164 | ||
|
|
6b8bc618dc | ||
|
|
258f79604f | ||
|
|
81ae70ee04 | ||
|
|
6b02d290a5 | ||
|
|
7ab1bbaf62 | ||
|
|
079083d0f2 | ||
|
|
0504707aab | ||
|
|
fb4aab875d | ||
|
|
7bb32ecf7e | ||
|
|
e129a5c179 | ||
|
|
8045f26c19 | ||
|
|
86516421b5 | ||
|
|
bce88ccd44 | ||
|
|
66d408b7db | ||
|
|
297bf3205f | ||
|
|
7366b32349 | ||
|
|
b7e0169c9b | ||
|
|
6b6f85cc7e | ||
|
|
2c014a2f4b | ||
|
|
d6df675821 | ||
|
|
427baf4c79 | ||
|
|
4f2b682341 | ||
|
|
e03f888cb2 | ||
|
|
513396d498 | ||
|
|
8f04f0758b | ||
|
|
7a77bfc248 | ||
|
|
ddd29374ea | ||
|
|
4b0d7a6c3d | ||
|
|
20bfc02a4e | ||
|
|
2511724b73 | ||
|
|
a851ee3fab | ||
|
|
40f6e6f8a8 | ||
|
|
e2fd104c2d | ||
|
|
a3eafa54fa | ||
|
|
b90b4a1910 | ||
|
|
247df4d5b9 | ||
|
|
4a3889a76e | ||
|
|
224a6e069c | ||
|
|
2c3097eeb7 | ||
|
|
3289ede0e8 | ||
|
|
ddf21c17d2 | ||
|
|
c9a24a0d28 | ||
|
|
fa4b130b18 | ||
|
|
8561a7547f | ||
|
|
e85f7b8232 | ||
|
|
2f91d25ed4 | ||
|
|
ae304b5af7 | ||
|
|
363d34b5e5 | ||
|
|
1c51f8b32e | ||
|
|
aaff07bba2 | ||
|
|
4bc38d5e0f | ||
|
|
fdfca00886 | ||
|
|
d872e393f8 | ||
|
|
686d8e5be7 | ||
|
|
4e30efd737 | ||
|
|
aa4935c505 | ||
|
|
0e381ab850 | ||
|
|
4a03ee4518 | ||
|
|
be414d8279 | ||
|
|
c47b1f2413 | ||
|
|
ba9ee052a6 | ||
|
|
b1c6708762 | ||
|
|
0ba31b6c79 | ||
|
|
14f402f186 | ||
|
|
b811e97ea5 | ||
|
|
6d167ce1d6 | ||
|
|
9ac1d23fd9 | ||
|
|
dea3a34e3d | ||
|
|
39de34f083 | ||
|
|
02d5f0fdf3 | ||
|
|
0bb7cbe8d9 | ||
|
|
3e3b88b8c2 | ||
|
|
1438beb93b | ||
|
|
c1ec5dc60d | ||
|
|
3f513f2f4d | ||
|
|
d1b573f6f9 | ||
|
|
0e08abc46f | ||
|
|
49bdf9f203 | ||
|
|
b3e9df4f3d | ||
|
|
3b8cf4a60a | ||
|
|
b7ccb9a34c | ||
|
|
b8ffa872c7 | ||
|
|
ef866f7851 | ||
|
|
f27515bf1d | ||
|
|
ddf74c5d7c | ||
|
|
8ea12695b3 | ||
|
|
d6324e6ba6 | ||
|
|
2325982801 | ||
|
|
b103c45e65 | ||
|
|
409989eddb | ||
|
|
3f5fcae280 | ||
|
|
d5123c793a | ||
|
|
e6707c1e4a | ||
|
|
41be5cc4a8 | ||
|
|
e82a4a1d23 | ||
|
|
e30e4edfce | ||
|
|
bd72ef7950 | ||
|
|
539034df2a | ||
|
|
6da6afc5a1 | ||
|
|
fea523972d | ||
|
|
5cfc6c2949 | ||
|
|
6dd0c25d49 | ||
|
|
ab9b3e47b9 | ||
|
|
33ebdf2831 | ||
|
|
ef95939763 | ||
|
|
0394deaeef | ||
|
|
337a60c8a4 | ||
|
|
47b341d50e | ||
|
|
a5fd39adf8 | ||
|
|
5772117dc8 | ||
|
|
f73c8a45d9 | ||
|
|
109d4190ae | ||
|
|
317de82be6 | ||
|
|
3604d69463 | ||
|
|
96cf774652 | ||
|
|
d2b39976ba | ||
|
|
06161bc963 | ||
|
|
2ab1d3dbfa | ||
|
|
ecdc7919ae | ||
|
|
a24541ac2b | ||
|
|
d832690548 | ||
|
|
2844710ea8 | ||
|
|
84ad4071ad | ||
|
|
7f501241f0 | ||
|
|
5dcfa66c5d | ||
|
|
f428a21279 | ||
|
|
ccdd4963cd | ||
|
|
2092a3729c | ||
|
|
a628420adb | ||
|
|
23de147ca1 | ||
|
|
93f55f5619 | ||
|
|
b10933898f | ||
|
|
1727b754d4 | ||
|
|
ffb0c12c08 | ||
|
|
c332808fe4 | ||
|
|
8f0538c886 | ||
|
|
e6bb7e2ca9 | ||
|
|
2a012520d0 | ||
|
|
c71333d9cb | ||
|
|
728515c225 | ||
|
|
e0f88e01f9 | ||
|
|
1aa94a12c0 | ||
|
|
de2d3361a7 | ||
|
|
680937e50b | ||
|
|
6751c50514 | ||
|
|
0c389701fe | ||
|
|
9454d8c100 | ||
|
|
166f9e817b | ||
|
|
d2865c637c | ||
|
|
9698932bde | ||
|
|
44026fcd41 | ||
|
|
d938af0c2c | ||
|
|
fd658400a6 | ||
|
|
dcbb17b164 | ||
|
|
49741875bd | ||
|
|
0fcd9733ff | ||
|
|
62d50169d7 | ||
|
|
1d3d5a1e6a | ||
|
|
d309fa745e | ||
|
|
f7031992d5 | ||
|
|
fcdf68ea15 | ||
|
|
b0a6692179 | ||
|
|
e1e763575d | ||
|
|
4236d1179c | ||
|
|
09365bcabe | ||
|
|
be29ddcbd6 | ||
|
|
522194ca8d | ||
|
|
5af8f584f6 | ||
|
|
adc08f8865 | ||
|
|
0f39d54c3c | ||
|
|
9e6659e842 | ||
|
|
46a0f6e3f8 | ||
|
|
e90b26ebed | ||
|
|
4407f260ae | ||
|
|
d4392416c8 | ||
|
|
2d3cbd26b8 | ||
|
|
98b9660956 | ||
|
|
4e8a4e8914 | ||
|
|
96bcbc80f8 | ||
|
|
1dfc8e2973 | ||
|
|
311886f6c9 | ||
|
|
4a332f40e5 | ||
|
|
93a97a2f4c | ||
|
|
1dee098ca2 | ||
|
|
a07cc7e560 | ||
|
|
c26f7f5ebc | ||
|
|
5d801cf566 | ||
|
|
631b2d869e | ||
|
|
c02f54cc18 | ||
|
|
827a95515d | ||
|
|
9082152f1a | ||
|
|
0efbddeda4 | ||
|
|
b2e186957c | ||
|
|
d855e5cffb | ||
|
|
f3747edaa3 | ||
|
|
752932ef3d | ||
|
|
948cf9dae3 | ||
|
|
b2f93aa549 | ||
|
|
108f228edf | ||
|
|
fe6030140f | ||
|
|
003400cfa8 | ||
|
|
41a02f059d | ||
|
|
b4ed6fd107 | ||
|
|
36246da9e1 | ||
|
|
457b6b982c | ||
|
|
05a07dc4a1 | ||
|
|
85889c2cb9 | ||
|
|
be6ceaab04 | ||
|
|
f1b18688bb | ||
|
|
80c7decb81 | ||
|
|
3ef5a1e21a | ||
|
|
2eb0a4c754 | ||
|
|
10f5af5dda | ||
|
|
8b27ebb96b | ||
|
|
b28f82a881 | ||
|
|
c921606f3f | ||
|
|
c6c08f6c60 | ||
|
|
02cf620090 | ||
|
|
4a12cc76fa | ||
|
|
f4f74e223f | ||
|
|
8b4535c131 | ||
|
|
b15fd6c75a | ||
|
|
e1a25fa894 | ||
|
|
2bb3b71a70 | ||
|
|
4c55b9c304 | ||
|
|
639a629809 | ||
|
|
d6e3bd09b4 | ||
|
|
8d67a0d95f | ||
|
|
b9fc0175e7 | ||
|
|
dc5f52cc0d | ||
|
|
602aabdeb8 | ||
|
|
2f8aa79ec1 | ||
|
|
8af90432cf | ||
|
|
61da0733c2 | ||
|
|
33951482d5 | ||
|
|
4e8484ee7c | ||
|
|
071761a61e | ||
|
|
10a11d6725 | ||
|
|
c81178ae26 | ||
|
|
2bafae5397 | ||
|
|
6a1d201e0e | ||
|
|
8de544696d | ||
|
|
66c489da8f | ||
|
|
26c8f35688 | ||
|
|
28aeac4533 | ||
|
|
162b3d6192 | ||
|
|
b016d3fd9d | ||
|
|
f64ff58dbc | ||
|
|
d4d3d96bbb | ||
|
|
a5197ee544 | ||
|
|
8a5fd4f745 | ||
|
|
12cd7940c6 | ||
|
|
0c2cec46a7 | ||
|
|
8430921e4e | ||
|
|
c938abf606 | ||
|
|
3addfe8d4b | ||
|
|
5276556837 | ||
|
|
e47ad94666 | ||
|
|
7065763c7c | ||
|
|
86489d95c2 | ||
|
|
e2b1c83698 | ||
|
|
15373be63e | ||
|
|
8c9cd079b7 |
13
.github/workflows/tests.yml
vendored
@@ -2,13 +2,12 @@ name: Node.js CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@@ -17,9 +16,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
- name: Install pnpm
|
||||||
|
run: curl -f https://get.pnpm.io/v6.14.js | node - add --global pnpm@6
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm ci
|
cache: pnpm
|
||||||
- run: npm test
|
- name: Run tests
|
||||||
|
run: pnpm i && pnpm -r test
|
||||||
|
|||||||
6
.gitignore
vendored
@@ -104,3 +104,9 @@ tests/*/screenshots
|
|||||||
|
|
||||||
# Tests videos
|
# Tests videos
|
||||||
tests/*/videos
|
tests/*/videos
|
||||||
|
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
||||||
|
|
||||||
|
# Andrew's crazy Volar shim generator
|
||||||
|
shims-volar.d.ts
|
||||||
1
.husky/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
_
|
|
||||||
@@ -6,7 +6,7 @@ We as members, contributors, and leaders pledge to make participation in our
|
|||||||
community a harassment-free experience for everyone, regardless of age, body
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
and orientation.
|
and orientation.
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community include:
|
community include:
|
||||||
|
|
||||||
- Demonstrating empathy and kindness toward other people
|
* Demonstrating empathy and kindness toward other people
|
||||||
- Being respectful of differing opinions, viewpoints, and experiences
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
- Giving and gracefully accepting constructive feedback
|
* Giving and gracefully accepting constructive feedback
|
||||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
- Focusing on what is best not just for us as individuals, but for the
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
overall community
|
overall community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
- The use of sexualized language or imagery, and sexual attention or
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
advances of any kind
|
advances of any kind
|
||||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
- Public or private harassment
|
* Public or private harassment
|
||||||
- Publishing others' private information, such as a physical or email
|
* Publishing others' private information, such as a physical or email
|
||||||
address, without their explicit permission
|
address, without their explicit permission
|
||||||
- Other conduct which could reasonably be considered inappropriate in a
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
[INSERT CONTACT METHOD].
|
support@hoppscotch.io.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
|||||||
### 4. Permanent Ban
|
### 4. Permanent Ban
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
**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.
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
@@ -116,17 +116,13 @@ the community.
|
|||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
version 2.0, available at
|
version 2.0, available at
|
||||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
[Mozilla's code of conduct enforcement ladder][mozilla coc].
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][faq]. Translations are available
|
|
||||||
at [https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
[homepage]: https://www.contributor-covenant.org
|
||||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
|
||||||
[mozilla coc]: https://github.com/mozilla/diversity
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
[faq]: https://www.contributor-covenant.org/faq
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
https://www.contributor-covenant.org/translations.
|
||||||
|
|||||||
16
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM node:12-alpine
|
FROM node:lts-alpine
|
||||||
|
|
||||||
LABEL maintainer="Hoppscotch (support@hoppscotch.io)"
|
LABEL maintainer="Hoppscotch (support@hoppscotch.io)"
|
||||||
|
|
||||||
@@ -9,17 +9,19 @@ RUN apk add --update --no-cache \
|
|||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
ADD . /app/
|
ADD . /app/
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
RUN pnpm i --unsafe-perm=true
|
||||||
|
|
||||||
ENV HOST 0.0.0.0
|
ENV HOST 0.0.0.0
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
RUN mv .env.example .env
|
RUN mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env
|
||||||
|
|
||||||
CMD ["npm", "run", "dev"]
|
RUN pnpm run generate
|
||||||
|
|
||||||
|
CMD ["pnpm", "run", "start"]
|
||||||
|
|||||||
31
README.md
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://hoppscotch.io">
|
<a href="https://hoppscotch.io">
|
||||||
<img
|
<img
|
||||||
src="https://raw.githubusercontent.com/hoppscotch/hoppscotch/main/static/logo.png"
|
src="https://avatars.githubusercontent.com/u/56705483"
|
||||||
alt="Hoppscotch Logo"
|
alt="Hoppscotch Logo"
|
||||||
height="64"
|
height="64"
|
||||||
/>
|
/>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<p>
|
<p>
|
||||||
<a href="https://hoppscotch.io">
|
<a href="https://hoppscotch.io">
|
||||||
<img
|
<img
|
||||||
src="https://tiny.cc/hoppscotch_screenshot_1"
|
src="https://raw.githubusercontent.com/hoppscotch/hoppscotch/main/packages/hoppscotch-app/static/banner.png"
|
||||||
alt="Screenshot"
|
alt="Screenshot"
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
- `TRACE` - Performs a message loop-back test along the path to the target resource
|
- `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.
|
- `<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).
|
🌈 **Make it yours:** Customizable combinations for background, foreground and accent colors — [customize now](https://hoppscotch.io/settings).
|
||||||
|
|
||||||
**Theming**
|
**Theming**
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ _Collections are synced with cloud / local session storage_
|
|||||||
|
|
||||||
- Hide your IP address
|
- Hide your IP address
|
||||||
- Fixes [`CORS`](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross Origin Resource Sharing) issues
|
- Fixes [`CORS`](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross Origin Resource Sharing) issues
|
||||||
- Access APIs served in non-HTTPS `[http://]` endpoints
|
- Access APIs served in non-HTTPS (`http://`) endpoints
|
||||||
- Use your own Proxy URL
|
- Use your own Proxy URL
|
||||||
|
|
||||||
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/privacy)**_
|
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/privacy)**_
|
||||||
@@ -256,9 +256,12 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
|||||||
|
|
||||||
👨👩👧👦 **Teams β:** Helps you collaborate across your team to design, develop, and test APIs faster.
|
👨👩👧👦 **Teams β:** Helps you collaborate across your team to design, develop, and test APIs faster.
|
||||||
|
|
||||||
- Unlimited team collections and shared requests
|
- Unlimited teams
|
||||||
|
- Unlimited shared collections
|
||||||
- Unlimited team members
|
- Unlimited team members
|
||||||
- User roles
|
- Role-based access control
|
||||||
|
- Cloud sync
|
||||||
|
- Multiple devices
|
||||||
|
|
||||||
🚚 **Bulk Edit:** Edit key-value pairs in bulk.
|
🚚 **Bulk Edit:** Edit key-value pairs in bulk.
|
||||||
|
|
||||||
@@ -290,7 +293,7 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
|||||||
|
|
||||||
## **Developing**
|
## **Developing**
|
||||||
|
|
||||||
0. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/.env.example) file found in repository's root directory with your own keys and rename it to `.env`.
|
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 works with the [production build](https://hoppscotch.io)._
|
_Sample keys only works with the [production build](https://hoppscotch.io)._
|
||||||
|
|
||||||
@@ -302,9 +305,10 @@ _Sample keys only works with the [production build](https://hoppscotch.io)._
|
|||||||
### Local development environment
|
### Local development environment
|
||||||
|
|
||||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||||
2. Install dependencies by running `npm install` within the directory that you cloned (probably `hoppscotch`).
|
2. Install pnpm using npm by running `npm install -g pnpm`.
|
||||||
3. Start the development server with `npm run dev`.
|
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
|
||||||
4. Open development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
4. Start the development server with `pnpm run dev`.
|
||||||
|
5. Open development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||||
|
|
||||||
### Docker compose
|
### Docker compose
|
||||||
|
|
||||||
@@ -323,9 +327,10 @@ docker run --rm --name hoppscotch -p 3000:3000 hoppscotch/hoppscotch:latest
|
|||||||
## **Releasing**
|
## **Releasing**
|
||||||
|
|
||||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||||
2. Install dependencies by running `npm install` within the directory that you cloned (probably `hoppscotch`).
|
2. Install pnpm using npm by running `npm install -g pnpm`.
|
||||||
3. Build the release files with `npm run generate`.
|
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
|
||||||
4. Find the built project in `./dist`.
|
4. Build the release files with `pnpm run generate`.
|
||||||
|
5. Find the built project in `packages/hoppscotch-app/dist`.
|
||||||
|
|
||||||
## **Contributing**
|
## **Contributing**
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ if there is no existing translation, you can create a new one by following these
|
|||||||
|
|
||||||
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
|
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
|
||||||
2. **Create a new branch for your translation.**
|
2. **Create a new branch for your translation.**
|
||||||
3. **Create target language file in the [`locales`](https://github.com/hoppscotch/hoppscotch/tree/main/locales) directory.**
|
3. **Create target language file in the [`locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-app/locales) directory.**
|
||||||
4. **Copy the contents of the source file [`locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/locales/en.json) to the target language file.**
|
4. **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.**
|
||||||
5. **Translate the strings in the target language file.**
|
5. **Translate the strings in the target language file.**
|
||||||
6. **Add your language entry to [`languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/languages.json).**
|
6. **Add your language entry to [`languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/languages.json).**
|
||||||
7. **Save & commit changes.**
|
7. **Save & commit changes.**
|
||||||
8. **Send a pull request.**
|
8. **Send a pull request.**
|
||||||
|
|
||||||
|
|||||||
@@ -1 +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" class="feather feather-align-left"><line x1="17" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="17" y1="18" x2="3" y2="18"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 396 B |
@@ -1 +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"><polyline points="21 8 21 21 3 21 3 8"></polyline><rect x="1" y="3" width="22" height="5"></rect><line x1="10" y1="12" x2="14" y2="12"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 330 B |
@@ -1 +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"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 278 B |
@@ -1 +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"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 279 B |
@@ -1 +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"><circle cx="12" cy="12" r="4"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 291 B |
@@ -1 +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="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 306 B |
@@ -1 +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="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 317 B |
@@ -1 +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="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 292 B |
@@ -1 +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"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 279 B |
@@ -1 +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"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 323 B |
@@ -1 +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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 338 B |
@@ -1 +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="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 337 B |
@@ -1 +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="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 351 B |
@@ -1 +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="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 429 B |
@@ -1 +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="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
|
||||||
|
Before Width: | Height: | Size: 289 B |
@@ -1 +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="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="12" y1="18" x2="12" y2="12"></line><line x1="9" y1="15" x2="15" y2="15"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 398 B |
@@ -1 +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="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 440 B |
@@ -1 +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="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 309 B |
@@ -1 +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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="9" y1="14" x2="15" y2="14"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 325 B |
@@ -1 +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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="12" y1="11" x2="12" y2="17"></line><line x1="9" y1="14" x2="15" y2="14"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 370 B |
@@ -1 +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="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 281 B |
@@ -1 +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"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 453 B |
@@ -1 +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 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 497 B |
@@ -1 +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"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 380 B |
@@ -1 +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"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 330 B |
@@ -1 +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="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 304 B |
@@ -1 +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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 376 B |
@@ -1 +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"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 319 B |
@@ -1 +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="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 325 B |
@@ -1 +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"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 335 B |
@@ -1 +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"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle><line x1="4.93" y1="4.93" x2="9.17" y2="9.17"></line><line x1="14.83" y1="14.83" x2="19.07" y2="19.07"></line><line x1="14.83" y1="9.17" x2="19.07" y2="4.93"></line><line x1="14.83" y1="9.17" x2="18.36" y2="5.64"></line><line x1="4.93" y1="19.07" x2="9.17" y2="14.83"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 542 B |
@@ -1 +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="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 325 B |
@@ -1 +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"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 584 B |
@@ -1 +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"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 293 B |
@@ -1 +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 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 336 B |
@@ -1 +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="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 326 B |
@@ -1 +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"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 366 B |
@@ -1 +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="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 299 B |
@@ -1 +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="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 390 B |
@@ -1 +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"><polyline points="4 14 10 14 10 20"></polyline><polyline points="20 10 14 10 14 4"></polyline><line x1="14" y1="10" x2="21" y2="3"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 370 B |
@@ -1 +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="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 299 B |
@@ -1 +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"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 339 B |
@@ -1 +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="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 253 B |
@@ -1 +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"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
|
||||||
|
Before Width: | Height: | Size: 304 B |
@@ -1 +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="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 319 B |
@@ -1 +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"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 316 B |
@@ -1 +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"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 276 B |
@@ -1 +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"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 366 B |
@@ -1 +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"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 283 B |
@@ -1 +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="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 364 B |
@@ -1 +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"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 278 B |
@@ -1 +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"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 978 B |
@@ -1 +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"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 414 B |
@@ -1 +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"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 292 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
||||||
|
Before Width: | Height: | Size: 319 B |
@@ -1 +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"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
|
||||||
|
Before Width: | Height: | Size: 311 B |
@@ -1 +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"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 623 B |
@@ -1 +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"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 278 B |
@@ -1 +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"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 417 B |
@@ -1 +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"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 327 B |
@@ -1 +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="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 377 B |
@@ -1 +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"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 324 B |
@@ -1 +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"><polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 395 B |
@@ -1 +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="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 375 B |
@@ -1 +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="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
|
||||||
|
Before Width: | Height: | Size: 285 B |
@@ -1 +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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 371 B |
@@ -1 +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"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
||||||
|
Before Width: | Height: | Size: 274 B |
@@ -1 +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"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
|
||||||
|
Before Width: | Height: | Size: 255 B |
@@ -1,166 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<header
|
|
||||||
class="flex space-x-2 flex-1 py-2 px-2 items-center justify-between"
|
|
||||||
>
|
|
||||||
<div class="space-x-2 inline-flex items-center">
|
|
||||||
<ButtonSecondary
|
|
||||||
class="tracking-wide !font-bold !text-secondaryDark"
|
|
||||||
label="HOPPSCOTCH"
|
|
||||||
to="/"
|
|
||||||
/>
|
|
||||||
<AppGitHubStarButton class="mt-1.5 transition hidden sm:flex" />
|
|
||||||
</div>
|
|
||||||
<div class="space-x-2 inline-flex items-center">
|
|
||||||
<ButtonSecondary
|
|
||||||
id="installPWA"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('header.install_pwa')"
|
|
||||||
svg="download"
|
|
||||||
class="rounded"
|
|
||||||
@click.native="showInstallPrompt()"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${$t('app.search')} <kbd>/</kbd>`"
|
|
||||||
svg="search"
|
|
||||||
class="rounded"
|
|
||||||
@click.native="showSearch = true"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${$t('support.title')} <kbd>?</kbd>`"
|
|
||||||
svg="life-buoy"
|
|
||||||
class="rounded"
|
|
||||||
@click.native="showSupport = true"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="currentUser === null"
|
|
||||||
svg="upload-cloud"
|
|
||||||
:label="$t('header.save_workspace')"
|
|
||||||
filled
|
|
||||||
class="hidden !font-semibold md:flex"
|
|
||||||
@click.native="showLogin = true"
|
|
||||||
/>
|
|
||||||
<ButtonPrimary
|
|
||||||
v-if="currentUser === null"
|
|
||||||
:label="$t('header.login')"
|
|
||||||
@click.native="showLogin = true"
|
|
||||||
/>
|
|
||||||
<span v-else class="px-2">
|
|
||||||
<tippy ref="user" interactive trigger="click" theme="popover" arrow>
|
|
||||||
<template #trigger>
|
|
||||||
<ProfilePicture
|
|
||||||
v-if="currentUser.photoURL"
|
|
||||||
v-tippy="{
|
|
||||||
theme: 'tooltip',
|
|
||||||
}"
|
|
||||||
:url="currentUser.photoURL"
|
|
||||||
:alt="currentUser.displayName"
|
|
||||||
:title="currentUser.displayName"
|
|
||||||
indicator
|
|
||||||
:indicator-styles="isOnLine ? 'bg-green-500' : 'bg-red-500'"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-else
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('header.account')"
|
|
||||||
class="rounded"
|
|
||||||
svg="user"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<SmartItem
|
|
||||||
to="/settings"
|
|
||||||
svg="settings"
|
|
||||||
:label="$t('navigation.settings')"
|
|
||||||
@click.native="$refs.user.tippy().hide()"
|
|
||||||
/>
|
|
||||||
<FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" />
|
|
||||||
</tippy>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<AppAnnouncement v-if="!isOnLine" />
|
|
||||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
|
||||||
<AppSupport :show="showSupport" @hide-modal="showSupport = false" />
|
|
||||||
<AppPowerSearch :show="showSearch" @hide-modal="showSearch = false" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
|
||||||
import intializePwa from "~/helpers/pwa"
|
|
||||||
import { currentUser$ } from "~/helpers/fb/auth"
|
|
||||||
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
|
|
||||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const showSupport = ref(false)
|
|
||||||
const showSearch = ref(false)
|
|
||||||
|
|
||||||
defineActionHandler("modals.support.toggle", () => {
|
|
||||||
showSupport.value = !showSupport.value
|
|
||||||
})
|
|
||||||
defineActionHandler("modals.search.toggle", () => {
|
|
||||||
showSearch.value = !showSearch.value
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentUser: useReadonlyStream(currentUser$, null),
|
|
||||||
showSupport,
|
|
||||||
showSearch,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// Once the PWA code is initialized, this holds a method
|
|
||||||
// that can be called to show the user the installation
|
|
||||||
// prompt.
|
|
||||||
showInstallPrompt: null,
|
|
||||||
showLogin: false,
|
|
||||||
isOnLine: navigator.onLine,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
window.addEventListener("online", () => {
|
|
||||||
this.isOnLine = true
|
|
||||||
})
|
|
||||||
window.addEventListener("offline", () => {
|
|
||||||
this.isOnLine = false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Initializes the PWA code - checks if the app is installed,
|
|
||||||
// etc.
|
|
||||||
this.showInstallPrompt = await intializePwa()
|
|
||||||
|
|
||||||
const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes"
|
|
||||||
if (!cookiesAllowed) {
|
|
||||||
this.$toast.show(this.$t("app.we_use_cookies").toString(), {
|
|
||||||
icon: "cookie",
|
|
||||||
duration: 0,
|
|
||||||
action: [
|
|
||||||
{
|
|
||||||
text: this.$t("action.learn_more").toString(),
|
|
||||||
onClick: (_, toastObject) => {
|
|
||||||
setLocalConfig("cookiesAllowed", "yes")
|
|
||||||
toastObject.goAway(0)
|
|
||||||
window
|
|
||||||
.open("https://docs.hoppscotch.io/privacy", "_blank")
|
|
||||||
.focus()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$t("action.dismiss").toString(),
|
|
||||||
onClick: (_, toastObject) => {
|
|
||||||
setLocalConfig("cookiesAllowed", "yes")
|
|
||||||
toastObject.goAway(0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
cursor-pointer
|
|
||||||
flex
|
|
||||||
py-2
|
|
||||||
px-6
|
|
||||||
transition
|
|
||||||
items-center
|
|
||||||
group
|
|
||||||
hover:bg-primaryLight
|
|
||||||
focus:outline-none
|
|
||||||
focus-visible:bg-primaryLight
|
|
||||||
"
|
|
||||||
tabindex="0"
|
|
||||||
@click="$emit('action', shortcut.action)"
|
|
||||||
@keydown.enter="$emit('action', shortcut.action)"
|
|
||||||
>
|
|
||||||
<SmartIcon
|
|
||||||
class="
|
|
||||||
mr-4
|
|
||||||
opacity-75
|
|
||||||
transition
|
|
||||||
svg-icons
|
|
||||||
group-hover:opacity-100
|
|
||||||
group-focus:opacity-100
|
|
||||||
"
|
|
||||||
:name="shortcut.icon"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="
|
|
||||||
flex flex-1
|
|
||||||
mr-4
|
|
||||||
transition
|
|
||||||
group-hover:text-secondaryDark
|
|
||||||
group-focus:text-secondaryDark
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ $t(shortcut.label) }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-for="(key, keyIndex) in shortcut.keys"
|
|
||||||
:key="`key-${keyIndex}`"
|
|
||||||
class="shortcut-key"
|
|
||||||
>
|
|
||||||
{{ key }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
shortcut: Object
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.shortcut-key {
|
|
||||||
@apply bg-dividerLight;
|
|
||||||
@apply rounded;
|
|
||||||
@apply ml-2;
|
|
||||||
@apply py-1;
|
|
||||||
@apply px-2;
|
|
||||||
@apply inline-flex;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section :id="label.toLowerCase()" class="flex flex-col flex-1 relative">
|
|
||||||
<slot></slot>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: "Section",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SmartModal
|
|
||||||
v-if="show"
|
|
||||||
:title="$t('app.invite_your_friends')"
|
|
||||||
@close="$emit('hide-modal')"
|
|
||||||
>
|
|
||||||
<template #body>
|
|
||||||
<p class="text-secondaryLight mb-8 px-2">
|
|
||||||
{{ $t("app.invite_description") }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-col space-y-2 px-2">
|
|
||||||
<div class="grid gap-4 grid-cols-3">
|
|
||||||
<a
|
|
||||||
v-for="(platform, index) in platforms"
|
|
||||||
:key="`platform-${index}`"
|
|
||||||
:href="platform.link"
|
|
||||||
target="_blank"
|
|
||||||
class="share-link"
|
|
||||||
>
|
|
||||||
<SmartIcon :name="platform.icon" class="h-6 w-6" />
|
|
||||||
<span class="mt-3">
|
|
||||||
{{ platform.name }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<button class="share-link" @click="copyAppLink">
|
|
||||||
<SmartIcon class="h-6 text-xl w-6" :name="copyIcon" />
|
|
||||||
<span class="mt-3">
|
|
||||||
{{ $t("app.copy") }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</SmartModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
show: Boolean,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const url = "https://hoppscotch.io"
|
|
||||||
const text = "Hoppscotch - Open source API development ecosystem."
|
|
||||||
const description =
|
|
||||||
"Helps you create requests faster, saving precious time on development."
|
|
||||||
const subject =
|
|
||||||
"Checkout Hoppscotch - an open source API development ecosystem"
|
|
||||||
const summary = `Hi there!%0D%0A%0D%0AI thought you’ll like this new platform that I joined called Hoppscotch - https://hoppscotch.io.%0D%0AIt is a simple and intuitive interface for creating and managing your APIs. You can build, test, document, and share your APIs.%0D%0A%0D%0AThe best part about Hoppscotch is that it is open source and free to get started.%0D%0A%0D%0A`
|
|
||||||
const twitter = "hoppscotch_io"
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: "https://hoppscotch.io",
|
|
||||||
copyIcon: "copy",
|
|
||||||
platforms: [
|
|
||||||
{
|
|
||||||
name: "Email",
|
|
||||||
icon: "mail",
|
|
||||||
link: `mailto:?subject=${subject}&body=${summary}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Twitter",
|
|
||||||
icon: "brands/twitter",
|
|
||||||
link: `https://twitter.com/intent/tweet?text=${text} ${description}&url=${url}&via=${twitter}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Facebook",
|
|
||||||
icon: "brands/facebook",
|
|
||||||
link: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reddit",
|
|
||||||
icon: "brands/reddit",
|
|
||||||
link: `https://www.reddit.com/submit?url=${url}&title=${text}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "LinkedIn",
|
|
||||||
icon: "brands/linkedin",
|
|
||||||
link: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copyAppLink() {
|
|
||||||
copyToClipboard(this.url)
|
|
||||||
this.copyIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.copied_to_clipboard").toString(), {
|
|
||||||
icon: "content_paste",
|
|
||||||
})
|
|
||||||
setTimeout(() => (this.copyIcon = "copy"), 1000)
|
|
||||||
},
|
|
||||||
hideModal() {
|
|
||||||
this.$emit("hide-modal")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.share-link {
|
|
||||||
@apply border border-dividerLight;
|
|
||||||
@apply rounded;
|
|
||||||
@apply flex-col flex;
|
|
||||||
@apply p-4;
|
|
||||||
@apply items-center;
|
|
||||||
@apply justify-center;
|
|
||||||
@apply hover:(bg-primaryLight text-secondaryDark);
|
|
||||||
@apply focus:outline-none;
|
|
||||||
@apply focus-visible:border-divider;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
@apply opacity-80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
svg {
|
|
||||||
@apply opacity-100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<transition v-if="show" name="fade" appear>
|
|
||||||
<div class="inset-0 transition-opacity z-20 fixed" @keydown.esc="close()">
|
|
||||||
<div
|
|
||||||
class="bg-primaryDark opacity-90 inset-0 absolute"
|
|
||||||
tabindex="0"
|
|
||||||
@click="close()"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
<aside
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
flex flex-col
|
|
||||||
h-full
|
|
||||||
max-w-full
|
|
||||||
transform
|
|
||||||
transition
|
|
||||||
top-0
|
|
||||||
ease-in-out
|
|
||||||
right-0
|
|
||||||
w-96
|
|
||||||
z-30
|
|
||||||
duration-300
|
|
||||||
fixed
|
|
||||||
overflow-auto
|
|
||||||
"
|
|
||||||
:class="show ? 'shadow-xl translate-x-0' : 'translate-x-full'"
|
|
||||||
>
|
|
||||||
<slot name="content"></slot>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
show: {
|
|
||||||
immediate: true,
|
|
||||||
handler(show) {
|
|
||||||
if (process.client) {
|
|
||||||
if (show) document.body.style.setProperty("overflow", "hidden")
|
|
||||||
else document.body.style.removeProperty("overflow")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
document.addEventListener("keydown", (e) => {
|
|
||||||
if (e.keyCode === 27 && this.show) this.close()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
this.$emit("close")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SmartModal v-if="show" :title="$t('collection.save_as')" @close="hideModal">
|
|
||||||
<template #body>
|
|
||||||
<div class="flex flex-col px-2">
|
|
||||||
<div class="flex relative">
|
|
||||||
<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="px-4 pt-4 pb-4">
|
|
||||||
{{ $t("collection.select_location") }}
|
|
||||||
</label>
|
|
||||||
<CollectionsGraphql
|
|
||||||
v-if="mode === 'graphql'"
|
|
||||||
:doc="false"
|
|
||||||
:show-coll-actions="false"
|
|
||||||
:picked="picked"
|
|
||||||
:saving-mode="true"
|
|
||||||
@select="onSelect"
|
|
||||||
/>
|
|
||||||
<Collections
|
|
||||||
v-else
|
|
||||||
:picked="picked"
|
|
||||||
:save-request="true"
|
|
||||||
@select="onSelect"
|
|
||||||
@update-collection="collectionsType.type = $event"
|
|
||||||
@update-coll-type="onUpdateCollType"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<span>
|
|
||||||
<ButtonPrimary
|
|
||||||
:label="$t('action.save')"
|
|
||||||
@click.native="saveRequestAs"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
:label="$t('action.cancel')"
|
|
||||||
@click.native="hideModal"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</SmartModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import * as teamUtils from "~/helpers/teams/utils"
|
|
||||||
import {
|
|
||||||
saveRESTRequestAs,
|
|
||||||
editRESTRequest,
|
|
||||||
editGraphqlRequest,
|
|
||||||
saveGraphqlRequestAs,
|
|
||||||
} from "~/newstore/collections"
|
|
||||||
import { getGQLSession, useGQLRequestName } from "~/newstore/GQLSession"
|
|
||||||
import {
|
|
||||||
getRESTRequest,
|
|
||||||
useRESTRequestName,
|
|
||||||
setRESTSaveContext,
|
|
||||||
} from "~/newstore/RESTSession"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
// mode can be either "graphql" or "rest"
|
|
||||||
mode: { type: String, default: "rest" },
|
|
||||||
show: Boolean,
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
return {
|
|
||||||
requestName:
|
|
||||||
props.mode === "rest" ? useRESTRequestName() : useGQLRequestName(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
requestData: {
|
|
||||||
name: this.requestName,
|
|
||||||
collectionIndex: undefined,
|
|
||||||
folderName: undefined,
|
|
||||||
requestIndex: undefined,
|
|
||||||
},
|
|
||||||
collectionsType: {
|
|
||||||
type: "my-collections",
|
|
||||||
selectedTeam: undefined,
|
|
||||||
},
|
|
||||||
picked: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
"requestData.collectionIndex": function resetFolderAndRequestIndex() {
|
|
||||||
// if user has chosen some folder, than selected other collection, which doesn't have any folders
|
|
||||||
// than `requestUpdateData.folderName` won't be reseted
|
|
||||||
this.$data.requestData.folderName = undefined
|
|
||||||
this.$data.requestData.requestIndex = undefined
|
|
||||||
},
|
|
||||||
"requestData.folderName": function resetRequestIndex() {
|
|
||||||
this.$data.requestData.requestIndex = undefined
|
|
||||||
},
|
|
||||||
editingRequest({ name }) {
|
|
||||||
this.$data.requestData.name = name || this.$data.defaultRequestName
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onUpdateCollType(newCollType) {
|
|
||||||
this.collectionsType = newCollType
|
|
||||||
},
|
|
||||||
onSelect({ picked }) {
|
|
||||||
this.picked = picked
|
|
||||||
},
|
|
||||||
async saveRequestAs() {
|
|
||||||
if (!this.requestName) {
|
|
||||||
this.$toast.error(this.$t("error.empty_req_name"), {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.picked == null) {
|
|
||||||
this.$toast.error(this.$t("collection.select"), {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestUpdated =
|
|
||||||
this.mode === "rest" ? getRESTRequest() : getGQLSession()
|
|
||||||
|
|
||||||
// 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 (this.picked.pickedType === "my-request") {
|
|
||||||
editRESTRequest(
|
|
||||||
this.picked.folderPath,
|
|
||||||
this.picked.requestIndex,
|
|
||||||
requestUpdated
|
|
||||||
)
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "user-collection",
|
|
||||||
folderPath: this.picked.folderPath,
|
|
||||||
requestIndex: this.picked.requestIndex,
|
|
||||||
})
|
|
||||||
} else if (this.picked.pickedType === "my-folder") {
|
|
||||||
const insertionIndex = saveRESTRequestAs(
|
|
||||||
this.picked.folderPath,
|
|
||||||
requestUpdated
|
|
||||||
)
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "user-collection",
|
|
||||||
folderPath: this.picked.folderPath,
|
|
||||||
requestIndex: insertionIndex,
|
|
||||||
})
|
|
||||||
} else if (this.picked.pickedType === "my-collection") {
|
|
||||||
const insertionIndex = saveRESTRequestAs(
|
|
||||||
`${this.picked.collectionIndex}`,
|
|
||||||
requestUpdated
|
|
||||||
)
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "user-collection",
|
|
||||||
folderPath: `${this.picked.collectionIndex}`,
|
|
||||||
requestIndex: insertionIndex,
|
|
||||||
})
|
|
||||||
} else if (this.picked.pickedType === "teams-request") {
|
|
||||||
teamUtils.overwriteRequestTeams(
|
|
||||||
this.$apollo,
|
|
||||||
JSON.stringify(requestUpdated),
|
|
||||||
requestUpdated.name,
|
|
||||||
this.picked.requestID
|
|
||||||
)
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "team-collection",
|
|
||||||
requestID: this.picked.requestID,
|
|
||||||
})
|
|
||||||
} else if (this.picked.pickedType === "teams-folder") {
|
|
||||||
const req = await teamUtils.saveRequestAsTeams(
|
|
||||||
this.$apollo,
|
|
||||||
JSON.stringify(requestUpdated),
|
|
||||||
requestUpdated.name,
|
|
||||||
this.collectionsType.selectedTeam.id,
|
|
||||||
this.picked.folderID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (req && req.id) {
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "team-collection",
|
|
||||||
requestID: req.id,
|
|
||||||
teamID: this.collectionsType.selectedTeam.id,
|
|
||||||
collectionID: this.picked.folderID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (this.picked.pickedType === "teams-collection") {
|
|
||||||
const req = await teamUtils.saveRequestAsTeams(
|
|
||||||
this.$apollo,
|
|
||||||
JSON.stringify(requestUpdated),
|
|
||||||
requestUpdated.name,
|
|
||||||
this.collectionsType.selectedTeam.id,
|
|
||||||
this.picked.collectionID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (req && req.id) {
|
|
||||||
setRESTSaveContext({
|
|
||||||
originLocation: "team-collection",
|
|
||||||
requestID: req.id,
|
|
||||||
teamID: this.collectionsType.selectedTeam.id,
|
|
||||||
collectionID: this.picked.collectionID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (this.picked.pickedType === "gql-my-request") {
|
|
||||||
editGraphqlRequest(
|
|
||||||
this.picked.folderPath,
|
|
||||||
this.picked.requestIndex,
|
|
||||||
requestUpdated
|
|
||||||
)
|
|
||||||
} else if (this.picked.pickedType === "gql-my-folder") {
|
|
||||||
saveGraphqlRequestAs(this.picked.folderPath, requestUpdated)
|
|
||||||
} else if (this.picked.pickedType === "gql-my-collection") {
|
|
||||||
saveGraphqlRequestAs(`${this.picked.collectionIndex}`, requestUpdated)
|
|
||||||
}
|
|
||||||
this.$toast.success(this.$t("request.added"), {
|
|
||||||
icon: "post_add",
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hideModal()
|
|
||||||
},
|
|
||||||
hideModal() {
|
|
||||||
this.picked = null
|
|
||||||
this.$emit("hide-modal")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="opacity-0 show-if-initialized" :class="{ initialized }">
|
|
||||||
<pre ref="editor" :class="styles"></pre>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ace from "ace-builds"
|
|
||||||
import "ace-builds/webpack-resolver"
|
|
||||||
import "ace-builds/src-noconflict/ext-language_tools"
|
|
||||||
import "ace-builds/src-noconflict/mode-graphqlschema"
|
|
||||||
import * as gql from "graphql"
|
|
||||||
import { getAutocompleteSuggestions } from "graphql-language-service-interface"
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import { defineGQLLanguageMode } from "~/helpers/syntax/gqlQueryLangMode"
|
|
||||||
import debounce from "~/helpers/utils/debounce"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
onRunGQLQuery: {
|
|
||||||
type: Function,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
initialized: false,
|
|
||||||
editor: null,
|
|
||||||
cacheValue: "",
|
|
||||||
validationSchema: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
appFontSize() {
|
|
||||||
return getComputedStyle(document.documentElement).getPropertyValue(
|
|
||||||
"--body-font-size"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
value(value) {
|
|
||||||
if (value !== this.cacheValue) {
|
|
||||||
this.editor.session.setValue(value, 1)
|
|
||||||
this.cacheValue = value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme() {
|
|
||||||
this.initialized = false
|
|
||||||
this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.initialized = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
options(value) {
|
|
||||||
this.editor.setOptions(value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
defineGQLLanguageMode(ace)
|
|
||||||
|
|
||||||
const langTools = ace.require("ace/ext/language_tools")
|
|
||||||
|
|
||||||
const editor = ace.edit(this.$refs.editor, {
|
|
||||||
mode: `ace/mode/gql-query`,
|
|
||||||
enableBasicAutocompletion: true,
|
|
||||||
enableLiveAutocompletion: true,
|
|
||||||
...this.options,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set the theme and show the editor only after it's been set to prevent FOUC.
|
|
||||||
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.initialized = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set the theme and show the editor only after it's been set to prevent FOUC.
|
|
||||||
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.initialized = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.setFontSize(this.appFontSize)
|
|
||||||
|
|
||||||
const completer = {
|
|
||||||
getCompletions: (
|
|
||||||
editor,
|
|
||||||
_session,
|
|
||||||
{ row, column },
|
|
||||||
_prefix,
|
|
||||||
callback
|
|
||||||
) => {
|
|
||||||
if (this.validationSchema) {
|
|
||||||
const completions = getAutocompleteSuggestions(
|
|
||||||
this.validationSchema,
|
|
||||||
editor.getValue(),
|
|
||||||
{
|
|
||||||
line: row,
|
|
||||||
character: column,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
callback(
|
|
||||||
null,
|
|
||||||
completions.map(({ label, detail }) => ({
|
|
||||||
name: label,
|
|
||||||
value: label,
|
|
||||||
score: 1.0,
|
|
||||||
meta: detail,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
callback(null, [])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
langTools.setCompleters([completer])
|
|
||||||
|
|
||||||
if (this.value) editor.setValue(this.value, 1)
|
|
||||||
|
|
||||||
this.editor = editor
|
|
||||||
this.cacheValue = this.value
|
|
||||||
|
|
||||||
editor.commands.addCommand({
|
|
||||||
name: "runGQLQuery",
|
|
||||||
exec: () => this.onRunGQLQuery(this.editor.getValue()),
|
|
||||||
bindKey: {
|
|
||||||
mac: "cmd-enter",
|
|
||||||
win: "ctrl-enter",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.commands.addCommand({
|
|
||||||
name: "prettifyGQLQuery",
|
|
||||||
exec: () => this.prettifyQuery(),
|
|
||||||
bindKey: {
|
|
||||||
mac: "cmd-p",
|
|
||||||
win: "ctrl-p",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.on("change", () => {
|
|
||||||
const content = editor.getValue()
|
|
||||||
this.$emit("input", content)
|
|
||||||
this.parseContents(content)
|
|
||||||
this.cacheValue = content
|
|
||||||
})
|
|
||||||
|
|
||||||
this.parseContents(this.value)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.editor.destroy()
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
prettifyQuery() {
|
|
||||||
try {
|
|
||||||
this.$emit("update-query", gql.print(gql.parse(this.editor.getValue())))
|
|
||||||
} catch (e) {
|
|
||||||
this.$toast.error(this.$t("error.gql_prettify_invalid_query"), {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
defineTheme() {
|
|
||||||
if (this.theme) {
|
|
||||||
return this.theme
|
|
||||||
}
|
|
||||||
const strip = (str) =>
|
|
||||||
str.replace(/#/g, "").replace(/ /g, "").replace(/"/g, "")
|
|
||||||
return strip(
|
|
||||||
window
|
|
||||||
.getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue("--editor-theme")
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
setValidationSchema(schema) {
|
|
||||||
this.validationSchema = schema
|
|
||||||
this.parseContents(this.cacheValue)
|
|
||||||
},
|
|
||||||
|
|
||||||
parseContents: debounce(function (content) {
|
|
||||||
if (content !== "") {
|
|
||||||
try {
|
|
||||||
const doc = gql.parse(content)
|
|
||||||
|
|
||||||
if (this.validationSchema) {
|
|
||||||
this.editor.session.setAnnotations(
|
|
||||||
gql
|
|
||||||
.validate(this.validationSchema, doc)
|
|
||||||
.map(({ locations, message }) => ({
|
|
||||||
row: locations[0].line - 1,
|
|
||||||
column: locations[0].column - 1,
|
|
||||||
text: message,
|
|
||||||
type: "error",
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.editor.session.setAnnotations([
|
|
||||||
{
|
|
||||||
row: e.locations[0].line - 1,
|
|
||||||
column: e.locations[0].column - 1,
|
|
||||||
text: e.message,
|
|
||||||
type: "error",
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.editor.session.setAnnotations([])
|
|
||||||
}
|
|
||||||
}, 2000),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.show-if-initialized {
|
|
||||||
&.initialized {
|
|
||||||
@apply opacity-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
@apply transition-none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,555 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<SmartTabs styles="sticky bg-primary top-upperPrimaryStickyFold z-10">
|
|
||||||
<SmartTab :id="'query'" :label="$t('tab.query')" :selected="true">
|
|
||||||
<AppSection label="query">
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-upperSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
gqlRunQuery
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("request.query") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
:label="$t('request.run')"
|
|
||||||
svg="play"
|
|
||||||
class="rounded-none !text-accent"
|
|
||||||
@click.native="runQuery()"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyQueryIcon"
|
|
||||||
@click.native="copyQuery"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${$t(
|
|
||||||
'action.prettify'
|
|
||||||
)} <kbd>${getSpecialKey()}</kbd><kbd>P</kbd>`"
|
|
||||||
:svg="prettifyQueryIcon"
|
|
||||||
@click.native="prettifyQuery"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
ref="saveRequest"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('request.save')"
|
|
||||||
svg="folder-plus"
|
|
||||||
@click.native="saveRequest"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<GraphqlQueryEditor
|
|
||||||
ref="queryEditor"
|
|
||||||
v-model="gqlQueryString"
|
|
||||||
:on-run-g-q-l-query="runQuery"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
@update-query="updateQuery"
|
|
||||||
/>
|
|
||||||
</AppSection>
|
|
||||||
</SmartTab>
|
|
||||||
|
|
||||||
<SmartTab :id="'variables'" :label="$t('tab.variables')">
|
|
||||||
<AppSection label="variables">
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-upperSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("request.variables") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyVariablesIcon"
|
|
||||||
@click.native="copyVariables"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SmartAceEditor
|
|
||||||
ref="variableEditor"
|
|
||||||
v-model="variableString"
|
|
||||||
:lang="'json'"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
</AppSection>
|
|
||||||
</SmartTab>
|
|
||||||
|
|
||||||
<SmartTab :id="'headers'" :label="$t('tab.headers')">
|
|
||||||
<AppSection label="headers">
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-upperSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("tab.headers") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.clear_all')"
|
|
||||||
svg="trash-2"
|
|
||||||
:disabled="bulkMode"
|
|
||||||
@click.native="headers = []"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('state.bulk_mode')"
|
|
||||||
svg="edit"
|
|
||||||
:class="{ '!text-accent': bulkMode }"
|
|
||||||
@click.native="bulkMode = !bulkMode"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('add.new')"
|
|
||||||
svg="plus"
|
|
||||||
:disabled="bulkMode"
|
|
||||||
@click.native="addRequestHeader"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="bulkMode" class="flex">
|
|
||||||
<textarea-autosize
|
|
||||||
v-model="bulkHeaders"
|
|
||||||
v-focus
|
|
||||||
name="bulk-parameters"
|
|
||||||
class="
|
|
||||||
bg-transparent
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex
|
|
||||||
font-mono
|
|
||||||
flex-1
|
|
||||||
py-2
|
|
||||||
px-4
|
|
||||||
whitespace-pre
|
|
||||||
resize-y
|
|
||||||
overflow-auto
|
|
||||||
"
|
|
||||||
rows="10"
|
|
||||||
:placeholder="$t('state.bulk_mode_placeholder')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div
|
|
||||||
v-for="(header, index) in headers"
|
|
||||||
:key="`header-${index}`"
|
|
||||||
class="
|
|
||||||
divide-x divide-dividerLight
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<SmartAutoComplete
|
|
||||||
:placeholder="$t('count.header', { count: index + 1 })"
|
|
||||||
:source="commonHeaders"
|
|
||||||
:spellcheck="false"
|
|
||||||
:value="header.key"
|
|
||||||
autofocus
|
|
||||||
styles="
|
|
||||||
bg-transparent
|
|
||||||
flex
|
|
||||||
flex-1
|
|
||||||
py-1
|
|
||||||
px-4
|
|
||||||
truncate
|
|
||||||
focus:outline-none
|
|
||||||
"
|
|
||||||
@input="
|
|
||||||
updateGQLHeader(index, {
|
|
||||||
key: $event,
|
|
||||||
value: header.value,
|
|
||||||
active: header.active,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="bg-transparent flex flex-1 py-2 px-4"
|
|
||||||
:placeholder="$t('count.value', { count: index + 1 })"
|
|
||||||
:name="`value ${index}`"
|
|
||||||
:value="header.value"
|
|
||||||
autofocus
|
|
||||||
@change="
|
|
||||||
updateGQLHeader(index, {
|
|
||||||
key: header.key,
|
|
||||||
value: $event.target.value,
|
|
||||||
active: header.active,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="
|
|
||||||
header.hasOwnProperty('active')
|
|
||||||
? header.active
|
|
||||||
? $t('action.turn_off')
|
|
||||||
: $t('action.turn_on')
|
|
||||||
: $t('action.turn_off')
|
|
||||||
"
|
|
||||||
:svg="
|
|
||||||
header.hasOwnProperty('active')
|
|
||||||
? header.active
|
|
||||||
? 'check-circle'
|
|
||||||
: 'circle'
|
|
||||||
: 'check-circle'
|
|
||||||
"
|
|
||||||
color="green"
|
|
||||||
@click.native="
|
|
||||||
updateGQLHeader(index, {
|
|
||||||
key: header.key,
|
|
||||||
value: header.value,
|
|
||||||
active: !header.active,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.remove')"
|
|
||||||
svg="trash"
|
|
||||||
color="red"
|
|
||||||
@click.native="removeRequestHeader(index)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="headers.length === 0"
|
|
||||||
class="
|
|
||||||
flex flex-col
|
|
||||||
text-secondaryLight
|
|
||||||
p-4
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="text-center pb-4">
|
|
||||||
{{ $t("empty.headers") }}
|
|
||||||
</span>
|
|
||||||
<ButtonSecondary
|
|
||||||
:label="$t('add.new')"
|
|
||||||
filled
|
|
||||||
svg="plus"
|
|
||||||
@click.native="addRequestHeader"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AppSection>
|
|
||||||
</SmartTab>
|
|
||||||
</SmartTabs>
|
|
||||||
|
|
||||||
<CollectionsSaveRequest
|
|
||||||
mode="graphql"
|
|
||||||
:show="showSaveRequestModal"
|
|
||||||
@hide-modal="hideRequestModal"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
onMounted,
|
|
||||||
PropType,
|
|
||||||
ref,
|
|
||||||
useContext,
|
|
||||||
watch,
|
|
||||||
} from "@nuxtjs/composition-api"
|
|
||||||
import clone from "lodash/clone"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
import {
|
|
||||||
useNuxt,
|
|
||||||
useReadonlyStream,
|
|
||||||
useStream,
|
|
||||||
} from "~/helpers/utils/composables"
|
|
||||||
import {
|
|
||||||
addGQLHeader,
|
|
||||||
gqlHeaders$,
|
|
||||||
gqlQuery$,
|
|
||||||
gqlResponse$,
|
|
||||||
gqlURL$,
|
|
||||||
gqlVariables$,
|
|
||||||
removeGQLHeader,
|
|
||||||
setGQLHeaders,
|
|
||||||
setGQLQuery,
|
|
||||||
setGQLResponse,
|
|
||||||
setGQLVariables,
|
|
||||||
updateGQLHeader,
|
|
||||||
} from "~/newstore/GQLSession"
|
|
||||||
import { commonHeaders } from "~/helpers/headers"
|
|
||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
|
||||||
import { makeGQLHistoryEntry, addGraphqlHistoryEntry } from "~/newstore/history"
|
|
||||||
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
|
|
||||||
import { getCurrentStrategyID } from "~/helpers/network"
|
|
||||||
import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
conn: {
|
|
||||||
type: Object as PropType<GQLConnection>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const {
|
|
||||||
$toast,
|
|
||||||
app: { i18n },
|
|
||||||
} = useContext()
|
|
||||||
const t = i18n.t.bind(i18n)
|
|
||||||
const nuxt = useNuxt()
|
|
||||||
|
|
||||||
const bulkMode = ref(false)
|
|
||||||
const bulkHeaders = ref("")
|
|
||||||
|
|
||||||
watch(bulkHeaders, () => {
|
|
||||||
try {
|
|
||||||
const transformation = bulkHeaders.value.split("\n").map((item) => ({
|
|
||||||
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
|
|
||||||
value: item.substring(item.indexOf(":") + 1).trim(),
|
|
||||||
active: !item.trim().startsWith("//"),
|
|
||||||
}))
|
|
||||||
setGQLHeaders(transformation)
|
|
||||||
} catch (e) {
|
|
||||||
$toast.error(t("error.something_went_wrong").toString(), {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const url = useReadonlyStream(gqlURL$, "")
|
|
||||||
const gqlQueryString = useStream(gqlQuery$, "", setGQLQuery)
|
|
||||||
const variableString = useStream(gqlVariables$, "", setGQLVariables)
|
|
||||||
const headers = useStream(gqlHeaders$, [], setGQLHeaders)
|
|
||||||
|
|
||||||
const queryEditor = ref<any | null>(null)
|
|
||||||
|
|
||||||
const copyQueryIcon = ref("copy")
|
|
||||||
const prettifyQueryIcon = ref("align-left")
|
|
||||||
const copyVariablesIcon = ref("copy")
|
|
||||||
|
|
||||||
const showSaveRequestModal = ref(false)
|
|
||||||
|
|
||||||
const schema = useReadonlyStream(props.conn.schemaString$, "")
|
|
||||||
|
|
||||||
watch(
|
|
||||||
headers,
|
|
||||||
() => {
|
|
||||||
if (
|
|
||||||
(headers.value[headers.value.length - 1]?.key !== "" ||
|
|
||||||
headers.value[headers.value.length - 1]?.value !== "") &&
|
|
||||||
headers.value.length
|
|
||||||
)
|
|
||||||
addRequestHeader()
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (!headers.value?.length) {
|
|
||||||
addRequestHeader()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const copyQuery = () => {
|
|
||||||
copyToClipboard(gqlQueryString.value)
|
|
||||||
copyQueryIcon.value = "check"
|
|
||||||
setTimeout(() => (copyQueryIcon.value = "copy"), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = useStream(gqlResponse$, "", setGQLResponse)
|
|
||||||
|
|
||||||
const runQuery = async () => {
|
|
||||||
const startTime = Date.now()
|
|
||||||
|
|
||||||
nuxt.value.$loading.start()
|
|
||||||
response.value = t("state.loading").toString()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const runURL = clone(url.value)
|
|
||||||
const runHeaders = clone(headers.value)
|
|
||||||
const runQuery = clone(gqlQueryString.value)
|
|
||||||
const runVariables = clone(variableString.value)
|
|
||||||
|
|
||||||
const responseText = await props.conn.runQuery(
|
|
||||||
runURL,
|
|
||||||
runHeaders,
|
|
||||||
runQuery,
|
|
||||||
runVariables
|
|
||||||
)
|
|
||||||
const duration = Date.now() - startTime
|
|
||||||
|
|
||||||
nuxt.value.$loading.finish()
|
|
||||||
|
|
||||||
response.value = JSON.stringify(JSON.parse(responseText), null, 2)
|
|
||||||
|
|
||||||
addGraphqlHistoryEntry(
|
|
||||||
makeGQLHistoryEntry({
|
|
||||||
request: makeGQLRequest({
|
|
||||||
name: "",
|
|
||||||
url: runURL,
|
|
||||||
query: runQuery,
|
|
||||||
headers: runHeaders,
|
|
||||||
variables: runVariables,
|
|
||||||
}),
|
|
||||||
response: response.value,
|
|
||||||
star: false,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
$toast.success(t("state.finished_in", { duration }).toString(), {
|
|
||||||
icon: "done",
|
|
||||||
})
|
|
||||||
} catch (e: any) {
|
|
||||||
response.value = `${e}. ${t("error.check_console_details")}`
|
|
||||||
nuxt.value.$loading.finish()
|
|
||||||
|
|
||||||
$toast.error(`${e} ${t("error.f12_details").toString()}`, {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
logHoppRequestRunToAnalytics({
|
|
||||||
platform: "graphql-query",
|
|
||||||
strategy: getCurrentStrategyID(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const hideRequestModal = () => {
|
|
||||||
showSaveRequestModal.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const prettifyQuery = () => {
|
|
||||||
queryEditor.value.prettifyQuery()
|
|
||||||
prettifyQueryIcon.value = "check"
|
|
||||||
setTimeout(() => (prettifyQueryIcon.value = "align-left"), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveRequest = () => {
|
|
||||||
showSaveRequestModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why ?
|
|
||||||
const updateQuery = (updatedQuery: string) => {
|
|
||||||
gqlQueryString.value = updatedQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyVariables = () => {
|
|
||||||
copyToClipboard(variableString.value)
|
|
||||||
copyVariablesIcon.value = "check"
|
|
||||||
setTimeout(() => (copyVariablesIcon.value = "copy"), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addRequestHeader = () => {
|
|
||||||
addGQLHeader({
|
|
||||||
key: "",
|
|
||||||
value: "",
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeRequestHeader = (index: number) => {
|
|
||||||
removeGQLHeader(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
gqlQueryString,
|
|
||||||
variableString,
|
|
||||||
headers,
|
|
||||||
copyQueryIcon,
|
|
||||||
prettifyQueryIcon,
|
|
||||||
copyVariablesIcon,
|
|
||||||
|
|
||||||
queryEditor,
|
|
||||||
|
|
||||||
showSaveRequestModal,
|
|
||||||
hideRequestModal,
|
|
||||||
|
|
||||||
schema,
|
|
||||||
|
|
||||||
copyQuery,
|
|
||||||
runQuery,
|
|
||||||
prettifyQuery,
|
|
||||||
saveRequest,
|
|
||||||
updateQuery,
|
|
||||||
copyVariables,
|
|
||||||
addRequestHeader,
|
|
||||||
removeRequestHeader,
|
|
||||||
|
|
||||||
getSpecialKey: getPlatformSpecialKey,
|
|
||||||
|
|
||||||
commonHeaders,
|
|
||||||
updateGQLHeader,
|
|
||||||
bulkMode,
|
|
||||||
bulkHeaders,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
<template>
|
|
||||||
<AppSection ref="response" label="response">
|
|
||||||
<div
|
|
||||||
v-if="responseString"
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
pl-4
|
|
||||||
top-0
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("response.title") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
ref="downloadResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadResponseIcon"
|
|
||||||
@click.native="downloadResponse"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
ref="copyResponseButton"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyResponseIcon"
|
|
||||||
@click.native="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SmartAceEditor
|
|
||||||
v-if="responseString"
|
|
||||||
:value="responseString"
|
|
||||||
:lang="'json'"
|
|
||||||
:lint="false"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="
|
|
||||||
flex flex-col flex-1
|
|
||||||
text-secondaryLight
|
|
||||||
p-4
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="flex space-x-2 pb-4">
|
|
||||||
<div class="flex flex-col space-y-4 items-end">
|
|
||||||
<span class="flex flex-1 items-center">
|
|
||||||
{{ $t("shortcut.request.send_request") }}
|
|
||||||
</span>
|
|
||||||
<span class="flex flex-1 items-center">
|
|
||||||
{{ $t("shortcut.general.show_all") }}
|
|
||||||
</span>
|
|
||||||
<!-- <span class="flex flex-1 items-center">
|
|
||||||
{{ $t("shortcut.general.command_menu") }}
|
|
||||||
</span>
|
|
||||||
<span class="flex flex-1 items-center">
|
|
||||||
{{ $t("shortcut.general.help_menu") }}
|
|
||||||
</span> -->
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-4">
|
|
||||||
<div class="flex">
|
|
||||||
<span class="shortcut-key">{{ getSpecialKey() }}</span>
|
|
||||||
<span class="shortcut-key">G</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<span class="shortcut-key">{{ getSpecialKey() }}</span>
|
|
||||||
<span class="shortcut-key">K</span>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="flex">
|
|
||||||
<span class="shortcut-key">/</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<span class="shortcut-key">?</span>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ButtonSecondary
|
|
||||||
:label="$t('app.documentation')"
|
|
||||||
to="https://docs.hoppscotch.io"
|
|
||||||
svg="external-link"
|
|
||||||
blank
|
|
||||||
outline
|
|
||||||
reverse
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AppSection>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
PropType,
|
|
||||||
ref,
|
|
||||||
useContext,
|
|
||||||
} from "@nuxtjs/composition-api"
|
|
||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
|
||||||
import { gqlResponse$ } from "~/newstore/GQLSession"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
conn: {
|
|
||||||
type: Object as PropType<GQLConnection>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const {
|
|
||||||
$toast,
|
|
||||||
app: { i18n },
|
|
||||||
} = useContext()
|
|
||||||
const t = i18n.t.bind(i18n)
|
|
||||||
|
|
||||||
const responseString = useReadonlyStream(gqlResponse$, "")
|
|
||||||
|
|
||||||
const downloadResponseIcon = ref("download")
|
|
||||||
const copyResponseIcon = ref("copy")
|
|
||||||
|
|
||||||
const copyResponse = () => {
|
|
||||||
copyToClipboard(responseString.value!)
|
|
||||||
copyResponseIcon.value = "check"
|
|
||||||
setTimeout(() => (copyResponseIcon.value = "copy"), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadResponse = () => {
|
|
||||||
const dataToWrite = responseString.value
|
|
||||||
const file = new Blob([dataToWrite!], { type: "application/json" })
|
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
|
||||||
a.href = url
|
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
downloadResponseIcon.value = "check"
|
|
||||||
$toast.success(t("state.download_started").toString(), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
downloadResponseIcon.value = "download"
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
responseString,
|
|
||||||
|
|
||||||
downloadResponseIcon,
|
|
||||||
copyResponseIcon,
|
|
||||||
|
|
||||||
downloadResponse,
|
|
||||||
copyResponse,
|
|
||||||
|
|
||||||
getSpecialKey: getPlatformSpecialKey,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.shortcut-key {
|
|
||||||
@apply bg-dividerLight;
|
|
||||||
@apply rounded;
|
|
||||||
@apply ml-2;
|
|
||||||
@apply py-1;
|
|
||||||
@apply px-2;
|
|
||||||
@apply inline-flex;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
<template>
|
|
||||||
<aside>
|
|
||||||
<SmartTabs styles="sticky bg-primary z-10 top-0">
|
|
||||||
<SmartTab :id="'docs'" :label="`Docs`" :selected="true">
|
|
||||||
<AppSection label="docs">
|
|
||||||
<div class="bg-primary flex top-sidebarPrimaryStickyFold z-10 sticky">
|
|
||||||
<input
|
|
||||||
v-model="graphqlFieldsFilterText"
|
|
||||||
type="search"
|
|
||||||
autocomplete="off"
|
|
||||||
:placeholder="$t('action.search')"
|
|
||||||
class="bg-transparent flex w-full p-4 py-2"
|
|
||||||
/>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io/quickstart/graphql"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SmartTabs
|
|
||||||
ref="gqlTabs"
|
|
||||||
styles="border-t border-dividerLight bg-primary sticky z-10 top-sidebarSecondaryStickyFold"
|
|
||||||
>
|
|
||||||
<div class="gqlTabs">
|
|
||||||
<SmartTab
|
|
||||||
v-if="queryFields.length > 0"
|
|
||||||
:id="'queries'"
|
|
||||||
:label="$t('tab.queries')"
|
|
||||||
:selected="true"
|
|
||||||
class="divide-y divide-dividerLight"
|
|
||||||
>
|
|
||||||
<GraphqlField
|
|
||||||
v-for="(field, index) in filteredQueryFields"
|
|
||||||
:key="`field-${index}`"
|
|
||||||
:gql-field="field"
|
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
|
||||||
/>
|
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
v-if="mutationFields.length > 0"
|
|
||||||
:id="'mutations'"
|
|
||||||
:label="$t('graphql.mutations')"
|
|
||||||
class="divide-y divide-dividerLight"
|
|
||||||
>
|
|
||||||
<GraphqlField
|
|
||||||
v-for="(field, index) in filteredMutationFields"
|
|
||||||
:key="`field-${index}`"
|
|
||||||
:gql-field="field"
|
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
|
||||||
/>
|
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
v-if="subscriptionFields.length > 0"
|
|
||||||
:id="'subscriptions'"
|
|
||||||
:label="$t('graphql.subscriptions')"
|
|
||||||
class="divide-y divide-dividerLight"
|
|
||||||
>
|
|
||||||
<GraphqlField
|
|
||||||
v-for="(field, index) in filteredSubscriptionFields"
|
|
||||||
:key="`field-${index}`"
|
|
||||||
:gql-field="field"
|
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
class="p-4"
|
|
||||||
/>
|
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
v-if="graphqlTypes.length > 0"
|
|
||||||
:id="'types'"
|
|
||||||
ref="typesTab"
|
|
||||||
:label="$t('tab.types')"
|
|
||||||
class="divide-y divide-dividerLight"
|
|
||||||
>
|
|
||||||
<GraphqlType
|
|
||||||
v-for="(type, index) in filteredGraphqlTypes"
|
|
||||||
:key="`type-${index}`"
|
|
||||||
:gql-type="type"
|
|
||||||
:gql-types="graphqlTypes"
|
|
||||||
:is-highlighted="isGqlTypeHighlighted(type)"
|
|
||||||
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
|
||||||
:jump-type-callback="handleJumpToType"
|
|
||||||
/>
|
|
||||||
</SmartTab>
|
|
||||||
</div>
|
|
||||||
</SmartTabs>
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
queryFields.length === 0 &&
|
|
||||||
mutationFields.length === 0 &&
|
|
||||||
subscriptionFields.length === 0 &&
|
|
||||||
graphqlTypes.length === 0
|
|
||||||
"
|
|
||||||
class="
|
|
||||||
flex flex-col
|
|
||||||
text-secondaryLight
|
|
||||||
p-4
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="opacity-75 pb-2 material-icons">link</i>
|
|
||||||
<span class="text-center">
|
|
||||||
{{ $t("empty.schema") }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</AppSection>
|
|
||||||
</SmartTab>
|
|
||||||
|
|
||||||
<SmartTab :id="'history'" :label="$t('tab.history')">
|
|
||||||
<History
|
|
||||||
ref="graphqlHistoryComponent"
|
|
||||||
:page="'graphql'"
|
|
||||||
@useHistory="handleUseHistory"
|
|
||||||
/>
|
|
||||||
</SmartTab>
|
|
||||||
|
|
||||||
<SmartTab :id="'collections'" :label="$t('tab.collections')">
|
|
||||||
<CollectionsGraphql />
|
|
||||||
</SmartTab>
|
|
||||||
|
|
||||||
<SmartTab :id="'schema'" :label="`Schema`">
|
|
||||||
<AppSection ref="schema" label="schema">
|
|
||||||
<div
|
|
||||||
v-if="schemaString"
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
flex flex-1
|
|
||||||
top-sidebarPrimaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("graphql.schema") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io/quickstart/graphql"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
ref="downloadSchema"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadSchemaIcon"
|
|
||||||
@click.native="downloadSchema"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
ref="copySchemaCode"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copySchemaIcon"
|
|
||||||
@click.native="copySchema"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SmartAceEditor
|
|
||||||
v-if="schemaString"
|
|
||||||
v-model="schemaString"
|
|
||||||
:lang="'graphqlschema'"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="
|
|
||||||
flex flex-col
|
|
||||||
text-secondaryLight
|
|
||||||
p-4
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="opacity-75 pb-2 material-icons">link</i>
|
|
||||||
<span class="text-center">
|
|
||||||
{{ $t("empty.schema") }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</AppSection>
|
|
||||||
</SmartTab>
|
|
||||||
</SmartTabs>
|
|
||||||
</aside>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
nextTick,
|
|
||||||
PropType,
|
|
||||||
ref,
|
|
||||||
useContext,
|
|
||||||
} from "@nuxtjs/composition-api"
|
|
||||||
import { GraphQLField, GraphQLType } from "graphql"
|
|
||||||
import { map } from "rxjs/operators"
|
|
||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
|
||||||
import { GQLHeader } from "~/helpers/types/HoppGQLRequest"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
|
||||||
import {
|
|
||||||
setGQLHeaders,
|
|
||||||
setGQLQuery,
|
|
||||||
setGQLResponse,
|
|
||||||
setGQLURL,
|
|
||||||
setGQLVariables,
|
|
||||||
} from "~/newstore/GQLSession"
|
|
||||||
|
|
||||||
function isTextFoundInGraphqlFieldObject(
|
|
||||||
text: string,
|
|
||||||
field: GraphQLField<any, any>
|
|
||||||
) {
|
|
||||||
const normalizedText = text.toLowerCase()
|
|
||||||
|
|
||||||
const isFilterTextFoundInDescription = field.description
|
|
||||||
? field.description.toLowerCase().includes(normalizedText)
|
|
||||||
: false
|
|
||||||
const isFilterTextFoundInName = field.name
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(normalizedText)
|
|
||||||
|
|
||||||
return isFilterTextFoundInDescription || isFilterTextFoundInName
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilteredGraphqlFields(
|
|
||||||
filterText: string,
|
|
||||||
fields: GraphQLField<any, any>[]
|
|
||||||
) {
|
|
||||||
if (!filterText) return fields
|
|
||||||
|
|
||||||
return fields.filter((field) =>
|
|
||||||
isTextFoundInGraphqlFieldObject(filterText, field)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilteredGraphqlTypes(filterText: string, types: GraphQLType[]) {
|
|
||||||
if (!filterText) return types
|
|
||||||
|
|
||||||
return types.filter((type) => {
|
|
||||||
const isFilterTextMatching = isTextFoundInGraphqlFieldObject(
|
|
||||||
filterText,
|
|
||||||
type as any
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isFilterTextMatching) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFilterTextMatchingAtLeastOneField = Object.values(
|
|
||||||
(type as any)._fields || {}
|
|
||||||
).some((field) => isTextFoundInGraphqlFieldObject(filterText, field as any))
|
|
||||||
|
|
||||||
return isFilterTextMatchingAtLeastOneField
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveRootType(type: GraphQLType) {
|
|
||||||
let t: any = type
|
|
||||||
while (t.ofType) t = t.ofType
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
type GQLHistoryEntry = {
|
|
||||||
url: string
|
|
||||||
headers: GQLHeader[]
|
|
||||||
query: string
|
|
||||||
response: string
|
|
||||||
variables: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
conn: {
|
|
||||||
type: Object as PropType<GQLConnection>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const {
|
|
||||||
$toast,
|
|
||||||
app: { i18n },
|
|
||||||
} = useContext()
|
|
||||||
const t = i18n.t.bind(i18n)
|
|
||||||
|
|
||||||
const queryFields = useReadonlyStream(
|
|
||||||
props.conn.queryFields$.pipe(map((x) => x ?? [])),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const mutationFields = useReadonlyStream(
|
|
||||||
props.conn.mutationFields$.pipe(map((x) => x ?? [])),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const subscriptionFields = useReadonlyStream(
|
|
||||||
props.conn.subscriptionFields$.pipe(map((x) => x ?? [])),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const graphqlTypes = useReadonlyStream(
|
|
||||||
props.conn.graphqlTypes$.pipe(map((x) => x ?? [])),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const downloadSchemaIcon = ref("download")
|
|
||||||
const copySchemaIcon = ref("copy")
|
|
||||||
|
|
||||||
const graphqlFieldsFilterText = ref("")
|
|
||||||
|
|
||||||
const gqlTabs = ref<any | null>(null)
|
|
||||||
const typesTab = ref<any | null>(null)
|
|
||||||
|
|
||||||
const filteredQueryFields = computed(() => {
|
|
||||||
return getFilteredGraphqlFields(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
queryFields.value as any
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredMutationFields = computed(() => {
|
|
||||||
return getFilteredGraphqlFields(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
mutationFields.value as any
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredSubscriptionFields = computed(() => {
|
|
||||||
return getFilteredGraphqlFields(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
subscriptionFields.value as any
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredGraphqlTypes = computed(() => {
|
|
||||||
return getFilteredGraphqlTypes(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
graphqlTypes.value as any
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isGqlTypeHighlighted = (gqlType: GraphQLType) => {
|
|
||||||
if (!graphqlFieldsFilterText.value) return false
|
|
||||||
|
|
||||||
return isTextFoundInGraphqlFieldObject(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
gqlType as any
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getGqlTypeHighlightedFields = (gqlType: GraphQLType) => {
|
|
||||||
if (!graphqlFieldsFilterText.value) return []
|
|
||||||
|
|
||||||
const fields = Object.values((gqlType as any)._fields || {})
|
|
||||||
if (!fields || fields.length === 0) return []
|
|
||||||
|
|
||||||
return fields.filter((field) =>
|
|
||||||
isTextFoundInGraphqlFieldObject(
|
|
||||||
graphqlFieldsFilterText.value,
|
|
||||||
field as any
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleJumpToType = async (type: GraphQLType) => {
|
|
||||||
gqlTabs.value.selectTab(typesTab.value)
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
const rootTypeName = resolveRootType(type).name
|
|
||||||
|
|
||||||
const target = document.getElementById(`type_${rootTypeName}`)
|
|
||||||
if (target) {
|
|
||||||
gqlTabs.value.$el
|
|
||||||
.querySelector(".gqlTabs")
|
|
||||||
.scrollTo({ top: target.offsetTop, behavior: "smooth" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const schemaString = useReadonlyStream(
|
|
||||||
props.conn.schemaString$.pipe(map((x) => x ?? "")),
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
const downloadSchema = () => {
|
|
||||||
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
|
||||||
a.href = url
|
|
||||||
a.download = `${
|
|
||||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
|
||||||
}.graphql`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
downloadSchemaIcon.value = "check"
|
|
||||||
$toast.success(t("state.download_started").toString(), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
downloadSchemaIcon.value = "download"
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const copySchema = () => {
|
|
||||||
if (!schemaString.value) return
|
|
||||||
|
|
||||||
copyToClipboard(schemaString.value)
|
|
||||||
copySchemaIcon.value = "check"
|
|
||||||
setTimeout(() => (copySchemaIcon.value = "copy"), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUseHistory = (entry: GQLHistoryEntry) => {
|
|
||||||
const url = entry.url
|
|
||||||
const headers = entry.headers
|
|
||||||
const gqlQueryString = entry.query
|
|
||||||
const variableString = entry.variables
|
|
||||||
const responseText = entry.response
|
|
||||||
|
|
||||||
setGQLURL(url)
|
|
||||||
setGQLHeaders(headers)
|
|
||||||
setGQLQuery(gqlQueryString)
|
|
||||||
setGQLVariables(variableString)
|
|
||||||
setGQLResponse(responseText)
|
|
||||||
props.conn.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
queryFields,
|
|
||||||
mutationFields,
|
|
||||||
subscriptionFields,
|
|
||||||
graphqlTypes,
|
|
||||||
schemaString,
|
|
||||||
|
|
||||||
graphqlFieldsFilterText,
|
|
||||||
|
|
||||||
filteredQueryFields,
|
|
||||||
filteredMutationFields,
|
|
||||||
filteredSubscriptionFields,
|
|
||||||
filteredGraphqlTypes,
|
|
||||||
|
|
||||||
isGqlTypeHighlighted,
|
|
||||||
getGqlTypeHighlightedFields,
|
|
||||||
|
|
||||||
gqlTabs,
|
|
||||||
typesTab,
|
|
||||||
handleJumpToType,
|
|
||||||
|
|
||||||
downloadSchema,
|
|
||||||
downloadSchemaIcon,
|
|
||||||
copySchemaIcon,
|
|
||||||
copySchema,
|
|
||||||
handleUseHistory,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SmartModal v-if="show" :title="$t('import.curl')" @close="hideModal">
|
|
||||||
<template #body>
|
|
||||||
<div class="flex flex-col px-2">
|
|
||||||
<textarea-autosize
|
|
||||||
id="import-curl"
|
|
||||||
v-model="curl"
|
|
||||||
class="font-mono textarea floating-input"
|
|
||||||
autofocus
|
|
||||||
rows="8"
|
|
||||||
placeholder=" "
|
|
||||||
/>
|
|
||||||
<label for="import-curl">
|
|
||||||
{{ $t("request.enter_curl") }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<span>
|
|
||||||
<ButtonPrimary
|
|
||||||
:label="$t('import.title')"
|
|
||||||
@click.native="handleImport"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
:label="$t('action.cancel')"
|
|
||||||
@click.native="hideModal"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</SmartModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import parseCurlCommand from "~/helpers/curlparser"
|
|
||||||
import {
|
|
||||||
HoppRESTHeader,
|
|
||||||
HoppRESTParam,
|
|
||||||
makeRESTRequest,
|
|
||||||
} from "~/helpers/types/HoppRESTRequest"
|
|
||||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
show: Boolean,
|
|
||||||
},
|
|
||||||
emits: ["hide-modal"],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
curl: "",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hideModal() {
|
|
||||||
this.$emit("hide-modal")
|
|
||||||
},
|
|
||||||
handleImport() {
|
|
||||||
const text = this.curl
|
|
||||||
try {
|
|
||||||
const parsedCurl = parseCurlCommand(text)
|
|
||||||
const { origin, pathname } = new URL(
|
|
||||||
parsedCurl.url.replace(/"/g, "").replace(/'/g, "")
|
|
||||||
)
|
|
||||||
const endpoint = origin + pathname
|
|
||||||
const headers: HoppRESTHeader[] = []
|
|
||||||
const params: HoppRESTParam[] = []
|
|
||||||
if (parsedCurl.query) {
|
|
||||||
for (const key of Object.keys(parsedCurl.query)) {
|
|
||||||
const val = parsedCurl.query[key]!
|
|
||||||
|
|
||||||
if (Array.isArray(val)) {
|
|
||||||
val.forEach((value) => {
|
|
||||||
params.push({
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
params.push({
|
|
||||||
key,
|
|
||||||
value: val!,
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parsedCurl.headers) {
|
|
||||||
for (const key of Object.keys(parsedCurl.headers)) {
|
|
||||||
headers.push({
|
|
||||||
key,
|
|
||||||
value: parsedCurl.headers[key],
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const method = parsedCurl.method.toUpperCase()
|
|
||||||
// let rawInput = false
|
|
||||||
// let rawParams: any | null = null
|
|
||||||
|
|
||||||
// if (parsedCurl.data) {
|
|
||||||
// rawInput = true
|
|
||||||
// rawParams = parsedCurl.data
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.showCurlImportModal = false
|
|
||||||
|
|
||||||
setRESTRequest(
|
|
||||||
makeRESTRequest({
|
|
||||||
name: "Untitled request",
|
|
||||||
endpoint,
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
headers,
|
|
||||||
preRequestScript: "",
|
|
||||||
testScript: "",
|
|
||||||
auth: {
|
|
||||||
authType: "none",
|
|
||||||
authActive: true,
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
contentType: "application/json",
|
|
||||||
body: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
this.$toast.error(this.$t("error.curl_invalid_format").toString(), {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.hideModal()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-upperTertiaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("request.raw_body") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
to="https://docs.hoppscotch.io/features/body"
|
|
||||||
blank
|
|
||||||
:title="$t('app.wiki')"
|
|
||||||
svg="help-circle"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.clear')"
|
|
||||||
svg="trash-2"
|
|
||||||
@click.native="clearContent('rawParams', $event)"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="contentType && contentType.endsWith('json')"
|
|
||||||
ref="prettifyRequest"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.prettify')"
|
|
||||||
:svg="prettifyIcon"
|
|
||||||
@click.native="prettifyRequestBody"
|
|
||||||
/>
|
|
||||||
<label for="payload">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('import.json')"
|
|
||||||
svg="file-plus"
|
|
||||||
@click.native="$refs.payload.click()"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
ref="payload"
|
|
||||||
class="input"
|
|
||||||
name="payload"
|
|
||||||
type="file"
|
|
||||||
@change="uploadPayload"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<SmartAceEditor
|
|
||||||
v-model="rawParamsBody"
|
|
||||||
:lang="rawInputEditorLang"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import { getEditorLangForMimeType } from "~/helpers/editorutils"
|
|
||||||
import { pluckRef } from "~/helpers/utils/composables"
|
|
||||||
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
contentType: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
rawParamsBody: pluckRef(useRESTRequestBody(), "body"),
|
|
||||||
prettifyIcon: "align-left",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
rawInputEditorLang() {
|
|
||||||
return getEditorLangForMimeType(this.contentType)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clearContent() {
|
|
||||||
this.rawParamsBody = ""
|
|
||||||
},
|
|
||||||
uploadPayload() {
|
|
||||||
const file = this.$refs.payload.files[0]
|
|
||||||
if (file !== undefined && file !== null) {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.onload = ({ target }) => {
|
|
||||||
this.rawParamsBody = target.result
|
|
||||||
}
|
|
||||||
reader.readAsText(file)
|
|
||||||
this.$toast.success(this.$t("state.file_imported"), {
|
|
||||||
icon: "attach_file",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.$toast.error(this.$t("action.choose_file"), {
|
|
||||||
icon: "attach_file",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.$refs.payload.value = ""
|
|
||||||
},
|
|
||||||
prettifyRequestBody() {
|
|
||||||
try {
|
|
||||||
const jsonObj = JSON.parse(this.rawParamsBody)
|
|
||||||
this.rawParamsBody = JSON.stringify(jsonObj, null, 2)
|
|
||||||
this.prettifyIcon = "check"
|
|
||||||
setTimeout(() => (this.prettifyIcon = "align-left"), 1000)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
this.$toast.error(`${this.$t("error.json_prettify_invalid_body")}`, {
|
|
||||||
icon: "error_outline",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primaryLight
|
|
||||||
rounded
|
|
||||||
flex
|
|
||||||
grid
|
|
||||||
p-4
|
|
||||||
gap-4
|
|
||||||
grid-cols-1
|
|
||||||
md:grid-cols-2
|
|
||||||
lg:grid-cols-3
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(cta, index) in ctas"
|
|
||||||
:key="`cta-${index}`"
|
|
||||||
class="flex-col p-8 inline-flex"
|
|
||||||
>
|
|
||||||
<i class="text-accent text-3xl material-icons">{{ cta.icon }}</i>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h2 class="mt-4 text-lg text-secondaryDark mb-2 transition">
|
|
||||||
{{ cta.title }}
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
{{ cta.description }}
|
|
||||||
</p>
|
|
||||||
<p class="mt-2">
|
|
||||||
<SmartLink :to="cta.link.target" class="link" blank>
|
|
||||||
{{ cta.link.title }}
|
|
||||||
<SmartIcon name="chevron-right" class="svg-icons" />
|
|
||||||
</SmartLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ctas: [
|
|
||||||
{
|
|
||||||
icon: "layers",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Get up and running with Kooli in as little as 10 minutes.",
|
|
||||||
link: {
|
|
||||||
title: "Feature",
|
|
||||||
target: "https://docs.hoppscotch.io/api",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "local_library",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Explore and start integrating Kooli's products and tools.",
|
|
||||||
link: {
|
|
||||||
title: "Feature",
|
|
||||||
target: "https://docs.hoppscotch.io/guides",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "local_library",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Explore and start integrating Kooli's products and tools.",
|
|
||||||
link: {
|
|
||||||
title: "Feature",
|
|
||||||
target: "https://docs.hoppscotch.io/guides",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col p-4">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="my-4 text-center text-accent tracking-widest">FEATURES</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<div
|
|
||||||
v-for="(feature, index) in features"
|
|
||||||
:key="`feature-${index}`"
|
|
||||||
class="flex-col p-8 inline-flex"
|
|
||||||
>
|
|
||||||
<i class="text-accent text-4xl material-icons">{{ feature.icon }}</i>
|
|
||||||
<div class="flex-grow">
|
|
||||||
<h2 class="mt-4 text-lg text-secondaryDark mb-2 transition">
|
|
||||||
{{ feature.title }}
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
{{ feature.description }}
|
|
||||||
</p>
|
|
||||||
<p class="mt-2">
|
|
||||||
<NuxtLink :to="feature.link.target" class="link">
|
|
||||||
{{ feature.link.title }}
|
|
||||||
<SmartIcon name="chevron-right" class="svg-icons" />
|
|
||||||
</NuxtLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
icon: "offline_bolt",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "stars",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "supervised_user_circle",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "build_circle",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "monetization_on",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "group_work",
|
|
||||||
title: "Feature",
|
|
||||||
description:
|
|
||||||
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
|
|
||||||
link: { title: "Learn more", target: "/settings" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
<template>
|
|
||||||
<footer class="flex flex-col p-6">
|
|
||||||
<nav class="grid gap-4 grid-cols-2 md:grid-cols-4">
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<h4 class="my-2">Hoppscotch</h4>
|
|
||||||
<ul class="space-y-4">
|
|
||||||
<li>
|
|
||||||
<SmartChangeLanguage />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<SmartColorModePicker />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<h4 class="my-2">Solutions</h4>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in navigation.solutions"
|
|
||||||
:key="`item-${index}`"
|
|
||||||
>
|
|
||||||
<SmartAnchor
|
|
||||||
:label="item.name"
|
|
||||||
:to="item.link"
|
|
||||||
class="footer-nav"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<h4 class="my-2">Platform</h4>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in navigation.platform"
|
|
||||||
:key="`item-${index}`"
|
|
||||||
>
|
|
||||||
<SmartAnchor
|
|
||||||
:label="item.name"
|
|
||||||
:to="item.link"
|
|
||||||
class="footer-nav"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<h4 class="my-2">Company</h4>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in navigation.company"
|
|
||||||
:key="`item-${index}`"
|
|
||||||
>
|
|
||||||
<SmartAnchor
|
|
||||||
:label="item.name"
|
|
||||||
:to="item.link"
|
|
||||||
class="footer-nav"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</footer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
navigation: {
|
|
||||||
solutions: [
|
|
||||||
{
|
|
||||||
name: "RESTful",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "WebSocket",
|
|
||||||
link: "/realtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SSE",
|
|
||||||
link: "/realtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Socket.IO",
|
|
||||||
link: "/realtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MQTT",
|
|
||||||
link: "/realtime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GraphQL",
|
|
||||||
link: "/graphql",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
platform: [
|
|
||||||
{
|
|
||||||
name: "API Designing",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "API Development",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "API Testing",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "API Deployment",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "API Documentation",
|
|
||||||
link: "/documentation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Integrations",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
company: [
|
|
||||||
{
|
|
||||||
name: "About",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Careers",
|
|
||||||
link: "/careers",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Support",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Contact",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Blog",
|
|
||||||
link: "https://blog.hoppscotch.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Community",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Open Source",
|
|
||||||
link: "https://github.com/hoppscotch",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.footer-nav {
|
|
||||||
@apply px-2 py-1;
|
|
||||||
@apply -mx-2 -my-1;
|
|
||||||
@apply hover:text-secondaryDark;
|
|
||||||
@apply focus-visible:text-secondaryDark;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col p-6 relative">
|
|
||||||
<div class="flex flex-col mt-16 items-center justify-center">
|
|
||||||
<h2
|
|
||||||
class="
|
|
||||||
font-bold
|
|
||||||
text-accent text-center
|
|
||||||
leading-none
|
|
||||||
tracking-tighter
|
|
||||||
text-4xl
|
|
||||||
md:text-6xl
|
|
||||||
lg:text-8xl
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Open Source
|
|
||||||
</h2>
|
|
||||||
<h3
|
|
||||||
class="
|
|
||||||
font-extrabold
|
|
||||||
my-4
|
|
||||||
text-center text-secondaryDark
|
|
||||||
leading-none
|
|
||||||
tracking-tighter
|
|
||||||
text-3xl
|
|
||||||
md:text-4xl
|
|
||||||
lg:text-5xl
|
|
||||||
"
|
|
||||||
>
|
|
||||||
API Development Ecosystem
|
|
||||||
</h3>
|
|
||||||
<p class="my-4 text-lg text-center max-w-2xl">
|
|
||||||
Thousands of developers and companies build, ship, and maintain their
|
|
||||||
APIs on Hoppscotch — the transparent and most flexible API development
|
|
||||||
ecosystem in the world.
|
|
||||||
</p>
|
|
||||||
<div class="flex space-x-4 my-8 justify-center items-center">
|
|
||||||
<ButtonPrimary
|
|
||||||
label="Get Started"
|
|
||||||
svg="arrow-right"
|
|
||||||
reverse
|
|
||||||
large
|
|
||||||
@click.native="showLogin = true"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
to="https://github.com/hoppscotch/hoppscotch"
|
|
||||||
blank
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
label="GitHub"
|
|
||||||
svg="github"
|
|
||||||
large
|
|
||||||
:shortcut="['30k Stars']"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<LandingStats />
|
|
||||||
<LandingScreenshot />
|
|
||||||
</div>
|
|
||||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showLogin: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-gradient-to-r
|
|
||||||
from-gradientFrom
|
|
||||||
via-gradientVia
|
|
||||||
to-gradientTo
|
|
||||||
flex flex-col
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://tiny.cc/hoppscotch_screenshot_1"
|
|
||||||
alt="Screenshot"
|
|
||||||
class="rounded-lg ring-dividerLight mt-8 max-w-5/6 ring-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex space-x-16 p-6">
|
|
||||||
<div v-for="(stat, index) in stats" :key="`stat-${index}`">
|
|
||||||
<span class="text-xl">
|
|
||||||
{{ stat.count }}<span class="text-secondaryLight">+</span>
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span class="text-sm">
|
|
||||||
{{ stat.audience }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
stats: [
|
|
||||||
{ count: "350k", audience: "Developers" },
|
|
||||||
{ count: "5k", audience: "Organizations" },
|
|
||||||
{ count: "1m", audience: "Requests" },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="bg-primaryLight rounded flex flex-col mx-6 p-4">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<p class="my-4 text-center tracking-widest">EMPOWERING DEVELOPERS FROM</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-4 grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
|
|
||||||
<div
|
|
||||||
v-for="(user, index) in users"
|
|
||||||
:key="`user-${index}`"
|
|
||||||
class="flex-col px-4 inline-flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="`/images/users/${user.image}`"
|
|
||||||
alt="Profile picture"
|
|
||||||
loading="lazy"
|
|
||||||
class="flex-col object-contain object-center h-24 w-24 inline-flex"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
users: [
|
|
||||||
{ title: "Accenture", image: "accenture.svg" },
|
|
||||||
{ title: "ByteDance", image: "bytedance.svg" },
|
|
||||||
{ title: "Decathlon", image: "decathlon.svg" },
|
|
||||||
{ title: "GitHub", image: "github.svg" },
|
|
||||||
{ title: "Gojek", image: "gojek.svg" },
|
|
||||||
{ title: "Google", image: "google.svg" },
|
|
||||||
{ title: "Microsoft", image: "microsoft.svg" },
|
|
||||||
{ title: "PayTM", image: "paytm.svg" },
|
|
||||||
{ title: "Twilio", image: "twilio.svg" },
|
|
||||||
{ title: "Udemy", image: "udemy.svg" },
|
|
||||||
{ title: "Zendesk", image: "zendesk.svg" },
|
|
||||||
{ title: "Zoho", image: "zoho.svg" },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-lowerSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("response.body") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="
|
|
||||||
previewEnabled ? $t('hide.preview') : $t('response.preview_html')
|
|
||||||
"
|
|
||||||
:svg="!previewEnabled ? 'eye' : 'eye-off'"
|
|
||||||
@click.native.prevent="togglePreview"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="downloadResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadIcon"
|
|
||||||
@click.native="downloadResponse"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="copyResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyIcon"
|
|
||||||
@click.native="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<SmartAceEditor
|
|
||||||
:value="responseBodyText"
|
|
||||||
:lang="'html'"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
<iframe
|
|
||||||
ref="previewFrame"
|
|
||||||
:class="{ hidden: !previewEnabled }"
|
|
||||||
class="covers-response"
|
|
||||||
src="about:blank"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [TextContentRendererMixin],
|
|
||||||
props: {
|
|
||||||
response: { type: Object, default: () => {} },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downloadIcon: "download",
|
|
||||||
copyIcon: "copy",
|
|
||||||
previewEnabled: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
downloadResponse() {
|
|
||||||
const dataToWrite = this.responseBodyText
|
|
||||||
const file = new Blob([dataToWrite], { type: "text/html" })
|
|
||||||
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]}`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
this.downloadIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.download_started"), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
this.downloadIcon = "download"
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
copyResponse() {
|
|
||||||
copyToClipboard(this.responseBodyText)
|
|
||||||
this.copyIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.copied_to_clipboard"), {
|
|
||||||
icon: "content_paste",
|
|
||||||
})
|
|
||||||
setTimeout(() => (this.copyIcon = "copy"), 1000)
|
|
||||||
},
|
|
||||||
togglePreview() {
|
|
||||||
this.previewEnabled = !this.previewEnabled
|
|
||||||
if (this.previewEnabled) {
|
|
||||||
if (
|
|
||||||
this.$refs.previewFrame.getAttribute("data-previewing-url") ===
|
|
||||||
this.url
|
|
||||||
)
|
|
||||||
return
|
|
||||||
// Use DOMParser to parse document HTML.
|
|
||||||
const previewDocument = new DOMParser().parseFromString(
|
|
||||||
this.responseBodyText,
|
|
||||||
"text/html"
|
|
||||||
)
|
|
||||||
// Inject <base href="..."> tag to head, to fix relative CSS/HTML paths.
|
|
||||||
previewDocument.head.innerHTML =
|
|
||||||
`<base href="${this.url}">` + previewDocument.head.innerHTML
|
|
||||||
// Finally, set the iframe source to the resulting HTML.
|
|
||||||
this.$refs.previewFrame.srcdoc =
|
|
||||||
previewDocument.documentElement.outerHTML
|
|
||||||
this.$refs.previewFrame.setAttribute("data-previewing-url", this.url)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.covers-response {
|
|
||||||
@apply absolute;
|
|
||||||
@apply inset-0;
|
|
||||||
@apply bg-white;
|
|
||||||
@apply h-full;
|
|
||||||
@apply w-full;
|
|
||||||
@apply border;
|
|
||||||
@apply border-dividerLight;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-lowerSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("response.body") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="downloadResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadIcon"
|
|
||||||
@click.native="downloadResponse"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="copyResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyIcon"
|
|
||||||
@click.native="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<SmartAceEditor
|
|
||||||
:value="jsonBodyText"
|
|
||||||
:lang="'json'"
|
|
||||||
:provide-outline="true"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [TextContentRendererMixin],
|
|
||||||
props: {
|
|
||||||
response: { type: Object, default: () => {} },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downloadIcon: "download",
|
|
||||||
copyIcon: "copy",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
jsonBodyText() {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(this.responseBodyText), null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
// Most probs invalid JSON was returned, so drop prettification (should we warn ?)
|
|
||||||
return this.responseBodyText
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responseType() {
|
|
||||||
return (
|
|
||||||
this.response.headers.find(
|
|
||||||
(h) => h.key.toLowerCase() === "content-type"
|
|
||||||
).value || ""
|
|
||||||
)
|
|
||||||
.split(";")[0]
|
|
||||||
.toLowerCase()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
downloadResponse() {
|
|
||||||
const dataToWrite = this.responseBodyText
|
|
||||||
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]}`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
this.downloadIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.download_started"), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
this.downloadIcon = "download"
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
copyResponse() {
|
|
||||||
copyToClipboard(this.responseBodyText)
|
|
||||||
this.copyIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.copied_to_clipboard"), {
|
|
||||||
icon: "content_paste",
|
|
||||||
})
|
|
||||||
setTimeout(() => (this.copyIcon = "copy"), 1000)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-lowerSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("response.body") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="downloadResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadIcon"
|
|
||||||
@click.native="downloadResponse"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="copyResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyIcon"
|
|
||||||
@click.native="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<SmartAceEditor
|
|
||||||
:value="responseBodyText"
|
|
||||||
:lang="'plain_text'"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [TextContentRendererMixin],
|
|
||||||
props: {
|
|
||||||
response: { type: Object, default: () => {} },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downloadIcon: "download",
|
|
||||||
copyIcon: "copy",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
responseType() {
|
|
||||||
return (
|
|
||||||
this.response.headers.find(
|
|
||||||
(h) => h.key.toLowerCase() === "content-type"
|
|
||||||
).value || ""
|
|
||||||
)
|
|
||||||
.split(";")[0]
|
|
||||||
.toLowerCase()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
downloadResponse() {
|
|
||||||
const dataToWrite = this.responseBodyText
|
|
||||||
const file = new Blob([dataToWrite], { type: this.responseType })
|
|
||||||
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]}`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
this.downloadIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.download_started"), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
this.downloadIcon = "download"
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
copyResponse() {
|
|
||||||
copyToClipboard(this.responseBodyText)
|
|
||||||
this.copyIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.copied_to_clipboard"), {
|
|
||||||
icon: "content_paste",
|
|
||||||
})
|
|
||||||
setTimeout(() => (this.copyIcon = "copy"), 1000)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
bg-primary
|
|
||||||
border-b border-dividerLight
|
|
||||||
flex flex-1
|
|
||||||
top-lowerSecondaryStickyFold
|
|
||||||
pl-4
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
items-center
|
|
||||||
justify-between
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<label class="font-semibold text-secondaryLight">
|
|
||||||
{{ $t("response.body") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="downloadResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.download_file')"
|
|
||||||
:svg="downloadIcon"
|
|
||||||
@click.native="downloadResponse"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
|
||||||
v-if="response.body"
|
|
||||||
ref="copyResponse"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t('action.copy')"
|
|
||||||
:svg="copyIcon"
|
|
||||||
@click.native="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<SmartAceEditor
|
|
||||||
:value="responseBodyText"
|
|
||||||
:lang="'xml'"
|
|
||||||
:options="{
|
|
||||||
maxLines: Infinity,
|
|
||||||
minLines: 16,
|
|
||||||
autoScrollEditorIntoView: true,
|
|
||||||
readOnly: true,
|
|
||||||
showPrintMargin: false,
|
|
||||||
useWorker: false,
|
|
||||||
}"
|
|
||||||
styles="border-b border-dividerLight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
|
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [TextContentRendererMixin],
|
|
||||||
props: {
|
|
||||||
response: { type: Object, default: () => {} },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
copyIcon: "copy",
|
|
||||||
downloadIcon: "download",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
responseType() {
|
|
||||||
return (
|
|
||||||
this.response.headers.find(
|
|
||||||
(h) => h.key.toLowerCase() === "content-type"
|
|
||||||
).value || ""
|
|
||||||
)
|
|
||||||
.split(";")[0]
|
|
||||||
.toLowerCase()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
downloadResponse() {
|
|
||||||
const dataToWrite = this.responseBodyText
|
|
||||||
const file = new Blob([dataToWrite], { type: this.responseType })
|
|
||||||
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]}`
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
this.downloadIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.download_started"), {
|
|
||||||
icon: "downloading",
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
this.downloadIcon = "download"
|
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
copyResponse() {
|
|
||||||
copyToClipboard(this.responseBodyText)
|
|
||||||
this.copyIcon = "check"
|
|
||||||
this.$toast.success(this.$t("state.copied_to_clipboard"), {
|
|
||||||
icon: "content_paste",
|
|
||||||
})
|
|
||||||
setTimeout(() => (this.copyIcon = "copy"), 1000)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="show-if-initialized" :class="{ initialized }">
|
|
||||||
<pre ref="editor" :class="styles"></pre>
|
|
||||||
<div
|
|
||||||
v-if="provideOutline"
|
|
||||||
class="
|
|
||||||
bg-primaryLight
|
|
||||||
border-t border-divider
|
|
||||||
flex flex-nowrap flex-1
|
|
||||||
py-1
|
|
||||||
px-4
|
|
||||||
bottom-0
|
|
||||||
z-10
|
|
||||||
sticky
|
|
||||||
overflow-auto
|
|
||||||
hide-scrollbar
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(p, index) in currentPath"
|
|
||||||
:key="`p-${index}`"
|
|
||||||
class="
|
|
||||||
cursor-pointer
|
|
||||||
flex-grow-0 flex-shrink-0
|
|
||||||
text-secondaryLight
|
|
||||||
inline-flex
|
|
||||||
items-center
|
|
||||||
hover:text-secondary
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span @click="onBlockClick(index)">
|
|
||||||
{{ p }}
|
|
||||||
</span>
|
|
||||||
<i v-if="index + 1 !== currentPath.length" class="mx-2 material-icons">
|
|
||||||
chevron_right
|
|
||||||
</i>
|
|
||||||
<tippy
|
|
||||||
v-if="siblingDropDownIndex == index"
|
|
||||||
ref="options"
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
arrow
|
|
||||||
>
|
|
||||||
<SmartItem
|
|
||||||
v-for="(sibling, siblingIndex) in currentSibling"
|
|
||||||
:key="`p-${index}-sibling-${siblingIndex}`"
|
|
||||||
:label="sibling.key ? sibling.key.value : i"
|
|
||||||
@click.native="goToSibling(sibling)"
|
|
||||||
/>
|
|
||||||
</tippy>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ace from "ace-builds"
|
|
||||||
import "ace-builds/webpack-resolver"
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import jsonParse from "~/helpers/jsonParse"
|
|
||||||
import debounce from "~/helpers/utils/debounce"
|
|
||||||
import outline from "~/helpers/outline"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
provideOutline: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
lang: {
|
|
||||||
type: String,
|
|
||||||
default: "json",
|
|
||||||
},
|
|
||||||
lint: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
initialized: false,
|
|
||||||
editor: null,
|
|
||||||
cacheValue: "",
|
|
||||||
outline: outline(),
|
|
||||||
currentPath: [],
|
|
||||||
currentSibling: [],
|
|
||||||
siblingDropDownIndex: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
appFontSize() {
|
|
||||||
return getComputedStyle(document.documentElement).getPropertyValue(
|
|
||||||
"--body-font-size"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
value(value) {
|
|
||||||
if (value !== this.cacheValue) {
|
|
||||||
this.editor.session.setValue(value, 1)
|
|
||||||
this.cacheValue = value
|
|
||||||
if (this.lint) this.provideLinting(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme() {
|
|
||||||
this.initialized = false
|
|
||||||
this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.initialized = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lang(value) {
|
|
||||||
this.editor.getSession().setMode(`ace/mode/${value}`)
|
|
||||||
},
|
|
||||||
options(value) {
|
|
||||||
this.editor.setOptions(value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
const editor = ace.edit(this.$refs.editor, {
|
|
||||||
mode: `ace/mode/${this.lang}`,
|
|
||||||
...this.options,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set the theme and show the editor only after it's been set to prevent FOUC.
|
|
||||||
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.initialized = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.setFontSize(this.appFontSize)
|
|
||||||
|
|
||||||
if (this.value) editor.setValue(this.value, 1)
|
|
||||||
|
|
||||||
this.editor = editor
|
|
||||||
this.cacheValue = this.value
|
|
||||||
|
|
||||||
if (this.lang === "json" && this.provideOutline)
|
|
||||||
this.initOutline(this.value)
|
|
||||||
|
|
||||||
editor.on("change", () => {
|
|
||||||
const content = editor.getValue()
|
|
||||||
this.$emit("input", content)
|
|
||||||
this.cacheValue = content
|
|
||||||
|
|
||||||
if (this.provideOutline) debounce(this.initOutline(content), 500)
|
|
||||||
|
|
||||||
if (this.lint) this.provideLinting(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.lang === "json" && this.provideOutline) {
|
|
||||||
editor.session.selection.on("changeCursor", () => {
|
|
||||||
const index = editor.session.doc.positionToIndex(
|
|
||||||
editor.selection.getCursor(),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
const path = this.outline.genPath(index)
|
|
||||||
if (path.success) {
|
|
||||||
this.currentPath = path.res
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable linting, if lint prop is false
|
|
||||||
if (this.lint) this.provideLinting(this.value)
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed() {
|
|
||||||
this.editor.destroy()
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
defineTheme() {
|
|
||||||
if (this.theme) {
|
|
||||||
return this.theme
|
|
||||||
}
|
|
||||||
const strip = (str) =>
|
|
||||||
str.replace(/#/g, "").replace(/ /g, "").replace(/"/g, "")
|
|
||||||
return strip(
|
|
||||||
window
|
|
||||||
.getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue("--editor-theme")
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
provideLinting: debounce(function (code) {
|
|
||||||
if (this.lang === "json") {
|
|
||||||
try {
|
|
||||||
jsonParse(code)
|
|
||||||
this.editor.session.setAnnotations([])
|
|
||||||
} catch (e) {
|
|
||||||
const pos = this.editor.session
|
|
||||||
.getDocument()
|
|
||||||
.indexToPosition(e.start, 0)
|
|
||||||
this.editor.session.setAnnotations([
|
|
||||||
{
|
|
||||||
row: pos.row,
|
|
||||||
column: pos.column,
|
|
||||||
text: e.message,
|
|
||||||
type: "error",
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 2000),
|
|
||||||
|
|
||||||
onBlockClick(index) {
|
|
||||||
if (this.siblingDropDownIndex === index) {
|
|
||||||
this.clearSiblingList()
|
|
||||||
} else {
|
|
||||||
this.currentSibling = this.outline.getSiblings(index)
|
|
||||||
if (this.currentSibling.length) this.siblingDropDownIndex = index
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearSiblingList() {
|
|
||||||
this.currentSibling = []
|
|
||||||
this.siblingDropDownIndex = null
|
|
||||||
},
|
|
||||||
goToSibling(obj) {
|
|
||||||
this.clearSiblingList()
|
|
||||||
if (obj.start) {
|
|
||||||
const pos = this.editor.session.doc.indexToPosition(obj.start, 0)
|
|
||||||
if (pos) {
|
|
||||||
this.editor.session.selection.moveCursorTo(pos.row, pos.column, true)
|
|
||||||
this.editor.session.selection.clearSelection()
|
|
||||||
this.editor.scrollToLine(pos.row, false, true, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initOutline: debounce(function (content) {
|
|
||||||
if (this.lang === "json") {
|
|
||||||
try {
|
|
||||||
this.outline.init(content)
|
|
||||||
|
|
||||||
if (content[0] === "[") this.currentPath.push("[]")
|
|
||||||
else this.currentPath.push("{}")
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Outline error: ", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.show-if-initialized {
|
|
||||||
&.initialized {
|
|
||||||
@apply opacity-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
@apply transition-none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex">
|
|
||||||
<ButtonSecondary
|
|
||||||
v-for="(color, index) of colors"
|
|
||||||
:key="`color-${index}`"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="$t(getColorModeName(color))"
|
|
||||||
:class="{
|
|
||||||
'bg-primaryLight !text-accent hover:text-accent': color === active,
|
|
||||||
}"
|
|
||||||
class="rounded"
|
|
||||||
:svg="getIcon(color)"
|
|
||||||
@click.native="setBGMode(color)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
|
||||||
import {
|
|
||||||
applySetting,
|
|
||||||
HoppBgColor,
|
|
||||||
HoppBgColors,
|
|
||||||
useSetting,
|
|
||||||
} from "~/newstore/settings"
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
colors: HoppBgColors,
|
|
||||||
active: useSetting("BG_COLOR"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setBGMode(color: HoppBgColor) {
|
|
||||||
applySetting("BG_COLOR", color)
|
|
||||||
},
|
|
||||||
getIcon(color: HoppBgColor) {
|
|
||||||
switch (color) {
|
|
||||||
case "system":
|
|
||||||
return "monitor"
|
|
||||||
case "light":
|
|
||||||
return "sun"
|
|
||||||
case "dark":
|
|
||||||
return "cloud"
|
|
||||||
case "black":
|
|
||||||
return "moon"
|
|
||||||
default:
|
|
||||||
return "monitor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getColorModeName(colorMode: string) {
|
|
||||||
switch (colorMode) {
|
|
||||||
case "system":
|
|
||||||
return "settings.system_mode"
|
|
||||||
case "light":
|
|
||||||
return "settings.light_mode"
|
|
||||||
case "dark":
|
|
||||||
return "settings.dark_mode"
|
|
||||||
case "black":
|
|
||||||
return "settings.black_mode"
|
|
||||||
default:
|
|
||||||
return "settings.system_mode"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||