Compare commits
210 Commits
bug/withDe
...
v2.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
758460210a | ||
|
|
1350919c78 | ||
|
|
6205b5f163 | ||
|
|
3bb65f2115 | ||
|
|
238e41ccda | ||
|
|
5043606701 | ||
|
|
6b0494ddba | ||
|
|
ed73f4179a | ||
|
|
647c347eb1 | ||
|
|
ddff126aaa | ||
|
|
91d0b52222 | ||
|
|
38fddccc5b | ||
|
|
1719bb7bc9 | ||
|
|
588d1119b5 | ||
|
|
e323b4355e | ||
|
|
f4d98b9f43 | ||
|
|
67934fd19a | ||
|
|
396804a4d6 | ||
|
|
bcadb142fd | ||
|
|
aeb848399a | ||
|
|
ffb063ad94 | ||
|
|
6537a51854 | ||
|
|
04d73a855d | ||
|
|
f6fc3782b5 | ||
|
|
05ca0c0966 | ||
|
|
9e6a3883ac | ||
|
|
7f7338faeb | ||
|
|
b40b8070c1 | ||
|
|
24fabc8b7b | ||
|
|
a9bd52d154 | ||
|
|
4cec5a7bb4 | ||
|
|
720d54f91d | ||
|
|
e9e791ce90 | ||
|
|
d6ec3158bc | ||
|
|
fac82200d1 | ||
|
|
d5866fbb3b | ||
|
|
660479f8a6 | ||
|
|
7dace3f9b4 | ||
|
|
8ac38d7517 | ||
|
|
5175a86145 | ||
|
|
a9292eed9e | ||
|
|
681a957611 | ||
|
|
b6e05d42f3 | ||
|
|
ac979239e8 | ||
|
|
137d562c86 | ||
|
|
ffd1acdfae | ||
|
|
dd7aebaf94 | ||
|
|
86ef1a4e14 | ||
|
|
914d20ad37 | ||
|
|
f17cdff3e1 | ||
|
|
75ab7fdb00 | ||
|
|
6e86e27437 | ||
|
|
67baf74edc | ||
|
|
c3aedac77e | ||
|
|
41e39e2141 | ||
|
|
2b4155e969 | ||
|
|
bb5acf97c6 | ||
|
|
78ad61f153 | ||
|
|
2ec401c766 | ||
|
|
314c0968b1 | ||
|
|
4770b86948 | ||
|
|
312bc98c53 | ||
|
|
e56dcf5d7a | ||
|
|
4ecd69cc5a | ||
|
|
b78b2d0e78 | ||
|
|
5f0d1ae52f | ||
|
|
da5e7fdde5 | ||
|
|
9454a983da | ||
|
|
ca131697b6 | ||
|
|
50e30ee4ea | ||
|
|
aa4fedb71c | ||
|
|
f8bbf6613f | ||
|
|
07171396ad | ||
|
|
c493a53ece | ||
|
|
22676957c3 | ||
|
|
1be2c12062 | ||
|
|
1baf73a710 | ||
|
|
b343789554 | ||
|
|
80956fbd27 | ||
|
|
4791590869 | ||
|
|
a0f7201fae | ||
|
|
2de73ae7b4 | ||
|
|
93fa3bcd51 | ||
|
|
065fc95c19 | ||
|
|
3baebb4ec8 | ||
|
|
dbb0582613 | ||
|
|
a1decaf6ad | ||
|
|
923c24e3bb | ||
|
|
724e1e4c8f | ||
|
|
ce94fa54ee | ||
|
|
6fdeffe9d6 | ||
|
|
253170f7ac | ||
|
|
579c5e5b78 | ||
|
|
1dca0fd9ad | ||
|
|
10cb433e80 | ||
|
|
e89d033bfe | ||
|
|
1eb9eb911e | ||
|
|
10586e5288 | ||
|
|
889b59e1e4 | ||
|
|
3e8ff8ebc9 | ||
|
|
a517b571d6 | ||
|
|
43dace6248 | ||
|
|
ba3df75a23 | ||
|
|
ba3d3430c0 | ||
|
|
9c209bb009 | ||
|
|
2c2bc141e8 | ||
|
|
daa617f277 | ||
|
|
4de953fe57 | ||
|
|
f94ee7c73f | ||
|
|
fac1288ef2 | ||
|
|
827f2157fa | ||
|
|
39d6b1bfeb | ||
|
|
76cbd99df8 | ||
|
|
11fe908017 | ||
|
|
d305168dc3 | ||
|
|
f174086281 | ||
|
|
156011f2cd | ||
|
|
655e6dc2a4 | ||
|
|
2f50cff404 | ||
|
|
a38c05febb | ||
|
|
65ee2ebcb2 | ||
|
|
6e53adb5e7 | ||
|
|
c7fd68b0d8 | ||
|
|
af35d68dfe | ||
|
|
1a1baa715d | ||
|
|
29b5913fac | ||
|
|
49c0069c68 | ||
|
|
34fe94215c | ||
|
|
8ca059cf4a | ||
|
|
daffc3fe0e | ||
|
|
78ed95bcaa | ||
|
|
e4f40bce53 | ||
|
|
0022efe844 | ||
|
|
a2757a0335 | ||
|
|
70555cd4a6 | ||
|
|
141697f9da | ||
|
|
188c9ae5ca | ||
|
|
71b4b6e366 | ||
|
|
870b493b80 | ||
|
|
e31dc6e08d | ||
|
|
5a9d08a3f1 | ||
|
|
d997f88fbe | ||
|
|
4e0bb1a243 | ||
|
|
fe5fe03b3c | ||
|
|
534fe8030f | ||
|
|
67002a204e | ||
|
|
75e34feabf | ||
|
|
8a7f3927b1 | ||
|
|
fe13de2ea0 | ||
|
|
50a57433d0 | ||
|
|
dec26ce2aa | ||
|
|
b0f02fee57 | ||
|
|
02be413eff | ||
|
|
42849e6853 | ||
|
|
e7535d505e | ||
|
|
fb6015ce26 | ||
|
|
f3a38bab3c | ||
|
|
1a1f45401c | ||
|
|
4a43807f34 | ||
|
|
88cc21e3d4 | ||
|
|
ecce830787 | ||
|
|
084039f59f | ||
|
|
0de9f3d8c3 | ||
|
|
4d6d30c92b | ||
|
|
38755bf3e3 | ||
|
|
2884854aab | ||
|
|
d24d07e420 | ||
|
|
cc81242294 | ||
|
|
a508909471 | ||
|
|
475a28b159 | ||
|
|
520ac8ede5 | ||
|
|
2a59557851 | ||
|
|
4089bc288c | ||
|
|
1999819846 | ||
|
|
0bf856291c | ||
|
|
da8c446ad7 | ||
|
|
3614877964 | ||
|
|
85c8171aa8 | ||
|
|
b58278d55e | ||
|
|
b398ed1e90 | ||
|
|
afa750e409 | ||
|
|
fdf12a24ed | ||
|
|
4c5ca1b31d | ||
|
|
bcb9b97b6b | ||
|
|
757294ae38 | ||
|
|
8d4dd8c428 | ||
|
|
508809eba1 | ||
|
|
d0386ef86f | ||
|
|
56cdb79773 | ||
|
|
6ed4211004 | ||
|
|
222f0800d2 | ||
|
|
12a9dd1058 | ||
|
|
4a32fc6180 | ||
|
|
1e08e7f73d | ||
|
|
8b05b063ff | ||
|
|
0bcfeb86ae | ||
|
|
03a056b6c1 | ||
|
|
d57e465806 | ||
|
|
5ab24d1439 | ||
|
|
47661de974 | ||
|
|
26429466e9 | ||
|
|
cad8f3e856 | ||
|
|
1a4eb1fabe | ||
|
|
680f61b7dc | ||
|
|
f602a1e2d3 | ||
|
|
1572ff9e67 | ||
|
|
fe7b236ad9 | ||
|
|
3080af1ea5 | ||
|
|
0f83c8b490 | ||
|
|
7d590ab966 |
57
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Bug report
|
||||
description: Create a bug report to help us improve Hoppscotch
|
||||
title: "[bug]: "
|
||||
labels: [bug, need testing]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out this bug report.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current behavior
|
||||
description: A concise description of what you're experiencing and what you expect
|
||||
placeholder: |
|
||||
When I do <X>, <Y> happens and I see the error message attached below:
|
||||
```...```
|
||||
What I expect is <Z>
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Add steps to reproduce this behaviour, include console or network logs and screenshots
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: env
|
||||
attributes:
|
||||
label: Environment
|
||||
options:
|
||||
- Production
|
||||
- Release
|
||||
- Deploy preview
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
options:
|
||||
- Cloud
|
||||
- Self-hosted
|
||||
- Local
|
||||
validations:
|
||||
required: true
|
||||
28
.github/ISSUE_TEMPLATE/--feature-request.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature to improve Hoppscotch
|
||||
title: "[feature]: "
|
||||
labels: [feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to request a feature for Hoppscotch
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue related to this feature request already exists
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One paragraph description of the feature
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why should this be worked on?
|
||||
description: A concise description of the problems or use cases for this feature request
|
||||
validations:
|
||||
required: true
|
||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: Help and support
|
||||
url: https://github.com/hoppscotch/hoppscotch#support
|
||||
about: Reach out to us on our Discord server or Telegram group or GitHub discussions.
|
||||
- name: Dedicated support
|
||||
url: mailto:support@hoppscotch.io
|
||||
about: Write to us if you'd like dedicated support using Hoppscotch
|
||||
8
.github/ISSUE_TEMPLATE/custom.md
vendored
@@ -1,8 +0,0 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
26
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Thanks for creating this pull request 🤗
|
||||
|
||||
Please make sure that the pull request is limited to one type (docs, feature, etc.) and keep it as small as possible. You can open multiple prs instead of opening a huge one.
|
||||
-->
|
||||
|
||||
<!-- If this pull request closes an issue, please mention the issue number below -->
|
||||
Closes # <!-- Issue # here -->
|
||||
|
||||
### Description
|
||||
<!-- Add a brief description of the pull request -->
|
||||
|
||||
<!-- You can also choose to add a list of changes and if they have been completed or not by using the markdown to-do list syntax
|
||||
- [ ] Not Completed
|
||||
- [x] Completed
|
||||
-->
|
||||
|
||||
### Checks
|
||||
<!-- Make sure your pull request passes the CI checks and do check the following fields as needed - -->
|
||||
- [ ] My pull request adheres to the code style of this project
|
||||
- [ ] My code requires changes to the documentation
|
||||
- [ ] I have updated the documentation as required
|
||||
- [ ] All the tests have passed
|
||||
|
||||
### Additional Information
|
||||
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->
|
||||
832
CHANGELOG.md
@@ -1,833 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
## [v1.12.0](https://github.com/hoppscotch/hoppscotch/tree/v1.12.0) (2020-05-27)
|
||||
|
||||
[Full Changelog](https://github.com/hoppscotch/hoppscotch/compare/v1.10.0...v1.12.0)
|
||||
|
||||
## [v1.10.0](https://github.com/hoppscotch/hoppscotch/tree/v1.10.0) (2020-04-10)
|
||||
|
||||
[Full Changelog](https://github.com/hoppscotch/hoppscotch/compare/v1.9.9...v1.10.0)
|
||||
|
||||
## [v1.9.9](https://github.com/liyasthomas/postwoman/tree/v1.9.9) (2020-07-30)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.9.7...v1.9.9)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- TextDecoder.decode\(\) TypeError hangs the whole app [\#1032](https://github.com/liyasthomas/postwoman/issues/1032)
|
||||
- response content doesn't fit to the text area when resizing [\#970](https://github.com/liyasthomas/postwoman/issues/970)
|
||||
- typing into headers input fields [\#912](https://github.com/liyasthomas/postwoman/issues/912)
|
||||
- Environment variable template \(\<\<foo\>\>\) appears urlencoded \(%3C%3Cfoo%3E%3E\) [\#896](https://github.com/liyasthomas/postwoman/issues/896)
|
||||
- TypeError: Cannot read property 'startsWith' of undefined - after getting 401 response [\#894](https://github.com/liyasthomas/postwoman/issues/894)
|
||||
- When deleting the header, the key is not updated [\#886](https://github.com/liyasthomas/postwoman/issues/886)
|
||||
- Cannot introduce query parameters in URL for WebSocket [\#873](https://github.com/liyasthomas/postwoman/issues/873)
|
||||
- Response content-type as `text/html` with content in `json` cause content area display empty [\#869](https://github.com/liyasthomas/postwoman/issues/869)
|
||||
- Proxy privacy policy link [\#865](https://github.com/liyasthomas/postwoman/issues/865)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Collections | Request UI Issue [\#1028](https://github.com/liyasthomas/postwoman/issues/1028)
|
||||
- JSON not showing up in the correct format [\#1023](https://github.com/liyasthomas/postwoman/issues/1023)
|
||||
- ignore duplicates in history [\#1022](https://github.com/liyasthomas/postwoman/issues/1022)
|
||||
- change history menu [\#1021](https://github.com/liyasthomas/postwoman/issues/1021)
|
||||
- integrate parameters with history [\#1020](https://github.com/liyasthomas/postwoman/issues/1020)
|
||||
- Why some Chrome do not have the ability to install PWA? [\#1015](https://github.com/liyasthomas/postwoman/issues/1015)
|
||||
- Shall we have the team management ability and some public documents? [\#1014](https://github.com/liyasthomas/postwoman/issues/1014)
|
||||
- I have edit this config, but it is not available to login. [\#1013](https://github.com/liyasthomas/postwoman/issues/1013)
|
||||
- User login is disabled after i run it on our local server. [\#1012](https://github.com/liyasthomas/postwoman/issues/1012)
|
||||
- postwoman google login doesn't work behind ingress or reverse proxy [\#1009](https://github.com/liyasthomas/postwoman/issues/1009)
|
||||
- Compile error [\#1006](https://github.com/liyasthomas/postwoman/issues/1006)
|
||||
- Postman Web is now out. It might be great to find a USP for Postwoman [\#1000](https://github.com/liyasthomas/postwoman/issues/1000)
|
||||
- Saving response data in env variable [\#984](https://github.com/liyasthomas/postwoman/issues/984)
|
||||
- contentType 无法使用 form-date 上传文件 [\#983](https://github.com/liyasthomas/postwoman/issues/983)
|
||||
- Currently completely broken [\#980](https://github.com/liyasthomas/postwoman/issues/980)
|
||||
- localhost request error [\#979](https://github.com/liyasthomas/postwoman/issues/979)
|
||||
- Installing postwoman locally [\#969](https://github.com/liyasthomas/postwoman/issues/969)
|
||||
- Do I install NodeJS for my online environment [\#968](https://github.com/liyasthomas/postwoman/issues/968)
|
||||
- Collections and Environment Module [\#967](https://github.com/liyasthomas/postwoman/issues/967)
|
||||
- Textarea display problem in super hi-dpi [\#965](https://github.com/liyasthomas/postwoman/issues/965)
|
||||
- TypeError: Cannot read property 'value' of undefined - when logged in [\#961](https://github.com/liyasthomas/postwoman/issues/961)
|
||||
- Enable user-select on websocket and other realtime message logs [\#951](https://github.com/liyasthomas/postwoman/issues/951)
|
||||
- POST requet error [\#947](https://github.com/liyasthomas/postwoman/issues/947)
|
||||
- Unable to fetch schema from localhost GraphQL server. [\#940](https://github.com/liyasthomas/postwoman/issues/940)
|
||||
- Support downloading binary responses [\#929](https://github.com/liyasthomas/postwoman/issues/929)
|
||||
- Integrate PostWoman In our Webapp [\#918](https://github.com/liyasthomas/postwoman/issues/918)
|
||||
- proxy issue [\#911](https://github.com/liyasthomas/postwoman/issues/911)
|
||||
- Button to cancel requests [\#909](https://github.com/liyasthomas/postwoman/issues/909)
|
||||
- How to upload a file with a post request [\#908](https://github.com/liyasthomas/postwoman/issues/908)
|
||||
- Cant Import Postman Global Environment Variables [\#907](https://github.com/liyasthomas/postwoman/issues/907)
|
||||
- Postwoman Docker Container behind Reverse Proxy [\#906](https://github.com/liyasthomas/postwoman/issues/906)
|
||||
- `pw.response` seems not to work [\#905](https://github.com/liyasthomas/postwoman/issues/905)
|
||||
- Could postman add Sign in with LDAP server? [\#901](https://github.com/liyasthomas/postwoman/issues/901)
|
||||
- Collections & Environments not synced [\#900](https://github.com/liyasthomas/postwoman/issues/900)
|
||||
- Add authentication to MQTT [\#898](https://github.com/liyasthomas/postwoman/issues/898)
|
||||
- Labels are lost when using requests from collections [\#897](https://github.com/liyasthomas/postwoman/issues/897)
|
||||
- Handle JSON Parameter list validation [\#891](https://github.com/liyasthomas/postwoman/issues/891)
|
||||
- Nuxt fatal error [\#883](https://github.com/liyasthomas/postwoman/issues/883)
|
||||
- Cannot connect my local websocket server [\#880](https://github.com/liyasthomas/postwoman/issues/880)
|
||||
- Environments not synced after edit [\#877](https://github.com/liyasthomas/postwoman/issues/877)
|
||||
- Can't find Desktop app link anywhere [\#872](https://github.com/liyasthomas/postwoman/issues/872)
|
||||
- Show request completion time [\#871](https://github.com/liyasthomas/postwoman/issues/871)
|
||||
- Make docs on self-hosting Postwoman [\#868](https://github.com/liyasthomas/postwoman/issues/868)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Add trailing backslash to generated cURL code for easier paste-and-execute [\#1033](https://github.com/liyasthomas/postwoman/pull/1033) ([ushuz](https://github.com/ushuz))
|
||||
- Update zh-CN.json [\#1031](https://github.com/liyasthomas/postwoman/pull/1031) ([hantianwei](https://github.com/hantianwei))
|
||||
- Bump firebase from 7.17.0 to 7.17.1 [\#1026](https://github.com/liyasthomas/postwoman/pull/1026) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Update zh-CN.json [\#1024](https://github.com/liyasthomas/postwoman/pull/1024) ([hantianwei](https://github.com/hantianwei))
|
||||
- Bump @nuxtjs/gtm from 2.3.0 to 2.3.2 [\#1019](https://github.com/liyasthomas/postwoman/pull/1019) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump firebase from 7.16.1 to 7.17.0 [\#1018](https://github.com/liyasthomas/postwoman/pull/1018) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Fix bugs with the renderer mixins [\#1008](https://github.com/liyasthomas/postwoman/pull/1008) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Bump eslint from 7.4.0 to 7.5.0 [\#1005](https://github.com/liyasthomas/postwoman/pull/1005) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Add Collections section in Docs page [\#1004](https://github.com/liyasthomas/postwoman/pull/1004) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Bump lodash from 4.17.15 to 4.17.19 in /functions [\#999](https://github.com/liyasthomas/postwoman/pull/999) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump @nuxtjs/google-analytics from 2.3.0 to 2.4.0 [\#998](https://github.com/liyasthomas/postwoman/pull/998) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump firebase from 7.16.0 to 7.16.1 [\#997](https://github.com/liyasthomas/postwoman/pull/997) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Fixed broken network requests in GraphQL [\#995](https://github.com/liyasthomas/postwoman/pull/995) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- fix: replaceWithJSON used wrong commit name [\#993](https://github.com/liyasthomas/postwoman/pull/993) ([perseveringman](https://github.com/perseveringman))
|
||||
- ⬆️ Bump @nuxtjs/toast from 3.3.0 to 3.3.1 [\#992](https://github.com/liyasthomas/postwoman/pull/992) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump start-server-and-test from 1.11.1 to 1.11.2 [\#991](https://github.com/liyasthomas/postwoman/pull/991) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump @nuxtjs/axios from 5.11.0 to 5.12.0 [\#990](https://github.com/liyasthomas/postwoman/pull/990) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump firebase from 7.15.5 to 7.16.0 [\#989](https://github.com/liyasthomas/postwoman/pull/989) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump start-server-and-test from 1.11.0 to 1.11.1 [\#988](https://github.com/liyasthomas/postwoman/pull/988) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump sass-loader from 9.0.1 to 9.0.2 [\#986](https://github.com/liyasthomas/postwoman/pull/986) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump cypress from 4.9.0 to 4.10.0 [\#985](https://github.com/liyasthomas/postwoman/pull/985) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump ace-builds from 1.4.11 to 1.4.12 [\#982](https://github.com/liyasthomas/postwoman/pull/982) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump vuefire from 2.2.2 to 2.2.3 [\#981](https://github.com/liyasthomas/postwoman/pull/981) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump eslint from 7.3.1 to 7.4.0 [\#978](https://github.com/liyasthomas/postwoman/pull/978) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump sass-loader from 9.0.0 to 9.0.1 [\#977](https://github.com/liyasthomas/postwoman/pull/977) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump graphql from 15.2.0 to 15.3.0 [\#976](https://github.com/liyasthomas/postwoman/pull/976) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump nuxt-i18n from 6.13.0 to 6.13.1 [\#975](https://github.com/liyasthomas/postwoman/pull/975) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump sass-loader from 8.0.2 to 9.0.0 [\#973](https://github.com/liyasthomas/postwoman/pull/973) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump nuxt-i18n from 6.12.2 to 6.13.0 [\#972](https://github.com/liyasthomas/postwoman/pull/972) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump graphql from 15.1.0 to 15.2.0 [\#966](https://github.com/liyasthomas/postwoman/pull/966) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump @nuxtjs/sitemap from 2.3.2 to 2.4.0 [\#963](https://github.com/liyasthomas/postwoman/pull/963) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump firebase from 7.15.4 to 7.15.5 [\#962](https://github.com/liyasthomas/postwoman/pull/962) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump eslint from 7.3.0 to 7.3.1 [\#958](https://github.com/liyasthomas/postwoman/pull/958) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump cypress from 4.8.0 to 4.9.0 [\#957](https://github.com/liyasthomas/postwoman/pull/957) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump firebase from 7.15.3 to 7.15.4 [\#956](https://github.com/liyasthomas/postwoman/pull/956) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Binary Responses & Response Lenses [\#955](https://github.com/liyasthomas/postwoman/pull/955) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Improving SEO [\#954](https://github.com/liyasthomas/postwoman/pull/954) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Isolate Netlify, Firebase and Helper functions + Import from absolute… [\#953](https://github.com/liyasthomas/postwoman/pull/953) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Added ability to select text in realtime log [\#952](https://github.com/liyasthomas/postwoman/pull/952) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- ⬆️ Bump firebase from 7.15.1 to 7.15.3 [\#950](https://github.com/liyasthomas/postwoman/pull/950) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump eslint from 7.2.0 to 7.3.0 [\#949](https://github.com/liyasthomas/postwoman/pull/949) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Revert "⬆️ Bump nuxt from 2.12.2 to 2.13.0" [\#946](https://github.com/liyasthomas/postwoman/pull/946) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- ⬆️ Bump nuxt from 2.12.2 to 2.13.0 [\#942](https://github.com/liyasthomas/postwoman/pull/942) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump @nuxtjs/sitemap from 2.3.1 to 2.3.2 [\#939](https://github.com/liyasthomas/postwoman/pull/939) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump graphql from 14.6.0 to 15.1.0 [\#938](https://github.com/liyasthomas/postwoman/pull/938) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Updated readme [\#937](https://github.com/liyasthomas/postwoman/pull/937) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- ⬆️ Bump graphql-language-service-interface from 2.3.3 to 2.4.0 [\#936](https://github.com/liyasthomas/postwoman/pull/936) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- ⬆️ Bump firebase from 7.15.0 to 7.15.1 [\#935](https://github.com/liyasthomas/postwoman/pull/935) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Transpiled ES5 code to ES6/ES7 [\#934](https://github.com/liyasthomas/postwoman/pull/934) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Create Dependabot config file [\#932](https://github.com/liyasthomas/postwoman/pull/932) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Hide download response button for non-JSON responses [\#931](https://github.com/liyasthomas/postwoman/pull/931) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump cypress from 4.7.0 to 4.8.0 [\#928](https://github.com/liyasthomas/postwoman/pull/928) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- fix: environment and collection sync issue with firebase [\#926](https://github.com/liyasthomas/postwoman/pull/926) ([myussufz](https://github.com/myussufz))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.10.3 to 5.11.0 [\#925](https://github.com/liyasthomas/postwoman/pull/925) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump eslint from 7.1.0 to 7.2.0 [\#924](https://github.com/liyasthomas/postwoman/pull/924) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- GraphQL response options only visible when a response is shown [\#923](https://github.com/liyasthomas/postwoman/pull/923) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps\): bump firebase from 7.14.6 to 7.15.0 [\#922](https://github.com/liyasthomas/postwoman/pull/922) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/sitemap from 2.3.0 to 2.3.1 [\#921](https://github.com/liyasthomas/postwoman/pull/921) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added ability to download GraphQL responses [\#920](https://github.com/liyasthomas/postwoman/pull/920) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.12.1 to 6.12.2 [\#919](https://github.com/liyasthomas/postwoman/pull/919) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/gtm from 2.2.3 to 2.3.0 [\#917](https://github.com/liyasthomas/postwoman/pull/917) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Cancel Request from the Keyboard [\#916](https://github.com/liyasthomas/postwoman/pull/916) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Cancellable Requests [\#915](https://github.com/liyasthomas/postwoman/pull/915) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.12.0 to 6.12.1 [\#914](https://github.com/liyasthomas/postwoman/pull/914) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.14.5 to 7.14.6 [\#913](https://github.com/liyasthomas/postwoman/pull/913) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
|
||||
## [v1.9.7](https://github.com/liyasthomas/postwoman/tree/v1.9.7) (2020-05-12)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.9.5...v1.9.7)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Empty header in headers list results in SyntaxError: Failed to execute 'setRequestHeader' [\#765](https://github.com/liyasthomas/postwoman/issues/765)
|
||||
- Getting cannot read value of undefined [\#731](https://github.com/liyasthomas/postwoman/issues/731)
|
||||
- Environment variables in collections [\#642](https://github.com/liyasthomas/postwoman/issues/642)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Import/Export collections from private github repos to share among teams. [\#867](https://github.com/liyasthomas/postwoman/issues/867)
|
||||
- Unable to use postwoman with latest docker image from docker hub [\#866](https://github.com/liyasthomas/postwoman/issues/866)
|
||||
- Access to nonexistent routes will not be redirect to page 404 [\#849](https://github.com/liyasthomas/postwoman/issues/849)
|
||||
- Error: Network Error. Check console for details. [\#827](https://github.com/liyasthomas/postwoman/issues/827)
|
||||
- 'Documentation Generated' response stacking past top of page if submit clicked enough times/fast enough [\#826](https://github.com/liyasthomas/postwoman/issues/826)
|
||||
- The UI could use some improvements [\#825](https://github.com/liyasthomas/postwoman/issues/825)
|
||||
- Postwoman won't build, produces 'FATAL Nuxt build error' [\#824](https://github.com/liyasthomas/postwoman/issues/824)
|
||||
- Improve contrast of UI components in all themes [\#819](https://github.com/liyasthomas/postwoman/issues/819)
|
||||
- Add an option to hide and/or collapse the right panel [\#818](https://github.com/liyasthomas/postwoman/issues/818)
|
||||
- Docker Cannot log in normally in the container [\#817](https://github.com/liyasthomas/postwoman/issues/817)
|
||||
- Body in Request [\#815](https://github.com/liyasthomas/postwoman/issues/815)
|
||||
- How to run postwoman under reverse proxy [\#812](https://github.com/liyasthomas/postwoman/issues/812)
|
||||
- Call local support [\#811](https://github.com/liyasthomas/postwoman/issues/811)
|
||||
- feature [\#810](https://github.com/liyasthomas/postwoman/issues/810)
|
||||
- support response json array [\#805](https://github.com/liyasthomas/postwoman/issues/805)
|
||||
- socket binnery support [\#801](https://github.com/liyasthomas/postwoman/issues/801)
|
||||
- Ability to join and leave rooms in Socket.IO connection [\#796](https://github.com/liyasthomas/postwoman/issues/796)
|
||||
- How can I synchronize my data on local? [\#794](https://github.com/liyasthomas/postwoman/issues/794)
|
||||
- I cant login [\#792](https://github.com/liyasthomas/postwoman/issues/792)
|
||||
- Unresolved merge conflict in index.vue.orig [\#786](https://github.com/liyasthomas/postwoman/issues/786)
|
||||
- You send data my request to Google [\#780](https://github.com/liyasthomas/postwoman/issues/780)
|
||||
- I have question by \#750, it's closed,but my problem is still.... [\#770](https://github.com/liyasthomas/postwoman/issues/770)
|
||||
- Add Format Body option [\#767](https://github.com/liyasthomas/postwoman/issues/767)
|
||||
- Body scroll after modal is open [\#766](https://github.com/liyasthomas/postwoman/issues/766)
|
||||
- text.match is not a function [\#764](https://github.com/liyasthomas/postwoman/issues/764)
|
||||
- Request : Copy response headers [\#763](https://github.com/liyasthomas/postwoman/issues/763)
|
||||
- The accordion \(expand\) labels are out of place on mobile [\#762](https://github.com/liyasthomas/postwoman/issues/762)
|
||||
- why does the graphql case can't be saved? [\#761](https://github.com/liyasthomas/postwoman/issues/761)
|
||||
- Mobile responsiveness issues [\#760](https://github.com/liyasthomas/postwoman/issues/760)
|
||||
- Allow importing environment variables via Postman environment json files [\#759](https://github.com/liyasthomas/postwoman/issues/759)
|
||||
- Report abuse: liyasthomas/postwoman \(Contact Links\) [\#754](https://github.com/liyasthomas/postwoman/issues/754)
|
||||
- Report abuse: liyasthomas/postwoman \(Contact Links\) [\#753](https://github.com/liyasthomas/postwoman/issues/753)
|
||||
- Request headers kept same after changing request type [\#752](https://github.com/liyasthomas/postwoman/issues/752)
|
||||
- I used it to post test,but response network error [\#750](https://github.com/liyasthomas/postwoman/issues/750)
|
||||
- Add compatibility for postman collections & environments [\#746](https://github.com/liyasthomas/postwoman/issues/746)
|
||||
- Improve documentation on how to use environments [\#742](https://github.com/liyasthomas/postwoman/issues/742)
|
||||
- Add GraphQL syntax highlighting [\#741](https://github.com/liyasthomas/postwoman/issues/741)
|
||||
- Broken link to translations branch [\#737](https://github.com/liyasthomas/postwoman/issues/737)
|
||||
- Add docker Images for all Tags [\#722](https://github.com/liyasthomas/postwoman/issues/722)
|
||||
- Provide post-build resources and default setting files [\#714](https://github.com/liyasthomas/postwoman/issues/714)
|
||||
- Theme lacks of contrast [\#709](https://github.com/liyasthomas/postwoman/issues/709)
|
||||
- Postwoman raiase a connection error when communicate with localhost. [\#708](https://github.com/liyasthomas/postwoman/issues/708)
|
||||
- CORS issue when hosting [\#707](https://github.com/liyasthomas/postwoman/issues/707)
|
||||
- Add a description to the request or collection when saving it [\#706](https://github.com/liyasthomas/postwoman/issues/706)
|
||||
- Error: Network Error. Check console for details [\#704](https://github.com/liyasthomas/postwoman/issues/704)
|
||||
- Save collections on account sync turn on [\#679](https://github.com/liyasthomas/postwoman/issues/679)
|
||||
- Tests should be saved together with requests [\#643](https://github.com/liyasthomas/postwoman/issues/643)
|
||||
- npm modules of postwoman's vue components [\#384](https://github.com/liyasthomas/postwoman/issues/384)
|
||||
- \[request\] VS code extension [\#313](https://github.com/liyasthomas/postwoman/issues/313)
|
||||
- remove prerequest \<\< \>\> bindings when pre-request script is toggled off [\#234](https://github.com/liyasthomas/postwoman/issues/234)
|
||||
- Dynamic Headers [\#91](https://github.com/liyasthomas/postwoman/issues/91)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- chore\(deps\): bump @nuxtjs/sitemap from 2.2.1 to 2.3.0 [\#864](https://github.com/liyasthomas/postwoman/pull/864) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- docs: add sboulema as a contributor [\#863](https://github.com/liyasthomas/postwoman/pull/863) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- Allow importing environment variables via Postman environment json files [\#862](https://github.com/liyasthomas/postwoman/pull/862) ([sboulema](https://github.com/sboulema))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.11.0 to 6.11.1 [\#861](https://github.com/liyasthomas/postwoman/pull/861) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Produce valid output when showing/copying as code [\#857](https://github.com/liyasthomas/postwoman/pull/857) ([Hydrophobefireman](https://github.com/Hydrophobefireman))
|
||||
- dotenv [\#856](https://github.com/liyasthomas/postwoman/pull/856) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Save Collections/Environments on enabling sync [\#854](https://github.com/liyasthomas/postwoman/pull/854) ([sboulema](https://github.com/sboulema))
|
||||
- chore\(deps\): bump firebase from 7.14.2 to 7.14.3 [\#853](https://github.com/liyasthomas/postwoman/pull/853) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Environment variables in collections [\#851](https://github.com/liyasthomas/postwoman/pull/851) ([sboulema](https://github.com/sboulema))
|
||||
- Add format body option [\#847](https://github.com/liyasthomas/postwoman/pull/847) ([sboulema](https://github.com/sboulema))
|
||||
- Save GraphQL Docs [\#846](https://github.com/liyasthomas/postwoman/pull/846) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump node-sass from 4.14.0 to 4.14.1 [\#844](https://github.com/liyasthomas/postwoman/pull/844) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Remove not-deleted index.vue merge file [\#842](https://github.com/liyasthomas/postwoman/pull/842) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- URL Path Parameters \#834 [\#840](https://github.com/liyasthomas/postwoman/pull/840) ([sboulema](https://github.com/sboulema))
|
||||
- chore\(deps\): remove stale dependency [\#839](https://github.com/liyasthomas/postwoman/pull/839) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- GraphQL Query Editor Syntax Highlighting [\#838](https://github.com/liyasthomas/postwoman/pull/838) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.2.1 to 10.2.2 [\#837](https://github.com/liyasthomas/postwoman/pull/837) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(store\): better code structure [\#835](https://github.com/liyasthomas/postwoman/pull/835) ([jameslahm](https://github.com/jameslahm))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.10.2 to 5.10.3 [\#832](https://github.com/liyasthomas/postwoman/pull/832) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.10.1 to 6.11.0 [\#831](https://github.com/liyasthomas/postwoman/pull/831) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.2.0 to 10.2.1 [\#830](https://github.com/liyasthomas/postwoman/pull/830) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Add ability to navigate through message history [\#828](https://github.com/liyasthomas/postwoman/pull/828) ([jinyus](https://github.com/jinyus))
|
||||
- chore\(config\): delete render option [\#823](https://github.com/liyasthomas/postwoman/pull/823) ([jameslahm](https://github.com/jameslahm))
|
||||
- chore\(deps-dev\): bump cypress from 4.4.1 to 4.5.0 [\#822](https://github.com/liyasthomas/postwoman/pull/822) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.7 to 10.2.0 [\#821](https://github.com/liyasthomas/postwoman/pull/821) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Realtime SocketIO support for json user input [\#820](https://github.com/liyasthomas/postwoman/pull/820) ([feydan](https://github.com/feydan))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.10.1 to 5.10.2 [\#816](https://github.com/liyasthomas/postwoman/pull/816) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Modify responseType by Object\(json\) or Array\(json5\) [\#813](https://github.com/liyasthomas/postwoman/pull/813) ([shtakai](https://github.com/shtakai))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.9.2 to 6.10.1 [\#809](https://github.com/liyasthomas/postwoman/pull/809) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.14.1 to 7.14.2 [\#808](https://github.com/liyasthomas/postwoman/pull/808) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump node-sass from 4.13.1 to 4.14.0 [\#807](https://github.com/liyasthomas/postwoman/pull/807) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/sitemap from 2.2.0 to 2.2.1 [\#806](https://github.com/liyasthomas/postwoman/pull/806) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.10.0 to 5.10.1 [\#803](https://github.com/liyasthomas/postwoman/pull/803) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.9.1 to 6.9.2 [\#802](https://github.com/liyasthomas/postwoman/pull/802) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.6 to 10.1.7 [\#800](https://github.com/liyasthomas/postwoman/pull/800) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump prettier from 2.0.4 to 2.0.5 [\#799](https://github.com/liyasthomas/postwoman/pull/799) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.7 to 5.10.0 [\#798](https://github.com/liyasthomas/postwoman/pull/798) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 4.4.0 to 4.4.1 [\#797](https://github.com/liyasthomas/postwoman/pull/797) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Listen to all events in socket.io connection [\#795](https://github.com/liyasthomas/postwoman/pull/795) ([konradkalemba](https://github.com/konradkalemba))
|
||||
- Fix postman import with empty url [\#791](https://github.com/liyasthomas/postwoman/pull/791) ([Nikita240](https://github.com/Nikita240))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.5 to 10.1.6 [\#789](https://github.com/liyasthomas/postwoman/pull/789) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.3 to 10.1.5 [\#787](https://github.com/liyasthomas/postwoman/pull/787) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.14.0 to 7.14.1 [\#782](https://github.com/liyasthomas/postwoman/pull/782) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump yargs-parser from 18.1.2 to 18.1.3 [\#781](https://github.com/liyasthomas/postwoman/pull/781) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump start-server-and-test from 1.10.11 to 1.11.0 [\#778](https://github.com/liyasthomas/postwoman/pull/778) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.8.1 to 6.9.1 [\#776](https://github.com/liyasthomas/postwoman/pull/776) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump ace-builds from 1.4.9 to 1.4.11 [\#775](https://github.com/liyasthomas/postwoman/pull/775) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 4.3.0 to 4.4.0 [\#774](https://github.com/liyasthomas/postwoman/pull/774) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.13.2 to 7.14.0 [\#758](https://github.com/liyasthomas/postwoman/pull/758) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump husky from 4.2.3 to 4.2.5 [\#757](https://github.com/liyasthomas/postwoman/pull/757) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.2 to 10.1.3 [\#756](https://github.com/liyasthomas/postwoman/pull/756) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Fix indicator if extension is installed [\#748](https://github.com/liyasthomas/postwoman/pull/748) ([levrik](https://github.com/levrik))
|
||||
- Remove support for legacy extensions [\#747](https://github.com/liyasthomas/postwoman/pull/747) ([levrik](https://github.com/levrik))
|
||||
- Fix GQL introspection query not sent through extension [\#745](https://github.com/liyasthomas/postwoman/pull/745) ([levrik](https://github.com/levrik))
|
||||
- chore\(deps-dev\): bump prettier from 2.0.2 to 2.0.4 [\#744](https://github.com/liyasthomas/postwoman/pull/744) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/sitemap from 2.1.0 to 2.2.0 [\#743](https://github.com/liyasthomas/postwoman/pull/743) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.1 to 10.1.2 [\#740](https://github.com/liyasthomas/postwoman/pull/740) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.8.0 to 6.8.1 [\#736](https://github.com/liyasthomas/postwoman/pull/736) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump ace-builds from 1.4.8 to 1.4.9 [\#735](https://github.com/liyasthomas/postwoman/pull/735) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.13.1 to 7.13.2 [\#734](https://github.com/liyasthomas/postwoman/pull/734) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump vue-virtual-scroll-list from 1.4.6 to 1.4.7 [\#733](https://github.com/liyasthomas/postwoman/pull/733) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.7.2 to 6.8.0 [\#732](https://github.com/liyasthomas/postwoman/pull/732) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt from 2.12.1 to 2.12.2 [\#729](https://github.com/liyasthomas/postwoman/pull/729) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.7.1 to 6.7.2 [\#728](https://github.com/liyasthomas/postwoman/pull/728) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.1.0 to 10.1.1 [\#727](https://github.com/liyasthomas/postwoman/pull/727) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 4.2.0 to 4.3.0 [\#726](https://github.com/liyasthomas/postwoman/pull/726) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.0.10 to 10.1.0 [\#725](https://github.com/liyasthomas/postwoman/pull/725) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.7.0 to 6.7.1 [\#724](https://github.com/liyasthomas/postwoman/pull/724) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.6 to 5.9.7 [\#723](https://github.com/liyasthomas/postwoman/pull/723) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.0.9 to 10.0.10 [\#721](https://github.com/liyasthomas/postwoman/pull/721) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.5 to 5.9.6 [\#719](https://github.com/liyasthomas/postwoman/pull/719) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/sitemap from 2.0.1 to 2.1.0 [\#718](https://github.com/liyasthomas/postwoman/pull/718) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.13.0 to 7.13.1 [\#717](https://github.com/liyasthomas/postwoman/pull/717) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump yargs-parser from 18.1.1 to 18.1.2 [\#713](https://github.com/liyasthomas/postwoman/pull/713) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.6.1 to 6.7.0 [\#712](https://github.com/liyasthomas/postwoman/pull/712) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt from 2.12.0 to 2.12.1 [\#711](https://github.com/liyasthomas/postwoman/pull/711) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.12.0 to 7.13.0 [\#710](https://github.com/liyasthomas/postwoman/pull/710) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Updating the UI and style files [\#705](https://github.com/liyasthomas/postwoman/pull/705) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.0.8 to 10.0.9 [\#703](https://github.com/liyasthomas/postwoman/pull/703) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Improving performance [\#702](https://github.com/liyasthomas/postwoman/pull/702) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- :package: Updating packages [\#701](https://github.com/liyasthomas/postwoman/pull/701) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps-dev\): bump prettier from 2.0.1 to 2.0.2 [\#700](https://github.com/liyasthomas/postwoman/pull/700) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump prettier from 1.19.1 to 2.0.1 [\#697](https://github.com/liyasthomas/postwoman/pull/697) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
|
||||
## [v1.9.5](https://github.com/liyasthomas/postwoman/tree/v1.9.5) (2020-03-22)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.9.0...v1.9.5)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Test script is not run after failing request [\#644](https://github.com/liyasthomas/postwoman/issues/644)
|
||||
- \[HELP\] Auth permission denied [\#621](https://github.com/liyasthomas/postwoman/issues/621)
|
||||
- Can't login on Brave Browser [\#607](https://github.com/liyasthomas/postwoman/issues/607)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- \[UI/UX\] - Change place of Send button [\#696](https://github.com/liyasthomas/postwoman/issues/696)
|
||||
- Support preview of JSON:API's "application/vnd.api+json" Content-Type [\#694](https://github.com/liyasthomas/postwoman/issues/694)
|
||||
- Report Portal integration [\#691](https://github.com/liyasthomas/postwoman/issues/691)
|
||||
- Docs request: how to prevent secrets from leaving local storage wrt. sync. [\#686](https://github.com/liyasthomas/postwoman/issues/686)
|
||||
- \[bug\] - Can't make a request to HTTP [\#676](https://github.com/liyasthomas/postwoman/issues/676)
|
||||
- Looking forward to that the postwoman Compatible 'swagger ' at next version [\#675](https://github.com/liyasthomas/postwoman/issues/675)
|
||||
- Error: Network Error. Check console for details. [\#673](https://github.com/liyasthomas/postwoman/issues/673)
|
||||
- \[Bug\] - Can't login to Github and Google [\#661](https://github.com/liyasthomas/postwoman/issues/661)
|
||||
- A question that has been raised but not resolved [\#658](https://github.com/liyasthomas/postwoman/issues/658)
|
||||
- An unknown error occurred whilst the proxy was processing your request. [\#656](https://github.com/liyasthomas/postwoman/issues/656)
|
||||
- Running app from downloaded zip fails to compile [\#651](https://github.com/liyasthomas/postwoman/issues/651)
|
||||
- Environment variable in path won't update [\#641](https://github.com/liyasthomas/postwoman/issues/641)
|
||||
- Info: The current domain is not authorized for OAuth operations Error [\#637](https://github.com/liyasthomas/postwoman/issues/637)
|
||||
- A suggestion for UI [\#635](https://github.com/liyasthomas/postwoman/issues/635)
|
||||
- How to use postwoman for local development and testing [\#634](https://github.com/liyasthomas/postwoman/issues/634)
|
||||
- How to debug localhost \(cors\) [\#630](https://github.com/liyasthomas/postwoman/issues/630)
|
||||
- Support SocketIO connections on Realtime page [\#611](https://github.com/liyasthomas/postwoman/issues/611)
|
||||
- Requests to local API returning error response [\#608](https://github.com/liyasthomas/postwoman/issues/608)
|
||||
- Why does the URL input field display only one line [\#604](https://github.com/liyasthomas/postwoman/issues/604)
|
||||
- Parameter list not showing JSON object fields \(force raw?\) [\#597](https://github.com/liyasthomas/postwoman/issues/597)
|
||||
- Add setting to disable scroll animations [\#592](https://github.com/liyasthomas/postwoman/issues/592)
|
||||
- Bigger URL and/or Path input field [\#581](https://github.com/liyasthomas/postwoman/issues/581)
|
||||
- Ability to connect to a MQTT broker [\#342](https://github.com/liyasthomas/postwoman/issues/342)
|
||||
- \[request\] Offline cross-platform native build [\#267](https://github.com/liyasthomas/postwoman/issues/267)
|
||||
- On Save Update existing API [\#204](https://github.com/liyasthomas/postwoman/issues/204)
|
||||
- Import and export environments from JSON [\#190](https://github.com/liyasthomas/postwoman/issues/190)
|
||||
- Fast URL entry [\#62](https://github.com/liyasthomas/postwoman/issues/62)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Add application/vnd.api+json [\#695](https://github.com/liyasthomas/postwoman/pull/695) ([allthesignals](https://github.com/allthesignals))
|
||||
- Fix raw input \(JSON\) [\#693](https://github.com/liyasthomas/postwoman/pull/693) ([leomp12](https://github.com/leomp12))
|
||||
- chore\(deps\): bump firebase from 7.11.0 to 7.12.0 [\#689](https://github.com/liyasthomas/postwoman/pull/689) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump vuefire from 2.2.1 to 2.2.2 [\#688](https://github.com/liyasthomas/postwoman/pull/688) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 4.1.0 to 4.2.0 [\#685](https://github.com/liyasthomas/postwoman/pull/685) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump start-server-and-test from 1.10.10 to 1.10.11 [\#684](https://github.com/liyasthomas/postwoman/pull/684) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.6.0 to 6.6.1 [\#683](https://github.com/liyasthomas/postwoman/pull/683) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt from 2.11.0 to 2.12.0 [\#682](https://github.com/liyasthomas/postwoman/pull/682) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Fix setting default raw params [\#681](https://github.com/liyasthomas/postwoman/pull/681) ([leomp12](https://github.com/leomp12))
|
||||
- Fix handling content type and raw input [\#678](https://github.com/liyasthomas/postwoman/pull/678) ([leomp12](https://github.com/leomp12))
|
||||
- \[Snyk\] Security upgrade yargs-parser from 18.1.0 to 18.1.1 [\#674](https://github.com/liyasthomas/postwoman/pull/674) ([snyk-bot](https://github.com/snyk-bot))
|
||||
- chore\(deps-dev\): bump start-server-and-test from 1.10.9 to 1.10.10 [\#672](https://github.com/liyasthomas/postwoman/pull/672) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.10.0 to 7.11.0 [\#671](https://github.com/liyasthomas/postwoman/pull/671) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ✅ Updating tests [\#669](https://github.com/liyasthomas/postwoman/pull/669) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Updating tests [\#668](https://github.com/liyasthomas/postwoman/pull/668) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- APIs [\#667](https://github.com/liyasthomas/postwoman/pull/667) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Insecure Websocket connection issue while connecting to MQTT broker. [\#666](https://github.com/liyasthomas/postwoman/pull/666) ([rahulnpadalkar](https://github.com/rahulnpadalkar))
|
||||
- Improving performance [\#664](https://github.com/liyasthomas/postwoman/pull/664) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Feature/mqtt [\#663](https://github.com/liyasthomas/postwoman/pull/663) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Added Support for MQTT [\#662](https://github.com/liyasthomas/postwoman/pull/662) ([rahulnpadalkar](https://github.com/rahulnpadalkar))
|
||||
- chore\(deps\): bump yargs-parser from 18.0.0 to 18.1.0 [\#660](https://github.com/liyasthomas/postwoman/pull/660) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump start-server-and-test from 1.10.8 to 1.10.9 [\#659](https://github.com/liyasthomas/postwoman/pull/659) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added icon slot to tabs [\#657](https://github.com/liyasthomas/postwoman/pull/657) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Refactor/ui [\#655](https://github.com/liyasthomas/postwoman/pull/655) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- even [\#654](https://github.com/liyasthomas/postwoman/pull/654) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump yargs-parser from 17.0.0 to 18.0.0 [\#653](https://github.com/liyasthomas/postwoman/pull/653) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.9.3 to 7.10.0 [\#652](https://github.com/liyasthomas/postwoman/pull/652) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added the ability to prettify GraphQL queries [\#650](https://github.com/liyasthomas/postwoman/pull/650) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Add http/https support to socketio url valid [\#648](https://github.com/liyasthomas/postwoman/pull/648) ([moonrailgun](https://github.com/moonrailgun))
|
||||
- Refactor/ui [\#647](https://github.com/liyasthomas/postwoman/pull/647) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Even [\#646](https://github.com/liyasthomas/postwoman/pull/646) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Run tests even after failed request [\#645](https://github.com/liyasthomas/postwoman/pull/645) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Feature: add socket io support [\#640](https://github.com/liyasthomas/postwoman/pull/640) ([moonrailgun](https://github.com/moonrailgun))
|
||||
- Removed linting for the collection docs import editor [\#639](https://github.com/liyasthomas/postwoman/pull/639) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Moving or renaming files [\#638](https://github.com/liyasthomas/postwoman/pull/638) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Refactor/ui [\#636](https://github.com/liyasthomas/postwoman/pull/636) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Updated messages for when GraphQL Get Schema fails [\#633](https://github.com/liyasthomas/postwoman/pull/633) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Minor GraphQL page improvements [\#631](https://github.com/liyasthomas/postwoman/pull/631) ([dmitryyankowski](https://github.com/dmitryyankowski))
|
||||
- Ignore empty GQL Variable Strings [\#629](https://github.com/liyasthomas/postwoman/pull/629) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump cypress from 4.0.2 to 4.1.0 [\#628](https://github.com/liyasthomas/postwoman/pull/628) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.5.0 to 6.6.0 [\#627](https://github.com/liyasthomas/postwoman/pull/627) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.9.1 to 7.9.3 [\#626](https://github.com/liyasthomas/postwoman/pull/626) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/google-tag-manager from 2.3.1 to 2.3.2 [\#625](https://github.com/liyasthomas/postwoman/pull/625) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- test: purge travis [\#623](https://github.com/liyasthomas/postwoman/pull/623) ([yubathom](https://github.com/yubathom))
|
||||
- Added shortcut to quickly run the GraphQL query [\#620](https://github.com/liyasthomas/postwoman/pull/620) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- docs: add dmitryyankowski as a contributor [\#619](https://github.com/liyasthomas/postwoman/pull/619) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- Link multiple auth providers [\#618](https://github.com/liyasthomas/postwoman/pull/618) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Add --staged parameter to pretty-quick pre-commit [\#617](https://github.com/liyasthomas/postwoman/pull/617) ([dmitryyankowski](https://github.com/dmitryyankowski))
|
||||
- :bug: FIxed URI not updating on Clear content, minor formData improve… [\#612](https://github.com/liyasthomas/postwoman/pull/612) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Update proxy information. [\#610](https://github.com/liyasthomas/postwoman/pull/610) ([NBTX](https://github.com/NBTX))
|
||||
- Fixed install extension toast appearing even when extension is installed [\#609](https://github.com/liyasthomas/postwoman/pull/609) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump lint-staged from 10.0.7 to 10.0.8 [\#606](https://github.com/liyasthomas/postwoman/pull/606) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- JSON linting in the code editor [\#605](https://github.com/liyasthomas/postwoman/pull/605) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Added regex to handle url parts [\#603](https://github.com/liyasthomas/postwoman/pull/603) ([JacobAnavisca](https://github.com/JacobAnavisca))
|
||||
- GraphQL page improvements, and more [\#602](https://github.com/liyasthomas/postwoman/pull/602) ([dmitryyankowski](https://github.com/dmitryyankowski))
|
||||
- I18n [\#601](https://github.com/liyasthomas/postwoman/pull/601) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- feat\(i18n\): add Korean [\#600](https://github.com/liyasthomas/postwoman/pull/600) ([9j](https://github.com/9j))
|
||||
- Improve page load/unload experience \(remove FOUCs\) [\#599](https://github.com/liyasthomas/postwoman/pull/599) ([NBTX](https://github.com/NBTX))
|
||||
- Feature: Add prettier/pretty-quick formatting w/ Husky pre-commit [\#596](https://github.com/liyasthomas/postwoman/pull/596) ([dmitryyankowski](https://github.com/dmitryyankowski))
|
||||
|
||||
## [v1.9.0](https://github.com/liyasthomas/postwoman/tree/v1.9.0) (2020-02-24)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.8.0...v1.9.0)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Auto Theme Selection is kinda difficult to see [\#563](https://github.com/liyasthomas/postwoman/issues/563)
|
||||
- Can't send request to localhost via Chrome extention [\#560](https://github.com/liyasthomas/postwoman/issues/560)
|
||||
- Validation for duplicate collection ignores letter case [\#547](https://github.com/liyasthomas/postwoman/issues/547)
|
||||
- Build failed [\#327](https://github.com/liyasthomas/postwoman/issues/327)
|
||||
- Fixed typo in translation file for Auto theme [\#556](https://github.com/liyasthomas/postwoman/pull/556) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- don't run [\#577](https://github.com/liyasthomas/postwoman/issues/577)
|
||||
- Get correct response data but occurs with error "Cannot read property 'value' of undefined" [\#575](https://github.com/liyasthomas/postwoman/issues/575)
|
||||
- firebase_app\_\_WEBPACK_IMPORTED_MODULE_2\_\_\_default.a.firestore is not a function [\#558](https://github.com/liyasthomas/postwoman/issues/558)
|
||||
- Disable SSL cert for websockets [\#557](https://github.com/liyasthomas/postwoman/issues/557)
|
||||
- relative module not found during start [\#552](https://github.com/liyasthomas/postwoman/issues/552)
|
||||
- Feature Request: Subfolders [\#540](https://github.com/liyasthomas/postwoman/issues/540)
|
||||
- Feature request: Keyboard shortcuts for folder creation [\#539](https://github.com/liyasthomas/postwoman/issues/539)
|
||||
- Add max-height and overflow: auto to "parameter list" textarea [\#532](https://github.com/liyasthomas/postwoman/issues/532)
|
||||
- Friendly minded GraphQL [\#468](https://github.com/liyasthomas/postwoman/issues/468)
|
||||
- multipart/form-data support [\#434](https://github.com/liyasthomas/postwoman/issues/434)
|
||||
- IE Support [\#386](https://github.com/liyasthomas/postwoman/issues/386)
|
||||
- ⏬ Import a Postman's Collection [\#333](https://github.com/liyasthomas/postwoman/issues/333)
|
||||
- Implement pre-request and post-request scripts \(and request chaining\) [\#218](https://github.com/liyasthomas/postwoman/issues/218)
|
||||
- Environment management and configuration [\#147](https://github.com/liyasthomas/postwoman/issues/147)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- POST request body editor reacts to the content type [\#594](https://github.com/liyasthomas/postwoman/pull/594) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Fix variablesJSONString store default for GraphQL page [\#593](https://github.com/liyasthomas/postwoman/pull/593) ([dmitryyankowski](https://github.com/dmitryyankowski))
|
||||
- Environment Mangement [\#591](https://github.com/liyasthomas/postwoman/pull/591) ([JacobAnavisca](https://github.com/JacobAnavisca))
|
||||
- GraphQL Query Autocompletion [\#590](https://github.com/liyasthomas/postwoman/pull/590) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Refactor/lint [\#589](https://github.com/liyasthomas/postwoman/pull/589) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Even [\#588](https://github.com/liyasthomas/postwoman/pull/588) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump firebase from 7.9.0 to 7.9.1 [\#587](https://github.com/liyasthomas/postwoman/pull/587) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Even [\#586](https://github.com/liyasthomas/postwoman/pull/586) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump firebase from 7.8.2 to 7.9.0 [\#585](https://github.com/liyasthomas/postwoman/pull/585) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump vue-virtual-scroll-list from 1.4.5 to 1.4.6 [\#584](https://github.com/liyasthomas/postwoman/pull/584) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Adapt extension check to new extensions [\#583](https://github.com/liyasthomas/postwoman/pull/583) ([levrik](https://github.com/levrik))
|
||||
- Update link to extension repo in README [\#582](https://github.com/liyasthomas/postwoman/pull/582) ([levrik](https://github.com/levrik))
|
||||
- Even [\#579](https://github.com/liyasthomas/postwoman/pull/579) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Refactor/lint [\#578](https://github.com/liyasthomas/postwoman/pull/578) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump vue-virtual-scroll-list from 1.4.4 to 1.4.5 [\#576](https://github.com/liyasthomas/postwoman/pull/576) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Postman collection parsing [\#574](https://github.com/liyasthomas/postwoman/pull/574) ([JacobAnavisca](https://github.com/JacobAnavisca))
|
||||
- Unify Chrome and Firefox extensions [\#573](https://github.com/liyasthomas/postwoman/pull/573) ([levrik](https://github.com/levrik))
|
||||
- fix: drop the toast which doesn't show up [\#572](https://github.com/liyasthomas/postwoman/pull/572) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- :sparkles: Native share + updated meta description [\#571](https://github.com/liyasthomas/postwoman/pull/571) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump firebase from 7.8.1 to 7.8.2 [\#570](https://github.com/liyasthomas/postwoman/pull/570) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 4.0.1 to 4.0.2 [\#569](https://github.com/liyasthomas/postwoman/pull/569) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added create collection and save request syncs [\#568](https://github.com/liyasthomas/postwoman/pull/568) ([JacobAnavisca](https://github.com/JacobAnavisca))
|
||||
- Moved common headers to a separate file [\#566](https://github.com/liyasthomas/postwoman/pull/566) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(deps-dev\): bump cypress from 4.0.0 to 4.0.1 [\#565](https://github.com/liyasthomas/postwoman/pull/565) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump yargs-parser from 16.1.0 to 17.0.0 [\#564](https://github.com/liyasthomas/postwoman/pull/564) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 3.8.3 to 4.0.0 [\#562](https://github.com/liyasthomas/postwoman/pull/562) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump firebase from 7.8.0 to 7.8.1 [\#561](https://github.com/liyasthomas/postwoman/pull/561) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore: use typeof as an operator and make use of localizable strings [\#559](https://github.com/liyasthomas/postwoman/pull/559) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- Support for Formdata [\#555](https://github.com/liyasthomas/postwoman/pull/555) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump @nuxtjs/pwa from 3.0.0-beta.19 to 3.0.0-beta.20 [\#554](https://github.com/liyasthomas/postwoman/pull/554) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.4 to 5.9.5 [\#553](https://github.com/liyasthomas/postwoman/pull/553) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added toggle to decide whether extensions should be used [\#551](https://github.com/liyasthomas/postwoman/pull/551) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Show Ctrl instead of Command for shortcuts non-Apple platforms [\#549](https://github.com/liyasthomas/postwoman/pull/549) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- fix\(chore\): Take letter casing into account while checking for duplicate collection [\#548](https://github.com/liyasthomas/postwoman/pull/548) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- update e2e tests [\#546](https://github.com/liyasthomas/postwoman/pull/546) ([yubathom](https://github.com/yubathom))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.3 to 5.9.4 [\#545](https://github.com/liyasthomas/postwoman/pull/545) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Refactor [\#543](https://github.com/liyasthomas/postwoman/pull/543) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump firebase from 7.7.0 to 7.8.0 [\#542](https://github.com/liyasthomas/postwoman/pull/542) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump graphql from 14.5.8 to 14.6.0 [\#541](https://github.com/liyasthomas/postwoman/pull/541) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- i18n [\#538](https://github.com/liyasthomas/postwoman/pull/538) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Modification of French translations [\#537](https://github.com/liyasthomas/postwoman/pull/537) ([thomasbnt](https://github.com/thomasbnt))
|
||||
- Even [\#535](https://github.com/liyasthomas/postwoman/pull/535) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Updated GraphQL Query Variable Editor [\#534](https://github.com/liyasthomas/postwoman/pull/534) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Updating spanish translation [\#529](https://github.com/liyasthomas/postwoman/pull/529) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- even merge [\#528](https://github.com/liyasthomas/postwoman/pull/528) ([liyasthomas](https://github.com/liyasthomas))
|
||||
|
||||
## [v1.8.0](https://github.com/liyasthomas/postwoman/tree/v1.8.0) (2020-01-28)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.5.0...v1.8.0)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Warn the user if name field was left blank while creating a new collection [\#515](https://github.com/liyasthomas/postwoman/issues/515)
|
||||
- Multiple collections with the same name shouldn't exist [\#509](https://github.com/liyasthomas/postwoman/issues/509)
|
||||
- GraphQL String variables are null [\#497](https://github.com/liyasthomas/postwoman/issues/497)
|
||||
- Post request body is empty [\#473](https://github.com/liyasthomas/postwoman/issues/473)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Allow importing Postman collections [\#524](https://github.com/liyasthomas/postwoman/issues/524)
|
||||
- Request descriptions [\#511](https://github.com/liyasthomas/postwoman/issues/511)
|
||||
- Sync collection with a cloud storage \(e.g: Google drive\) [\#507](https://github.com/liyasthomas/postwoman/issues/507)
|
||||
- Ability to run all requests of a folder/collection [\#498](https://github.com/liyasthomas/postwoman/issues/498)
|
||||
- Change import/export collection on requests page icon [\#495](https://github.com/liyasthomas/postwoman/issues/495)
|
||||
- Application contains many hard-coded strings that aren't translatable [\#488](https://github.com/liyasthomas/postwoman/issues/488)
|
||||
- import cURL error [\#477](https://github.com/liyasthomas/postwoman/issues/477)
|
||||
- move to postwoman org [\#475](https://github.com/liyasthomas/postwoman/issues/475)
|
||||
- Create standalone vue components of the request builder. [\#474](https://github.com/liyasthomas/postwoman/issues/474)
|
||||
- ULR parsing and var auto creation [\#469](https://github.com/liyasthomas/postwoman/issues/469)
|
||||
- What about additional loaders: + Pug, TypeScript, SASS, material-vue ? [\#467](https://github.com/liyasthomas/postwoman/issues/467)
|
||||
- \[suggestion\] - Tests tab [\#465](https://github.com/liyasthomas/postwoman/issues/465)
|
||||
- cookie not found support [\#443](https://github.com/liyasthomas/postwoman/issues/443)
|
||||
- Feature Request: Consumer Driven Contract Testing [\#420](https://github.com/liyasthomas/postwoman/issues/420)
|
||||
- Feature Request: Support OAuth2/OIDC [\#337](https://github.com/liyasthomas/postwoman/issues/337)
|
||||
- Enable running proxy as a backend for Request Capture [\#325](https://github.com/liyasthomas/postwoman/issues/325)
|
||||
- Label doesn't change when switching between collection requests [\#291](https://github.com/liyasthomas/postwoman/issues/291)
|
||||
- Add DB cache [\#26](https://github.com/liyasthomas/postwoman/issues/26)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Enhancements [\#531](https://github.com/liyasthomas/postwoman/pull/531) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- Merge pull request \#530 from liyasthomas/feature/post-request-tests [\#530](https://github.com/liyasthomas/postwoman/pull/530) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Refactor [\#523](https://github.com/liyasthomas/postwoman/pull/523) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- chore\(deps\): bump nuxt-i18n from 6.4.1 to 6.5.0 [\#522](https://github.com/liyasthomas/postwoman/pull/522) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump v-tooltip from 2.0.2 to 2.0.3 [\#521](https://github.com/liyasthomas/postwoman/pull/521) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump cypress from 3.8.2 to 3.8.3 [\#520](https://github.com/liyasthomas/postwoman/pull/520) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Feature/post request tests [\#518](https://github.com/liyasthomas/postwoman/pull/518) ([nickpalenchar](https://github.com/nickpalenchar))
|
||||
- Validations for edit and create collections activity [\#516](https://github.com/liyasthomas/postwoman/pull/516) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- Auth [\#513](https://github.com/liyasthomas/postwoman/pull/513) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Support for Google Chrome Extension [\#512](https://github.com/liyasthomas/postwoman/pull/512) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Validate duplicate collections [\#510](https://github.com/liyasthomas/postwoman/pull/510) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- GraphQL query validation based on schema [\#508](https://github.com/liyasthomas/postwoman/pull/508) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Lint and refactor [\#506](https://github.com/liyasthomas/postwoman/pull/506) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Syntax Error marking in GraphQL query editor [\#505](https://github.com/liyasthomas/postwoman/pull/505) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Merge pull request \#504 from liyasthomas/dependabot/npm_and_yarn/node-sass-4.13.1 [\#504](https://github.com/liyasthomas/postwoman/pull/504) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump @nuxtjs/axios from 5.9.2 to 5.9.3 [\#503](https://github.com/liyasthomas/postwoman/pull/503) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps-dev\): bump sass-loader from 8.0.1 to 8.0.2 [\#502](https://github.com/liyasthomas/postwoman/pull/502) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- chore\(deps\): bump ace-builds from 1.4.7 to 1.4.8 [\#501](https://github.com/liyasthomas/postwoman/pull/501) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Refactoring proxy handling to be done in strategies [\#500](https://github.com/liyasthomas/postwoman/pull/500) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- 💚 Fixed \#497 [\#499](https://github.com/liyasthomas/postwoman/pull/499) ([pushrbx](https://github.com/pushrbx))
|
||||
- Feat/firefox strategy [\#496](https://github.com/liyasthomas/postwoman/pull/496) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Firefox Extension compatibility [\#494](https://github.com/liyasthomas/postwoman/pull/494) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- i18n Japanese: Added new translations [\#492](https://github.com/liyasthomas/postwoman/pull/492) ([reefqi037](https://github.com/reefqi037))
|
||||
- Merge pull request \#491 from liyasthomas/i18n [\#491](https://github.com/liyasthomas/postwoman/pull/491) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Replaced hard-coded strings with localizable strings [\#490](https://github.com/liyasthomas/postwoman/pull/490) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Network Strategies [\#487](https://github.com/liyasthomas/postwoman/pull/487) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- chore\(oauth\): Added method signatures as per JSDoc conventions [\#486](https://github.com/liyasthomas/postwoman/pull/486) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- chore: Minor tweaks [\#485](https://github.com/liyasthomas/postwoman/pull/485) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- ⬆️ Bump cypress from 3.8.1 to 3.8.2 [\#483](https://github.com/liyasthomas/postwoman/pull/483) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump sass-loader from 8.0.0 to 8.0.1 [\#482](https://github.com/liyasthomas/postwoman/pull/482) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump @nuxtjs/google-analytics from 2.2.2 to 2.2.3 [\#481](https://github.com/liyasthomas/postwoman/pull/481) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- GraphQL Type Highlight and Links [\#479](https://github.com/liyasthomas/postwoman/pull/479) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- OAuth 2.0/OIDC Access Token Retrieval Support [\#476](https://github.com/liyasthomas/postwoman/pull/476) ([reefqi037](https://github.com/reefqi037))
|
||||
|
||||
## [v1.5.0](https://github.com/liyasthomas/postwoman/tree/v1.5.0) (2020-01-04)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v1.0.0...v1.5.0)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- WebSocket page freezes when pasting long URL [\#471](https://github.com/liyasthomas/postwoman/issues/471)
|
||||
- API Documentation won't be generated [\#456](https://github.com/liyasthomas/postwoman/issues/456)
|
||||
- Sharing Requests via link is not working [\#435](https://github.com/liyasthomas/postwoman/issues/435)
|
||||
- URL input text is so stutters [\#412](https://github.com/liyasthomas/postwoman/issues/412)
|
||||
- Save to collections after deleting all the collections causes an error page [\#390](https://github.com/liyasthomas/postwoman/issues/390)
|
||||
- Bearer token doesn’t work with CORS when only authorization header is allowed [\#353](https://github.com/liyasthomas/postwoman/issues/353)
|
||||
- Make the UI more compact [\#314](https://github.com/liyasthomas/postwoman/issues/314)
|
||||
- Allow reserved characters on websocket URI [\#289](https://github.com/liyasthomas/postwoman/issues/289)
|
||||
- Unable to edit or delete row in history table [\#281](https://github.com/liyasthomas/postwoman/issues/281)
|
||||
- Allow url request with `/` at eol [\#275](https://github.com/liyasthomas/postwoman/issues/275)
|
||||
- \[request\] localhost support [\#274](https://github.com/liyasthomas/postwoman/issues/274)
|
||||
- Code generation for Fetch request type of some methods \(POST, PUT, PATCH\) won't be shown [\#268](https://github.com/liyasthomas/postwoman/issues/268)
|
||||
- \[BUG\] \[UI\] \[Mobile\] Get results not scrollable. [\#266](https://github.com/liyasthomas/postwoman/issues/266)
|
||||
- POSTing large raw JSON packets [\#265](https://github.com/liyasthomas/postwoman/issues/265)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Can WSDL be implemented, similar to SoapUI? [\#461](https://github.com/liyasthomas/postwoman/issues/461)
|
||||
- Module not found: Error: Can't resolve '../.postwoman/version.json' [\#457](https://github.com/liyasthomas/postwoman/issues/457)
|
||||
- \* ../.postwoman/version.json in ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./layouts/default.vue?vue&type=script&lang=js& friendly-errors 11:12:37 [\#448](https://github.com/liyasthomas/postwoman/issues/448)
|
||||
- Raw Request Body should be supported to format the JSON string [\#446](https://github.com/liyasthomas/postwoman/issues/446)
|
||||
- npm run dev module was not found: ../.postwoman/version.json [\#442](https://github.com/liyasthomas/postwoman/issues/442)
|
||||
- graphql and websocket work, but http and https do not [\#441](https://github.com/liyasthomas/postwoman/issues/441)
|
||||
- Can I test localhost? [\#433](https://github.com/liyasthomas/postwoman/issues/433)
|
||||
- No 'Access-Control-Allow-Origin' [\#426](https://github.com/liyasthomas/postwoman/issues/426)
|
||||
- When uninstall the PWA the "install PWA" link in postwoman.io isn't appear anymore [\#419](https://github.com/liyasthomas/postwoman/issues/419)
|
||||
- Toggling options will reset the UI to English [\#417](https://github.com/liyasthomas/postwoman/issues/417)
|
||||
- Ability to send Binary data using Postwoman [\#415](https://github.com/liyasthomas/postwoman/issues/415)
|
||||
- Oh my dear god why don't we just wrap it in electron [\#413](https://github.com/liyasthomas/postwoman/issues/413)
|
||||
- UI improvement suggestion for request method drop down [\#409](https://github.com/liyasthomas/postwoman/issues/409)
|
||||
- Can I share a request with my team? [\#408](https://github.com/liyasthomas/postwoman/issues/408)
|
||||
- Does it not support the post method? [\#403](https://github.com/liyasthomas/postwoman/issues/403)
|
||||
- Post can't send request [\#401](https://github.com/liyasthomas/postwoman/issues/401)
|
||||
- \[Bug\] fix header icons overlap [\#399](https://github.com/liyasthomas/postwoman/issues/399)
|
||||
- Custom request method [\#398](https://github.com/liyasthomas/postwoman/issues/398)
|
||||
- Improve translate for pt-BR \(i18n\) [\#395](https://github.com/liyasthomas/postwoman/issues/395)
|
||||
- Raw input disabled is not working properly [\#394](https://github.com/liyasthomas/postwoman/issues/394)
|
||||
- Input area is not clearly to identify for users because the dark mode [\#393](https://github.com/liyasthomas/postwoman/issues/393)
|
||||
- \[UX\] Setting to make sidebar buttons small [\#389](https://github.com/liyasthomas/postwoman/issues/389)
|
||||
- \[UX\] Improve responsive breaking points [\#388](https://github.com/liyasthomas/postwoman/issues/388)
|
||||
- \[UX\] Hide history/collections [\#387](https://github.com/liyasthomas/postwoman/issues/387)
|
||||
- Clearing shortcut overrides browser default [\#374](https://github.com/liyasthomas/postwoman/issues/374)
|
||||
- Proxy server default configuration [\#373](https://github.com/liyasthomas/postwoman/issues/373)
|
||||
- Intent to translate [\#367](https://github.com/liyasthomas/postwoman/issues/367)
|
||||
- \[request\]: CLI possibilities [\#363](https://github.com/liyasthomas/postwoman/issues/363)
|
||||
- Feature request: OAuth header support/integration [\#358](https://github.com/liyasthomas/postwoman/issues/358)
|
||||
- Static builds on releases [\#352](https://github.com/liyasthomas/postwoman/issues/352)
|
||||
- fix:SSE onclose handle [\#349](https://github.com/liyasthomas/postwoman/issues/349)
|
||||
- i18n support [\#348](https://github.com/liyasthomas/postwoman/issues/348)
|
||||
- Separate layers in dockerfile to improve image build [\#339](https://github.com/liyasthomas/postwoman/issues/339)
|
||||
- Internal server environment usage requirements [\#336](https://github.com/liyasthomas/postwoman/issues/336)
|
||||
- Server Sent Events [\#329](https://github.com/liyasthomas/postwoman/issues/329)
|
||||
- Generate API documentation [\#326](https://github.com/liyasthomas/postwoman/issues/326)
|
||||
- \[Request\] Use responses for next request? [\#324](https://github.com/liyasthomas/postwoman/issues/324)
|
||||
- Auth info on WebSocket connections [\#321](https://github.com/liyasthomas/postwoman/issues/321)
|
||||
- Set response panel to fullscreen [\#320](https://github.com/liyasthomas/postwoman/issues/320)
|
||||
- Graphql support [\#312](https://github.com/liyasthomas/postwoman/issues/312)
|
||||
- Keyboard shortcuts [\#302](https://github.com/liyasthomas/postwoman/issues/302)
|
||||
- File/binary request body support [\#298](https://github.com/liyasthomas/postwoman/issues/298)
|
||||
- Make response body area expandable [\#294](https://github.com/liyasthomas/postwoman/issues/294)
|
||||
- It's possible to tab into read only and non-form elements [\#287](https://github.com/liyasthomas/postwoman/issues/287)
|
||||
- Change cursor to disabled on disabled inputs [\#286](https://github.com/liyasthomas/postwoman/issues/286)
|
||||
- Hover Styling on Inputs [\#285](https://github.com/liyasthomas/postwoman/issues/285)
|
||||
- Focus Styles on Buttons [\#284](https://github.com/liyasthomas/postwoman/issues/284)
|
||||
- Mobile can't see console for request errors [\#283](https://github.com/liyasthomas/postwoman/issues/283)
|
||||
- Missing Focus on Inputs [\#279](https://github.com/liyasthomas/postwoman/issues/279)
|
||||
- Download the request result into a file. [\#278](https://github.com/liyasthomas/postwoman/issues/278)
|
||||
- Improve UI Contrast [\#277](https://github.com/liyasthomas/postwoman/issues/277)
|
||||
- Duplicated query string in generated code [\#272](https://github.com/liyasthomas/postwoman/issues/272)
|
||||
- Query parameters are duplicated [\#271](https://github.com/liyasthomas/postwoman/issues/271)
|
||||
- Generated code is incorrect [\#269](https://github.com/liyasthomas/postwoman/issues/269)
|
||||
- \[UI\] \[UX\] Allow app to take width of browser [\#236](https://github.com/liyasthomas/postwoman/issues/236)
|
||||
- Extend syntax highlighting with ACE for pre-request script textarea [\#235](https://github.com/liyasthomas/postwoman/issues/235)
|
||||
- Store pre-request scripts in history [\#233](https://github.com/liyasthomas/postwoman/issues/233)
|
||||
- Lacking documentation and wiki [\#232](https://github.com/liyasthomas/postwoman/issues/232)
|
||||
- Store the time spent on fetching a response [\#225](https://github.com/liyasthomas/postwoman/issues/225)
|
||||
- I can't send POST method [\#210](https://github.com/liyasthomas/postwoman/issues/210)
|
||||
- Cache view [\#188](https://github.com/liyasthomas/postwoman/issues/188)
|
||||
- Handling request failures when build number is obtained from GitHub [\#122](https://github.com/liyasthomas/postwoman/issues/122)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- ⬆️ Bump @nuxtjs/axios from 5.9.0 to 5.9.2 [\#472](https://github.com/liyasthomas/postwoman/pull/472) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added variables to graphql page. [\#464](https://github.com/liyasthomas/postwoman/pull/464) ([pushrbx](https://github.com/pushrbx))
|
||||
- i18n [\#463](https://github.com/liyasthomas/postwoman/pull/463) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- ⬆️ Bump cypress from 3.8.0 to 3.8.1 [\#460](https://github.com/liyasthomas/postwoman/pull/460) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- i18n\(de-DE\): improve some translations [\#459](https://github.com/liyasthomas/postwoman/pull/459) ([gabschne](https://github.com/gabschne))
|
||||
- bn-BD i18n [\#455](https://github.com/liyasthomas/postwoman/pull/455) ([hmtanbir](https://github.com/hmtanbir))
|
||||
- API documentation page [\#451](https://github.com/liyasthomas/postwoman/pull/451) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- ⬆️ Bump @nuxtjs/axios from 5.8.0 to 5.9.0 [\#450](https://github.com/liyasthomas/postwoman/pull/450) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump nuxt from 2.10.2 to 2.11.0 [\#449](https://github.com/liyasthomas/postwoman/pull/449) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Various UI tweaks [\#439](https://github.com/liyasthomas/postwoman/pull/439) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- i18n [\#438](https://github.com/liyasthomas/postwoman/pull/438) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Burmese translation added [\#437](https://github.com/liyasthomas/postwoman/pull/437) ([ZattWine](https://github.com/ZattWine))
|
||||
- chore: stick to Vue.js best practices [\#432](https://github.com/liyasthomas/postwoman/pull/432) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- Styled select input [\#431](https://github.com/liyasthomas/postwoman/pull/431) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Bumped dependencies and Improved UI contrast [\#430](https://github.com/liyasthomas/postwoman/pull/430) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- ⬆️ Bump cypress from 3.7.0 to 3.8.0 [\#429](https://github.com/liyasthomas/postwoman/pull/429) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- I18n [\#428](https://github.com/liyasthomas/postwoman/pull/428) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- I18n Japanese translation added [\#427](https://github.com/liyasthomas/postwoman/pull/427) ([reefqi037](https://github.com/reefqi037))
|
||||
- Even [\#424](https://github.com/liyasthomas/postwoman/pull/424) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- I18n [\#423](https://github.com/liyasthomas/postwoman/pull/423) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- I18n German translation added [\#422](https://github.com/liyasthomas/postwoman/pull/422) ([NJannasch](https://github.com/NJannasch))
|
||||
- Header key autocompletion [\#421](https://github.com/liyasthomas/postwoman/pull/421) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Update id-ID.js [\#416](https://github.com/liyasthomas/postwoman/pull/416) ([williamsp](https://github.com/williamsp))
|
||||
- Improving translation for id-ID [\#414](https://github.com/liyasthomas/postwoman/pull/414) ([williamsp](https://github.com/williamsp))
|
||||
- Fixing bug on request saving [\#410](https://github.com/liyasthomas/postwoman/pull/410) ([adevr](https://github.com/adevr))
|
||||
- ⬆️ Bump @nuxtjs/google-analytics from 2.2.1 to 2.2.2 [\#407](https://github.com/liyasthomas/postwoman/pull/407) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump vue-virtual-scroll-list from 1.4.3 to 1.4.4 [\#406](https://github.com/liyasthomas/postwoman/pull/406) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump nuxt-i18n from 6.4.0 to 6.4.1 [\#405](https://github.com/liyasthomas/postwoman/pull/405) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- I18n [\#404](https://github.com/liyasthomas/postwoman/pull/404) ([yubathom](https://github.com/yubathom))
|
||||
- Custom methods support [\#400](https://github.com/liyasthomas/postwoman/pull/400) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- App UI [\#391](https://github.com/liyasthomas/postwoman/pull/391) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- i18n [\#383](https://github.com/liyasthomas/postwoman/pull/383) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Added Turkish Language Support [\#382](https://github.com/liyasthomas/postwoman/pull/382) ([AliAnilKocak](https://github.com/AliAnilKocak))
|
||||
- Translated new words to Farsi lang [\#380](https://github.com/liyasthomas/postwoman/pull/380) ([hosseinnedaee](https://github.com/hosseinnedaee))
|
||||
- Two Way Data Binding \(v-model\) to Ace Editor component [\#379](https://github.com/liyasthomas/postwoman/pull/379) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- fix: twitter summary card image url [\#378](https://github.com/liyasthomas/postwoman/pull/378) ([peterpeterparker](https://github.com/peterpeterparker))
|
||||
- Added nav shortcuts to GraphQL query and response, updated GraphQL shortcut icons [\#377](https://github.com/liyasthomas/postwoman/pull/377) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Bump cypress from 3.6.1 to 3.7.0 [\#376](https://github.com/liyasthomas/postwoman/pull/376) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Bump vuex-persist from 2.1.1 to 2.2.0 [\#375](https://github.com/liyasthomas/postwoman/pull/375) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- I18n [\#372](https://github.com/liyasthomas/postwoman/pull/372) ([EdikWang](https://github.com/EdikWang))
|
||||
- Use GraphQL logo for GraphQL tab [\#371](https://github.com/liyasthomas/postwoman/pull/371) ([NBTX](https://github.com/NBTX))
|
||||
- Update i18n keywords for Bahasa Indonesia [\#369](https://github.com/liyasthomas/postwoman/pull/369) ([wahwahid](https://github.com/wahwahid))
|
||||
- Intent to translate to Spanish on I18n [\#368](https://github.com/liyasthomas/postwoman/pull/368) ([adlpaf](https://github.com/adlpaf))
|
||||
- I18n [\#366](https://github.com/liyasthomas/postwoman/pull/366) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Add translations for FR/EN catalogues [\#364](https://github.com/liyasthomas/postwoman/pull/364) ([LaurentBrieu](https://github.com/LaurentBrieu))
|
||||
- Added Bahasa Indonesia language support [\#362](https://github.com/liyasthomas/postwoman/pull/362) ([wahwahid](https://github.com/wahwahid))
|
||||
- i18n [\#361](https://github.com/liyasthomas/postwoman/pull/361) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Add Simplified Chinese language [\#360](https://github.com/liyasthomas/postwoman/pull/360) ([EdikWang](https://github.com/EdikWang))
|
||||
- Added Brazilian Portuguese language support [\#359](https://github.com/liyasthomas/postwoman/pull/359) ([tetri](https://github.com/tetri))
|
||||
- Added Farsi language support [\#357](https://github.com/liyasthomas/postwoman/pull/357) ([hosseinnedaee](https://github.com/hosseinnedaee))
|
||||
- Adding french language basic [\#355](https://github.com/liyasthomas/postwoman/pull/355) ([thomasbnt](https://github.com/thomasbnt))
|
||||
- Basic i18n support [\#351](https://github.com/liyasthomas/postwoman/pull/351) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Undo header/param/body param deletion [\#350](https://github.com/liyasthomas/postwoman/pull/350) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Added ability to run GraphQL queries [\#346](https://github.com/liyasthomas/postwoman/pull/346) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Add Proxy URL option [\#345](https://github.com/liyasthomas/postwoman/pull/345) ([NBTX](https://github.com/NBTX))
|
||||
- ♻️ Refactor Functions [\#344](https://github.com/liyasthomas/postwoman/pull/344) ([athul](https://github.com/athul))
|
||||
- refactor: minor improvements [\#343](https://github.com/liyasthomas/postwoman/pull/343) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
|
||||
## [v1.0.0](https://github.com/liyasthomas/postwoman/tree/v1.0.0) (2019-11-04)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/v0.1.0...v1.0.0)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Bearer Token value still left even after being cleared [\#212](https://github.com/liyasthomas/postwoman/issues/212)
|
||||
- All changes in input fields lost when you switch to another page [\#203](https://github.com/liyasthomas/postwoman/issues/203)
|
||||
- POST request json bodies aren't sent [\#180](https://github.com/liyasthomas/postwoman/issues/180)
|
||||
- Headers turn into 0 : \[Object object\] [\#166](https://github.com/liyasthomas/postwoman/issues/166)
|
||||
- Send Again Button Constantly Flickering [\#157](https://github.com/liyasthomas/postwoman/issues/157)
|
||||
- There are cross-domain problems [\#128](https://github.com/liyasthomas/postwoman/issues/128)
|
||||
- Raw requests are not being sent [\#124](https://github.com/liyasthomas/postwoman/issues/124)
|
||||
- Request Body Is Not Sent [\#113](https://github.com/liyasthomas/postwoman/issues/113)
|
||||
- default menu option - 'Http' is not highlighted when launched from installed pwa app \(UI bug\) [\#100](https://github.com/liyasthomas/postwoman/issues/100)
|
||||
- App is broken with old history in localStorage [\#74](https://github.com/liyasthomas/postwoman/issues/74)
|
||||
- Last added history entry is removed automatically after refresh [\#66](https://github.com/liyasthomas/postwoman/issues/66)
|
||||
- Cannot use localhost as base url [\#56](https://github.com/liyasthomas/postwoman/issues/56)
|
||||
- \[CORS\] No 'Access-Control-Allow-Origin' header is present on the requested resource [\#2](https://github.com/liyasthomas/postwoman/issues/2)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Section labels don't display properly in Firefox [\#237](https://github.com/liyasthomas/postwoman/issues/237)
|
||||
- Unsupported URLs \[BUG\]? [\#229](https://github.com/liyasthomas/postwoman/issues/229)
|
||||
- Credentials are still being included in Permalink even when "Include in URL" is turned off [\#227](https://github.com/liyasthomas/postwoman/issues/227)
|
||||
- Display sendRequest runtime errors in the console [\#206](https://github.com/liyasthomas/postwoman/issues/206)
|
||||
- Chain requests. Execute a bunch of requests one by one and produce results [\#196](https://github.com/liyasthomas/postwoman/issues/196)
|
||||
- Allow User to Choose Whether to Include Authentication in Permalink [\#178](https://github.com/liyasthomas/postwoman/issues/178)
|
||||
- Allow HTTP \(not HTTPS\) on postwoman.io [\#175](https://github.com/liyasthomas/postwoman/issues/175)
|
||||
- Docker-compose in development [\#168](https://github.com/liyasthomas/postwoman/issues/168)
|
||||
- Add Docker [\#164](https://github.com/liyasthomas/postwoman/issues/164)
|
||||
- Missing "Landing/start page" [\#162](https://github.com/liyasthomas/postwoman/issues/162)
|
||||
- Response with content-type "application/hal+json" shows as \[Object object\] [\#158](https://github.com/liyasthomas/postwoman/issues/158)
|
||||
- Clear Input [\#155](https://github.com/liyasthomas/postwoman/issues/155)
|
||||
- A place to discuss [\#149](https://github.com/liyasthomas/postwoman/issues/149)
|
||||
- Inconsistent version name [\#141](https://github.com/liyasthomas/postwoman/issues/141)
|
||||
- introduce some script language to parse the response and pass environment variable as request parameter [\#139](https://github.com/liyasthomas/postwoman/issues/139)
|
||||
- Add links to the footer version and commit sha [\#134](https://github.com/liyasthomas/postwoman/issues/134)
|
||||
- Please add a label for each request. It will be helpful. [\#133](https://github.com/liyasthomas/postwoman/issues/133)
|
||||
- Use 'icon buttons' instead of 'text buttons' [\#130](https://github.com/liyasthomas/postwoman/issues/130)
|
||||
- Change .editorconfig [\#115](https://github.com/liyasthomas/postwoman/issues/115)
|
||||
- \[UX\] Provide Focus State for Buttons, etc. [\#112](https://github.com/liyasthomas/postwoman/issues/112)
|
||||
- Autoresize the textarea [\#102](https://github.com/liyasthomas/postwoman/issues/102)
|
||||
- Content-Type revamping [\#99](https://github.com/liyasthomas/postwoman/issues/99)
|
||||
- Add linter semistandard [\#98](https://github.com/liyasthomas/postwoman/issues/98)
|
||||
- Add version number in footer [\#97](https://github.com/liyasthomas/postwoman/issues/97)
|
||||
- Show "Send" button all over the page or enable hotkeys [\#94](https://github.com/liyasthomas/postwoman/issues/94)
|
||||
- Import request from cURL [\#93](https://github.com/liyasthomas/postwoman/issues/93)
|
||||
- Search on History [\#92](https://github.com/liyasthomas/postwoman/issues/92)
|
||||
- Add support for "application/hal+json" Content-Type [\#88](https://github.com/liyasthomas/postwoman/issues/88)
|
||||
- The query string is built incorrectly when the path contains a parameter [\#87](https://github.com/liyasthomas/postwoman/issues/87)
|
||||
- The history doesn't show a date with the timestamp. [\#81](https://github.com/liyasthomas/postwoman/issues/81)
|
||||
- Option to Copy request as Fetch or XHR Or CURL [\#76](https://github.com/liyasthomas/postwoman/issues/76)
|
||||
- Not working on Brave Browser anymore [\#71](https://github.com/liyasthomas/postwoman/issues/71)
|
||||
- Why da fuq is your name plastered all over the README? [\#70](https://github.com/liyasthomas/postwoman/issues/70)
|
||||
- Comparison with Postman is missing [\#69](https://github.com/liyasthomas/postwoman/issues/69)
|
||||
- Add Tests [\#65](https://github.com/liyasthomas/postwoman/issues/65)
|
||||
- HTTP request with different library [\#61](https://github.com/liyasthomas/postwoman/issues/61)
|
||||
- Editorconfig file [\#60](https://github.com/liyasthomas/postwoman/issues/60)
|
||||
- 500 this.isValidURL is not a function [\#58](https://github.com/liyasthomas/postwoman/issues/58)
|
||||
- Request Headers [\#57](https://github.com/liyasthomas/postwoman/issues/57)
|
||||
- Colored response codes based on status code [\#46](https://github.com/liyasthomas/postwoman/issues/46)
|
||||
- Improve SEO [\#45](https://github.com/liyasthomas/postwoman/issues/45)
|
||||
- Add html preview to response section [\#41](https://github.com/liyasthomas/postwoman/issues/41)
|
||||
- websocket support [\#40](https://github.com/liyasthomas/postwoman/issues/40)
|
||||
- Styling with Tailwindcss [\#38](https://github.com/liyasthomas/postwoman/issues/38)
|
||||
- Not Working in IE 11 [\#37](https://github.com/liyasthomas/postwoman/issues/37)
|
||||
- Raw request body for POST requests and Authorization key/value in Header [\#36](https://github.com/liyasthomas/postwoman/issues/36)
|
||||
- Code highlight on response body [\#33](https://github.com/liyasthomas/postwoman/issues/33)
|
||||
- Template selector [\#32](https://github.com/liyasthomas/postwoman/issues/32)
|
||||
- Vue template [\#31](https://github.com/liyasthomas/postwoman/issues/31)
|
||||
- Add copy response to clipboard button [\#30](https://github.com/liyasthomas/postwoman/issues/30)
|
||||
- Ability to store/share/create collections [\#29](https://github.com/liyasthomas/postwoman/issues/29)
|
||||
- PWA not installable [\#19](https://github.com/liyasthomas/postwoman/issues/19)
|
||||
- Send request on Enter Key press [\#17](https://github.com/liyasthomas/postwoman/issues/17)
|
||||
- Simple Misspelling [\#8](https://github.com/liyasthomas/postwoman/issues/8)
|
||||
- Readable [\#5](https://github.com/liyasthomas/postwoman/issues/5)
|
||||
- Serialize a request into JSON? [\#4](https://github.com/liyasthomas/postwoman/issues/4)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- docs: add liyasthomas as a contributor [\#264](https://github.com/liyasthomas/postwoman/pull/264) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add jamesgeorge007 as a contributor [\#263](https://github.com/liyasthomas/postwoman/pull/263) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add NBTX as a contributor [\#262](https://github.com/liyasthomas/postwoman/pull/262) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- Fix .all-contributorsrc badge template. [\#260](https://github.com/liyasthomas/postwoman/pull/260) ([NBTX](https://github.com/NBTX))
|
||||
- docs: add hosseinnedaee as a contributor [\#259](https://github.com/liyasthomas/postwoman/pull/259) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add nityanandagohain as a contributor [\#257](https://github.com/liyasthomas/postwoman/pull/257) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add JacobAnavisca as a contributor [\#256](https://github.com/liyasthomas/postwoman/pull/256) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add izerozlu as a contributor [\#255](https://github.com/liyasthomas/postwoman/pull/255) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add vlad0337187 as a contributor [\#254](https://github.com/liyasthomas/postwoman/pull/254) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add AndrewBastin as a contributor [\#253](https://github.com/liyasthomas/postwoman/pull/253) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add terranblake as a contributor [\#252](https://github.com/liyasthomas/postwoman/pull/252) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add nickpalenchar as a contributor [\#251](https://github.com/liyasthomas/postwoman/pull/251) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add yubathom as a contributor [\#250](https://github.com/liyasthomas/postwoman/pull/250) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add larouxn as a contributor [\#249](https://github.com/liyasthomas/postwoman/pull/249) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add NBTX as a contributor [\#248](https://github.com/liyasthomas/postwoman/pull/248) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- docs: add liyasthomas as a contributor [\#247](https://github.com/liyasthomas/postwoman/pull/247) ([allcontributors[bot]](https://github.com/apps/allcontributors))
|
||||
- Make page changes more fluid [\#246](https://github.com/liyasthomas/postwoman/pull/246) ([NBTX](https://github.com/NBTX))
|
||||
- Minor tweaks [\#245](https://github.com/liyasthomas/postwoman/pull/245) ([liyasthomas](https://github.com/liyasthomas))
|
||||
- Add brand new logo to the project [\#244](https://github.com/liyasthomas/postwoman/pull/244) ([caneco](https://github.com/caneco))
|
||||
- ⬆️ Bump @nuxtjs/google-tag-manager from 2.3.0 to 2.3.1 [\#243](https://github.com/liyasthomas/postwoman/pull/243) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump yargs-parser from 15.0.0 to 16.1.0 [\#242](https://github.com/liyasthomas/postwoman/pull/242) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump @nuxtjs/toast from 3.2.1 to 3.3.0 [\#241](https://github.com/liyasthomas/postwoman/pull/241) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump highlight.js from 9.15.10 to 9.16.2 [\#240](https://github.com/liyasthomas/postwoman/pull/240) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump cypress from 3.5.0 to 3.6.0 [\#239](https://github.com/liyasthomas/postwoman/pull/239) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Fix legend labels in Firefox, fix colored labels slider [\#238](https://github.com/liyasthomas/postwoman/pull/238) ([NBTX](https://github.com/NBTX))
|
||||
- Feature/pre request script [\#231](https://github.com/liyasthomas/postwoman/pull/231) ([nickpalenchar](https://github.com/nickpalenchar))
|
||||
- Documentation Cleanup [\#230](https://github.com/liyasthomas/postwoman/pull/230) ([amitdash291](https://github.com/amitdash291))
|
||||
- Fix \#227 Exclude credentials from permalink [\#228](https://github.com/liyasthomas/postwoman/pull/228) ([reefqi037](https://github.com/reefqi037))
|
||||
- ⬆️ Bump cypress from 3.4.1 to 3.5.0 [\#224](https://github.com/liyasthomas/postwoman/pull/224) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump @nuxtjs/axios from 5.6.0 to 5.8.0 [\#223](https://github.com/liyasthomas/postwoman/pull/223) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump node-sass from 4.12.0 to 4.13.0 [\#222](https://github.com/liyasthomas/postwoman/pull/222) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump nuxt from 2.10.1 to 2.10.2 [\#221](https://github.com/liyasthomas/postwoman/pull/221) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump @nuxtjs/google-analytics from 2.2.0 to 2.2.1 [\#220](https://github.com/liyasthomas/postwoman/pull/220) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump vuex-persist from 2.1.0 to 2.1.1 [\#219](https://github.com/liyasthomas/postwoman/pull/219) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Add the ApolloTV proxy server [\#217](https://github.com/liyasthomas/postwoman/pull/217) ([NBTX](https://github.com/NBTX))
|
||||
- Fixed frame colors toggle [\#216](https://github.com/liyasthomas/postwoman/pull/216) ([mateusppereira](https://github.com/mateusppereira))
|
||||
- Re-order sections and add toggle for including authentication in URL [\#215](https://github.com/liyasthomas/postwoman/pull/215) ([NBTX](https://github.com/NBTX))
|
||||
- chore: minor code refactor [\#214](https://github.com/liyasthomas/postwoman/pull/214) ([jamesgeorge007](https://github.com/jamesgeorge007))
|
||||
- Fix \#212 Clear bearer token value [\#213](https://github.com/liyasthomas/postwoman/pull/213) ([reefqi037](https://github.com/reefqi037))
|
||||
- bug: keeping information on page change [\#211](https://github.com/liyasthomas/postwoman/pull/211) ([breno-pereira](https://github.com/breno-pereira))
|
||||
- Work in Progress: feature/allow-collections-importing [\#209](https://github.com/liyasthomas/postwoman/pull/209) ([vlad0337187](https://github.com/vlad0337187))
|
||||
- fix: don't display 'Collection is empty' label if collection has any … [\#208](https://github.com/liyasthomas/postwoman/pull/208) ([vlad0337187](https://github.com/vlad0337187))
|
||||
- Feature/log errors [\#207](https://github.com/liyasthomas/postwoman/pull/207) ([nickpalenchar](https://github.com/nickpalenchar))
|
||||
- Use returned value from toggle component on change event [\#205](https://github.com/liyasthomas/postwoman/pull/205) ([hosseinnedaee](https://github.com/hosseinnedaee))
|
||||
- Fix proxy URL [\#201](https://github.com/liyasthomas/postwoman/pull/201) ([NBTX](https://github.com/NBTX))
|
||||
- Fix CORS and Mixed-Content issue & Bug Fixes [\#200](https://github.com/liyasthomas/postwoman/pull/200) ([NBTX](https://github.com/NBTX))
|
||||
- Fix CORS and mixed content issue [\#199](https://github.com/liyasthomas/postwoman/pull/199) ([hosseinnedaee](https://github.com/hosseinnedaee))
|
||||
- ⬆️ Bump start-server-and-test from 1.10.5 to 1.10.6 [\#198](https://github.com/liyasthomas/postwoman/pull/198) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Added Tooltips [\#197](https://github.com/liyasthomas/postwoman/pull/197) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- ⬆️ Bump start-server-and-test from 1.10.3 to 1.10.5 [\#194](https://github.com/liyasthomas/postwoman/pull/194) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump @nuxtjs/google-tag-manager from 2.2.1 to 2.3.0 [\#193](https://github.com/liyasthomas/postwoman/pull/193) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump nuxt from 2.10.0 to 2.10.1 [\#192](https://github.com/liyasthomas/postwoman/pull/192) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- ⬆️ Bump yargs-parser from 14.0.0 to 15.0.0 [\#191](https://github.com/liyasthomas/postwoman/pull/191) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- Add quotation marks for generated code [\#187](https://github.com/liyasthomas/postwoman/pull/187) ([johnhenry](https://github.com/johnhenry))
|
||||
- Added auto theme [\#185](https://github.com/liyasthomas/postwoman/pull/185) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
- Add Request name label for every requests [\#184](https://github.com/liyasthomas/postwoman/pull/184) ([sharath2106](https://github.com/sharath2106))
|
||||
- updated threshold and rootMargin for IntersectionObserver [\#182](https://github.com/liyasthomas/postwoman/pull/182) ([edisonaugusthy](https://github.com/edisonaugusthy))
|
||||
- Add basic e2e tests [\#181](https://github.com/liyasthomas/postwoman/pull/181) ([yubathom](https://github.com/yubathom))
|
||||
- ⬆️ Bump nuxt from 2.9.2 to 2.10.0 [\#179](https://github.com/liyasthomas/postwoman/pull/179) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
|
||||
- 🐛 Fixed sitemap configuration [\#177](https://github.com/liyasthomas/postwoman/pull/177) ([NicoPennec](https://github.com/NicoPennec))
|
||||
- Collections [\#176](https://github.com/liyasthomas/postwoman/pull/176) ([TheHollidayInn](https://github.com/TheHollidayInn))
|
||||
- Code Refactoring [\#173](https://github.com/liyasthomas/postwoman/pull/173) ([edisonaugusthy](https://github.com/edisonaugusthy))
|
||||
- Added Black Theme [\#172](https://github.com/liyasthomas/postwoman/pull/172) ([AndrewBastin](https://github.com/AndrewBastin))
|
||||
|
||||
## [v0.1.0](https://github.com/liyasthomas/postwoman/tree/v0.1.0) (2019-08-22)
|
||||
|
||||
[Full Changelog](https://github.com/liyasthomas/postwoman/compare/91c08a5e6305cc95a0df46a33fdd0013bf7339b4...v0.1.0)
|
||||
|
||||
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
|
||||
Visit [releases](https://github.com/hoppscotch/hoppscotch/releases) for full changelog.
|
||||
|
||||
@@ -3,8 +3,10 @@ FROM node:lts-alpine
|
||||
LABEL maintainer="Hoppscotch (support@hoppscotch.io)"
|
||||
|
||||
# Add git as the prebuild target requires it to parse version information
|
||||
RUN apk add --update --no-cache \
|
||||
git
|
||||
RUN apk add --no-cache --virtual .gyp \
|
||||
python3 \
|
||||
make \
|
||||
g++
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020
|
||||
Copyright (c) 2022
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
55
README.md
@@ -46,7 +46,7 @@
|
||||
|
||||
#### **Support**
|
||||
|
||||
[](https://hoppscotch.io/discord) [](https://hoppscotch.io/telegram)
|
||||
[](https://hoppscotch.io/discord) [](https://hoppscotch.io/telegram) [](https://github.com/hoppscotch/hoppscotch/discussions)
|
||||
|
||||
<details open>
|
||||
<summary><b>Table of contents</b></summary>
|
||||
@@ -89,13 +89,13 @@
|
||||
- `TRACE` - Performs a message loop-back test along the path to the target resource
|
||||
- `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods.
|
||||
|
||||
🌈 **Make it yours:** Customizable combinations for background, foreground and accent colors — [customize now](https://hoppscotch.io/settings).
|
||||
🌈 **Make it yours:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings).
|
||||
|
||||
**Theming**
|
||||
|
||||
- Choose theme: System (default), Light, Dark and Black
|
||||
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red and Pink
|
||||
- Distraction free Zen mode
|
||||
- Choose a theme: System (default), Light, Dark, and Black
|
||||
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
|
||||
- Distraction-free Zen mode
|
||||
|
||||
_Customized themes are synced with cloud / local session_
|
||||
|
||||
@@ -120,11 +120,11 @@ _Customized themes are synced with cloud / local session_
|
||||
|
||||
🔌 **WebSocket:** Establish full-duplex communication channels over a single TCP connection.
|
||||
|
||||
📡 **Server Sent Events:** Receive a stream of updates from a server over a HTTP connection without resorting to polling.
|
||||
📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling.
|
||||
|
||||
🌩 **Socket.IO:** Send and Receive data with SocketIO server.
|
||||
|
||||
🦟 **MQTT:** Subscribe and Publish to topics of a MQTT Broker.
|
||||
🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker.
|
||||
|
||||
🔮 **GraphQL:** GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
|
||||
|
||||
@@ -134,7 +134,7 @@ _Customized themes are synced with cloud / local session_
|
||||
- Query schema
|
||||
- Get query response
|
||||
|
||||
🔐 **Authorization:** Allows to identify the end user.
|
||||
🔐 **Authorization:** Allows to identify the end-user.
|
||||
|
||||
- None
|
||||
- Basic
|
||||
@@ -149,10 +149,10 @@ _Customized themes are synced with cloud / local session_
|
||||
📃 **Request Body:** Used to send and receive data via the REST API.
|
||||
|
||||
- Set `Content Type`
|
||||
- FormData, JSON and many more
|
||||
- FormData, JSON, and many more
|
||||
- Toggle between key-value and RAW input parameter list
|
||||
|
||||
👋 **Response:** Contains the status line, headers and the message/response body.
|
||||
👋 **Response:** Contains the status line, headers, and the message/response body.
|
||||
|
||||
- Copy response to clipboard
|
||||
- Download response as a file
|
||||
@@ -163,22 +163,22 @@ _Customized themes are synced with cloud / local session_
|
||||
|
||||
📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click.
|
||||
|
||||
- Unlimited collections, folders and requests
|
||||
- Unlimited collections, folders, and requests
|
||||
- Nested folders
|
||||
- Export and import as file or GitHub gist
|
||||
- Export and import as a file or GitHub gist
|
||||
|
||||
_Collections are synced with cloud / local session storage_
|
||||
|
||||
🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs.
|
||||
|
||||
- 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
|
||||
- Use your own Proxy URL
|
||||
- Use your Proxy URL
|
||||
|
||||
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/privacy)**_
|
||||
|
||||
📜 **Pre-Request Scripts β:** Snippets of code associated with a request that are executed before the request is sent.
|
||||
📜 **Pre-Request Scripts β:** Snippets of code associated with a request that is executed before the request is sent.
|
||||
|
||||
- Set environment variables
|
||||
- Include timestamp in the request headers
|
||||
@@ -195,7 +195,7 @@ _Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/h
|
||||
|
||||
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/features/shortcuts)**
|
||||
|
||||
🌎 **i18n:** Experience the app in your own language.
|
||||
🌎 **i18n:** Experience the app in your language.
|
||||
|
||||
Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
|
||||
|
||||
@@ -228,7 +228,7 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
- Environments
|
||||
- Settings
|
||||
|
||||
✅ **Post-Request Tests β:** Write tests associated with a request that are executed after the request response.
|
||||
✅ **Post-Request Tests β:** Write tests associated with a request that is executed after the request's response.
|
||||
|
||||
- Check the status code as an integer
|
||||
- Filter response headers
|
||||
@@ -238,7 +238,7 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
🌱 **Environments** : Environment variables allow you to store and reuse values in your requests and scripts.
|
||||
|
||||
- Unlimited environments and variables
|
||||
- Initialize through pre-request script
|
||||
- Initialize through the pre-request script
|
||||
- Export as / import from GitHub gist
|
||||
|
||||
<details>
|
||||
@@ -277,10 +277,9 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
|
||||
## **Usage**
|
||||
|
||||
1. Choose `method`
|
||||
2. Enter `URL`
|
||||
3. Send request
|
||||
4. Get response
|
||||
1. Provide your API endpoint in the URL field
|
||||
2. CLick "Send" to simulate the request
|
||||
3. View the response
|
||||
|
||||
## **Built with**
|
||||
|
||||
@@ -295,9 +294,9 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
|
||||
|
||||
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 work with the [production build](https://hoppscotch.io)._
|
||||
|
||||
### Browser based development environment
|
||||
### Browser-based development environment
|
||||
|
||||
- [GitHub codespace](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace)
|
||||
- [Gitpod](https://gitpod.io/#https://github.com/hoppscotch/hoppscotch)
|
||||
@@ -308,13 +307,13 @@ _Sample keys only works with the [production build](https://hoppscotch.io)._
|
||||
2. Install pnpm using npm by running `npm install -g pnpm`.
|
||||
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
|
||||
4. Start the development server with `pnpm run dev`.
|
||||
5. Open development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
5. Open the development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
|
||||
### Docker compose
|
||||
|
||||
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
|
||||
2. Run `docker-compose up`
|
||||
3. Open development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
2. Run `docker-compose up` within the directory that you cloned (probably `hoppscotch`).
|
||||
3. Open the development site by going to [`http://localhost:3000`](http://localhost:3000) in your browser.
|
||||
|
||||
## **Docker**
|
||||
|
||||
@@ -348,7 +347,7 @@ See the [`CHANGELOG`](CHANGELOG.md) file for details.
|
||||
|
||||
## **Authors**
|
||||
|
||||
This project exists thanks to all the people who contribute — [make a contribution](CONTRIBUTING.md).
|
||||
This project exists thanks to all the people who contribute — [contribute](CONTRIBUTING.md).
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors">
|
||||
|
||||
26
SECURITY.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Security Policy
|
||||
|
||||
This document outlines security procedures and general policies for the Hoppscotch project.
|
||||
|
||||
1. [Reporting a security vulnerability](#reporting-a-security-vulnerability)
|
||||
3. [Incident response process](#incident-response-process)
|
||||
|
||||
## Reporting a security vulnerability
|
||||
|
||||
Report security vulnerabilities by emailing the Hoppscotch Support team at support@hoppscotch.io.
|
||||
|
||||
The primary security point of contact from Hoppscotch Support team will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
|
||||
**Do not create a GitHub issue ticket to report a security vulnerability.**
|
||||
|
||||
The Hoppscotch team and community take all security vulnerability reports in Hoppscotch seriously. Thank you for improving the security of Hoppscotch. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
|
||||
|
||||
Report security bugs in third-party modules to the person or team maintaining the module.
|
||||
|
||||
## Incident response process
|
||||
|
||||
In case an incident is discovered or reported, we will follow the following process to contain, respond, and remediate:
|
||||
|
||||
1. Confirm the problem and determine the affected versions.
|
||||
2. Audit code to find any potential similar problems.
|
||||
3. Prepare fixes for all releases still under maintenance. These fixes will be deployed as fast as possible to production.
|
||||
@@ -9,16 +9,19 @@ Before you start working on a new language, please look through the [open pull r
|
||||
if there is no existing translation, you can create a new one by following these steps:
|
||||
|
||||
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
|
||||
2. **Create a new branch for your translation.**
|
||||
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/packages/hoppscotch-app/locales/en.json) to 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/packages/hoppscotch-app/languages.json).**
|
||||
7. **Save & commit changes.**
|
||||
8. **Send a pull request.**
|
||||
2. **Checkout the `i18n` branch for latest translations.**
|
||||
3. **Create a new branch for your translation with base branch `i18n`.**
|
||||
4. **Create target language file in the [`locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-app/locales) directory.**
|
||||
5. **Copy the contents of the source file [`locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/locales/en.json) to the target language file.**
|
||||
6. **Translate the strings in the target language file.**
|
||||
7. **Add your language entry to [`languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/languages.json).**
|
||||
8. **Save & commit changes.**
|
||||
9. **Send a pull request.**
|
||||
|
||||
_You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._
|
||||
|
||||
`i18n` branch will be merged into `main` branch once every week.
|
||||
|
||||
Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests.
|
||||
|
||||
## Updating a translation
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
match /{document=**} {
|
||||
allow read, write: if request.auth.uid != null;
|
||||
}
|
||||
// Make sure the uid of the requesting user matches the name of the user
|
||||
// Make sure the uid of the requesting user matches name of the user
|
||||
// document. The wildcard expression {userId} makes the userId variable
|
||||
// available in rules.
|
||||
match /users/{userId} {
|
||||
allow read, update, delete: if request.auth.uid == userId;
|
||||
allow create: if request.auth.uid != null;
|
||||
allow read, write, create, update, delete: if request.auth.uid != null && request.auth.uid == userId;
|
||||
}
|
||||
match /users/{userId}/{document=**} {
|
||||
allow read, write, create, update, delete: if request.auth.uid != null && request.auth.uid == userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
[[redirects]]
|
||||
from = "/careers"
|
||||
to = "https://hoppscotch.notion.site/3b9d5d5239a043bfb91701faabf5b8f0"
|
||||
to = "https://company.hoppscotch.io/careers"
|
||||
status = 301
|
||||
force = true
|
||||
|
||||
@@ -54,3 +54,9 @@
|
||||
to = "https://github.com/hoppscotch/hoppscotch"
|
||||
status = 301
|
||||
force = true
|
||||
|
||||
[[redirects]]
|
||||
from = "/announcements"
|
||||
to = "https://company.hoppscotch.io/announcements"
|
||||
status = 301
|
||||
force = true
|
||||
|
||||
10
package.json
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "hoppscotch-app",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"description": "Open source API development ecosystem",
|
||||
"author": "Hoppscotch (support@hoppscotch.io)",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky install",
|
||||
@@ -20,10 +21,11 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.0.2"
|
||||
"lint-staged": "^12.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^14.1.0",
|
||||
"@commitlint/config-conventional": "^14.1.0"
|
||||
"@commitlint/cli": "^16.1.0",
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@types/node": "^17.0.10"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"name": "@hoppscotch/codemirror-lang-graphql",
|
||||
"version": "0.1.0",
|
||||
"description": "GraphQL language support for CodeMirror",
|
||||
"author": "Hoppscotch (support@hoppscotch.io)",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepare": "rollup -c"
|
||||
},
|
||||
@@ -15,17 +17,16 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@codemirror/highlight": "^0.19.0",
|
||||
"@codemirror/language": "^0.19.0",
|
||||
"@lezer/lr": "^0.15.0"
|
||||
"@codemirror/highlight": "^0.19.6",
|
||||
"@codemirror/language": "^0.19.7",
|
||||
"@lezer/lr": "^0.15.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^0.15.0",
|
||||
"mocha": "^9.0.1",
|
||||
"rollup": "^2.35.1",
|
||||
"rollup-plugin-dts": "^3.0.2",
|
||||
"rollup-plugin-ts": "^1.4.0",
|
||||
"typescript": "^4.3.4"
|
||||
},
|
||||
"license": "MIT"
|
||||
"@lezer/generator": "^0.15.3",
|
||||
"mocha": "^9.1.4",
|
||||
"rollup": "^2.66.0",
|
||||
"rollup-plugin-dts": "^4.1.0",
|
||||
"rollup-plugin-ts": "^2.0.5",
|
||||
"typescript": "^4.5.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import typescript from "rollup-plugin-ts"
|
||||
import {lezer} from "@lezer/generator/rollup"
|
||||
import { lezer } from "@lezer/generator/rollup"
|
||||
|
||||
export default {
|
||||
input: "src/index.js",
|
||||
external: id => id != "tslib" && !/^(\.?\/|\w:)/.test(id),
|
||||
external: (id) => id != "tslib" && !/^(\.?\/|\w:)/.test(id),
|
||||
output: [
|
||||
{file: "dist/index.cjs", format: "cjs"},
|
||||
{dir: "./dist", format: "es"}
|
||||
{ file: "dist/index.cjs", format: "cjs" },
|
||||
{ dir: "./dist", format: "es" },
|
||||
],
|
||||
plugins: [lezer(), typescript()]
|
||||
plugins: [lezer(), typescript()],
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import {parser} from "./syntax.grammar"
|
||||
import {LRLanguage, LanguageSupport, indentNodeProp, foldNodeProp, foldInside, delimitedIndent} from "@codemirror/language"
|
||||
import {styleTags, tags as t} from "@codemirror/highlight"
|
||||
import { parser } from "./syntax.grammar"
|
||||
import {
|
||||
LRLanguage,
|
||||
LanguageSupport,
|
||||
indentNodeProp,
|
||||
foldNodeProp,
|
||||
foldInside,
|
||||
delimitedIndent,
|
||||
} from "@codemirror/language"
|
||||
import { styleTags, tags as t } from "@codemirror/highlight"
|
||||
|
||||
export const GQLLanguage = LRLanguage.define({
|
||||
parser: parser.configure({
|
||||
props: [
|
||||
indentNodeProp.add({
|
||||
"SelectionSet FieldsDefinition ObjectValue SchemaDefinition RootTypeDef": delimitedIndent({ closing: "}", align: true }),
|
||||
"SelectionSet FieldsDefinition ObjectValue SchemaDefinition RootTypeDef":
|
||||
delimitedIndent({ closing: "}", align: true }),
|
||||
}),
|
||||
foldNodeProp.add({
|
||||
Application: foldInside,
|
||||
"SelectionSet FieldsDefinition ObjectValue RootOperationTypeDefinition RootTypeDef": (node) => {
|
||||
return {
|
||||
from: node.from,
|
||||
to: node.to
|
||||
}
|
||||
|
||||
}
|
||||
"SelectionSet FieldsDefinition ObjectValue RootOperationTypeDefinition RootTypeDef":
|
||||
(node) => {
|
||||
return {
|
||||
from: node.from,
|
||||
to: node.to,
|
||||
}
|
||||
},
|
||||
}),
|
||||
styleTags({
|
||||
Name: t.definition(t.variableName),
|
||||
@@ -29,13 +37,13 @@ export const GQLLanguage = LRLanguage.define({
|
||||
NullValue: t.null,
|
||||
ObjectValue: t.brace,
|
||||
Comment: t.lineComment,
|
||||
})
|
||||
]
|
||||
}),
|
||||
],
|
||||
}),
|
||||
languageData: {
|
||||
commentTokens: { line: "#" },
|
||||
closeBrackets: { brackets: ["(", "[", "{", '"', '"""'] }
|
||||
}
|
||||
closeBrackets: { brackets: ["(", "[", "{", '"', '"""'] },
|
||||
},
|
||||
})
|
||||
|
||||
export function GQL() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"newLine": "lf",
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["src/*"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<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="9 10 4 15 9 20"></polyline>
|
||||
<path d="M20 4v7a4 4 0 01-4 4H4"></path>
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 253 B |
@@ -1,4 +1,3 @@
|
||||
<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 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"></path>
|
||||
<line x1="9" y1="14" x2="15" y2="14"></line>
|
||||
</svg>
|
||||
<path d="M6 17l2-5h14l-3 8a2 2 0 01-2 1H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h7a2 2 0 012 2v4"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 291 B |
1
packages/hoppscotch-app/assets/icons/insomnia.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M12 0C5.417 0 0 5.417 0 12s5.417 12 12 12 12-5.417 12-12S18.583 0 12 0zm0 2.478c5.256 0 9.522 4.266 9.522 9.522S17.256 21.522 12 21.522 2.478 17.256 2.478 12c0-.885.12-1.741.347-2.554a4.76 4.76 0 0 0 3.925 2.066 4.764 4.764 0 0 0 4.762-4.762 4.758 4.758 0 0 0-2.067-3.925A9.526 9.526 0 0 1 12 2.478Z"/></svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -1,10 +1,3 @@
|
||||
<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>
|
||||
<path d="M21 12a9 9 0 11-6.219-8.56"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 235 B |
25
packages/hoppscotch-app/assets/icons/logo.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 16C0 7.16344 7.16344 0 16 0H112C120.837 0 128 7.16344 128 16V112C128 120.837 120.837 128 112 128H16C7.16344 128 0 120.837 0 112V16Z" fill="#10B981"/>
|
||||
<ellipse cx="49.94" cy="39.46" rx="2.5" ry="2.5" fill="white" fill-opacity="0.75"/>
|
||||
<ellipse cx="65.44" cy="40.46" rx="3" ry="3" fill="white" fill-opacity="0.75"/>
|
||||
<ellipse cx="80.44" cy="44.46" rx="2.50001" ry="2.5" fill="white" fill-opacity="0.75"/>
|
||||
<path d="M86.7407 58.919C87.2596 55.7445 77.0229 51.468 63.9989 49.3955C50.9765 47.3216 39.8979 48.2292 39.379 51.4037C39.3147 51.6624 39.379 51.8568 39.4448 52.1169C39.1203 51.9868 20.2663 104.92 20.2663 104.92H91.5347C91.5347 104.92 87.1295 59.5665 86.482 59.5665C86.6107 59.3735 86.7407 59.1792 86.7407 58.919Z" fill="url(#paint0_linear_202_73)"/>
|
||||
<path d="M79.2254 56.8465C78.9009 58.8547 71.5157 59.3078 62.6397 57.9471C53.828 56.5221 46.8943 53.7363 47.2188 51.7925C47.3489 51.145 48.1907 50.6261 49.5514 50.3675C45.0162 50.5618 42.0361 51.4037 41.7774 52.8287C41.3887 55.4858 50.4591 59.1134 62.1208 60.993C73.7826 62.8711 83.5662 62.1593 84.0193 59.5679C84.2795 58.0114 81.6867 56.3277 77.4116 54.7083C78.6422 55.4201 79.2897 56.1976 79.2254 56.8465Z" fill="#A7F3D0" fill-opacity="0.5"/>
|
||||
<path d="M83.8892 39.3532C83.1131 31.319 76.9571 24.5155 68.6642 23.2205C60.3713 21.9241 52.5315 26.4593 49.227 33.7803C54.5397 34.1047 60.2412 34.6879 66.2029 35.6598C72.5519 36.6318 78.5122 37.9282 83.8892 39.3532Z" fill="url(#paint1_radial_202_73)"/>
|
||||
<path d="M19.0356 39.5005C18.3882 43.4526 26.6153 48.3123 39.5091 52.2643C39.4448 52.0042 39.4448 51.8098 39.4448 51.5511C39.9622 48.3766 50.9765 47.469 64.0647 49.5429C77.1515 51.6154 87.3239 55.8919 86.805 59.0664C86.7407 59.3266 86.6764 59.5209 86.5463 59.7139C99.9576 59.9083 109.288 57.8358 109.936 53.8837C110.842 47.8577 91.2759 39.7592 66.203 35.8072C41.0642 31.8552 19.9418 33.4746 19.0356 39.5005ZM47.9321 39.1761C48.1264 38.1398 49.0984 37.3623 50.1346 37.5567C51.1709 37.7511 51.9484 38.723 51.754 39.7592C51.6239 40.7955 50.5877 41.5087 49.5515 41.3786C48.5152 41.1843 47.7377 40.2123 47.9321 39.1761ZM62.8984 40.2123C63.0928 38.7873 64.4535 37.8154 65.8785 38.0755C67.3035 38.2699 68.2754 39.6292 68.0168 41.0556C67.7566 42.4164 66.3973 43.3883 65.0366 43.1939C63.6102 42.9995 62.6382 41.6388 62.8984 40.2123ZM78.6423 44.0358C78.8366 42.9995 79.8086 42.222 80.8448 42.4164C81.8811 42.6107 82.6586 43.5827 82.4642 44.6189C82.3356 45.6552 81.2979 46.4327 80.2617 46.2383C79.2254 46.0439 78.4479 45.072 78.6423 44.0358Z" fill="url(#paint2_radial_202_73)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_202_73" x1="56.7496" y1="39.4014" x2="56.7496" y2="100.924" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#86EFAC" stop-opacity="0.75"/>
|
||||
<stop offset="0.635417" stop-color="white" stop-opacity="0.2"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_202_73" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(66.5581 31.1766) rotate(90) scale(8.17659 17.3311)">
|
||||
<stop stop-color="#047857"/>
|
||||
<stop offset="1" stop-color="#064E3B"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_202_73" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(64.4593 46.6885) scale(347.403)">
|
||||
<stop stop-color="#047857"/>
|
||||
<stop offset="0.114583" stop-color="#064E3B"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
1
packages/hoppscotch-app/assets/icons/postman.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M13.527.099C6.955-.744.942 3.9.099 10.473c-.843 6.572 3.8 12.584 10.373 13.428 6.573.843 12.587-3.801 13.428-10.374C24.744 6.955 20.101.943 13.527.099zm2.471 7.485a.855.855 0 0 0-.593.25l-4.453 4.453-.307-.307-.643-.643c4.389-4.376 5.18-4.418 5.996-3.753zm-4.863 4.861 4.44-4.44a.62.62 0 1 1 .847.903l-4.699 4.125-.588-.588zm.33.694-1.1.238a.06.06 0 0 1-.067-.032.06.06 0 0 1 .01-.073l.645-.645.512.512zm-2.803-.459 1.172-1.172.879.878-1.979.426a.074.074 0 0 1-.085-.039.072.072 0 0 1 .013-.093zm-3.646 6.058a.076.076 0 0 1-.069-.083.077.077 0 0 1 .022-.046h.002l.946-.946 1.222 1.222-2.123-.147zm2.425-1.256a.228.228 0 0 0-.117.256l.203.865a.125.125 0 0 1-.211.117h-.003l-.934-.934-.294-.295 3.762-3.758 1.82-.393.874.874c-1.255 1.102-2.971 2.201-5.1 3.268zm5.279-3.428h-.002l-.839-.839 4.699-4.125a.952.952 0 0 0 .119-.127c-.148 1.345-2.029 3.245-3.977 5.091zm3.657-6.46-.003-.002a1.822 1.822 0 0 1 2.459-2.684l-1.61 1.613a.119.119 0 0 0 0 .169l1.247 1.247a1.817 1.817 0 0 1-2.093-.343zm2.578 0a1.714 1.714 0 0 1-.271.218h-.001l-1.207-1.207 1.533-1.533c.661.72.637 1.832-.054 2.522zm-.1-1.544a.143.143 0 0 0-.053.157.416.416 0 0 1-.053.45.14.14 0 0 0 .023.197.141.141 0 0 0 .084.03.14.14 0 0 0 .106-.05.691.691 0 0 0 .087-.751.138.138 0 0 0-.194-.033z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
4
packages/hoppscotch-app/assets/icons/shield-check.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<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="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
<path d="M9 12l2 2 4-4"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
5
packages/hoppscotch-app/assets/icons/upload.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<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 01-2 2H5a2 2 0 01-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 342 B |
5
packages/hoppscotch-app/assets/icons/user-minus.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<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 00-4-4H5a4 4 0 00-4 4v2"></path>
|
||||
<circle cx="8.5" cy="7" r="4"></circle>
|
||||
<line x1="23" y1="11" x2="17" y2="11"></line>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
6
packages/hoppscotch-app/assets/icons/user-x.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<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 00-4-4H5a4 4 0 00-4 4v2"></path>
|
||||
<circle cx="8.5" cy="7" r="4"></circle>
|
||||
<line x1="18" y1="8" x2="23" y2="13"></line>
|
||||
<line x1="23" y1="8" x2="18" y2="13"></line>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
6
packages/hoppscotch-app/assets/icons/wrap-text.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<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="3" y1="6" x2="21" y2="6"></line>
|
||||
<path d="M3 12h15a3 3 0 110 6h-4"></path>
|
||||
<polyline points="16 16 14 18 16 20"></polyline>
|
||||
<line x1="3" y1="18" x2="10" y2="18"></line>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
@@ -1,7 +1,9 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
* {
|
||||
@apply backface-hidden;
|
||||
@apply before:backface-hidden;
|
||||
@apply after:backface-hidden;
|
||||
@apply selection:bg-accentDark;
|
||||
@apply selection:text-accentContrast;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -19,7 +21,8 @@
|
||||
@apply bg-divider bg-clip-content;
|
||||
@apply rounded-full;
|
||||
@apply border-solid border-transparent border-4;
|
||||
@apply hover:(bg-dividerDark bg-clip-content);
|
||||
@apply hover:bg-dividerDark;
|
||||
@apply hover:bg-clip-content;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@@ -27,23 +30,19 @@
|
||||
@apply h-4;
|
||||
}
|
||||
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-divider;
|
||||
}
|
||||
|
||||
.cm-focused {
|
||||
@apply !outline-none;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder,
|
||||
.CodeMirror-empty {
|
||||
textarea::placeholder {
|
||||
@apply text-secondary;
|
||||
@apply opacity-25;
|
||||
@apply opacity-35;
|
||||
}
|
||||
|
||||
input,
|
||||
@@ -62,11 +61,10 @@ body {
|
||||
@apply font-medium;
|
||||
@apply select-none;
|
||||
@apply overflow-x-hidden;
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
|
||||
animation: fade 300ms forwards;
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
overflow: overlay;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
@@ -101,16 +99,18 @@ body {
|
||||
|
||||
.material-icons {
|
||||
@apply flex-shrink-0;
|
||||
@apply overflow-hidden;
|
||||
|
||||
font-size: var(--body-line-height) !important;
|
||||
width: var(--body-line-height);
|
||||
font-size: var(--line-height-body) !important;
|
||||
width: var(--line-height-body);
|
||||
}
|
||||
|
||||
.svg-icons {
|
||||
@apply flex-shrink-0;
|
||||
@apply overflow-hidden;
|
||||
|
||||
height: var(--body-line-height);
|
||||
width: var(--body-line-height);
|
||||
height: var(--line-height-body);
|
||||
width: var(--line-height-body);
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -119,9 +119,8 @@ a {
|
||||
@apply no-underline;
|
||||
@apply outline-none;
|
||||
@apply transition;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
|
||||
&.link {
|
||||
@apply items-center;
|
||||
@@ -130,48 +129,48 @@ a {
|
||||
@apply text-accent;
|
||||
@apply rounded;
|
||||
@apply hover:text-accentDark;
|
||||
@apply focus-visible:(ring ring-accent);
|
||||
@apply focus-visible:ring;
|
||||
@apply focus-visible:ring-accent;
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-popper {
|
||||
.tooltip-theme {
|
||||
@apply bg-tooltip;
|
||||
@apply text-primary;
|
||||
@apply font-semibold;
|
||||
@apply py-1 px-2;
|
||||
.tooltip-theme {
|
||||
@apply bg-tooltip;
|
||||
@apply text-primary;
|
||||
@apply font-semibold;
|
||||
@apply py-1 px-2;
|
||||
@apply rounded;
|
||||
@apply truncate;
|
||||
@apply shadow;
|
||||
@apply leading-body;
|
||||
|
||||
font-size: 86%;
|
||||
|
||||
kbd {
|
||||
@apply hidden;
|
||||
@apply sm:inline-flex;
|
||||
@apply font-sans;
|
||||
@apply bg-gray-500;
|
||||
@apply bg-opacity-45;
|
||||
@apply text-primaryLight;
|
||||
@apply rounded-sm;
|
||||
@apply px-1;
|
||||
@apply ml-1;
|
||||
@apply truncate;
|
||||
@apply shadow;
|
||||
|
||||
font-size: 88%;
|
||||
line-height: var(--body-line-height);
|
||||
|
||||
kbd {
|
||||
@apply inline-flex;
|
||||
@apply font-sans;
|
||||
@apply bg-gray-500;
|
||||
@apply bg-opacity-45;
|
||||
@apply text-primaryLight;
|
||||
@apply rounded-sm;
|
||||
@apply px-1;
|
||||
@apply ml-1;
|
||||
@apply truncate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-theme {
|
||||
@apply bg-popover;
|
||||
@apply text-secondary;
|
||||
@apply p-2;
|
||||
@apply shadow-lg;
|
||||
@apply focus:outline-none;
|
||||
.popover-theme {
|
||||
@apply bg-popover;
|
||||
@apply text-secondary;
|
||||
@apply p-2;
|
||||
@apply shadow-lg;
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
@apply focus:outline-none;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
|
||||
.tippy-roundarrow svg {
|
||||
@apply fill-popover;
|
||||
}
|
||||
.tippy-roundarrow svg {
|
||||
@apply fill-popover;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,13 +214,12 @@ input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
@apply focus:outline-none;
|
||||
@apply truncate;
|
||||
@apply transition;
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
@apply focus:outline-none;
|
||||
@apply disabled:cursor-not-allowed;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
}
|
||||
|
||||
.input[type="file"],
|
||||
@@ -231,7 +229,6 @@ button {
|
||||
}
|
||||
|
||||
.floating-input ~ label {
|
||||
@apply font-medium;
|
||||
@apply py-0.5;
|
||||
@apply px-2;
|
||||
@apply m-2;
|
||||
@@ -247,7 +244,7 @@ button {
|
||||
@apply transform;
|
||||
@apply origin-top-left;
|
||||
@apply scale-75;
|
||||
@apply -translate-y-5;
|
||||
@apply -translate-y-4;
|
||||
@apply translate-x-1;
|
||||
}
|
||||
|
||||
@@ -318,13 +315,12 @@ pre.ace_editor {
|
||||
&.toasted-primary {
|
||||
@apply bg-tooltip;
|
||||
@apply text-primary;
|
||||
@apply justify-start;
|
||||
@apply justify-between;
|
||||
@apply shadow;
|
||||
@apply font-medium;
|
||||
@apply transition;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
|
||||
.action {
|
||||
@apply bg-gray-500;
|
||||
@@ -336,11 +332,11 @@ pre.ace_editor {
|
||||
@apply rounded;
|
||||
@apply text-current;
|
||||
@apply normal-case;
|
||||
@apply hover:(bg-opacity-20 no-underline);
|
||||
@apply font-medium;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
line-height: var(--body-line-height);
|
||||
@apply text-body;
|
||||
@apply leading-body;
|
||||
@apply hover:bg-opacity-20;
|
||||
@apply hover:no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +387,7 @@ pre.ace_editor {
|
||||
.smart-splitter .splitpanes__splitter::before {
|
||||
@apply absolute;
|
||||
@apply inset-0;
|
||||
@apply bg-dividerLight;
|
||||
@apply bg-accentLight;
|
||||
@apply opacity-0;
|
||||
@apply z-20;
|
||||
@apply transition;
|
||||
@@ -435,30 +431,36 @@ pre.ace_editor {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
@apply !h-auto;
|
||||
.cm-focused {
|
||||
@apply select-auto;
|
||||
@apply !outline-none;
|
||||
|
||||
font-size: var(--body-font-size);
|
||||
|
||||
&:not(.CodeMirror-focused) .CodeMirror-activeline-background {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
.cm-activeLine {
|
||||
@apply bg-primaryLight;
|
||||
@apply border-dividerLight;
|
||||
@apply px-4;
|
||||
@apply py-2;
|
||||
@apply z-5;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
@apply min-h-64;
|
||||
.cm-activeLineGutter {
|
||||
@apply bg-primaryDark;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: "Roboto Mono", monospace;
|
||||
}
|
||||
.shortcut-key {
|
||||
@apply inline-flex;
|
||||
@apply font-sans;
|
||||
@apply text-tiny;
|
||||
@apply bg-divider;
|
||||
@apply rounded;
|
||||
@apply ml-2;
|
||||
@apply px-1;
|
||||
@apply transition;
|
||||
}
|
||||
|
||||
.capitalize-first {
|
||||
@apply first-letter:capitalize;
|
||||
}
|
||||
|
||||
details summary::-webkit-details-marker {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
--font-sans: "Inter", sans-serif;
|
||||
--font-mono: "Roboto Mono", monospace;
|
||||
--font-icon: "Material Icons";
|
||||
--font-size-tiny: calc(var(--font-size-body) - 0.062rem);
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
@@ -27,7 +28,7 @@
|
||||
--secondary-color: theme("colors.true-gray.500");
|
||||
--secondary-light-color: theme("colors.true-gray.400");
|
||||
--secondary-dark-color: theme("colors.true-gray.900");
|
||||
--divider-color: theme("colors.true-gray.200");
|
||||
--divider-color: theme("colors.gray.100");
|
||||
--divider-light-color: theme("colors.true-gray.100");
|
||||
--divider-dark-color: theme("colors.true-gray.300");
|
||||
--error-color: theme("colors.yellow.100");
|
||||
@@ -44,26 +45,26 @@
|
||||
--secondary-light-color: theme("colors.true-gray.500");
|
||||
--secondary-dark-color: theme("colors.true-gray.100");
|
||||
--divider-color: theme("colors.true-gray.800");
|
||||
--divider-light-color: theme("colors.dark.700");
|
||||
--divider-light-color: theme("colors.dark.800");
|
||||
--divider-dark-color: theme("colors.dark.300");
|
||||
--error-color: theme("colors.warm-gray.900");
|
||||
--tooltip-color: theme("colors.true-gray.100");
|
||||
--popover-color: theme("colors.dark.700");
|
||||
--popover-color: theme("colors.dark.600");
|
||||
--editor-theme: "twilight";
|
||||
}
|
||||
|
||||
@mixin dark-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.500");
|
||||
--editor-name-color: theme("colors.blue.500");
|
||||
--editor-operator-color: theme("colors.indigo.500");
|
||||
--editor-invalid-color: theme("colors.red.500");
|
||||
--editor-separator-color: theme("colors.gray.500");
|
||||
--editor-meta-color: theme("colors.gray.500");
|
||||
--editor-variable-color: theme("colors.green.500");
|
||||
--editor-link-color: theme("colors.cyan.500");
|
||||
--editor-process-color: theme("colors.gray.400");
|
||||
--editor-constant-color: theme("colors.fuchsia.500");
|
||||
--editor-keyword-color: theme("colors.pink.500");
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.blue.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.fuchsia.400");
|
||||
--editor-constant-color: theme("colors.violet.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@mixin light-editor-theme {
|
||||
@@ -82,15 +83,15 @@
|
||||
|
||||
@mixin black-editor-theme {
|
||||
--editor-type-color: theme("colors.purple.400");
|
||||
--editor-name-color: theme("colors.gray.400");
|
||||
--editor-name-color: theme("colors.fuchsia.400");
|
||||
--editor-operator-color: theme("colors.indigo.400");
|
||||
--editor-invalid-color: theme("colors.red.400");
|
||||
--editor-separator-color: theme("colors.gray.400");
|
||||
--editor-meta-color: theme("colors.gray.400");
|
||||
--editor-variable-color: theme("colors.green.400");
|
||||
--editor-link-color: theme("colors.cyan.400");
|
||||
--editor-process-color: theme("colors.blue.400");
|
||||
--editor-constant-color: theme("colors.fuchsia.400");
|
||||
--editor-process-color: theme("colors.violet.400");
|
||||
--editor-constant-color: theme("colors.blue.400");
|
||||
--editor-keyword-color: theme("colors.pink.400");
|
||||
}
|
||||
|
||||
@@ -243,8 +244,8 @@
|
||||
}
|
||||
|
||||
@mixin font-small {
|
||||
--body-font-size: 0.75rem;
|
||||
--body-line-height: 1rem;
|
||||
--font-size-body: 0.75rem;
|
||||
--line-height-body: 1rem;
|
||||
--upper-primary-sticky-fold: 4.125rem;
|
||||
--upper-secondary-sticky-fold: 6.125rem;
|
||||
--upper-tertiary-sticky-fold: 8.188rem;
|
||||
@@ -255,8 +256,8 @@
|
||||
}
|
||||
|
||||
@mixin font-medium {
|
||||
--body-font-size: 0.875rem;
|
||||
--body-line-height: 1.25rem;
|
||||
--font-size-body: 0.875rem;
|
||||
--line-height-body: 1.25rem;
|
||||
--upper-primary-sticky-fold: 4.375rem;
|
||||
--upper-secondary-sticky-fold: 6.625rem;
|
||||
--upper-tertiary-sticky-fold: 8.813rem;
|
||||
@@ -267,8 +268,8 @@
|
||||
}
|
||||
|
||||
@mixin font-large {
|
||||
--body-font-size: 1rem;
|
||||
--body-line-height: 1.5rem;
|
||||
--font-size-body: 1rem;
|
||||
--line-height-body: 1.5rem;
|
||||
--upper-primary-sticky-fold: 4.625rem;
|
||||
--upper-secondary-sticky-fold: 7.125rem;
|
||||
--upper-tertiary-sticky-fold: 9.5rem;
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<template>
|
||||
<div class="bg-error flex justify-between">
|
||||
<span
|
||||
class="
|
||||
flex
|
||||
py-2
|
||||
px-4
|
||||
transition
|
||||
relative
|
||||
items-center
|
||||
justify-center
|
||||
group
|
||||
"
|
||||
>
|
||||
<i class="mr-2 material-icons">info_outline</i>
|
||||
<span class="text-secondaryDark">
|
||||
<span class="md:hidden">
|
||||
{{ $t("helpers.offline_short") }}
|
||||
</span>
|
||||
<span class="hidden md:inline">
|
||||
{{ $t("helpers.offline") }}
|
||||
</span>
|
||||
<div
|
||||
class="relative flex items-center px-4 py-2 transition bg-error text-tiny group"
|
||||
>
|
||||
<i class="mr-2 material-icons">info_outline</i>
|
||||
<span class="text-secondaryDark">
|
||||
<span class="md:hidden">
|
||||
{{ t("helpers.offline_short") }}
|
||||
</span>
|
||||
<span class="<md:hidden">
|
||||
{{ t("helpers.offline") }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="EXPAND_NAVIGATION ? $t('hide.sidebar') : $t('show.sidebar')"
|
||||
:title="EXPAND_NAVIGATION ? t('hide.sidebar') : t('show.sidebar')"
|
||||
svg="sidebar"
|
||||
class="transform"
|
||||
:class="{ '-rotate-180': !EXPAND_NAVIGATION }"
|
||||
@@ -12,9 +12,9 @@
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${
|
||||
ZEN_MODE ? $t('action.turn_off') : $t('action.turn_on')
|
||||
} ${$t('layout.zen_mode')}`"
|
||||
:title="`${ZEN_MODE ? t('action.turn_off') : t('action.turn_on')} ${t(
|
||||
'layout.zen_mode'
|
||||
)}`"
|
||||
:svg="ZEN_MODE ? 'minimize' : 'maximize'"
|
||||
:class="{
|
||||
'!text-accent !focus-visible:text-accentDark !hover:text-accentDark':
|
||||
@@ -22,127 +22,162 @@
|
||||
}"
|
||||
@click.native="ZEN_MODE = !ZEN_MODE"
|
||||
/>
|
||||
<tippy
|
||||
ref="interceptorOptions"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('settings.interceptor')"
|
||||
svg="shield-check"
|
||||
/>
|
||||
</template>
|
||||
<AppInterceptor />
|
||||
</tippy>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
svg="help-circle"
|
||||
class="!rounded-none"
|
||||
:label="`${t('app.help')}`"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.d="documentation.$el.click()"
|
||||
@keyup.s="shortcuts.$el.click()"
|
||||
@keyup.c="chat.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
svg="help-circle"
|
||||
class="!rounded-none"
|
||||
:label="`${$t('app.help')}`"
|
||||
/>
|
||||
</template>
|
||||
<div class="flex flex-col">
|
||||
<SmartItem
|
||||
svg="book"
|
||||
:label="`${$t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io"
|
||||
blank
|
||||
@click.native="$refs.options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="zap"
|
||||
:label="`${$t('app.keyboard_shortcuts')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
showShortcuts = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="gift"
|
||||
:label="`${$t('app.whats_new')}`"
|
||||
to="https://docs.hoppscotch.io/changelog"
|
||||
blank
|
||||
@click.native="$refs.options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="message-circle"
|
||||
:label="`${$t('app.chat_with_us')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
chatWithUs()
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
svg="github"
|
||||
:label="`${$t('app.github')}`"
|
||||
to="https://github.com/hoppscotch/hoppscotch"
|
||||
blank
|
||||
@click.native="$refs.options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="twitter"
|
||||
:label="`${$t('app.twitter')}`"
|
||||
to="https://hoppscotch.io/twitter"
|
||||
blank
|
||||
@click.native="$refs.options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="user-plus"
|
||||
:label="`${$t('app.invite')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
showShare = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="lock"
|
||||
:label="`${$t('app.terms_and_privacy')}`"
|
||||
to="https://docs.hoppscotch.io/privacy"
|
||||
blank
|
||||
@click.native="$refs.options.tippy().hide()"
|
||||
/>
|
||||
<!-- <SmartItem :label="$t('app.status')" /> -->
|
||||
<div class="flex opacity-50 py-2 px-4">
|
||||
{{ `${$t("app.name")} ${$t("app.version")}` }}
|
||||
</div>
|
||||
<SmartItem
|
||||
ref="documentation"
|
||||
svg="book"
|
||||
:label="`${t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io"
|
||||
blank
|
||||
:shortcut="['D']"
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="shortcuts"
|
||||
svg="zap"
|
||||
:label="`${t('app.keyboard_shortcuts')}`"
|
||||
:shortcut="['S']"
|
||||
@click.native="
|
||||
() => {
|
||||
showShortcuts = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="chat"
|
||||
svg="message-circle"
|
||||
:label="`${t('app.chat_with_us')}`"
|
||||
:shortcut="['C']"
|
||||
@click.native="
|
||||
() => {
|
||||
chatWithUs()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="gift"
|
||||
:label="`${t('app.whats_new')}`"
|
||||
to="https://docs.hoppscotch.io/changelog"
|
||||
blank
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="activity"
|
||||
:label="t('app.status')"
|
||||
to="https://status.hoppscotch.io"
|
||||
blank
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
svg="github"
|
||||
:label="`${t('app.github')}`"
|
||||
to="https://github.com/hoppscotch/hoppscotch"
|
||||
blank
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="twitter"
|
||||
:label="`${t('app.twitter')}`"
|
||||
to="https://hoppscotch.io/twitter"
|
||||
blank
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="user-plus"
|
||||
:label="`${t('app.invite')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
showShare = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="lock"
|
||||
:label="`${t('app.terms_and_privacy')}`"
|
||||
to="https://docs.hoppscotch.io/privacy"
|
||||
blank
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<div class="flex px-4 py-2 opacity-50">
|
||||
{{ `${t("app.name")} v${$config.clientVersion}` }}
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</tippy>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="zap"
|
||||
:title="$t('app.shortcuts')"
|
||||
:title="t('app.shortcuts')"
|
||||
@click.native="showShortcuts = true"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="navigatorShare"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="share-2"
|
||||
:title="$t('request.share')"
|
||||
:title="t('request.share')"
|
||||
@click.native="nativeShare()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="COLUMN_LAYOUT ? $t('layout.row') : $t('layout.column')"
|
||||
:title="COLUMN_LAYOUT ? t('layout.row') : t('layout.column')"
|
||||
svg="columns"
|
||||
class="transform"
|
||||
:class="{ 'rotate-90': !COLUMN_LAYOUT }"
|
||||
@click.native="COLUMN_LAYOUT = !COLUMN_LAYOUT"
|
||||
/>
|
||||
<span
|
||||
class="transform transition"
|
||||
class="transition transform"
|
||||
:class="{
|
||||
'rotate-180': SIDEBAR_ON_LEFT,
|
||||
}"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
|
||||
:title="SIDEBAR ? t('hide.sidebar') : t('show.sidebar')"
|
||||
svg="sidebar-open"
|
||||
class="transform"
|
||||
:class="{ 'rotate-180': !SIDEBAR }"
|
||||
@@ -153,6 +188,7 @@
|
||||
</div>
|
||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
<AppPowerSearch :show="showSearch" @hide-modal="showSearch = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -161,9 +197,12 @@ import { ref, watch } from "@nuxtjs/composition-api"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { showChat } from "~/helpers/support"
|
||||
import { useSetting } from "~/newstore/settings"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
const showShortcuts = ref(false)
|
||||
const showShare = ref(false)
|
||||
const showSearch = ref(false)
|
||||
|
||||
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||
showShortcuts.value = !showShortcuts.value
|
||||
@@ -173,6 +212,10 @@ defineActionHandler("modals.share.toggle", () => {
|
||||
showShare.value = !showShare.value
|
||||
})
|
||||
|
||||
defineActionHandler("modals.search.toggle", () => {
|
||||
showSearch.value = !showSearch.value
|
||||
})
|
||||
|
||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||
const SIDEBAR = useSetting("SIDEBAR")
|
||||
const ZEN_MODE = useSetting("ZEN_MODE")
|
||||
@@ -206,4 +249,11 @@ const nativeShare = () => {
|
||||
const chatWithUs = () => {
|
||||
showChat()
|
||||
}
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const documentation = ref<any | null>(null)
|
||||
const shortcuts = ref<any | null>(null)
|
||||
const chat = ref<any | null>(null)
|
||||
const options = ref<any | null>(null)
|
||||
</script>
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
/>
|
||||
<div
|
||||
v-if="searchResults.length === 0"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">manage_search</i>
|
||||
<span class="text-center">
|
||||
{{ $t("state.nothing_found") }} "{{ search }}"
|
||||
<i class="pb-2 opacity-75 material-icons">manage_search</i>
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ search }}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,6 +26,9 @@ import { computed, onUnmounted, onMounted } from "@nuxtjs/composition-api"
|
||||
import Fuse from "fuse.js"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
import { HoppAction } from "~/helpers/actions"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
input: Record<string, any>[]
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
title="Star Hoppscotch"
|
||||
href="https://github.com/hoppscotch/hoppscotch"
|
||||
:data-color-scheme="
|
||||
$colorMode.value != 'light'
|
||||
? $colorMode.value == 'black'
|
||||
colorMode.value != 'light'
|
||||
? colorMode.value == 'black'
|
||||
? 'dark'
|
||||
: 'dark_dimmed'
|
||||
: 'light'
|
||||
@@ -20,6 +20,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import GithubButton from "vue-github-button"
|
||||
import { useColorMode } from "~/helpers/utils/composables"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
defineProps({
|
||||
size: {
|
||||
|
||||
@@ -1,68 +1,70 @@
|
||||
<template>
|
||||
<div>
|
||||
<header
|
||||
class="flex space-x-2 flex-1 py-2 px-2 items-center justify-between"
|
||||
class="flex items-center justify-between flex-1 px-2 py-2 space-x-2"
|
||||
>
|
||||
<div class="space-x-2 inline-flex items-center">
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<ButtonSecondary
|
||||
class="tracking-wide !font-bold !text-secondaryDark"
|
||||
class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
label="HOPPSCOTCH"
|
||||
to="/"
|
||||
/>
|
||||
<AppGitHubStarButton class="mt-1.5 transition hidden sm:flex" />
|
||||
<AppGitHubStarButton class="mt-1.5 transition <sm:hidden" />
|
||||
</div>
|
||||
<div class="space-x-2 inline-flex items-center">
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<ButtonSecondary
|
||||
id="installPWA"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('header.install_pwa')"
|
||||
:title="t('header.install_pwa')"
|
||||
svg="download"
|
||||
class="rounded"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click.native="showInstallPrompt()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${$t('app.search')} <kbd>/</kbd>`"
|
||||
:title="`${t('app.search')} <kbd>/</kbd>`"
|
||||
svg="search"
|
||||
class="rounded"
|
||||
@click.native="showSearch = true"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click.native="invokeAction('modals.search.toggle')"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${$t('support.title')} <kbd>?</kbd>`"
|
||||
:title="`${t('support.title')} <kbd>?</kbd>`"
|
||||
svg="life-buoy"
|
||||
class="rounded"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click.native="showSupport = true"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-if="currentUser === null"
|
||||
svg="upload-cloud"
|
||||
:label="$t('header.save_workspace')"
|
||||
:label="t('header.save_workspace')"
|
||||
filled
|
||||
class="hidden md:flex"
|
||||
@click.native="showLogin = true"
|
||||
/>
|
||||
<ButtonPrimary
|
||||
v-if="currentUser === null"
|
||||
:label="$t('header.login')"
|
||||
:label="t('header.login')"
|
||||
@click.native="showLogin = true"
|
||||
/>
|
||||
<div v-else class="space-x-2 inline-flex items-center">
|
||||
<div v-else class="inline-flex items-center space-x-2">
|
||||
<ButtonPrimary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('team.invite_tooltip')"
|
||||
:label="$t('team.invite')"
|
||||
:title="t('team.invite_tooltip')"
|
||||
:label="t('team.invite')"
|
||||
svg="user-plus"
|
||||
class="
|
||||
!bg-green-500
|
||||
!text-green-500
|
||||
!bg-opacity-15
|
||||
!hover:bg-opacity-10 !hover:text-green-600 !hover:bg-green-400
|
||||
"
|
||||
class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacity-10 !hover:bg-green-400 !hover:text-green-600"
|
||||
@click.native="showTeamsModal = true"
|
||||
/>
|
||||
<span class="px-2">
|
||||
<tippy ref="user" interactive trigger="click" theme="popover" arrow>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ProfilePicture
|
||||
v-if="currentUser.photoURL"
|
||||
@@ -78,24 +80,51 @@
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('header.account')"
|
||||
class="rounded"
|
||||
:title="t('header.account')"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
svg="user"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
to="/profile"
|
||||
svg="user"
|
||||
:label="$t('navigation.profile')"
|
||||
@click.native="$refs.user.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
to="/settings"
|
||||
svg="settings"
|
||||
:label="$t('navigation.settings')"
|
||||
@click.native="$refs.user.tippy().hide()"
|
||||
/>
|
||||
<FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" />
|
||||
<div class="flex flex-col px-2 text-tiny">
|
||||
<span class="inline-flex font-semibold truncate">
|
||||
{{ currentUser.displayName }}
|
||||
</span>
|
||||
<span class="inline-flex truncate text-secondaryLight">
|
||||
{{ currentUser.email }}
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.enter="profile.$el.click()"
|
||||
@keyup.s="settings.$el.click()"
|
||||
@keyup.l="logout.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="profile"
|
||||
to="/profile"
|
||||
svg="user"
|
||||
:label="t('navigation.profile')"
|
||||
:shortcut="['↩']"
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="settings"
|
||||
to="/settings"
|
||||
svg="settings"
|
||||
:label="t('navigation.settings')"
|
||||
:shortcut="['S']"
|
||||
@click.native="options.tippy().hide()"
|
||||
/>
|
||||
<FirebaseLogout
|
||||
ref="logout"
|
||||
:shortcut="['L']"
|
||||
@confirm-logout="options.tippy().hide()"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
@@ -104,24 +133,25 @@
|
||||
<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" />
|
||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { onMounted, ref } from "@nuxtjs/composition-api"
|
||||
import intializePwa from "~/helpers/pwa"
|
||||
import { probableUser$ } from "~/helpers/fb/auth"
|
||||
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import {
|
||||
useReadonlyStream,
|
||||
useI18n,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
/**
|
||||
* Once the PWA code is initialized, this holds a method
|
||||
@@ -131,7 +161,6 @@ const t = i18n.t.bind(i18n)
|
||||
const showInstallPrompt = ref(() => Promise.resolve()) // Async no-op till it is initialized
|
||||
|
||||
const showSupport = ref(false)
|
||||
const showSearch = ref(false)
|
||||
const showLogin = ref(false)
|
||||
const showTeamsModal = ref(false)
|
||||
|
||||
@@ -142,9 +171,6 @@ const currentUser = useReadonlyStream(probableUser$, null)
|
||||
defineActionHandler("modals.support.toggle", () => {
|
||||
showSupport.value = !showSupport.value
|
||||
})
|
||||
defineActionHandler("modals.search.toggle", () => {
|
||||
showSearch.value = !showSearch.value
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("online", () => {
|
||||
@@ -160,12 +186,11 @@ onMounted(() => {
|
||||
|
||||
const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes"
|
||||
if (!cookiesAllowed) {
|
||||
$toast.show(t("app.we_use_cookies").toString(), {
|
||||
icon: "cookie",
|
||||
toast.show(`${t("app.we_use_cookies")}`, {
|
||||
duration: 0,
|
||||
action: [
|
||||
{
|
||||
text: t("action.learn_more").toString(),
|
||||
text: `${t("action.learn_more")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setLocalConfig("cookiesAllowed", "yes")
|
||||
toastObject.goAway(0)
|
||||
@@ -173,7 +198,7 @@ onMounted(() => {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t("action.dismiss").toString(),
|
||||
text: `${t("action.dismiss")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setLocalConfig("cookiesAllowed", "yes")
|
||||
toastObject.goAway(0)
|
||||
@@ -183,4 +208,11 @@ onMounted(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const profile = ref<any | null>(null)
|
||||
const settings = ref<any | null>(null)
|
||||
const logout = ref<any | null>(null)
|
||||
const options = ref<any | null>(null)
|
||||
</script>
|
||||
|
||||
@@ -1,55 +1,112 @@
|
||||
<template>
|
||||
<div class="flex p-4 space-y-4 items-start flex-col">
|
||||
<SmartToggle
|
||||
:on="PROXY_ENABLED"
|
||||
@change="toggleSettingKey('PROXY_ENABLED')"
|
||||
<div class="flex flex-col p-4 space-y-4">
|
||||
<div class="flex flex-col">
|
||||
<h2 class="inline-flex pb-1 font-semibold text-secondaryDark">
|
||||
{{ t("settings.interceptor") }}
|
||||
</h2>
|
||||
<p class="inline-flex text-tiny">
|
||||
{{ t("settings.interceptor_description") }}
|
||||
</p>
|
||||
</div>
|
||||
<SmartRadioGroup
|
||||
:radios="interceptors"
|
||||
:selected="interceptorSelection"
|
||||
@change="toggleSettingKey"
|
||||
/>
|
||||
<div
|
||||
v-if="interceptorSelection == 'EXTENSIONS_ENABLED' && !extensionVersion"
|
||||
class="flex space-x-2"
|
||||
>
|
||||
{{ $t("settings.proxy") }}
|
||||
</SmartToggle>
|
||||
<SmartToggle
|
||||
:on="EXTENSIONS_ENABLED"
|
||||
@change="toggleSettingKey('EXTENSIONS_ENABLED')"
|
||||
>
|
||||
{{ $t("settings.extensions") }}:
|
||||
{{
|
||||
extensionVersion != null
|
||||
? `v${extensionVersion.major}.${extensionVersion.minor}`
|
||||
: $t("settings.extension_ver_not_reported")
|
||||
}}
|
||||
</SmartToggle>
|
||||
<ButtonSecondary
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
svg="brands/chrome"
|
||||
label="Chrome"
|
||||
outline
|
||||
class="!flex-1"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
svg="brands/firefox"
|
||||
label="Firefox"
|
||||
outline
|
||||
class="!flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { computed, ref, watchEffect } from "@nuxtjs/composition-api"
|
||||
import { KeysMatching } from "~/types/ts-utils"
|
||||
import { SettingsType, toggleSetting, useSetting } from "~/newstore/settings"
|
||||
import {
|
||||
applySetting,
|
||||
SettingsType,
|
||||
toggleSetting,
|
||||
useSetting,
|
||||
} from "~/newstore/settings"
|
||||
import { hasExtensionInstalled } from "~/helpers/strategies/ExtensionStrategy"
|
||||
import { useI18n, usePolled } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const PROXY_ENABLED = useSetting("PROXY_ENABLED")
|
||||
const EXTENSIONS_ENABLED = useSetting("EXTENSIONS_ENABLED")
|
||||
|
||||
const toggleSettingKey = <K extends KeysMatching<SettingsType, boolean>>(
|
||||
const toggleSettingKey = <
|
||||
K extends KeysMatching<SettingsType | "BROWSER_ENABLED", boolean>
|
||||
>(
|
||||
key: K
|
||||
) => {
|
||||
if (key === "EXTENSIONS_ENABLED" && PROXY_ENABLED.value) {
|
||||
toggleSetting("PROXY_ENABLED")
|
||||
interceptorSelection.value = key
|
||||
if (key === "EXTENSIONS_ENABLED") {
|
||||
applySetting("EXTENSIONS_ENABLED", true)
|
||||
if (PROXY_ENABLED.value) toggleSetting("PROXY_ENABLED")
|
||||
}
|
||||
if (key === "PROXY_ENABLED" && EXTENSIONS_ENABLED.value) {
|
||||
toggleSetting("EXTENSIONS_ENABLED")
|
||||
if (key === "PROXY_ENABLED") {
|
||||
applySetting("PROXY_ENABLED", true)
|
||||
if (EXTENSIONS_ENABLED.value) toggleSetting("EXTENSIONS_ENABLED")
|
||||
}
|
||||
if (key === "BROWSER_ENABLED") {
|
||||
applySetting("PROXY_ENABLED", false)
|
||||
applySetting("EXTENSIONS_ENABLED", false)
|
||||
}
|
||||
toggleSetting(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
extensionVersion: hasExtensionInstalled()
|
||||
? window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
|
||||
: null,
|
||||
}
|
||||
const extensionVersion = usePolled(5000, (stopPolling) => {
|
||||
const result = hasExtensionInstalled()
|
||||
? window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
|
||||
: null
|
||||
|
||||
// We don't need to poll anymore after we get value
|
||||
if (result) stopPolling()
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const interceptors = computed(() => [
|
||||
{ value: "BROWSER_ENABLED", label: t("state.none") },
|
||||
{ value: "PROXY_ENABLED", label: t("settings.proxy") },
|
||||
{
|
||||
value: "EXTENSIONS_ENABLED",
|
||||
label:
|
||||
`${t("settings.extensions")}: ` +
|
||||
(extensionVersion.value !== null
|
||||
? `v${extensionVersion.value.major}.${extensionVersion.value.minor}`
|
||||
: t("settings.extension_ver_not_reported")),
|
||||
},
|
||||
])
|
||||
|
||||
const interceptorSelection = ref("")
|
||||
|
||||
watchEffect(() => {
|
||||
if (PROXY_ENABLED.value) {
|
||||
interceptorSelection.value = "PROXY_ENABLED"
|
||||
} else if (EXTENSIONS_ENABLED.value) {
|
||||
interceptorSelection.value = "EXTENSIONS_ENABLED"
|
||||
} else {
|
||||
interceptorSelection.value = "BROWSER_ENABLED"
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -6,22 +6,39 @@
|
||||
@close="$emit('hide-modal')"
|
||||
>
|
||||
<template #body>
|
||||
<input
|
||||
id="command"
|
||||
v-model="search"
|
||||
v-focus
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
name="command"
|
||||
:placeholder="`${$t('app.type_a_command_search')}`"
|
||||
class="
|
||||
bg-transparent
|
||||
border-b border-dividerLight
|
||||
flex flex-shrink-0
|
||||
text-secondaryDark text-base
|
||||
p-6
|
||||
"
|
||||
/>
|
||||
<div class="flex transition flex-col border-b border-dividerLight">
|
||||
<input
|
||||
id="command"
|
||||
v-model="search"
|
||||
v-focus
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
name="command"
|
||||
:placeholder="`${t('app.type_a_command_search')}`"
|
||||
class="flex flex-shrink-0 p-6 text-base bg-transparent text-secondaryDark"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-shrink-0 text-tiny text-secondaryLight px-4 pb-4 justify-between whitespace-nowrap overflow-auto hide-scrollbar <sm:hidden"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<kbd class="shortcut-key">↑</kbd>
|
||||
<kbd class="shortcut-key">↓</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_navigate") }}
|
||||
</span>
|
||||
<kbd class="shortcut-key">↩</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_select") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<kbd class="shortcut-key">ESC</kbd>
|
||||
<span class="ml-2 truncate">
|
||||
{{ t("action.to_close") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AppFuse
|
||||
v-if="search && show"
|
||||
:input="fuse"
|
||||
@@ -30,22 +47,15 @@
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
divide-y divide-dividerLight
|
||||
flex flex-col
|
||||
space-y-4
|
||||
flex-1
|
||||
overflow-auto
|
||||
hide-scrollbar
|
||||
"
|
||||
class="flex flex-col flex-1 space-y-4 overflow-auto divide-y divide-dividerLight hide-scrollbar"
|
||||
>
|
||||
<div
|
||||
v-for="(map, mapIndex) in mappings"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<h5 class="my-2 text-secondaryLight py-2 px-6">
|
||||
{{ $t(map.section) }}
|
||||
<h5 class="px-6 py-2 my-2 text-secondaryLight">
|
||||
{{ t(map.section) }}
|
||||
</h5>
|
||||
<AppPowerSearchEntry
|
||||
v-for="(shortcut, shortcutIndex) in map.shortcuts"
|
||||
@@ -66,6 +76,9 @@ import { ref, computed, watch } from "@nuxtjs/composition-api"
|
||||
import { HoppAction, invokeAction } from "~/helpers/actions"
|
||||
import { spotlight as mappings, fuse } from "~/helpers/shortcuts"
|
||||
import { useArrowKeysNavigation } from "~/helpers/powerSearchNavigation"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
<template>
|
||||
<button
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
py-2
|
||||
px-6
|
||||
transition
|
||||
items-center
|
||||
search-entry
|
||||
focus:outline-none
|
||||
"
|
||||
class="flex items-center flex-1 px-6 py-3 font-medium transition cursor-pointer search-entry focus:outline-none"
|
||||
:class="{ active: active }"
|
||||
tabindex="-1"
|
||||
@click="$emit('action', shortcut.action)"
|
||||
@keydown.enter="$emit('action', shortcut.action)"
|
||||
>
|
||||
<SmartIcon
|
||||
class="mr-4 opacity-50 transition svg-icons"
|
||||
class="mr-4 transition opacity-50 svg-icons"
|
||||
:class="{ 'opacity-100 text-secondaryDark': active }"
|
||||
:name="shortcut.icon"
|
||||
/>
|
||||
<span
|
||||
class="flex flex-1 mr-4 font-medium transition"
|
||||
class="flex flex-1 mr-4 transition"
|
||||
:class="{ 'text-secondaryDark': active }"
|
||||
>
|
||||
{{ $t(shortcut.label) }}
|
||||
{{ t(shortcut.label) }}
|
||||
</span>
|
||||
<span
|
||||
v-for="(key, keyIndex) in shortcut.keys"
|
||||
@@ -37,8 +28,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
shortcut: Object
|
||||
shortcut: {
|
||||
label: string
|
||||
keys: string[]
|
||||
action: string
|
||||
icon: string
|
||||
}
|
||||
active: Boolean
|
||||
}>()
|
||||
</script>
|
||||
@@ -69,13 +69,4 @@ defineProps<{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-key {
|
||||
@apply bg-dividerLight;
|
||||
@apply rounded;
|
||||
@apply ml-2;
|
||||
@apply py-1;
|
||||
@apply px-2;
|
||||
@apply inline-flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<section :id="label.toLowerCase()" class="flex flex-col flex-1 relative">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: "Section",
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="$t('app.invite_your_friends')"
|
||||
:title="t('app.invite_your_friends')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<p class="text-secondaryLight mb-8 px-2">
|
||||
{{ $t("app.invite_description") }}
|
||||
<p class="px-2 mb-8 text-secondaryLight">
|
||||
{{ t("app.invite_description") }}
|
||||
</p>
|
||||
<div class="flex flex-col space-y-2 px-2">
|
||||
<div class="grid gap-4 grid-cols-3">
|
||||
<div class="flex flex-col px-2 space-y-2">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<a
|
||||
v-for="(platform, index) in platforms"
|
||||
:key="`platform-${index}`"
|
||||
@@ -17,15 +17,15 @@
|
||||
target="_blank"
|
||||
class="share-link"
|
||||
>
|
||||
<SmartIcon :name="platform.icon" class="h-6 w-6" />
|
||||
<SmartIcon :name="platform.icon" class="w-6 h-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" />
|
||||
<SmartIcon class="w-6 h-6 text-xl" :name="copyIcon" />
|
||||
<span class="mt-3">
|
||||
{{ $t("app.copy") }}
|
||||
{{ t("app.copy") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -35,14 +35,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { ref } from "@nuxtjs/composition-api"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
defineProps<{
|
||||
show: Boolean
|
||||
@@ -57,7 +56,7 @@ 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 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"
|
||||
|
||||
const copyIcon = ref("copy")
|
||||
@@ -92,9 +91,7 @@ const platforms = [
|
||||
const copyAppLink = () => {
|
||||
copyToClipboard(url)
|
||||
copyIcon.value = "check"
|
||||
$toast.success(t("state.copied_to_clipboard").toString(), {
|
||||
icon: "content_paste",
|
||||
})
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,31 @@
|
||||
<template>
|
||||
<AppSlideOver :show="show" @close="close()">
|
||||
<template #content>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex
|
||||
p-2
|
||||
top-0
|
||||
z-10
|
||||
items-center
|
||||
sticky
|
||||
justify-between
|
||||
"
|
||||
>
|
||||
<h3 class="ml-4 heading">{{ $t("app.shortcuts") }}</h3>
|
||||
<div class="flex">
|
||||
<ButtonSecondary svg="x" class="rounded" @click.native="close()" />
|
||||
<div class="sticky top-0 z-10 flex flex-col bg-primary">
|
||||
<div
|
||||
class="flex items-center justify-between p-2 border-b border-dividerLight"
|
||||
>
|
||||
<h3 class="ml-4 heading">{{ t("app.shortcuts") }}</h3>
|
||||
<ButtonSecondary svg="x" @click.native="close()" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-primary border-b border-dividerLight">
|
||||
<div class="flex flex-col my-4 mx-6">
|
||||
<div class="flex flex-col px-6 py-4 border-b border-dividerLight">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="
|
||||
bg-primaryLight
|
||||
border border-dividerLight
|
||||
rounded
|
||||
flex
|
||||
w-full
|
||||
py-2
|
||||
px-4
|
||||
focus-visible:border-divider
|
||||
"
|
||||
:placeholder="`${$t('action.search')}`"
|
||||
class="flex px-4 py-2 border rounded bg-primaryLight border-dividerLight focus-visible:border-divider"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filterText">
|
||||
<div v-if="filterText" class="flex flex-col divide-y divide-dividerLight">
|
||||
<div
|
||||
v-for="(map, mapIndex) in searchResults"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="space-y-4 py-4 px-6"
|
||||
class="px-6 py-4 space-y-4"
|
||||
>
|
||||
<h1 class="font-semibold text-secondaryDark">
|
||||
{{ $t(map.item.section) }}
|
||||
{{ t(map.item.section) }}
|
||||
</h1>
|
||||
<AppShortcutsEntry
|
||||
v-for="(shortcut, index) in map.item.shortcuts"
|
||||
@@ -56,36 +35,22 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="searchResults.length === 0"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">manage_search</i>
|
||||
<span class="text-center">
|
||||
{{ $t("state.nothing_found") }} "{{ filterText }}"
|
||||
<i class="pb-2 opacity-75 material-icons">manage_search</i>
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
divide-y divide-dividerLight
|
||||
flex flex-col flex-1
|
||||
overflow-auto
|
||||
hide-scrollbar
|
||||
"
|
||||
>
|
||||
<div v-else class="flex flex-col divide-y divide-dividerLight">
|
||||
<div
|
||||
v-for="(map, mapIndex) in mappings"
|
||||
:key="`map-${mapIndex}`"
|
||||
class="space-y-4 py-4 px-6"
|
||||
class="px-6 py-4 space-y-4"
|
||||
>
|
||||
<h1 class="font-semibold text-secondaryDark">
|
||||
{{ $t(map.section) }}
|
||||
{{ t(map.section) }}
|
||||
</h1>
|
||||
<AppShortcutsEntry
|
||||
v-for="(shortcut, shortcutIndex) in map.shortcuts"
|
||||
@@ -102,6 +67,9 @@
|
||||
import { computed, ref } from "@nuxtjs/composition-api"
|
||||
import Fuse from "fuse.js"
|
||||
import mappings from "~/helpers/shortcuts"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
@@ -126,14 +94,3 @@ const close = () => {
|
||||
emit("close")
|
||||
}
|
||||
</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,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center py-1">
|
||||
<span class="flex flex-1 mr-4">
|
||||
{{ $t(shortcut.label) }}
|
||||
{{ t(shortcut.label) }}
|
||||
</span>
|
||||
<span
|
||||
v-for="(key, index) in shortcut.keys"
|
||||
@@ -14,18 +14,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
shortcut: Object
|
||||
shortcut: {
|
||||
label: string
|
||||
keys: string[]
|
||||
}
|
||||
}>()
|
||||
</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,6 +1,6 @@
|
||||
<template>
|
||||
<aside class="flex h-full justify-between md:flex-col">
|
||||
<nav class="flex flex-nowrap md:flex-col flex-1 md:flex-none">
|
||||
<aside class="flex justify-between h-full md:flex-col">
|
||||
<nav class="flex flex-1 flex-nowrap md:flex-col md:flex-none">
|
||||
<NuxtLink
|
||||
v-for="(navigation, index) in primaryNavigation"
|
||||
:key="`navigation-${index}`"
|
||||
@@ -8,9 +8,6 @@
|
||||
class="nav-link"
|
||||
tabindex="0"
|
||||
>
|
||||
<i v-if="navigation.icon" class="material-icons">
|
||||
{{ navigation.icon }}
|
||||
</i>
|
||||
<div v-if="navigation.svg">
|
||||
<SmartIcon :name="navigation.svg" class="svg-icons" />
|
||||
</div>
|
||||
@@ -27,14 +24,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useContext } from "@nuxtjs/composition-api"
|
||||
import useWindowSize from "~/helpers/utils/useWindowSize"
|
||||
import { useSetting } from "~/newstore/settings"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const {
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const windowInnerWidth = useWindowSize()
|
||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||
@@ -106,9 +100,7 @@ const primaryNavigation = [
|
||||
|
||||
span {
|
||||
@apply mt-2;
|
||||
@apply font-font-medium;
|
||||
|
||||
font-size: calc(var(--body-font-size) - 0.062rem);
|
||||
@apply text-tiny;
|
||||
}
|
||||
|
||||
&.exact-active-link {
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<transition v-if="show" name="fade" appear>
|
||||
<div class="inset-0 transition-opacity z-20 fixed" @keydown.esc="close()">
|
||||
<div class="fixed inset-0 z-20 transition-opacity" @keydown.esc="close()">
|
||||
<div
|
||||
class="bg-primaryLight opacity-90 inset-0 absolute"
|
||||
class="absolute inset-0 bg-primaryLight opacity-90"
|
||||
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="fixed top-0 right-0 z-30 flex flex-col h-full max-w-full overflow-auto transition duration-300 ease-in-out transform bg-primary w-96 hide-scrollbar"
|
||||
:class="show ? 'shadow-xl translate-x-0' : 'translate-x-full'"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="$t('support.title')"
|
||||
:title="t('support.title')"
|
||||
max-width="sm:max-w-md"
|
||||
@close="$emit('hide-modal')"
|
||||
>
|
||||
@@ -9,9 +9,9 @@
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
svg="book"
|
||||
:label="$t('app.documentation')"
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io"
|
||||
:description="$t('support.documentation')"
|
||||
:description="t('support.documentation')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
blank
|
||||
@@ -19,17 +19,17 @@
|
||||
/>
|
||||
<SmartItem
|
||||
svg="zap"
|
||||
:label="$t('app.keyboard_shortcuts')"
|
||||
:description="$t('support.shortcuts')"
|
||||
:label="t('app.keyboard_shortcuts')"
|
||||
:description="t('support.shortcuts')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
@click.native="showShortcuts()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="gift"
|
||||
:label="$t('app.whats_new')"
|
||||
:label="t('app.whats_new')"
|
||||
to="https://docs.hoppscotch.io/changelog"
|
||||
:description="$t('support.changelog')"
|
||||
:description="t('support.changelog')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
blank
|
||||
@@ -37,28 +37,28 @@
|
||||
/>
|
||||
<SmartItem
|
||||
svg="message-circle"
|
||||
:label="$t('app.chat_with_us')"
|
||||
:description="$t('support.chat')"
|
||||
:label="t('app.chat_with_us')"
|
||||
:description="t('support.chat')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
@click.native="chatWithUs()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="brands/discord"
|
||||
:label="$t('app.join_discord_community')"
|
||||
:label="t('app.join_discord_community')"
|
||||
to="https://hoppscotch.io/discord"
|
||||
blank
|
||||
:description="$t('support.community')"
|
||||
:description="t('support.community')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
@click.native="hideModal()"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="brands/twitter"
|
||||
:label="$t('app.twitter')"
|
||||
:label="t('app.twitter')"
|
||||
to="https://hoppscotch.io/twitter"
|
||||
blank
|
||||
:description="$t('support.twitter')"
|
||||
:description="t('support.twitter')"
|
||||
info-icon="chevron_right"
|
||||
active
|
||||
@click.native="hideModal()"
|
||||
@@ -71,6 +71,9 @@
|
||||
<script setup lang="ts">
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { showChat } from "~/helpers/support"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
defineProps<{
|
||||
show: Boolean
|
||||
@@ -87,6 +90,7 @@ const chatWithUs = () => {
|
||||
|
||||
const showShortcuts = () => {
|
||||
invokeAction("flyouts.keybinds.toggle")
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
|
||||
@@ -3,16 +3,7 @@
|
||||
:to="`${/^\/(?!\/).*$/.test(to) ? localePath(to) : to}`"
|
||||
:exact="exact"
|
||||
:blank="blank"
|
||||
class="
|
||||
font-bold
|
||||
py-2
|
||||
transition
|
||||
inline-flex
|
||||
items-center
|
||||
justify-center
|
||||
focus:outline-none
|
||||
focus-visible:bg-accentDark
|
||||
"
|
||||
class="inline-flex items-center justify-center py-2 font-bold transition focus:outline-none focus-visible:bg-accentDark"
|
||||
:class="[
|
||||
color
|
||||
? `text-${color}-800 bg-${color}-200 hover:(text-${color}-900 bg-${color}-300) focus-visible:(text-${color}-900 bg-${color}-300)`
|
||||
@@ -61,11 +52,11 @@
|
||||
]"
|
||||
/>
|
||||
{{ label }}
|
||||
<div v-if="shortcut.length" class="ml-2">
|
||||
<div v-if="shortcut.length" class="ml-2 <sm:hidden">
|
||||
<kbd
|
||||
v-for="(key, index) in shortcut"
|
||||
:key="`key-${index}`"
|
||||
class="bg-accentLight rounded ml-1 px-1 inline-flex"
|
||||
class="shortcut-key !bg-accentLight"
|
||||
>
|
||||
{{ key }}
|
||||
</kbd>
|
||||
|
||||
@@ -3,22 +3,12 @@
|
||||
:to="`${/^\/(?!\/).*$/.test(to) ? localePath(to) : to}`"
|
||||
:exact="exact"
|
||||
:blank="blank"
|
||||
class="
|
||||
font-semibold
|
||||
py-2
|
||||
transition
|
||||
inline-flex
|
||||
items-center
|
||||
justify-center
|
||||
whitespace-nowrap
|
||||
hover:bg-primaryDark
|
||||
focus:outline-none
|
||||
focus-visible:bg-primaryDark
|
||||
"
|
||||
class="inline-flex items-center justify-center py-2 font-semibold transition whitespace-nowrap focus:outline-none"
|
||||
:class="[
|
||||
color
|
||||
? `text-${color}-500 hover:(text-${color}-600 text-${color}-600)`
|
||||
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
||||
: 'text-secondary hover:text-secondaryDark focus-visible:text-secondaryDark',
|
||||
{ 'pointer-events-none': loading },
|
||||
label ? 'rounded px-4' : 'px-2',
|
||||
{ 'rounded-full': rounded },
|
||||
{ 'opacity-75 cursor-not-allowed': disabled },
|
||||
@@ -28,46 +18,50 @@
|
||||
'border border-divider hover:border-dividerDark focus-visible:border-dividerDark':
|
||||
outline,
|
||||
},
|
||||
{ '!bg-primaryDark': filled },
|
||||
{
|
||||
'bg-primaryLight hover:bg-primaryDark focus-visible:bg-primaryDark':
|
||||
filled,
|
||||
},
|
||||
]"
|
||||
:disabled="disabled"
|
||||
:tabindex="loading ? '-1' : '0'"
|
||||
>
|
||||
<i
|
||||
v-if="icon"
|
||||
class="material-icons"
|
||||
:class="[
|
||||
{ '!text-2xl': large },
|
||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||
]"
|
||||
<span
|
||||
v-if="!loading"
|
||||
class="inline-flex items-center justify-center whitespace-nowrap"
|
||||
:class="{ 'flex-row-reverse': reverse }"
|
||||
>
|
||||
{{ icon }}
|
||||
</i>
|
||||
<SmartIcon
|
||||
v-if="svg"
|
||||
:name="svg"
|
||||
class="svg-icons"
|
||||
:class="[
|
||||
{ '!h-6 !w-6': large },
|
||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||
]"
|
||||
/>
|
||||
{{ label }}
|
||||
<div v-if="shortcut.length" class="ml-2">
|
||||
<kbd
|
||||
v-for="(key, index) in shortcut"
|
||||
:key="`key-${index}`"
|
||||
class="
|
||||
bg-dividerLight
|
||||
rounded
|
||||
text-secondaryLight
|
||||
ml-1
|
||||
px-1
|
||||
inline-flex
|
||||
"
|
||||
<i
|
||||
v-if="icon"
|
||||
class="material-icons"
|
||||
:class="[
|
||||
{ '!text-2xl': large },
|
||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||
]"
|
||||
>
|
||||
{{ key }}
|
||||
</kbd>
|
||||
</div>
|
||||
{{ icon }}
|
||||
</i>
|
||||
<SmartIcon
|
||||
v-if="svg"
|
||||
:name="svg"
|
||||
class="svg-icons"
|
||||
:class="[
|
||||
{ '!h-6 !w-6': large },
|
||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||
]"
|
||||
/>
|
||||
{{ label }}
|
||||
<div v-if="shortcut.length" class="ml-2 <sm:hidden">
|
||||
<kbd
|
||||
v-for="(key, index) in shortcut"
|
||||
:key="`key-${index}`"
|
||||
class="shortcut-key"
|
||||
>
|
||||
{{ key }}
|
||||
</kbd>
|
||||
</div>
|
||||
</span>
|
||||
<SmartSpinner v-else />
|
||||
</SmartLink>
|
||||
</template>
|
||||
|
||||
@@ -108,6 +102,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
reverse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
||||
@@ -47,9 +47,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
addNewCollection() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(this.$t("collection.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
|
||||
@@ -51,9 +51,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
addFolder() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(this.$t("folder.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("folder.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("add-folder", {
|
||||
|
||||
@@ -18,18 +18,7 @@
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
class="
|
||||
bg-transparent
|
||||
border-t border-dividerLight
|
||||
cursor-pointer
|
||||
flex
|
||||
font-semibold
|
||||
w-full
|
||||
py-2
|
||||
px-4
|
||||
appearance-none
|
||||
hover:bg-primaryDark
|
||||
"
|
||||
class="bg-transparent border-t border-dividerLight cursor-pointer flex font-semibold w-full py-2 px-4 appearance-none hover:bg-primaryDark"
|
||||
@change="updateSelectedTeam(myTeams[$event.target.value])"
|
||||
>
|
||||
<option
|
||||
|
||||
@@ -38,19 +38,22 @@ import { defineComponent } from "@nuxtjs/composition-api"
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
placeholderCollName: { type: String, default: null },
|
||||
editingCollectionName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingCollectionName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveCollection() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(this.$t("collection.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
|
||||
@@ -39,18 +39,22 @@ import { defineComponent } from "@nuxtjs/composition-api"
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
editingFolderName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingFolderName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editFolder() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(this.$t("folder.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("folder.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.name)
|
||||
|
||||
@@ -35,7 +35,7 @@ import { defineComponent } from "@nuxtjs/composition-api"
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
placeholderReqName: { type: String, default: null },
|
||||
editingRequestName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -44,12 +44,15 @@ export default defineComponent({
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingRequestName(val) {
|
||||
this.requestUpdateData.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveRequest() {
|
||||
if (!this.requestUpdateData.name) {
|
||||
this.$toast.error(this.$t("request.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("request.invalid_name"))
|
||||
return
|
||||
}
|
||||
this.$emit("submit", this.requestUpdateData)
|
||||
|
||||
@@ -1,62 +1,142 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="`${$t('modal.import_export')} ${$t('modal.collections')}`"
|
||||
:title="`${t('modal.collections')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #actions>
|
||||
<ButtonSecondary
|
||||
v-if="mode == 'import_from_my_collections'"
|
||||
v-if="importerType !== null"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.go_back')"
|
||||
class="rounded"
|
||||
:title="t('action.go_back')"
|
||||
svg="arrow-left"
|
||||
@click.native="
|
||||
() => {
|
||||
mode = 'import_export'
|
||||
mySelectedCollectionID = undefined
|
||||
}
|
||||
"
|
||||
@click.native="resetImport"
|
||||
/>
|
||||
<span>
|
||||
<tippy
|
||||
v-if="
|
||||
mode == 'import_export' && collectionsType.type == 'my-collections'
|
||||
"
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.more')"
|
||||
class="rounded"
|
||||
svg="more-vertical"
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="importerType !== null" class="flex flex-col">
|
||||
<div class="flex pb-6 flex-col px-2">
|
||||
<div
|
||||
v-for="(step, index) in importerSteps"
|
||||
:key="`step-${index}`"
|
||||
class="flex flex-col space-y-8"
|
||||
>
|
||||
<div v-if="step.name === 'FILE_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex border-4 border-primary items-center justify-center flex-shrink-0 mr-4 rounded-full text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasFile,
|
||||
}"
|
||||
>
|
||||
<i class="material-icons">check_circle</i>
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${step.metadata.caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<input
|
||||
id="inputChooseFileToImportFrom"
|
||||
ref="inputChooseFileToImportFrom"
|
||||
name="inputChooseFileToImportFrom"
|
||||
type="file"
|
||||
class="transition cursor-pointer file:transition file:cursor-pointer text-secondary hover:text-secondaryDark file:mr-2 file:py-2 file:px-4 file:rounded file:border-0 file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||
:accept="step.metadata.acceptedFileTypes"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="step.name === 'URL_IMPORT'" class="space-y-4">
|
||||
<p class="flex items-center">
|
||||
<span
|
||||
class="inline-flex border-4 border-primary items-center justify-center flex-shrink-0 mr-4 rounded-full text-dividerDark"
|
||||
:class="{
|
||||
'!text-green-500': hasGist,
|
||||
}"
|
||||
>
|
||||
<i class="material-icons">check_circle</i>
|
||||
</span>
|
||||
<span>
|
||||
{{ t(`${step.metadata.caption}`) }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex flex-col ml-10">
|
||||
<input
|
||||
v-model="inputChooseGistToImportFrom"
|
||||
type="url"
|
||||
class="input"
|
||||
:placeholder="`${$t('import.gist_url')}`"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="step.name === 'TARGET_MY_COLLECTION'"
|
||||
class="flex flex-col px-2"
|
||||
>
|
||||
<div class="select-wrapper">
|
||||
<select
|
||||
v-model="mySelectedCollectionID"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="select"
|
||||
autofocus
|
||||
>
|
||||
<option :key="undefined" :value="undefined" disabled selected>
|
||||
{{ t("collection.select") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(collection, collectionIndex) in myCollections"
|
||||
:key="`collection-${collectionIndex}`"
|
||||
:value="collectionIndex"
|
||||
>
|
||||
{{ collection.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonPrimary
|
||||
:label="t('import.title')"
|
||||
:disabled="enableImportButton"
|
||||
class="mx-2"
|
||||
:loading="importingMyCollections"
|
||||
@click.native="finishImport"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex flex-col px-2">
|
||||
<SmartExpand>
|
||||
<template #body>
|
||||
<SmartItem
|
||||
v-for="(importer, index) in importerModules"
|
||||
:key="`importer-${index}`"
|
||||
:svg="importer.icon"
|
||||
:label="t(`${importer.name}`)"
|
||||
@click.native="importerType = index"
|
||||
/>
|
||||
</template>
|
||||
</SmartExpand>
|
||||
<hr />
|
||||
<div class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
icon="assignment_returned"
|
||||
:label="$t('import.from_gist')"
|
||||
@click.native="
|
||||
() => {
|
||||
readCollectionGist
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.download_file')"
|
||||
svg="download"
|
||||
:label="t('export.as_json')"
|
||||
@click.native="exportJSON"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? $t('export.require_github')
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? $t('export.require_github')
|
||||
: null
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
class="flex"
|
||||
>
|
||||
<SmartItem
|
||||
:disabled="
|
||||
@@ -66,501 +146,276 @@
|
||||
? true
|
||||
: false
|
||||
"
|
||||
icon="assignment_turned_in"
|
||||
:label="$t('export.create_secret_gist')"
|
||||
svg="github"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click.native="
|
||||
() => {
|
||||
createCollectionGist()
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</tippy>
|
||||
</span>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="mode == 'import_export'" class="flex flex-col space-y-2">
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.replace_current')"
|
||||
svg="file"
|
||||
:label="$t('action.replace_json')"
|
||||
@click.native="openDialogChooseFileToReplaceWith"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToReplaceWith"
|
||||
class="input"
|
||||
type="file"
|
||||
style="display: none"
|
||||
accept="application/json"
|
||||
@change="replaceWithJSON"
|
||||
/>
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.preserve_current')"
|
||||
svg="folder-plus"
|
||||
:label="$t('import.json')"
|
||||
@click.native="openDialogChooseFileToImportFrom"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToImportFrom"
|
||||
class="input"
|
||||
type="file"
|
||||
style="display: none"
|
||||
accept="application/json"
|
||||
@change="importFromJSON"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="collectionsType.type == 'team-collections'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.preserve_current')"
|
||||
svg="user"
|
||||
:label="$t('import.from_my_collections')"
|
||||
@click.native="mode = 'import_from_my_collections'"
|
||||
/>
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.download_file')"
|
||||
svg="download"
|
||||
:label="$t('export.as_json')"
|
||||
@click.native="exportJSON"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="mode == 'import_from_my_collections'"
|
||||
class="flex flex-col px-2"
|
||||
>
|
||||
<div class="select-wrapper">
|
||||
<select
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="select"
|
||||
autofocus
|
||||
@change="
|
||||
($event) => {
|
||||
mySelectedCollectionID = $event.target.value
|
||||
}
|
||||
"
|
||||
>
|
||||
<option
|
||||
:key="undefined"
|
||||
:value="undefined"
|
||||
hidden
|
||||
disabled
|
||||
selected
|
||||
>
|
||||
Select Collection
|
||||
</option>
|
||||
<option
|
||||
v-for="(collection, index) in myCollections"
|
||||
:key="`collection-${index}`"
|
||||
:value="index"
|
||||
>
|
||||
{{ collection.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div v-if="mode == 'import_from_my_collections'">
|
||||
<span>
|
||||
<ButtonPrimary
|
||||
:disabled="mySelectedCollectionID == undefined"
|
||||
svg="folder-plus"
|
||||
:label="$t('import.title')"
|
||||
@click.native="importFromMyCollections"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { HoppRESTRequest, HoppCollection } from "@hoppscotch/data"
|
||||
import { apolloClient } from "~/helpers/apollo"
|
||||
import {
|
||||
useAxios,
|
||||
useI18n,
|
||||
useReadonlyStream,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import * as teamUtils from "~/helpers/teams/utils"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
restCollections$,
|
||||
setRESTCollections,
|
||||
appendRESTCollections,
|
||||
} from "~/newstore/collections"
|
||||
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
||||
import { RESTCollectionImporters } from "~/helpers/import-export/import/importers"
|
||||
import { StepReturnValue } from "~/helpers/import-export/steps"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
myCollections: useReadonlyStream(restCollections$, []),
|
||||
currentUser: useReadonlyStream(currentUser$, null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showJsonCode: false,
|
||||
mode: "import_export",
|
||||
mySelectedCollectionID: undefined,
|
||||
collectionJson: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createCollectionGist() {
|
||||
this.getJSONCollection()
|
||||
await this.$axios
|
||||
.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: this.collectionJson,
|
||||
},
|
||||
},
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
collectionsType:
|
||||
| {
|
||||
type: "team-collections"
|
||||
selectedTeam: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
| { type: "my-collections" }
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
(e: "update-team-collections"): void
|
||||
}>()
|
||||
|
||||
const axios = useAxios()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const myCollections = useReadonlyStream(restCollections$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
// Template refs
|
||||
const mode = ref("import_export")
|
||||
const mySelectedCollectionID = ref<undefined | number>(undefined)
|
||||
const collectionJson = ref("")
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement | any>()
|
||||
const inputChooseGistToImportFrom = ref<string>("")
|
||||
|
||||
const getJSONCollection = async () => {
|
||||
if (props.collectionsType.type === "my-collections") {
|
||||
collectionJson.value = JSON.stringify(myCollections.value, null, 2)
|
||||
} else {
|
||||
collectionJson.value = await teamUtils.exportAsJSON(
|
||||
apolloClient,
|
||||
props.collectionsType.selectedTeam.id
|
||||
)
|
||||
}
|
||||
return collectionJson.value
|
||||
}
|
||||
|
||||
const createCollectionGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
getJSONCollection()
|
||||
|
||||
try {
|
||||
const res = await axios.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: collectionJson.value,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${this.currentUser.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
this.$toast.success(this.$t("export.gist_created"), {
|
||||
icon: "done",
|
||||
})
|
||||
window.open(res.html_url)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
async readCollectionGist() {
|
||||
const gist = prompt(this.$t("import.gist_url"))
|
||||
if (!gist) return
|
||||
await this.$axios
|
||||
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
})
|
||||
.then(({ files }) => {
|
||||
const collections = JSON.parse(Object.values(files)[0].content)
|
||||
setRESTCollections(collections)
|
||||
this.fileImported()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.failedImport()
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.mode = "import_export"
|
||||
this.mySelectedCollectionID = undefined
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
openDialogChooseFileToReplaceWith() {
|
||||
this.$refs.inputChooseFileToReplaceWith.click()
|
||||
},
|
||||
openDialogChooseFileToImportFrom() {
|
||||
this.$refs.inputChooseFileToImportFrom.click()
|
||||
},
|
||||
replaceWithJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
let collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (
|
||||
name === "name" &&
|
||||
folders === "folders" &&
|
||||
requests === "requests"
|
||||
) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (
|
||||
collections.info &&
|
||||
collections.info.schema.includes("v2.1.0")
|
||||
) {
|
||||
collections = [this.parsePostmanCollection(collections)]
|
||||
} else {
|
||||
this.failedImport()
|
||||
}
|
||||
if (this.collectionsType.type === "team-collections") {
|
||||
teamUtils
|
||||
.replaceWithJSON(
|
||||
this.$apollo,
|
||||
collections,
|
||||
this.collectionsType.selectedTeam.id
|
||||
)
|
||||
.then((status) => {
|
||||
if (status) {
|
||||
this.fileImported()
|
||||
} else {
|
||||
this.failedImport()
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
this.failedImport()
|
||||
})
|
||||
} else {
|
||||
setRESTCollections(collections)
|
||||
this.fileImported()
|
||||
}
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
|
||||
this.$refs.inputChooseFileToReplaceWith.value = ""
|
||||
},
|
||||
importFromJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
let collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (
|
||||
name === "name" &&
|
||||
folders === "folders" &&
|
||||
requests === "requests"
|
||||
) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (
|
||||
collections.info &&
|
||||
collections.info.schema.includes("v2.1.0")
|
||||
) {
|
||||
// replace the variables, postman uses {{var}}, Hoppscotch uses <<var>>
|
||||
collections = JSON.parse(
|
||||
content.replaceAll(/{{([a-z]+)}}/gi, "<<$1>>")
|
||||
)
|
||||
collections = [this.parsePostmanCollection(collections)]
|
||||
} else {
|
||||
this.failedImport()
|
||||
return
|
||||
}
|
||||
if (this.collectionsType.type === "team-collections") {
|
||||
teamUtils
|
||||
.importFromJSON(
|
||||
this.$apollo,
|
||||
collections,
|
||||
this.collectionsType.selectedTeam.id
|
||||
)
|
||||
.then((status) => {
|
||||
if (status) {
|
||||
this.$emit("update-team-collections")
|
||||
this.fileImported()
|
||||
} else {
|
||||
this.failedImport()
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
this.failedImport()
|
||||
})
|
||||
} else {
|
||||
appendRESTCollections(collections)
|
||||
this.fileImported()
|
||||
}
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
|
||||
this.$refs.inputChooseFileToImportFrom.value = ""
|
||||
},
|
||||
importFromMyCollections() {
|
||||
teamUtils
|
||||
.importFromMyCollections(
|
||||
this.$apollo,
|
||||
this.mySelectedCollectionID,
|
||||
this.collectionsType.selectedTeam.id
|
||||
)
|
||||
.then((success) => {
|
||||
if (success) {
|
||||
this.fileImported()
|
||||
this.$emit("update-team-collections")
|
||||
} else {
|
||||
this.failedImport()
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
this.failedImport()
|
||||
})
|
||||
},
|
||||
async getJSONCollection() {
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
this.collectionJson = JSON.stringify(this.myCollections, null, 2)
|
||||
} else {
|
||||
this.collectionJson = await teamUtils.exportAsJSON(
|
||||
this.$apollo,
|
||||
this.collectionsType.selectedTeam.id
|
||||
)
|
||||
}
|
||||
return this.collectionJson
|
||||
},
|
||||
exportJSON() {
|
||||
this.getJSONCollection()
|
||||
const dataToWrite = this.collectionJson
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
// TODO get uri from meta
|
||||
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.$toast.success(this.$t("state.download_started"), {
|
||||
icon: "downloading",
|
||||
})
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
},
|
||||
fileImported() {
|
||||
this.$toast.success(this.$t("state.file_imported"), {
|
||||
icon: "folder_shared",
|
||||
})
|
||||
},
|
||||
failedImport() {
|
||||
this.$toast.error(this.$t("import.failed"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
},
|
||||
parsePostmanCollection({ info, name, item }) {
|
||||
const hoppscotchCollection = {
|
||||
name: "",
|
||||
folders: [],
|
||||
requests: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
hoppscotchCollection.name = info ? info.name : name
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (item && item.length > 0) {
|
||||
for (const collectionItem of item) {
|
||||
if (collectionItem.request) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
hoppscotchCollection,
|
||||
"folders"
|
||||
)
|
||||
) {
|
||||
hoppscotchCollection.name = info ? info.name : name
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
} else {
|
||||
hoppscotchCollection.name = name || ""
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
}
|
||||
} else if (this.hasFolder(collectionItem)) {
|
||||
hoppscotchCollection.folders.push(
|
||||
this.parsePostmanCollection(collectionItem)
|
||||
)
|
||||
} else {
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return hoppscotchCollection
|
||||
},
|
||||
parsePostmanRequest({ name, request }) {
|
||||
const pwRequest = {
|
||||
url: "",
|
||||
path: "",
|
||||
method: "",
|
||||
auth: "",
|
||||
httpUser: "",
|
||||
httpPassword: "",
|
||||
passwordFieldType: "password",
|
||||
bearerToken: "",
|
||||
headers: [],
|
||||
params: [],
|
||||
bodyParams: [],
|
||||
rawParams: "",
|
||||
rawInput: false,
|
||||
contentType: "",
|
||||
requestType: "",
|
||||
name: "",
|
||||
}
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
hideModal()
|
||||
}
|
||||
|
||||
pwRequest.name = name
|
||||
if (request.url) {
|
||||
const requestObjectUrl = request.url.raw.match(
|
||||
/^(.+:\/\/[^/]+|{[^/]+})(\/[^?]+|).*$/
|
||||
)
|
||||
if (requestObjectUrl) {
|
||||
pwRequest.url = requestObjectUrl[1]
|
||||
pwRequest.path = requestObjectUrl[2] ? requestObjectUrl[2] : ""
|
||||
}
|
||||
}
|
||||
pwRequest.method = request.method
|
||||
const itemAuth = request.auth ? request.auth : ""
|
||||
const authType = itemAuth ? itemAuth.type : ""
|
||||
if (authType === "basic") {
|
||||
pwRequest.auth = "Basic Auth"
|
||||
pwRequest.httpUser =
|
||||
itemAuth.basic[0].key === "username"
|
||||
? itemAuth.basic[0].value
|
||||
: itemAuth.basic[1].value
|
||||
pwRequest.httpPassword =
|
||||
itemAuth.basic[0].key === "password"
|
||||
? itemAuth.basic[0].value
|
||||
: itemAuth.basic[1].value
|
||||
} else if (authType === "oauth2") {
|
||||
pwRequest.auth = "OAuth 2.0"
|
||||
pwRequest.bearerToken =
|
||||
itemAuth.oauth2[0].key === "accessToken"
|
||||
? itemAuth.oauth2[0].value
|
||||
: itemAuth.oauth2[1].value
|
||||
} else if (authType === "bearer") {
|
||||
pwRequest.auth = "Bearer Token"
|
||||
pwRequest.bearerToken = itemAuth.bearer[0].value
|
||||
}
|
||||
const requestObjectHeaders = request.header
|
||||
if (requestObjectHeaders) {
|
||||
pwRequest.headers = requestObjectHeaders
|
||||
for (const header of pwRequest.headers) {
|
||||
delete header.name
|
||||
delete header.type
|
||||
}
|
||||
}
|
||||
if (request.url) {
|
||||
const requestObjectParams = request.url.query
|
||||
if (requestObjectParams) {
|
||||
pwRequest.params = requestObjectParams
|
||||
for (const param of pwRequest.params) {
|
||||
delete param.disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
if (request.body) {
|
||||
if (request.body.mode === "urlencoded") {
|
||||
const params = request.body.urlencoded
|
||||
pwRequest.bodyParams = params || []
|
||||
for (const param of pwRequest.bodyParams) {
|
||||
delete param.type
|
||||
}
|
||||
} else if (request.body.mode === "raw") {
|
||||
pwRequest.rawInput = true
|
||||
pwRequest.rawParams = request.body.raw
|
||||
}
|
||||
}
|
||||
return pwRequest
|
||||
},
|
||||
hasFolder(item) {
|
||||
return Object.prototype.hasOwnProperty.call(item, "item")
|
||||
},
|
||||
},
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
mode.value = "import_export"
|
||||
mySelectedCollectionID.value = undefined
|
||||
resetImport()
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const stepResults = ref<StepReturnValue[]>([])
|
||||
|
||||
watch(mySelectedCollectionID, (newValue) => {
|
||||
if (newValue === undefined) return
|
||||
stepResults.value = []
|
||||
stepResults.value.push(newValue)
|
||||
})
|
||||
|
||||
const importingMyCollections = ref(false)
|
||||
|
||||
const importToTeams = async (content: HoppCollection<HoppRESTRequest>) => {
|
||||
importingMyCollections.value = true
|
||||
if (props.collectionsType.type !== "team-collections") return
|
||||
await teamUtils
|
||||
.importFromJSON(
|
||||
apolloClient,
|
||||
content,
|
||||
props.collectionsType.selectedTeam.id
|
||||
)
|
||||
.then((status) => {
|
||||
if (status) {
|
||||
emit("update-team-collections")
|
||||
} else {
|
||||
console.error(status)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
importingMyCollections.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const exportJSON = () => {
|
||||
getJSONCollection()
|
||||
|
||||
const dataToWrite = collectionJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const importerModules = computed(() =>
|
||||
RESTCollectionImporters.filter(
|
||||
(i) => i.applicableTo?.includes(props.collectionsType.type) ?? true
|
||||
)
|
||||
)
|
||||
|
||||
const importerType = ref<number | null>(null)
|
||||
|
||||
const importerModule = computed(() =>
|
||||
importerType.value !== null ? importerModules.value[importerType.value] : null
|
||||
)
|
||||
|
||||
const importerSteps = computed(() => importerModule.value?.steps ?? null)
|
||||
|
||||
const finishImport = async () => {
|
||||
await importerAction(stepResults.value)
|
||||
}
|
||||
|
||||
const importerAction = async (stepResults: any[]) => {
|
||||
if (!importerModule.value) return
|
||||
const result = await importerModule.value?.importer(stepResults as any)()
|
||||
if (E.isLeft(result)) {
|
||||
failedImport()
|
||||
console.error("error", result.left)
|
||||
} else if (E.isRight(result)) {
|
||||
if (props.collectionsType.type === "team-collections") {
|
||||
importToTeams(result.right)
|
||||
fileImported()
|
||||
} else {
|
||||
appendRESTCollections(result.right)
|
||||
fileImported()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasFile = ref(false)
|
||||
const hasGist = ref(false)
|
||||
|
||||
watch(inputChooseGistToImportFrom, (v) => {
|
||||
stepResults.value = []
|
||||
if (v === "") {
|
||||
hasGist.value = false
|
||||
} else {
|
||||
hasGist.value = true
|
||||
stepResults.value.push(inputChooseGistToImportFrom.value)
|
||||
}
|
||||
})
|
||||
|
||||
const onFileChange = () => {
|
||||
stepResults.value = []
|
||||
if (!inputChooseFileToImportFrom.value[0]) {
|
||||
hasFile.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value[0].files ||
|
||||
inputChooseFileToImportFrom.value[0].files.length === 0
|
||||
) {
|
||||
inputChooseFileToImportFrom.value[0].value = ""
|
||||
hasFile.value = false
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
if (!content) {
|
||||
hasFile.value = false
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
stepResults.value.push(content)
|
||||
hasFile.value = !!content?.length
|
||||
}
|
||||
reader.readAsText(inputChooseFileToImportFrom.value[0].files[0])
|
||||
}
|
||||
|
||||
const enableImportButton = computed(
|
||||
() => !(stepResults.value.length === importerSteps.value?.length)
|
||||
)
|
||||
|
||||
const resetImport = () => {
|
||||
importerType.value = null
|
||||
stepResults.value = []
|
||||
inputChooseFileToImportFrom.value = ""
|
||||
hasFile.value = false
|
||||
inputChooseGistToImportFrom.value = ""
|
||||
hasGist.value = false
|
||||
mySelectedCollectionID.value = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="`${$t('collection.save_as')}`"
|
||||
:title="`${t('collection.save_as')}`"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
@@ -18,11 +18,11 @@
|
||||
@keyup.enter="saveRequestAs"
|
||||
/>
|
||||
<label for="selectLabelSaveReq">
|
||||
{{ $t("request.name") }}
|
||||
{{ t("request.name") }}
|
||||
</label>
|
||||
</div>
|
||||
<label class="p-4">
|
||||
{{ $t("collection.select_location") }}
|
||||
{{ t("collection.select_location") }}
|
||||
</label>
|
||||
<CollectionsGraphql
|
||||
v-if="mode === 'graphql'"
|
||||
@@ -45,11 +45,11 @@
|
||||
<template #footer>
|
||||
<span>
|
||||
<ButtonPrimary
|
||||
:label="`${$t('action.save')}`"
|
||||
:label="`${t('action.save')}`"
|
||||
@click.native="saveRequestAs"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('action.cancel')}`"
|
||||
:label="`${t('action.cancel')}`"
|
||||
@click.native="hideModal"
|
||||
/>
|
||||
</span>
|
||||
@@ -58,8 +58,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, useContext, watch } from "@nuxtjs/composition-api"
|
||||
import { isHoppRESTRequest } from "~/helpers/types/HoppRESTRequest"
|
||||
import { reactive, ref, watch } from "@nuxtjs/composition-api"
|
||||
import { HoppGQLRequest, isHoppRESTRequest } from "@hoppscotch/data"
|
||||
import cloneDeep from "lodash/cloneDeep"
|
||||
import {
|
||||
editGraphqlRequest,
|
||||
editRESTRequest,
|
||||
@@ -74,7 +75,9 @@ import {
|
||||
} from "~/newstore/RESTSession"
|
||||
import * as teamUtils from "~/helpers/teams/utils"
|
||||
import { apolloClient } from "~/helpers/apollo"
|
||||
import { HoppGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type CollectionType =
|
||||
| {
|
||||
@@ -137,12 +140,7 @@ const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
|
||||
const t = i18n.t.bind(i18n)
|
||||
const toast = useToast()
|
||||
|
||||
// TODO: Use a better implementation with computed ?
|
||||
// This implementation can't work across updates to mode prop (which won't happen tho)
|
||||
@@ -194,20 +192,20 @@ const hideModal = () => {
|
||||
|
||||
const saveRequestAs = async () => {
|
||||
if (!requestName.value) {
|
||||
$toast.error(`${t("error.empty_req_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("error.empty_req_name")}`)
|
||||
return
|
||||
}
|
||||
if (picked.value === null) {
|
||||
$toast.error(`${t("collection.select")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("collection.select")}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Clone Deep because objects are shared by reference so updating
|
||||
// just one bit will update other referenced shared instances
|
||||
const requestUpdated =
|
||||
props.mode === "rest" ? getRESTRequest() : getGQLSession().request
|
||||
props.mode === "rest"
|
||||
? cloneDeep(getRESTRequest())
|
||||
: cloneDeep(getGQLSession().request)
|
||||
|
||||
// // Filter out all REST file inputs
|
||||
// if (this.mode === "rest" && requestUpdated.bodyParams) {
|
||||
@@ -283,9 +281,7 @@ const saveRequestAs = async () => {
|
||||
requestSaved()
|
||||
})
|
||||
.catch((error) => {
|
||||
$toast.error(t("profile.no_permission").toString(), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
throw new Error(error)
|
||||
})
|
||||
|
||||
@@ -320,9 +316,7 @@ const saveRequestAs = async () => {
|
||||
|
||||
requestSaved()
|
||||
} catch (error) {
|
||||
$toast.error(t("profile.no_permission").toString(), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
console.error(error)
|
||||
}
|
||||
} else if (picked.value.pickedType === "teams-collection") {
|
||||
@@ -352,9 +346,7 @@ const saveRequestAs = async () => {
|
||||
|
||||
requestSaved()
|
||||
} catch (error) {
|
||||
$toast.error(t("profile.no_permission").toString(), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
console.error(error)
|
||||
}
|
||||
} else if (picked.value.pickedType === "gql-my-request") {
|
||||
@@ -386,9 +378,7 @@ const saveRequestAs = async () => {
|
||||
}
|
||||
|
||||
const requestSaved = () => {
|
||||
$toast.success(`${t("request.added")}`, {
|
||||
icon: "post_add",
|
||||
})
|
||||
toast.success(`${t("request.added")}`)
|
||||
hideModal()
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { HoppGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
import { addGraphqlCollection, makeCollection } from "~/newstore/collections"
|
||||
import { HoppGQLRequest, makeCollection } from "@hoppscotch/data"
|
||||
import { addGraphqlCollection } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -49,9 +49,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
addNewCollection() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate"> {{ collection.name }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collection.name }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
@@ -52,6 +47,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -60,55 +56,61 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="folder-plus"
|
||||
:label="`${$t('folder.new')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="`${$t('action.delete')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="`${$t('folder.new')}`"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="`${$t('action.delete')}`"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -149,26 +151,13 @@
|
||||
v-if="
|
||||
collection.folders.length === 0 && collection.requests.length === 0
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.collection')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${$t('empty.collection')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.collection") }}
|
||||
@@ -186,7 +175,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
removeGraphqlCollection,
|
||||
moveGraphqlRequest,
|
||||
@@ -202,6 +191,15 @@ export default defineComponent({
|
||||
doc: Boolean,
|
||||
isFiltered: Boolean,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
@@ -221,7 +219,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
@@ -251,16 +249,12 @@ export default defineComponent({
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
removeGraphqlCollection(this.collectionIndex)
|
||||
this.$toast.success(`${this.$t("state.deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
},
|
||||
dropEvent({ dataTransfer }: any) {
|
||||
this.dragging = !this.dragging
|
||||
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
|
||||
moveGraphqlRequest(folderPath, requestIndex, `${this.collectionIndex}`)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -45,18 +45,22 @@ export default defineComponent({
|
||||
show: Boolean,
|
||||
editingCollection: { type: Object, default: () => {} },
|
||||
editingCollectionIndex: { type: Number, default: null },
|
||||
editingCollectionName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null as string | null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingCollectionName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveCollection() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
const collectionUpdated = {
|
||||
|
||||
@@ -45,18 +45,22 @@ export default defineComponent({
|
||||
show: Boolean,
|
||||
folder: { type: Object, default: () => {} },
|
||||
folderPath: { type: String, default: null },
|
||||
editingFolderName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingFolderName(val) {
|
||||
this.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editFolder() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
editGraphqlFolder(this.folderPath, {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "@nuxtjs/composition-api"
|
||||
import { HoppGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { editGraphqlRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
@@ -47,6 +47,7 @@ export default defineComponent({
|
||||
folderPath: { type: String, default: null },
|
||||
request: { type: Object as PropType<HoppGQLRequest>, default: () => {} },
|
||||
requestIndex: { type: Number, default: null },
|
||||
editingRequestName: { type: String, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -55,12 +56,15 @@ export default defineComponent({
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editingRequestName(val) {
|
||||
this.requestUpdateData.name = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveRequest() {
|
||||
if (!this.requestUpdateData.name) {
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("collection.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
const requestUpdated = {
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate">
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -50,6 +43,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -58,53 +52,59 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="folder-plus"
|
||||
:label="`${$t('folder.new')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', { folder, folderPath })
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="`${$t('action.delete')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="`${$t('folder.new')}`"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', { folder, folderPath })
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="`${$t('action.delete')}`"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -148,26 +148,13 @@
|
||||
folder.requests &&
|
||||
folder.requests.length === 0
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.folder')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${$t('empty.folder')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.folder") }}
|
||||
@@ -185,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
@@ -201,6 +188,15 @@ export default defineComponent({
|
||||
doc: Boolean,
|
||||
isFiltered: Boolean,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
@@ -219,7 +215,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
@@ -250,15 +246,12 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
removeGraphqlFolder(this.folderPath)
|
||||
this.$toast.success(`${this.$t("state.deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
},
|
||||
dropEvent({ dataTransfer }: any) {
|
||||
this.dragging = !this.dragging
|
||||
const folderPath = dataTransfer.getData("folderPath")
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
|
||||
moveGraphqlRequest(folderPath, requestIndex, this.folderPath)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="`${$t('modal.import_export')} ${$t('modal.collections')}`"
|
||||
:title="`${t('modal.collections')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
@@ -11,27 +11,28 @@
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.more')"
|
||||
class="rounded"
|
||||
:title="t('action.more')"
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
icon="assignment_returned"
|
||||
:label="$t('import.from_gist')"
|
||||
:label="t('import.from_gist')"
|
||||
@click.native="
|
||||
readCollectionGist
|
||||
$refs.options.tippy().hide()
|
||||
() => {
|
||||
readCollectionGist()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? $t('export.require_github')
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? $t('export.require_github')
|
||||
: null
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<SmartItem
|
||||
@@ -43,10 +44,12 @@
|
||||
: false
|
||||
"
|
||||
icon="assignment_turned_in"
|
||||
:label="$t('export.create_secret_gist')"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click.native="
|
||||
createCollectionGist()
|
||||
$refs.options.tippy().hide()
|
||||
() => {
|
||||
createCollectionGist()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
@@ -54,26 +57,10 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex flex-col space-y-2 px-2">
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.replace_current')"
|
||||
svg="file"
|
||||
:label="$t('action.replace_json')"
|
||||
@click.native="openDialogChooseFileToReplaceWith"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToReplaceWith"
|
||||
class="input"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
@change="replaceWithJSON"
|
||||
/>
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.preserve_current')"
|
||||
svg="folder-plus"
|
||||
:label="$t('import.json')"
|
||||
:label="t('import.from_json')"
|
||||
@click.native="openDialogChooseFileToImportFrom"
|
||||
/>
|
||||
<input
|
||||
@@ -83,11 +70,12 @@
|
||||
accept="application/json"
|
||||
@change="importFromJSON"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.download_file')"
|
||||
:title="t('action.download_file')"
|
||||
svg="download"
|
||||
:label="$t('export.as_json')"
|
||||
:label="t('export.as_json')"
|
||||
@click.native="exportJSON"
|
||||
/>
|
||||
</div>
|
||||
@@ -95,305 +83,175 @@
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "@nuxtjs/composition-api"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
useAxios,
|
||||
useI18n,
|
||||
useReadonlyStream,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import {
|
||||
graphqlCollections$,
|
||||
setGraphqlCollections,
|
||||
appendGraphqlCollections,
|
||||
} from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
collections: useReadonlyStream(graphqlCollections$, []),
|
||||
currentUser: useReadonlyStream(currentUser$, null),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
collectionJson() {
|
||||
return JSON.stringify(this.collections, null, 2)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async createCollectionGist() {
|
||||
await this.$axios
|
||||
.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: this.collectionJson,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${this.currentUser.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
this.$toast.success(this.$t("export.gist_created"), {
|
||||
icon: "done",
|
||||
})
|
||||
window.open(res.html_url)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
async readCollectionGist() {
|
||||
const gist = prompt(this.$t("import.gist_url"))
|
||||
if (!gist) return
|
||||
await this.$axios
|
||||
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
})
|
||||
.then(({ files }) => {
|
||||
const collections = JSON.parse(Object.values(files)[0].content)
|
||||
setGraphqlCollections(collections)
|
||||
this.fileImported()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.failedImport()
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
openDialogChooseFileToReplaceWith() {
|
||||
this.$refs.inputChooseFileToReplaceWith.click()
|
||||
},
|
||||
openDialogChooseFileToImportFrom() {
|
||||
this.$refs.inputChooseFileToImportFrom.click()
|
||||
},
|
||||
replaceWithJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
let collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (
|
||||
name === "name" &&
|
||||
folders === "folders" &&
|
||||
requests === "requests"
|
||||
) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (
|
||||
collections.info &&
|
||||
collections.info.schema.includes("v2.1.0")
|
||||
) {
|
||||
collections = [this.parsePostmanCollection(collections)]
|
||||
} else {
|
||||
this.failedImport()
|
||||
return
|
||||
}
|
||||
setGraphqlCollections(collections)
|
||||
this.fileImported()
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
|
||||
this.$refs.inputChooseFileToReplaceWith.value = ""
|
||||
},
|
||||
importFromJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
let collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (
|
||||
name === "name" &&
|
||||
folders === "folders" &&
|
||||
requests === "requests"
|
||||
) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (
|
||||
collections.info &&
|
||||
collections.info.schema.includes("v2.1.0")
|
||||
) {
|
||||
// replace the variables, postman uses {{var}}, Hoppscotch uses <<var>>
|
||||
collections = JSON.parse(
|
||||
content.replaceAll(/{{([a-z]+)}}/gi, "<<$1>>")
|
||||
)
|
||||
collections = [this.parsePostmanCollection(collections)]
|
||||
} else {
|
||||
this.failedImport()
|
||||
return
|
||||
}
|
||||
appendGraphqlCollections(collections)
|
||||
this.fileImported()
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
|
||||
this.$refs.inputChooseFileToImportFrom.value = ""
|
||||
},
|
||||
exportJSON() {
|
||||
const dataToWrite = this.collectionJson
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
// TODO get uri from meta
|
||||
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.$toast.success(this.$t("state.download_started"), {
|
||||
icon: "downloading",
|
||||
})
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
},
|
||||
fileImported() {
|
||||
this.$toast.success(this.$t("state.file_imported"), {
|
||||
icon: "folder_shared",
|
||||
})
|
||||
},
|
||||
failedImport() {
|
||||
this.$toast.error(this.$t("import.failed"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
},
|
||||
parsePostmanCollection({ info, name, item }) {
|
||||
const hoppscotchCollection = {
|
||||
name: "",
|
||||
folders: [],
|
||||
requests: [],
|
||||
}
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
hoppscotchCollection.name = info ? info.name : name
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
if (item && item.length > 0) {
|
||||
for (const collectionItem of item) {
|
||||
if (collectionItem.request) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
hoppscotchCollection,
|
||||
"folders"
|
||||
)
|
||||
) {
|
||||
hoppscotchCollection.name = info ? info.name : name
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
} else {
|
||||
hoppscotchCollection.name = name || ""
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
}
|
||||
} else if (this.hasFolder(collectionItem)) {
|
||||
hoppscotchCollection.folders.push(
|
||||
this.parsePostmanCollection(collectionItem)
|
||||
)
|
||||
} else {
|
||||
hoppscotchCollection.requests.push(
|
||||
this.parsePostmanRequest(collectionItem)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return hoppscotchCollection
|
||||
},
|
||||
parsePostmanRequest({ name, request }) {
|
||||
const pwRequest = {
|
||||
url: "",
|
||||
path: "",
|
||||
method: "",
|
||||
auth: "",
|
||||
httpUser: "",
|
||||
httpPassword: "",
|
||||
passwordFieldType: "password",
|
||||
bearerToken: "",
|
||||
headers: [],
|
||||
params: [],
|
||||
bodyParams: [],
|
||||
rawParams: "",
|
||||
rawInput: false,
|
||||
contentType: "",
|
||||
requestType: "",
|
||||
name: "",
|
||||
}
|
||||
const axios = useAxios()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const collections = useReadonlyStream(graphqlCollections$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
pwRequest.name = name
|
||||
const requestObjectUrl = request.url.raw.match(
|
||||
/^(.+:\/\/[^/]+|{[^/]+})(\/[^?]+|).*$/
|
||||
)
|
||||
if (requestObjectUrl) {
|
||||
pwRequest.url = requestObjectUrl[1]
|
||||
pwRequest.path = requestObjectUrl[2] ? requestObjectUrl[2] : ""
|
||||
}
|
||||
pwRequest.method = request.method
|
||||
const itemAuth = request.auth ? request.auth : ""
|
||||
const authType = itemAuth ? itemAuth.type : ""
|
||||
if (authType === "basic") {
|
||||
pwRequest.auth = "Basic Auth"
|
||||
pwRequest.httpUser =
|
||||
itemAuth.basic[0].key === "username"
|
||||
? itemAuth.basic[0].value
|
||||
: itemAuth.basic[1].value
|
||||
pwRequest.httpPassword =
|
||||
itemAuth.basic[0].key === "password"
|
||||
? itemAuth.basic[0].value
|
||||
: itemAuth.basic[1].value
|
||||
} else if (authType === "oauth2") {
|
||||
pwRequest.auth = "OAuth 2.0"
|
||||
pwRequest.bearerToken =
|
||||
itemAuth.oauth2[0].key === "accessToken"
|
||||
? itemAuth.oauth2[0].value
|
||||
: itemAuth.oauth2[1].value
|
||||
} else if (authType === "bearer") {
|
||||
pwRequest.auth = "Bearer Token"
|
||||
pwRequest.bearerToken = itemAuth.bearer[0].value
|
||||
}
|
||||
const requestObjectHeaders = request.header
|
||||
if (requestObjectHeaders) {
|
||||
pwRequest.headers = requestObjectHeaders
|
||||
for (const header of pwRequest.headers) {
|
||||
delete header.name
|
||||
delete header.type
|
||||
}
|
||||
}
|
||||
const requestObjectParams = request.url.query
|
||||
if (requestObjectParams) {
|
||||
pwRequest.params = requestObjectParams
|
||||
for (const param of pwRequest.params) {
|
||||
delete param.disabled
|
||||
}
|
||||
}
|
||||
if (request.body) {
|
||||
if (request.body.mode === "urlencoded") {
|
||||
const params = request.body.urlencoded
|
||||
pwRequest.bodyParams = params || []
|
||||
for (const param of pwRequest.bodyParams) {
|
||||
delete param.type
|
||||
}
|
||||
} else if (request.body.mode === "raw") {
|
||||
pwRequest.rawInput = true
|
||||
pwRequest.rawParams = request.body.raw
|
||||
}
|
||||
}
|
||||
return pwRequest
|
||||
},
|
||||
hasFolder(item) {
|
||||
return Object.prototype.hasOwnProperty.call(item, "item")
|
||||
},
|
||||
},
|
||||
// Template refs
|
||||
const options = ref<any>()
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement>()
|
||||
|
||||
const collectionJson = computed(() => {
|
||||
return JSON.stringify(collections.value, null, 2)
|
||||
})
|
||||
|
||||
const createCollectionGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-collections.json": {
|
||||
content: collectionJson.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
}
|
||||
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const readCollectionGist = async () => {
|
||||
const gist = prompt(t("import.gist_url").toString())
|
||||
if (!gist) return
|
||||
|
||||
try {
|
||||
const { files } = (await axios.$get(
|
||||
`https://api.github.com/gists/${gist.split("/").pop()}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)) as {
|
||||
files: {
|
||||
[fileName: string]: {
|
||||
content: any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const collections = JSON.parse(Object.values(files)[0].content)
|
||||
setGraphqlCollections(collections)
|
||||
fileImported()
|
||||
} catch (e) {
|
||||
failedImport()
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const openDialogChooseFileToImportFrom = () => {
|
||||
if (inputChooseFileToImportFrom.value)
|
||||
inputChooseFileToImportFrom.value.click()
|
||||
}
|
||||
|
||||
const importFromJSON = () => {
|
||||
if (!inputChooseFileToImportFrom.value) return
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value.files ||
|
||||
inputChooseFileToImportFrom.value.files.length === 0
|
||||
) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
|
||||
if (!content) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const collections = JSON.parse(content)
|
||||
if (collections[0]) {
|
||||
const [name, folders, requests] = Object.keys(collections[0])
|
||||
if (name === "name" && folders === "folders" && requests === "requests") {
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
failedImport()
|
||||
return
|
||||
}
|
||||
appendGraphqlCollections(collections)
|
||||
fileImported()
|
||||
}
|
||||
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
||||
inputChooseFileToImportFrom.value.value = ""
|
||||
}
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = collectionJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,44 +1,31 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex
|
||||
px-2
|
||||
w-16
|
||||
justify-center
|
||||
items-center
|
||||
truncate
|
||||
"
|
||||
class="cursor-pointer flex px-2 w-16 items-center justify-center truncate"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="isSelected ? 'check-circle' : 'file'"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<span class="truncate"> {{ request.name }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
@@ -56,6 +43,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -64,45 +52,60 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="copy"
|
||||
:label="`${$t('action.duplicate')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('duplicate-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="`${$t('action.delete')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
svg="copy"
|
||||
:label="`${$t('action.duplicate')}`"
|
||||
:shortcut="['D']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('duplicate-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="`${$t('action.delete')}`"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
@@ -117,8 +120,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "@nuxtjs/composition-api"
|
||||
import { HoppGQLRequest, makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
import { defineComponent, PropType, ref } from "@nuxtjs/composition-api"
|
||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||
import { setGQLSession } from "~/newstore/GQLSession"
|
||||
|
||||
@@ -133,6 +136,15 @@ export default defineComponent({
|
||||
requestIndex: { type: Number, default: null },
|
||||
doc: Boolean,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
duplicate: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
@@ -194,9 +206,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
removeGraphqlRequest(this.folderPath, this.requestIndex)
|
||||
this.$toast.success(`${this.$t("state.deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<template>
|
||||
<AppSection
|
||||
label="collections"
|
||||
:class="{ 'rounded border border-divider': savingMode }"
|
||||
>
|
||||
<div :class="{ 'rounded border border-divider': savingMode }">
|
||||
<div
|
||||
class="
|
||||
divide-y divide-dividerLight
|
||||
border-b border-dividerLight
|
||||
flex flex-col
|
||||
top-0
|
||||
z-10
|
||||
sticky
|
||||
"
|
||||
class="divide-dividerLight divide-y border-b border-dividerLight flex flex-col top-0 z-10 sticky"
|
||||
:class="{ 'bg-primary': !savingMode }"
|
||||
>
|
||||
<input
|
||||
@@ -20,7 +10,7 @@
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="$t('action.search')"
|
||||
class="bg-transparent flex w-full py-2 px-4"
|
||||
class="bg-transparent flex py-2 px-4"
|
||||
/>
|
||||
<div class="flex flex-1 justify-between">
|
||||
<ButtonSecondary
|
||||
@@ -74,7 +64,7 @@
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
class="flex-col object-contain object-center h-16 my-4 w-16 inline-flex"
|
||||
:alt="$t('empty.collections')"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
@@ -91,7 +81,7 @@
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">manage_search</i>
|
||||
<span class="text-center">
|
||||
<span class="my-2 text-center">
|
||||
{{ $t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
@@ -103,6 +93,7 @@
|
||||
:show="showModalEdit"
|
||||
:editing-collection="editingCollection"
|
||||
:editing-collection-index="editingCollectionIndex"
|
||||
:editing-collection-name="editingCollection ? editingCollection.name : ''"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
/>
|
||||
<CollectionsGraphqlAddFolder
|
||||
@@ -117,6 +108,7 @@
|
||||
:folder="editingFolder"
|
||||
:folder-index="editingFolderIndex"
|
||||
:folder-path="editingFolderPath"
|
||||
:editing-folder-name="editingFolder ? editingFolder.name : ''"
|
||||
@hide-modal="displayModalEditFolder(false)"
|
||||
/>
|
||||
<CollectionsGraphqlEditRequest
|
||||
@@ -124,17 +116,19 @@
|
||||
:folder-path="editingFolderPath"
|
||||
:request="editingRequest"
|
||||
:request-index="editingRequestIndex"
|
||||
:editing-request-name="editingRequest ? editingRequest.name : ''"
|
||||
@hide-modal="displayModalEditRequest(false)"
|
||||
/>
|
||||
<CollectionsGraphqlImportExport
|
||||
:show="showModalImportExport"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import cloneDeep from "lodash/cloneDeep"
|
||||
import clone from "lodash/clone"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
<template>
|
||||
<AppSection
|
||||
label="collections"
|
||||
:class="{ 'rounded border border-divider': saveRequest }"
|
||||
>
|
||||
<div :class="{ 'rounded border border-divider': saveRequest }">
|
||||
<div
|
||||
class="
|
||||
divide-y divide-dividerLight
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
rounded-t
|
||||
flex flex-col
|
||||
top-0
|
||||
z-10
|
||||
sticky
|
||||
"
|
||||
class="divide-dividerLight divide-y bg-primary border-b border-dividerLight rounded-t flex flex-col z-10 sticky"
|
||||
:style="saveRequest ? 'top: calc(-1 * var(--font-size-body))' : 'top: 0'"
|
||||
>
|
||||
<div v-if="!saveRequest" class="search-wrappe">
|
||||
<div v-if="!saveRequest" class="flex flex-col">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="$t('action.search')"
|
||||
class="bg-transparent flex w-full py-2 pr-2 pl-4"
|
||||
class="bg-transparent py-2 pr-2 pl-4"
|
||||
/>
|
||||
</div>
|
||||
<CollectionsChooseType
|
||||
@@ -112,7 +101,7 @@
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
class="flex-col object-contain object-center h-16 my-4 w-16 inline-flex"
|
||||
:alt="$t('empty.collections')"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
@@ -143,7 +132,7 @@
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">manage_search</i>
|
||||
<span class="text-center">
|
||||
<span class="my-2 text-center">
|
||||
{{ $t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
@@ -154,8 +143,11 @@
|
||||
/>
|
||||
<CollectionsEdit
|
||||
:show="showModalEdit"
|
||||
:editing-coll-name="editingCollection ? editingCollection.name : ''"
|
||||
:placeholder-coll-name="editingCollection ? editingCollection.name : ''"
|
||||
:editing-collection-name="
|
||||
editingCollection
|
||||
? editingCollection.name || editingCollection.title
|
||||
: ''
|
||||
"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
@submit="updateEditingCollection"
|
||||
/>
|
||||
@@ -168,12 +160,15 @@
|
||||
/>
|
||||
<CollectionsEditFolder
|
||||
:show="showModalEditFolder"
|
||||
:editing-folder-name="
|
||||
editingFolder ? editingFolder.name || editingFolder.title : ''
|
||||
"
|
||||
@submit="updateEditingFolder"
|
||||
@hide-modal="displayModalEditFolder(false)"
|
||||
/>
|
||||
<CollectionsEditRequest
|
||||
:show="showModalEditRequest"
|
||||
:placeholder-req-name="editingRequest ? editingRequest.name : ''"
|
||||
:editing-request-name="editingRequest ? editingRequest.name : ''"
|
||||
@submit="updateEditingRequest"
|
||||
@hide-modal="displayModalEditRequest(false)"
|
||||
/>
|
||||
@@ -183,7 +178,7 @@
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
@update-team-collections="updateTeamCollections"
|
||||
/>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -361,14 +356,10 @@ export default defineComponent({
|
||||
this.collectionsType.selectedTeam.id
|
||||
)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("collection.created"), {
|
||||
icon: "done",
|
||||
})
|
||||
this.$toast.success(this.$t("collection.created"))
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -377,9 +368,7 @@ export default defineComponent({
|
||||
// Intented to be called by CollectionEdit modal submit event
|
||||
updateEditingCollection(newName) {
|
||||
if (!newName) {
|
||||
this.$toast.error(this.$t("collection.invalid_name"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("collection.invalid_name"))
|
||||
return
|
||||
}
|
||||
if (this.collectionsType.type === "my-collections") {
|
||||
@@ -396,14 +385,10 @@ export default defineComponent({
|
||||
teamUtils
|
||||
.renameCollection(this.$apollo, newName, this.editingCollection.id)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("collection.renamed"), {
|
||||
icon: "done",
|
||||
})
|
||||
this.$toast.success(this.$t("collection.renamed"))
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -420,14 +405,10 @@ export default defineComponent({
|
||||
teamUtils
|
||||
.renameCollection(this.$apollo, name, this.editingFolder.id)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("folder.renamed"), {
|
||||
icon: "done",
|
||||
})
|
||||
this.$toast.success(this.$t("folder.renamed"))
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -460,15 +441,11 @@ export default defineComponent({
|
||||
this.editingRequestIndex
|
||||
)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("request.renamed"), {
|
||||
icon: "done",
|
||||
})
|
||||
this.$toast.success(this.$t("request.renamed"))
|
||||
this.$emit("update-team-collections")
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -533,15 +510,11 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("folder.created"), {
|
||||
icon: "done",
|
||||
})
|
||||
this.$toast.success(this.$t("folder.created"))
|
||||
this.$emit("update-team-collections")
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -605,9 +578,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
removeRESTCollection(collectionIndex)
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(this.$t("state.deleted"))
|
||||
} else if (collectionsType.type === "team-collections") {
|
||||
// Cancel pick if picked collection is deleted
|
||||
if (
|
||||
@@ -633,14 +604,10 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(this.$t("state.deleted"))
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -658,9 +625,7 @@ export default defineComponent({
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
removeRESTRequest(folderPath, requestIndex)
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(this.$t("state.deleted"))
|
||||
} else if (this.collectionsType.type === "team-collections") {
|
||||
// Cancel pick if the picked item is being deleted
|
||||
if (
|
||||
@@ -674,23 +639,34 @@ export default defineComponent({
|
||||
teamUtils
|
||||
.deleteRequest(this.$apollo, requestIndex)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(this.$t("state.deleted"))
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(this.$t("error.something_went_wrong"))
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
duplicateRequest({ folderPath, request }) {
|
||||
saveRESTRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.$t("action.duplicate")}`,
|
||||
})
|
||||
duplicateRequest({ folderPath, request, collectionID }) {
|
||||
if (this.collectionsType.type === "team-collections") {
|
||||
const newReq = {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.$t("action.duplicate")}`,
|
||||
}
|
||||
|
||||
teamUtils.saveRequestAsTeams(
|
||||
this.$apollo,
|
||||
JSON.stringify(newReq),
|
||||
`${request.name} - ${this.$t("action.duplicate")}`,
|
||||
this.collectionsType.selectedTeam.id,
|
||||
collectionID
|
||||
)
|
||||
} else if (this.collectionsType.type === "my-collections") {
|
||||
saveRESTRequestAs(folderPath, {
|
||||
...cloneDeep(request),
|
||||
name: `${request.name} - ${this.$t("action.duplicate")}`,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate"> {{ collection.name }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collection.name }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
@@ -70,6 +65,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -78,56 +74,62 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="$t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -175,26 +177,13 @@
|
||||
(collection.requests == undefined ||
|
||||
collection.requests.length === 0)
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.collection')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${$t('empty.collection')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.collection") }}
|
||||
@@ -211,8 +200,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import { moveRESTRequest } from "~/newstore/collections"
|
||||
|
||||
export default defineComponent({
|
||||
@@ -226,6 +215,15 @@ export default defineComponent({
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
picked: { type: Object, default: () => {} },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
@@ -238,7 +236,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected() {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-collection" &&
|
||||
@@ -248,7 +246,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate">
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -39,7 +32,7 @@
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="folder-plus"
|
||||
:title="$t('folder.new')"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click.native="$emit('add-folder', { folder, path: folderPath })"
|
||||
/>
|
||||
@@ -50,66 +43,73 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.more')"
|
||||
:title="t('action.more')"
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -157,44 +157,32 @@
|
||||
folder.requests &&
|
||||
folder.requests.length === 0
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.folder')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.folder") }}
|
||||
{{ t("empty.folder") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="$t('confirm.remove_folder')"
|
||||
:title="t('confirm.remove_folder')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="removeFolder"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
import {
|
||||
removeRESTFolder,
|
||||
removeRESTRequest,
|
||||
@@ -214,6 +202,18 @@ export default defineComponent({
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
picked: { type: Object, default: () => {} },
|
||||
},
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
t,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
@@ -224,7 +224,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected() {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-folder" &&
|
||||
@@ -234,7 +234,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
@@ -262,9 +262,7 @@ export default defineComponent({
|
||||
this.$emit("select", { picked: null })
|
||||
}
|
||||
removeRESTFolder(this.folderPath)
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
},
|
||||
dropEvent({ dataTransfer }) {
|
||||
this.dragging = !this.dragging
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
<template>
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-center group"
|
||||
class="flex items-stretch group"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex
|
||||
px-2
|
||||
w-16
|
||||
justify-center
|
||||
items-center
|
||||
truncate
|
||||
"
|
||||
class="cursor-pointer flex px-2 w-16 items-center justify-center truncate"
|
||||
:class="getRequestLabelColor(request.method)"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<SmartIcon
|
||||
v-if="isSelected"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
name="check-circle"
|
||||
/>
|
||||
<span v-else class="truncate">
|
||||
@@ -32,19 +25,12 @@
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
items-center
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition items-center group-hover:text-secondaryDark"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<span class="truncate"> {{ request.name }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
active &&
|
||||
@@ -52,8 +38,18 @@
|
||||
active.folderPath === folderPath &&
|
||||
active.requestIndex === requestIndex
|
||||
"
|
||||
class="rounded-full bg-green-500 flex-shrink-0 h-1.5 mx-3 w-1.5"
|
||||
></span>
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
:title="`${$t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute animate-ping inline-flex flex-shrink-0 h-full w-full rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
@@ -71,6 +67,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -79,45 +76,66 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
$emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="copy"
|
||||
:label="$t('action.duplicate')"
|
||||
@click.native="
|
||||
$emit('duplicate-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
svg="copy"
|
||||
:label="$t('action.duplicate')"
|
||||
:shortcut="['D']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('duplicate-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="$t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
@@ -131,11 +149,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest"
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
safelyExtractRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
restSaveContext$,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
@@ -159,6 +181,11 @@ export default defineComponent({
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
return {
|
||||
active,
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
duplicate: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -175,7 +202,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected() {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "my-request" &&
|
||||
@@ -206,11 +233,17 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
else {
|
||||
setRESTRequest(translateToNewRequest(this.request), {
|
||||
originLocation: "user-collection",
|
||||
folderPath: this.folderPath,
|
||||
requestIndex: this.requestIndex,
|
||||
})
|
||||
setRESTRequest(
|
||||
safelyExtractRESTRequest(
|
||||
translateToNewRequest(this.request),
|
||||
getDefaultRESTRequest()
|
||||
),
|
||||
{
|
||||
originLocation: "user-collection",
|
||||
folderPath: this.folderPath,
|
||||
requestIndex: this.requestIndex,
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
dragStart({ dataTransfer }) {
|
||||
@@ -226,7 +259,7 @@ export default defineComponent({
|
||||
requestIndex: this.$props.requestIndex,
|
||||
})
|
||||
},
|
||||
getRequestLabelColor(method) {
|
||||
getRequestLabelColor(method: string): string {
|
||||
return (
|
||||
this.requestMethodLabels[method.toLowerCase()] ||
|
||||
this.requestMethodLabels.default
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center group">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate"> {{ collection.title }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ collection.title }}
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-if="doc && !selected"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('import.title')"
|
||||
:title="t('import.title')"
|
||||
svg="circle"
|
||||
color="green"
|
||||
@click.native="$emit('select-collection')"
|
||||
@@ -37,7 +40,7 @@
|
||||
<ButtonSecondary
|
||||
v-if="doc && selected"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.remove')"
|
||||
:title="t('action.remove')"
|
||||
svg="check-circle"
|
||||
color="green"
|
||||
@click.native="$emit('unselect-collection')"
|
||||
@@ -46,7 +49,7 @@
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="folder-plus"
|
||||
:title="$t('folder.new')"
|
||||
:title="t('folder.new')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click.native="
|
||||
$emit('add-folder', {
|
||||
@@ -63,67 +66,71 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.more')"
|
||||
:title="t('action.more')"
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', {
|
||||
folder: collection,
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-collection')
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -145,6 +152,7 @@
|
||||
@select="$emit('select', $event)"
|
||||
@expand-collection="expandCollection"
|
||||
@remove-request="removeRequest"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<CollectionsTeamsRequest
|
||||
v-for="(request, index) in collection.requests"
|
||||
@@ -156,11 +164,13 @@
|
||||
:request-index="request.id"
|
||||
:doc="doc"
|
||||
:save-request="saveRequest"
|
||||
:collection-i-d="collection.id"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
@edit-request="editRequest($event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="removeRequest"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
@@ -169,44 +179,34 @@
|
||||
(collection.requests == undefined ||
|
||||
collection.requests.length === 0)
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.collection')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${t('empty.collection')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.collection") }}
|
||||
{{ t("empty.collection") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="$t('confirm.remove_collection')"
|
||||
:title="t('confirm.remove_collection')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="removeCollection"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -219,9 +219,22 @@ export default defineComponent({
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
picked: { type: Object, default: () => {} },
|
||||
},
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
t,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
selectedFolder: {},
|
||||
confirmRemove: false,
|
||||
prevCursor: "",
|
||||
@@ -230,7 +243,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected() {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-collection" &&
|
||||
@@ -240,7 +253,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
@@ -277,6 +290,16 @@ export default defineComponent({
|
||||
expandCollection(collectionID) {
|
||||
this.$emit("expand-collection", collectionID)
|
||||
},
|
||||
async dropEvent({ dataTransfer }) {
|
||||
this.dragging = !this.dragging
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
const moveRequestResult = await moveRESTTeamRequest(
|
||||
requestIndex,
|
||||
this.collection.id
|
||||
)()
|
||||
if (E.isLeft(moveRequestResult))
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
},
|
||||
removeRequest({ collectionIndex, folderName, requestIndex }) {
|
||||
this.$emit("remove-request", {
|
||||
collectionIndex,
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center group">
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropEvent"
|
||||
@dragover="dragging = true"
|
||||
@drop="dragging = false"
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="cursor-pointer flex px-4 items-center justify-center"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<SmartIcon
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
:name="getCollectionIcon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition group-hover:text-secondaryDark"
|
||||
@click="toggleShowChildren()"
|
||||
>
|
||||
<span class="truncate">
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ folder.name ? folder.name : folder.title }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -44,6 +45,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -52,61 +54,64 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath: '',
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.n="folderAction.$el.click()"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="folderAction"
|
||||
svg="folder-plus"
|
||||
:label="$t('folder.new')"
|
||||
:shortcut="['N']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('add-folder', { folder, path: folderPath })
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-folder', {
|
||||
folder,
|
||||
folderIndex,
|
||||
collectionIndex,
|
||||
folderPath: '',
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="$t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showChildren || isFiltered" class="flex">
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
w-1
|
||||
transform
|
||||
transition
|
||||
cursor-nsResize
|
||||
ml-5.5
|
||||
bg-dividerLight
|
||||
hover:scale-x-125 hover:bg-dividerDark
|
||||
"
|
||||
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-1 hover:bg-dividerDark hover:scale-x-125"
|
||||
@click="toggleShowChildren()"
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
@@ -128,6 +133,7 @@
|
||||
@select="$emit('select', $event)"
|
||||
@expand-collection="expandCollection"
|
||||
@remove-request="removeRequest"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<CollectionsTeamsRequest
|
||||
v-for="(request, index) in folder.requests"
|
||||
@@ -141,35 +147,24 @@
|
||||
:save-request="saveRequest"
|
||||
:collections-type="collectionsType"
|
||||
:picked="picked"
|
||||
:collection-i-d="folder.id"
|
||||
@edit-request="$emit('edit-request', $event)"
|
||||
@select="$emit('select', $event)"
|
||||
@remove-request="removeRequest"
|
||||
@duplicate-request="$emit('duplicate-request', $event)"
|
||||
/>
|
||||
<div
|
||||
v-if="
|
||||
(folder.children == undefined || folder.children.length === 0) &&
|
||||
(folder.requests == undefined || folder.requests.length === 0)
|
||||
"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/pack.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
mb-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.folder')"
|
||||
class="flex-col object-contain object-center h-16 mb-4 w-16 inline-flex"
|
||||
:alt="`${$t('empty.folder')}`"
|
||||
/>
|
||||
<span class="text-center">
|
||||
{{ $t("empty.folder") }}
|
||||
@@ -186,8 +181,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { moveRESTTeamRequest } from "~/helpers/backend/mutations/TeamRequest"
|
||||
import * as teamUtils from "~/helpers/teams/utils"
|
||||
|
||||
export default defineComponent({
|
||||
@@ -203,16 +200,26 @@ export default defineComponent({
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
picked: { type: Object, default: () => {} },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
folderAction: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showChildren: false,
|
||||
dragging: false,
|
||||
confirmRemove: false,
|
||||
prevCursor: "",
|
||||
cursor: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelected() {
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.picked &&
|
||||
this.picked.pickedType === "teams-folder" &&
|
||||
@@ -222,7 +229,7 @@ export default defineComponent({
|
||||
getCollectionIcon() {
|
||||
if (this.isSelected) return "check-circle"
|
||||
else if (!this.showChildren && !this.isFiltered) return "folder"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-minus"
|
||||
else if (this.showChildren || this.isFiltered) return "folder-open"
|
||||
else return "folder"
|
||||
},
|
||||
},
|
||||
@@ -253,15 +260,11 @@ export default defineComponent({
|
||||
teamUtils
|
||||
.deleteCollection(this.$apollo, this.folder.id)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t("state.deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
this.$emit("update-team-collections")
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
})
|
||||
this.$emit("update-team-collections")
|
||||
@@ -270,6 +273,16 @@ export default defineComponent({
|
||||
expandCollection(collectionID) {
|
||||
this.$emit("expand-collection", collectionID)
|
||||
},
|
||||
async dropEvent({ dataTransfer }) {
|
||||
this.dragging = !this.dragging
|
||||
const requestIndex = dataTransfer.getData("requestIndex")
|
||||
const moveRequestResult = await moveRESTTeamRequest(
|
||||
requestIndex,
|
||||
this.folder.id
|
||||
)()
|
||||
if (E.isLeft(moveRequestResult))
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
},
|
||||
removeRequest({ collectionIndex, folderName, requestIndex }) {
|
||||
this.$emit("remove-request", {
|
||||
collectionIndex,
|
||||
|
||||
@@ -1,51 +1,54 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center group">
|
||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
draggable="true"
|
||||
@dragstart="dragStart"
|
||||
@dragover.stop
|
||||
@dragleave="dragging = false"
|
||||
@dragend="dragging = false"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex
|
||||
px-2
|
||||
w-16
|
||||
justify-center
|
||||
items-center
|
||||
truncate
|
||||
"
|
||||
class="cursor-pointer flex px-2 w-16 items-center justify-center truncate"
|
||||
:class="getRequestLabelColor(request.method)"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<SmartIcon
|
||||
v-if="isSelected"
|
||||
class="svg-icons"
|
||||
:class="{ 'text-green-500': isSelected }"
|
||||
:class="{ 'text-accent': isSelected }"
|
||||
name="check-circle"
|
||||
/>
|
||||
<span v-else>
|
||||
<span v-else class="truncate">
|
||||
{{ request.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
items-center
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="cursor-pointer flex flex-1 min-w-0 py-2 pr-2 transition items-center group-hover:text-secondaryDark"
|
||||
@click="!doc ? selectRequest() : {}"
|
||||
>
|
||||
<span class="truncate"> {{ request.name }} </span>
|
||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
||||
{{ request.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
active &&
|
||||
active.originLocation === 'team-collection' &&
|
||||
active.requestID === requestIndex
|
||||
"
|
||||
class="rounded-full bg-green-500 flex-shrink-0 h-1.5 mx-3 w-1.5"
|
||||
></span>
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
|
||||
:title="`${$t('collection.request_in_use')}`"
|
||||
>
|
||||
<span
|
||||
class="absolute animate-ping inline-flex flex-shrink-0 h-full w-full rounded-full bg-green-500 opacity-75"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
@@ -64,6 +67,7 @@
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
@@ -72,29 +76,62 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
@click.native="
|
||||
$emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
})
|
||||
$refs.options.tippy().hide()
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="$t('action.delete')"
|
||||
@click.native="
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
"
|
||||
/>
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="deleteAction.$el.click()"
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="$t('action.edit')"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-request', {
|
||||
collectionIndex,
|
||||
folderIndex,
|
||||
folderName,
|
||||
request,
|
||||
requestIndex,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
svg="copy"
|
||||
:label="$t('action.duplicate')"
|
||||
:shortcut="['D']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('duplicate-request', {
|
||||
request,
|
||||
requestIndex,
|
||||
collectionID,
|
||||
})
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="$t('action.delete')"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
@@ -109,10 +146,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest"
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
safelyExtractRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
restSaveContext$,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
@@ -130,15 +171,22 @@ export default defineComponent({
|
||||
saveRequest: Boolean,
|
||||
collectionsType: { type: Object, default: () => {} },
|
||||
picked: { type: Object, default: () => {} },
|
||||
collectionID: { type: String, default: null },
|
||||
},
|
||||
setup() {
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
return {
|
||||
active,
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
duplicate: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
requestMethodLabels: {
|
||||
get: "text-green-500",
|
||||
post: "text-yellow-500",
|
||||
@@ -176,10 +224,20 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
else
|
||||
setRESTRequest(translateToNewRequest(this.request), {
|
||||
originLocation: "team-collection",
|
||||
requestID: this.requestIndex as string,
|
||||
})
|
||||
setRESTRequest(
|
||||
safelyExtractRESTRequest(
|
||||
translateToNewRequest(this.request),
|
||||
getDefaultRESTRequest()
|
||||
),
|
||||
{
|
||||
originLocation: "team-collection",
|
||||
requestID: this.requestIndex as string,
|
||||
}
|
||||
)
|
||||
},
|
||||
dragStart({ dataTransfer }) {
|
||||
this.dragging = !this.dragging
|
||||
dataTransfer.setData("requestIndex", this.requestIndex)
|
||||
},
|
||||
removeRequest() {
|
||||
this.$emit("remove-request", {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="folder">
|
||||
<h3 class="heading">
|
||||
<SmartIcon name="folder-minus" class="svg-icons" />
|
||||
<SmartIcon name="folder-open" class="svg-icons" />
|
||||
{{ folder.name || $t("state.none") }}
|
||||
</h3>
|
||||
<div
|
||||
|
||||
@@ -52,9 +52,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
addNewEnvironment() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(`${this.$t("environment.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("environment.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
createEnvironment(this.name)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col px-2">
|
||||
<div class="flex relative">
|
||||
<div class="relative flex">
|
||||
<input
|
||||
id="selectLabelEnvEdit"
|
||||
v-model="name"
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ $t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-between items-center">
|
||||
<div class="flex items-center justify-between flex-1">
|
||||
<label for="variableList" class="p-4">
|
||||
{{ $t("environment.variable_list") }}
|
||||
</label>
|
||||
@@ -31,33 +31,31 @@
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.clear_all')"
|
||||
:svg="clearIcon"
|
||||
class="rounded"
|
||||
@click.native="clearContent()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="plus"
|
||||
:title="$t('add.new')"
|
||||
class="rounded"
|
||||
@click.native="addEnvironmentVariable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-dividerLight border-divider border rounded">
|
||||
<div class="border divide-y rounded divide-dividerLight border-divider">
|
||||
<div
|
||||
v-for="(variable, index) in vars"
|
||||
:key="`variable-${index}`"
|
||||
class="divide-x divide-dividerLight flex"
|
||||
class="flex divide-x divide-dividerLight"
|
||||
>
|
||||
<input
|
||||
v-model="variable.key"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
:placeholder="`${$t('count.variable', { count: index + 1 })}`"
|
||||
:name="'param' + index"
|
||||
/>
|
||||
<input
|
||||
v-model="variable.value"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
:placeholder="`${$t('count.value', { count: index + 1 })}`"
|
||||
:name="'value' + index"
|
||||
/>
|
||||
@@ -74,28 +72,15 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="vars.length === 0"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.environments')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${$t('empty.environments')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
<span class="pb-4 text-center">
|
||||
{{ $t("empty.environments") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
@@ -177,9 +162,7 @@ export default defineComponent({
|
||||
clearContent() {
|
||||
this.vars = []
|
||||
this.clearIcon = "check"
|
||||
this.$toast.success(`${this.$t("state.cleared")}`, {
|
||||
icon: "clear_all",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.cleared")}`)
|
||||
setTimeout(() => (this.clearIcon = "trash-2"), 1000)
|
||||
},
|
||||
addEnvironmentVariable() {
|
||||
@@ -193,9 +176,7 @@ export default defineComponent({
|
||||
},
|
||||
saveEnvironment() {
|
||||
if (!this.name) {
|
||||
this.$toast.error(`${this.$t("environment.invalid_name")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("environment.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
<template>
|
||||
<div class="flex items-center group">
|
||||
<div
|
||||
class="flex items-stretch group"
|
||||
@contextmenu.prevent="options.tippy().show()"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex px-4 justify-center items-center"
|
||||
class="flex items-center justify-center px-4 cursor-pointer"
|
||||
@click="$emit('edit-environment')"
|
||||
>
|
||||
<SmartIcon class="svg-icons" name="layers" />
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 transition cursor-pointer group-hover:text-secondaryDark"
|
||||
@click="$emit('edit-environment')"
|
||||
>
|
||||
<span class="truncate">
|
||||
@@ -23,7 +18,14 @@
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<tippy ref="options" interactive trigger="click" theme="popover" arrow>
|
||||
<tippy
|
||||
ref="options"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -31,38 +33,55 @@
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-environment')
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.e="edit.$el.click()"
|
||||
@keyup.d="duplicate.$el.click()"
|
||||
@keyup.delete="
|
||||
!(environmentIndex === 'Global') ? deleteAction.$el.click() : null
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
svg="copy"
|
||||
:label="`${$t('action.duplicate')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
duplicateEnvironment()
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="!(environmentIndex === 'Global')"
|
||||
svg="trash-2"
|
||||
color="red"
|
||||
:label="`${$t('action.delete')}`"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
$refs.options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
@keyup.escape="options.tippy().hide()"
|
||||
>
|
||||
<SmartItem
|
||||
ref="edit"
|
||||
svg="edit"
|
||||
:label="`${$t('action.edit')}`"
|
||||
:shortcut="['E']"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('edit-environment')
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
ref="duplicate"
|
||||
svg="copy"
|
||||
:label="`${$t('action.duplicate')}`"
|
||||
:shortcut="['D']"
|
||||
@click.native="
|
||||
() => {
|
||||
duplicateEnvironment()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
v-if="!(environmentIndex === 'Global')"
|
||||
ref="deleteAction"
|
||||
svg="trash-2"
|
||||
:label="`${$t('action.delete')}`"
|
||||
:shortcut="['⌫']"
|
||||
@click.native="
|
||||
() => {
|
||||
confirmRemove = true
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</tippy>
|
||||
</span>
|
||||
<SmartConfirmModal
|
||||
@@ -75,7 +94,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "@nuxtjs/composition-api"
|
||||
import { defineComponent, PropType, ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
deleteEnvironment,
|
||||
duplicateEnvironment,
|
||||
@@ -93,6 +112,15 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tippyActions: ref<any | null>(null),
|
||||
options: ref<any | null>(null),
|
||||
edit: ref<any | null>(null),
|
||||
duplicate: ref<any | null>(null),
|
||||
deleteAction: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
confirmRemove: false,
|
||||
@@ -102,9 +130,7 @@ export default defineComponent({
|
||||
removeEnvironment() {
|
||||
if (this.environmentIndex !== "Global")
|
||||
deleteEnvironment(this.environmentIndex)
|
||||
this.$toast.success(`${this.$t("state.deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
this.$toast.success(`${this.$t("state.deleted")}`)
|
||||
},
|
||||
duplicateEnvironment() {
|
||||
if (this.environmentIndex === "Global") {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SmartModal
|
||||
v-if="show"
|
||||
:title="`${$t('modal.import_export')} ${$t('environment.title')}`"
|
||||
:title="`${t('environment.title')}`"
|
||||
max-width="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
@@ -11,27 +11,28 @@
|
||||
<template #trigger>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.more')"
|
||||
class="rounded"
|
||||
:title="t('action.more')"
|
||||
svg="more-vertical"
|
||||
/>
|
||||
</template>
|
||||
<SmartItem
|
||||
icon="assignment_returned"
|
||||
:label="$t('import.from_gist')"
|
||||
:label="t('import.from_gist')"
|
||||
@click.native="
|
||||
readEnvironmentGist
|
||||
$refs.options.tippy().hide()
|
||||
() => {
|
||||
readEnvironmentGist()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
!currentUser
|
||||
? $t('export.require_github')
|
||||
? `${t('export.require_github')}`
|
||||
: currentUser.provider !== 'github.com'
|
||||
? $t('export.require_github')
|
||||
: null
|
||||
? `${t('export.require_github')}`
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<SmartItem
|
||||
@@ -43,10 +44,12 @@
|
||||
: false
|
||||
"
|
||||
icon="assignment_turned_in"
|
||||
:label="$t('export.create_secret_gist')"
|
||||
:label="t('export.create_secret_gist')"
|
||||
@click.native="
|
||||
createEnvironmentGist
|
||||
$refs.options.tippy().hide()
|
||||
() => {
|
||||
createEnvironmentGist()
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
@@ -54,26 +57,10 @@
|
||||
</span>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex flex-col px-2 space-y-2">
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.replace_current')"
|
||||
svg="file"
|
||||
:label="$t('action.replace_json')"
|
||||
@click.native="openDialogChooseFileToReplaceWith"
|
||||
/>
|
||||
<input
|
||||
ref="inputChooseFileToReplaceWith"
|
||||
class="input"
|
||||
type="file"
|
||||
accept="application/json"
|
||||
@change="replaceWithJSON"
|
||||
/>
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.preserve_current')"
|
||||
svg="folder-plus"
|
||||
:label="$t('import.json')"
|
||||
:label="t('import.from_json')"
|
||||
@click.native="openDialogChooseFileToImportFrom"
|
||||
/>
|
||||
<input
|
||||
@@ -83,11 +70,12 @@
|
||||
accept="application/json"
|
||||
@change="importFromJSON"
|
||||
/>
|
||||
<hr />
|
||||
<SmartItem
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.download_file')"
|
||||
:title="t('action.download_file')"
|
||||
svg="download"
|
||||
:label="$t('export.as_json')"
|
||||
:label="t('export.as_json')"
|
||||
@click.native="exportJSON"
|
||||
/>
|
||||
</div>
|
||||
@@ -95,154 +83,197 @@
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api"
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "@nuxtjs/composition-api"
|
||||
import { currentUser$ } from "~/helpers/fb/auth"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
useAxios,
|
||||
useI18n,
|
||||
useReadonlyStream,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import {
|
||||
environments$,
|
||||
replaceEnvironments,
|
||||
appendEnvironments,
|
||||
Environment,
|
||||
} from "~/newstore/environments"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
environments: useReadonlyStream(environments$, []),
|
||||
currentUser: useReadonlyStream(currentUser$, null),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
environmentJson() {
|
||||
return JSON.stringify(this.environments, null, 2)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async createEnvironmentGist() {
|
||||
await this.$axios
|
||||
.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-environments.json": {
|
||||
content: this.environmentJson,
|
||||
},
|
||||
},
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const axios = useAxios()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const environments = useReadonlyStream(environments$, [])
|
||||
const currentUser = useReadonlyStream(currentUser$, null)
|
||||
|
||||
// Template refs
|
||||
const options = ref<any>()
|
||||
const inputChooseFileToImportFrom = ref<HTMLInputElement>()
|
||||
|
||||
const environmentJson = computed(() => {
|
||||
return JSON.stringify(environments.value, null, 2)
|
||||
})
|
||||
|
||||
const createEnvironmentGist = async () => {
|
||||
if (!currentUser.value) {
|
||||
toast.error(t("profile.no_permission").toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.$post(
|
||||
"https://api.github.com/gists",
|
||||
{
|
||||
files: {
|
||||
"hoppscotch-environments.json": {
|
||||
content: environmentJson.value,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${this.currentUser.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
this.$toast.success(this.$t("export.gist_created"), {
|
||||
icon: "done",
|
||||
})
|
||||
window.open(res.html_url)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$toast.error(this.$t("error.something_went_wrong"), {
|
||||
icon: "error_outline",
|
||||
})
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
async readEnvironmentGist() {
|
||||
const gist = prompt(this.$t("import.gist_url"))
|
||||
if (!gist) return
|
||||
await this.$axios
|
||||
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
})
|
||||
.then(({ files }) => {
|
||||
const environments = JSON.parse(Object.values(files)[0].content)
|
||||
replaceEnvironments(environments)
|
||||
this.fileImported()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.failedImport()
|
||||
console.error(e)
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
openDialogChooseFileToReplaceWith() {
|
||||
this.$refs.inputChooseFileToReplaceWith.click()
|
||||
},
|
||||
openDialogChooseFileToImportFrom() {
|
||||
this.$refs.inputChooseFileToImportFrom.click()
|
||||
},
|
||||
replaceWithJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
const environments = JSON.parse(content)
|
||||
replaceEnvironments(environments)
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${currentUser.value.accessToken}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0])
|
||||
this.fileImported()
|
||||
this.$refs.inputChooseFileToReplaceWith.value = ""
|
||||
},
|
||||
importFromJSON() {
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target.result
|
||||
const importFileObj = JSON.parse(content)
|
||||
if (
|
||||
importFileObj._postman_variable_scope === "environment" ||
|
||||
importFileObj._postman_variable_scope === "globals"
|
||||
) {
|
||||
this.importFromPostman(importFileObj)
|
||||
} else {
|
||||
this.importFromHoppscotch(importFileObj)
|
||||
)
|
||||
|
||||
toast.success(t("export.gist_created").toString())
|
||||
window.open(res.html_url)
|
||||
} catch (e) {
|
||||
toast.error(t("error.something_went_wrong").toString())
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fileImported = () => {
|
||||
toast.success(t("state.file_imported").toString())
|
||||
}
|
||||
|
||||
const failedImport = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const readEnvironmentGist = async () => {
|
||||
const gist = prompt(t("import.gist_url").toString())
|
||||
if (!gist) return
|
||||
|
||||
try {
|
||||
const { files } = (await axios.$get(
|
||||
`https://api.github.com/gists/${gist.split("/").pop()}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
},
|
||||
}
|
||||
)) as {
|
||||
files: {
|
||||
[fileName: string]: {
|
||||
content: any
|
||||
}
|
||||
}
|
||||
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0])
|
||||
this.$refs.inputChooseFileToImportFrom.value = ""
|
||||
},
|
||||
importFromHoppscotch(environments) {
|
||||
appendEnvironments(environments)
|
||||
this.fileImported()
|
||||
},
|
||||
importFromPostman({ name, values }) {
|
||||
const environment = { name, variables: [] }
|
||||
values.forEach(({ key, value }) =>
|
||||
environment.variables.push({ key, value })
|
||||
)
|
||||
const environments = [environment]
|
||||
this.importFromHoppscotch(environments)
|
||||
},
|
||||
exportJSON() {
|
||||
const dataToWrite = this.environmentJson
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
// TODO get uri from meta
|
||||
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
this.$toast.success(this.$t("state.download_started"), {
|
||||
icon: "downloading",
|
||||
})
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
},
|
||||
fileImported() {
|
||||
this.$toast.success(this.$t("state.file_imported"), {
|
||||
icon: "folder_shared",
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
const environments = JSON.parse(Object.values(files)[0].content)
|
||||
replaceEnvironments(environments)
|
||||
fileImported()
|
||||
} catch (e) {
|
||||
failedImport()
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const openDialogChooseFileToImportFrom = () => {
|
||||
if (inputChooseFileToImportFrom.value)
|
||||
inputChooseFileToImportFrom.value.click()
|
||||
}
|
||||
|
||||
const importFromJSON = () => {
|
||||
if (!inputChooseFileToImportFrom.value) return
|
||||
|
||||
if (
|
||||
!inputChooseFileToImportFrom.value.files ||
|
||||
inputChooseFileToImportFrom.value.files.length === 0
|
||||
) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = ({ target }) => {
|
||||
const content = target!.result as string | null
|
||||
|
||||
if (!content) {
|
||||
toast.show(t("action.choose_file").toString())
|
||||
return
|
||||
}
|
||||
|
||||
const environments = JSON.parse(content)
|
||||
if (
|
||||
environments._postman_variable_scope === "environment" ||
|
||||
environments._postman_variable_scope === "globals"
|
||||
) {
|
||||
importFromPostman(environments)
|
||||
} else if (environments[0]) {
|
||||
const [name, variables] = Object.keys(environments[0])
|
||||
if (name === "name" && variables === "variables") {
|
||||
// Do nothing
|
||||
}
|
||||
importFromHoppscotch(environments)
|
||||
} else {
|
||||
failedImport()
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
||||
inputChooseFileToImportFrom.value.value = ""
|
||||
}
|
||||
|
||||
const importFromHoppscotch = (environments: Environment[]) => {
|
||||
appendEnvironments(environments)
|
||||
fileImported()
|
||||
}
|
||||
|
||||
const importFromPostman = ({
|
||||
name,
|
||||
values,
|
||||
}: {
|
||||
name: string
|
||||
values: { key: string; value: string }[]
|
||||
}) => {
|
||||
const environment: Environment = { name, variables: [] }
|
||||
values.forEach(({ key, value }) => environment.variables.push({ key, value }))
|
||||
const environments = [environment]
|
||||
importFromHoppscotch(environments)
|
||||
}
|
||||
|
||||
const exportJSON = () => {
|
||||
const dataToWrite = environmentJson.value
|
||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||
const a = document.createElement("a")
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
|
||||
// TODO: get uri from meta
|
||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
toast.success(t("state.download_started").toString())
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
<template>
|
||||
<AppSection :label="`${$t('environment.title')}`">
|
||||
<div class="bg-primary rounded-t flex flex-col top-0 z-10 sticky">
|
||||
<div>
|
||||
<div class="sticky top-0 z-10 flex flex-col rounded-t bg-primary">
|
||||
<tippy ref="options" interactive trigger="click" theme="popover" arrow>
|
||||
<template #trigger>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${$t('environment.select')}`"
|
||||
class="
|
||||
bg-transparent
|
||||
border-b border-dividerLight
|
||||
flex-1
|
||||
select-wrapper
|
||||
"
|
||||
class="flex-1 bg-transparent border-b border-dividerLight select-wrapper"
|
||||
>
|
||||
<ButtonSecondary
|
||||
v-if="selectedEnvironmentIndex !== -1"
|
||||
:label="environments[selectedEnvironmentIndex].name"
|
||||
class="rounded-none flex-1 pr-8"
|
||||
class="flex-1 pr-8 rounded-none"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-else
|
||||
:label="`${$t('environment.no_environment')}`"
|
||||
class="rounded-none flex-1 pr-8"
|
||||
class="flex-1 pr-8 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
@@ -50,7 +45,7 @@
|
||||
"
|
||||
/>
|
||||
</tippy>
|
||||
<div class="border-b border-dividerLight flex flex-1 justify-between">
|
||||
<div class="flex justify-between flex-1 border-b border-dividerLight">
|
||||
<ButtonSecondary
|
||||
svg="plus"
|
||||
:label="`${$t('action.new')}`"
|
||||
@@ -74,19 +69,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<EnvironmentsAdd
|
||||
:show="showModalAdd"
|
||||
@hide-modal="displayModalAdd(false)"
|
||||
/>
|
||||
<EnvironmentsEdit
|
||||
:show="showModalEdit"
|
||||
:editing-environment-index="editingEnvironmentIndex"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
/>
|
||||
<EnvironmentsImportExport
|
||||
:show="showModalImportExport"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<EnvironmentsEnvironment
|
||||
environment-index="Global"
|
||||
@@ -104,15 +86,15 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="environments.length === 0"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
:alt="$t('empty.environments')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${$t('empty.environments')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
<span class="pb-4 text-center">
|
||||
{{ $t("empty.environments") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
@@ -122,7 +104,20 @@
|
||||
@click.native="displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
</AppSection>
|
||||
<EnvironmentsAdd
|
||||
:show="showModalAdd"
|
||||
@hide-modal="displayModalAdd(false)"
|
||||
/>
|
||||
<EnvironmentsEdit
|
||||
:show="showModalEdit"
|
||||
:editing-environment-index="editingEnvironmentIndex"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
/>
|
||||
<EnvironmentsImportExport
|
||||
:show="showModalImportExport"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2 px-2">
|
||||
<div v-if="mode === 'sign-in'" class="flex flex-col px-2 space-y-2">
|
||||
<SmartItem
|
||||
:loading="signingInWithGitHub"
|
||||
svg="auth/github"
|
||||
@@ -56,9 +56,9 @@
|
||||
/>
|
||||
</form>
|
||||
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
||||
<div class="flex flex-col max-w-md justify-center items-center">
|
||||
<SmartIcon class="h-6 text-accent w-6" name="inbox" />
|
||||
<h3 class="my-2 text-center text-lg">
|
||||
<div class="flex flex-col items-center justify-center max-w-md">
|
||||
<SmartIcon class="w-6 h-6 text-accent" name="inbox" />
|
||||
<h3 class="my-2 text-lg text-center">
|
||||
{{ $t("auth.we_sent_magic_link") }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
@@ -95,7 +95,7 @@
|
||||
</p>
|
||||
<p
|
||||
v-if="mode === 'email-sent'"
|
||||
class="flex flex-1 text-secondaryLight justify-between"
|
||||
class="flex justify-between flex-1 text-secondaryLight"
|
||||
>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
@@ -155,9 +155,7 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
showLoginSuccess() {
|
||||
this.$toast.success(`${this.$t("auth.login_success")}`, {
|
||||
icon: "vpn_key",
|
||||
})
|
||||
this.$toast.success(`${this.$t("auth.login_success")}`)
|
||||
},
|
||||
async signInWithGoogle() {
|
||||
this.signingInWithGoogle = true
|
||||
@@ -174,7 +172,6 @@ export default defineComponent({
|
||||
// The pending Google credential.
|
||||
const pendingCred = e.credential
|
||||
this.$toast.info(`${this.$t("auth.account_exists")}`, {
|
||||
icon: "vpn_key",
|
||||
duration: 0,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
@@ -190,9 +187,7 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +213,6 @@ export default defineComponent({
|
||||
// The pending Google credential.
|
||||
const pendingCred = e.credential
|
||||
this.$toast.info(`${this.$t("auth.account_exists")}`, {
|
||||
icon: "vpn_key",
|
||||
duration: 0,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
@@ -234,9 +228,7 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,9 +248,7 @@ export default defineComponent({
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
this.$toast.error(e.message, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(e.message)
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div class="flex" @click="$refs.logout.$el.click()">
|
||||
<SmartItem
|
||||
ref="logout"
|
||||
svg="log-out"
|
||||
:label="`${$t('auth.logout')}`"
|
||||
:outline="outline"
|
||||
:shortcut="shortcut"
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('confirm-logout')
|
||||
@@ -30,6 +32,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
shortcut: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -40,14 +46,10 @@ export default defineComponent({
|
||||
async logout() {
|
||||
try {
|
||||
await signOutUser()
|
||||
this.$toast.success(`${this.$t("auth.logged_out")}`, {
|
||||
icon: "vpn_key",
|
||||
})
|
||||
this.$toast.success(`${this.$t("auth.logged_out")}`)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
this.$toast.error(`${this.$t("error.something_went_wrong")}`)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,28 +21,19 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.description"
|
||||
class="text-secondaryLight py-2 field-desc"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ gqlField.description }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gqlField.isDeprecated"
|
||||
class="
|
||||
rounded
|
||||
bg-yellow-200
|
||||
my-1
|
||||
text-black
|
||||
py-1
|
||||
px-2
|
||||
inline-block
|
||||
field-deprecated
|
||||
"
|
||||
class="inline-block px-2 py-1 my-1 text-black bg-yellow-200 rounded field-deprecated"
|
||||
>
|
||||
{{ $t("state.deprecated") }}
|
||||
</div>
|
||||
<div v-if="fieldArgs.length > 0">
|
||||
<h5 class="my-2">Arguments:</h5>
|
||||
<div class="border-divider border-l-2 pl-4">
|
||||
<div class="pl-4 border-l-2 border-divider">
|
||||
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
|
||||
<span>
|
||||
{{ field.name }}:
|
||||
@@ -53,7 +44,7 @@
|
||||
</span>
|
||||
<div
|
||||
v-if="field.description"
|
||||
class="text-secondaryLight py-2 field-desc"
|
||||
class="py-2 text-secondaryLight field-desc"
|
||||
>
|
||||
{{ field.description }}
|
||||
</div>
|
||||
@@ -86,7 +77,7 @@ export default defineComponent({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.field-highlighted {
|
||||
@apply border-b-2 border-accent;
|
||||
@apply border-accent border-b-2;
|
||||
}
|
||||
|
||||
.field-title {
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
<template>
|
||||
<div class="bg-primary flex p-4 top-0 z-10 sticky">
|
||||
<div class="space-x-2 flex-1 inline-flex">
|
||||
<div class="sticky top-0 z-10 flex p-4 bg-primary">
|
||||
<div class="inline-flex flex-1 space-x-2">
|
||||
<input
|
||||
id="url"
|
||||
v-model="url"
|
||||
type="url"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="
|
||||
bg-primaryLight
|
||||
border border-divider
|
||||
rounded
|
||||
text-secondaryDark
|
||||
w-full
|
||||
py-2
|
||||
px-4
|
||||
hover:border-dividerDark
|
||||
focus-visible:bg-transparent focus-visible:border-dividerDark
|
||||
"
|
||||
:placeholder="$t('request.url')"
|
||||
class="w-full px-4 py-2 border rounded bg-primaryLight border-divider text-secondaryDark hover:border-dividerDark focus-visible:bg-transparent focus-visible:border-dividerDark"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
:disabled="connected"
|
||||
@keyup.enter="onConnectClick"
|
||||
/>
|
||||
<ButtonPrimary
|
||||
id="get"
|
||||
name="get"
|
||||
:label="!connected ? $t('action.connect') : $t('action.disconnect')"
|
||||
:label="!connected ? t('action.connect') : t('action.disconnect')"
|
||||
class="w-32"
|
||||
@click.native="onConnectClick"
|
||||
/>
|
||||
@@ -37,9 +27,15 @@
|
||||
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
|
||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||
import { getCurrentStrategyID } from "~/helpers/network"
|
||||
import { useReadonlyStream, useStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
useReadonlyStream,
|
||||
useStream,
|
||||
useI18n,
|
||||
} from "~/helpers/utils/composables"
|
||||
import { gqlHeaders$, gqlURL$, setGQLURL } from "~/newstore/GQLSession"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
conn: GQLConnection
|
||||
}>()
|
||||
|
||||
@@ -1,170 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<SmartTabs styles="sticky bg-primary top-upperPrimaryStickyFold z-10">
|
||||
<template #actions>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('request.run')}`"
|
||||
svg="play"
|
||||
class="rounded-none !text-accent"
|
||||
@click.native="runQuery()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="saveRequest"
|
||||
:label="`${$t('request.save')}`"
|
||||
class="rounded-none"
|
||||
@click.native="saveRequest"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<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
|
||||
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.prettify')"
|
||||
:svg="`${prettifyQueryIcon}`"
|
||||
@click.native="prettifyQuery"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.copy')"
|
||||
:svg="`${copyQueryIcon}`"
|
||||
@click.native="copyQuery"
|
||||
/>
|
||||
</div>
|
||||
<SmartTab
|
||||
:id="'query'"
|
||||
:label="`${t('tab.query')}`"
|
||||
:selected="true"
|
||||
:indicator="gqlQueryString && gqlQueryString.length > 0 ? true : false"
|
||||
>
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold gqlRunQuery"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("request.query") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
:title="`${t(
|
||||
'request.run'
|
||||
)} <kbd>${getSpecialKey()}</kbd><kbd>G</kbd>`"
|
||||
:label="`${t('request.run')}`"
|
||||
svg="play"
|
||||
class="rounded-none !text-accent !hover:text-accentDark"
|
||||
@click.native="runQuery()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="saveRequest"
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
:title="`${t(
|
||||
'request.save'
|
||||
)} <kbd>${getSpecialKey()}</kbd><kbd>S</kbd>`"
|
||||
:label="`${t('request.save')}`"
|
||||
svg="save"
|
||||
class="rounded-none"
|
||||
@click.native="saveRequest"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/graphql"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
svg="trash-2"
|
||||
@click.native="clearGQLQuery()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.prettify')"
|
||||
:svg="`${prettifyQueryIcon}`"
|
||||
@click.native="prettifyQuery"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.copy')"
|
||||
:svg="`${copyQueryIcon}`"
|
||||
@click.native="copyQuery"
|
||||
/>
|
||||
</div>
|
||||
<div ref="queryEditor"></div>
|
||||
</AppSection>
|
||||
</div>
|
||||
<div ref="queryEditor"></div>
|
||||
</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>
|
||||
<SmartTab
|
||||
:id="'variables'"
|
||||
:label="`${t('tab.variables')}`"
|
||||
:indicator="variableString && variableString.length > 0 ? true : false"
|
||||
>
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("request.variables") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/graphql"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
svg="trash-2"
|
||||
@click.native="clearGQLVariables()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="prettifyRequest"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.prettify')"
|
||||
:svg="prettifyVariablesIcon"
|
||||
@click.native="prettifyVariableString"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.copy')"
|
||||
:svg="`${copyVariablesIcon}`"
|
||||
@click.native="copyVariables"
|
||||
/>
|
||||
</div>
|
||||
<div ref="variableEditor"></div>
|
||||
</AppSection>
|
||||
</div>
|
||||
<div ref="variableEditor"></div>
|
||||
</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"
|
||||
@click.native="bulkMode ? clearBulkEditor() : clearContent()"
|
||||
/>
|
||||
<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>
|
||||
<SmartTab
|
||||
:id="'headers'"
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="activeGQLHeadersCount === 0 ? null : `${activeGQLHeadersCount}`"
|
||||
>
|
||||
<div
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("tab.headers") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/graphql"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.clear_all')"
|
||||
svg="trash-2"
|
||||
@click.native="clearContent()"
|
||||
/>
|
||||
<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="addHeader"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="bulkMode" ref="bulkEditor"></div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="(header, index) in headers"
|
||||
:key="`header-${String(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="
|
||||
</div>
|
||||
<div v-if="bulkMode" ref="bulkEditor"></div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="(header, index) in workingHeaders"
|
||||
:key="`header-${String(index)}`"
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
||||
>
|
||||
<SmartAutoComplete
|
||||
:placeholder="`${t('count.header', { count: index + 1 })}`"
|
||||
:source="commonHeaders"
|
||||
:spellcheck="false"
|
||||
:value="header.key"
|
||||
autofocus
|
||||
styles="
|
||||
bg-transparent
|
||||
flex
|
||||
flex-1
|
||||
@@ -172,105 +170,90 @@
|
||||
px-4
|
||||
truncate
|
||||
"
|
||||
class="!flex flex-1"
|
||||
@input="
|
||||
updateRequestHeader(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 ${String(index)}`"
|
||||
:value="header.value"
|
||||
autofocus
|
||||
@change="
|
||||
updateRequestHeader(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="
|
||||
updateRequestHeader(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
|
||||
class="flex-1 !flex"
|
||||
@input="
|
||||
updateHeader(index, {
|
||||
key: $event,
|
||||
value: header.value,
|
||||
active: header.active,
|
||||
})
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_category.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.headers')"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
{{ $t("empty.headers") }}
|
||||
</span>
|
||||
/>
|
||||
<input
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
:name="`value ${String(index)}`"
|
||||
:value="header.value"
|
||||
autofocus
|
||||
@change="
|
||||
updateHeader(index, {
|
||||
key: header.key,
|
||||
value: $event.target.value,
|
||||
active: header.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('add.new')}`"
|
||||
filled
|
||||
svg="plus"
|
||||
class="mb-4"
|
||||
@click.native="addRequestHeader"
|
||||
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="
|
||||
updateHeader(index, {
|
||||
key: header.key,
|
||||
value: header.value,
|
||||
active: !header.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.remove')"
|
||||
svg="trash"
|
||||
color="red"
|
||||
@click.native="deleteHeader(index)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</AppSection>
|
||||
<div
|
||||
v-if="workingHeaders.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_category.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.headers')}`"
|
||||
/>
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.headers") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
svg="plus"
|
||||
class="mb-4"
|
||||
@click.native="addHeader"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SmartTab>
|
||||
</SmartTabs>
|
||||
|
||||
<CollectionsSaveRequest
|
||||
mode="graphql"
|
||||
:show="showSaveRequestModal"
|
||||
@@ -280,77 +263,61 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, useContext, watch } from "@nuxtjs/composition-api"
|
||||
import { Ref, computed, reactive, ref, watch } from "@nuxtjs/composition-api"
|
||||
import clone from "lodash/clone"
|
||||
import * as gql from "graphql"
|
||||
import { GQLHeader, makeGQLRequest } from "@hoppscotch/data"
|
||||
import isEqual from "lodash/isEqual"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import {
|
||||
useNuxt,
|
||||
useReadonlyStream,
|
||||
useStream,
|
||||
useI18n,
|
||||
useToast,
|
||||
} 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 { GQLHeader, makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import jsonLinter from "~/helpers/editor/linting/json"
|
||||
import { createGQLQueryLinter } from "~/helpers/editor/linting/gqlQuery"
|
||||
import queryCompleter from "~/helpers/editor/completion/gqlQuery"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
conn: GQLConnection
|
||||
}>()
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const toast = useToast()
|
||||
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")}`, {
|
||||
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 bulkMode = ref(false)
|
||||
const bulkHeaders = ref("")
|
||||
const bulkEditor = ref<any | null>(null)
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
useCodemirror(bulkEditor, bulkHeaders, {
|
||||
extendedEditorConfig: {
|
||||
mode: "text/x-yaml",
|
||||
@@ -358,18 +325,192 @@ useCodemirror(bulkEditor, bulkHeaders, {
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
|
||||
// The functional headers list (the headers actually in the system)
|
||||
const headers = useStream(gqlHeaders$, [], setGQLHeaders) as Ref<GQLHeader[]>
|
||||
|
||||
// The UI representation of the headers list (has the empty end header)
|
||||
const workingHeaders = ref<GQLHeader[]>([
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
|
||||
// Rule: Working Headers always have one empty header or the last element is always an empty header
|
||||
watch(workingHeaders, (headersList) => {
|
||||
if (
|
||||
headersList.length > 0 &&
|
||||
headersList[headersList.length - 1].key !== ""
|
||||
) {
|
||||
workingHeaders.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Sync logic between headers and working headers
|
||||
watch(
|
||||
headers,
|
||||
(newHeadersList) => {
|
||||
// Sync should overwrite working headers
|
||||
const filteredWorkingHeaders = workingHeaders.value.filter(
|
||||
(e) => e.key !== ""
|
||||
)
|
||||
|
||||
if (!isEqual(newHeadersList, filteredWorkingHeaders)) {
|
||||
workingHeaders.value = newHeadersList
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(workingHeaders, (newWorkingHeaders) => {
|
||||
const fixedHeaders = newWorkingHeaders.filter((e) => e.key !== "")
|
||||
if (!isEqual(headers.value, fixedHeaders)) {
|
||||
headers.value = fixedHeaders
|
||||
}
|
||||
})
|
||||
|
||||
// Bulk Editor Syncing with Working Headers
|
||||
watch(bulkHeaders, () => {
|
||||
try {
|
||||
const transformation = bulkHeaders.value
|
||||
.split("\n")
|
||||
.filter((x) => x.trim().length > 0 && x.includes(":"))
|
||||
.map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
const filteredHeaders = workingHeaders.value.filter((x) => x.key !== "")
|
||||
|
||||
if (!isEqual(filteredHeaders, transformation)) {
|
||||
workingHeaders.value = transformation
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
watch(workingHeaders, (newHeadersList) => {
|
||||
// If we are in bulk mode, don't apply direct changes
|
||||
if (bulkMode.value) return
|
||||
|
||||
try {
|
||||
const currentBulkHeaders = bulkHeaders.value.split("\n").map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
const filteredHeaders = newHeadersList.filter((x) => x.key !== "")
|
||||
|
||||
if (!isEqual(currentBulkHeaders, filteredHeaders)) {
|
||||
bulkHeaders.value = filteredHeaders
|
||||
.map((header) => {
|
||||
return `${header.active ? "" : "#"}${header.key}: ${header.value}`
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
const addHeader = () => {
|
||||
workingHeaders.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
const updateHeader = (index: number, header: GQLHeader) => {
|
||||
workingHeaders.value = workingHeaders.value.map((h, i) =>
|
||||
i === index ? header : h
|
||||
)
|
||||
}
|
||||
|
||||
const deleteHeader = (index: number) => {
|
||||
const headersBeforeDeletion = clone(workingHeaders.value)
|
||||
|
||||
if (
|
||||
!(
|
||||
headersBeforeDeletion.length > 0 &&
|
||||
index === headersBeforeDeletion.length - 1
|
||||
)
|
||||
) {
|
||||
if (deletionToast.value) {
|
||||
deletionToast.value.goAway(0)
|
||||
deletionToast.value = null
|
||||
}
|
||||
|
||||
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||
action: [
|
||||
{
|
||||
text: `${t("action.undo")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
workingHeaders.value = headersBeforeDeletion
|
||||
toastObject.goAway(0)
|
||||
deletionToast.value = null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onComplete: () => {
|
||||
deletionToast.value = null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
workingHeaders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
// set headers list to the initial state
|
||||
workingHeaders.value = [
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
|
||||
const activeGQLHeadersCount = computed(
|
||||
() =>
|
||||
headers.value.filter((x) => x.active && (x.key !== "" || x.value !== ""))
|
||||
.length
|
||||
)
|
||||
|
||||
const variableEditor = ref<any | null>(null)
|
||||
|
||||
useCodemirror(variableEditor, variableString, {
|
||||
extendedEditorConfig: {
|
||||
mode: "application/ld+json",
|
||||
placeholder: `${t("request.variables")}`,
|
||||
},
|
||||
linter: jsonLinter,
|
||||
completer: null,
|
||||
})
|
||||
useCodemirror(
|
||||
variableEditor,
|
||||
variableString,
|
||||
reactive({
|
||||
extendedEditorConfig: {
|
||||
mode: "application/ld+json",
|
||||
placeholder: `${t("request.variables")}`,
|
||||
},
|
||||
linter: computed(() =>
|
||||
variableString.value.length > 0 ? jsonLinter : null
|
||||
),
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
)
|
||||
|
||||
const queryEditor = ref<any | null>(null)
|
||||
const schemaString = useReadonlyStream(props.conn.schema$, null)
|
||||
@@ -381,52 +522,20 @@ useCodemirror(queryEditor, gqlQueryString, {
|
||||
},
|
||||
linter: createGQLQueryLinter(schemaString),
|
||||
completer: queryCompleter(schemaString),
|
||||
environmentHighlights: false,
|
||||
})
|
||||
|
||||
const copyQueryIcon = ref("copy")
|
||||
const prettifyQueryIcon = ref("wand")
|
||||
const copyVariablesIcon = ref("copy")
|
||||
const prettifyQueryIcon = ref("wand")
|
||||
const prettifyVariablesIcon = ref("wand")
|
||||
|
||||
const showSaveRequestModal = ref(false)
|
||||
|
||||
watch(
|
||||
headers,
|
||||
() => {
|
||||
if (
|
||||
(headers.value[headers.value.length - 1]?.key !== "" ||
|
||||
headers.value[headers.value.length - 1]?.value !== "") &&
|
||||
headers.value.length
|
||||
)
|
||||
addRequestHeader()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const editBulkHeadersLine = (index: number, item?: GQLHeader | null) => {
|
||||
bulkHeaders.value = headers.value
|
||||
.reduce((all, header, pIndex) => {
|
||||
const current =
|
||||
index === pIndex && item != null
|
||||
? `${item.active ? "" : "//"}${item.key}: ${item.value}`
|
||||
: `${header.active ? "" : "//"}${header.key}: ${header.value}`
|
||||
return [...all, current]
|
||||
}, [])
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
const clearBulkEditor = () => {
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!headers.value?.length) {
|
||||
addRequestHeader()
|
||||
}
|
||||
})
|
||||
|
||||
const copyQuery = () => {
|
||||
copyToClipboard(gqlQueryString.value)
|
||||
copyQueryIcon.value = "check"
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
setTimeout(() => (copyQueryIcon.value = "copy"), 1000)
|
||||
}
|
||||
|
||||
@@ -470,18 +579,14 @@ const runQuery = async () => {
|
||||
})
|
||||
)
|
||||
|
||||
$toast.success(`${t("state.finished_in", { duration })}`, {
|
||||
icon: "done",
|
||||
})
|
||||
toast.success(`${t("state.finished_in", { duration })}`)
|
||||
} catch (e: any) {
|
||||
response.value = `${e}`
|
||||
nuxt.value.$loading.finish()
|
||||
|
||||
$toast.error(
|
||||
toast.error(
|
||||
`${t("error.something_went_wrong")}. ${t("error.check_console_details")}`,
|
||||
{
|
||||
icon: "error_outline",
|
||||
}
|
||||
{}
|
||||
)
|
||||
console.error(e)
|
||||
}
|
||||
@@ -499,12 +604,11 @@ const hideRequestModal = () => {
|
||||
const prettifyQuery = () => {
|
||||
try {
|
||||
gqlQueryString.value = gql.print(gql.parse(gqlQueryString.value))
|
||||
prettifyQueryIcon.value = "check"
|
||||
} catch (e) {
|
||||
$toast.error(`${t("error.gql_prettify_invalid_query")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
||||
prettifyQueryIcon.value = "info"
|
||||
}
|
||||
prettifyQueryIcon.value = "check"
|
||||
setTimeout(() => (prettifyQueryIcon.value = "wand"), 1000)
|
||||
}
|
||||
|
||||
@@ -515,51 +619,32 @@ const saveRequest = () => {
|
||||
const copyVariables = () => {
|
||||
copyToClipboard(variableString.value)
|
||||
copyVariablesIcon.value = "check"
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
setTimeout(() => (copyVariablesIcon.value = "copy"), 1000)
|
||||
}
|
||||
|
||||
const addRequestHeader = () => {
|
||||
const empty = { key: "", value: "", active: true }
|
||||
const index = headers.value.length
|
||||
|
||||
addGQLHeader(empty)
|
||||
editBulkHeadersLine(index, empty)
|
||||
}
|
||||
|
||||
const updateRequestHeader = (
|
||||
index: number,
|
||||
item: { key: string; value: string; active: boolean }
|
||||
) => {
|
||||
updateGQLHeader(index, item)
|
||||
editBulkHeadersLine(index, item)
|
||||
}
|
||||
|
||||
const removeRequestHeader = (index: number) => {
|
||||
const headersBeforeDeletion = headers.value
|
||||
|
||||
removeGQLHeader(index)
|
||||
editBulkHeadersLine(index, null)
|
||||
|
||||
const deletedItem = headersBeforeDeletion[index]
|
||||
if (deletedItem.key || deletedItem.value) {
|
||||
$toast.success(t("state.deleted").toString(), {
|
||||
icon: "delete",
|
||||
action: [
|
||||
{
|
||||
text: t("action.undo").toString(),
|
||||
onClick: (_, toastObject) => {
|
||||
setGQLHeaders(headersBeforeDeletion as GQLHeader[])
|
||||
editBulkHeadersLine(index, deletedItem)
|
||||
toastObject.goAway(0)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
const prettifyVariableString = () => {
|
||||
try {
|
||||
const jsonObj = JSON.parse(variableString.value)
|
||||
variableString.value = JSON.stringify(jsonObj, null, 2)
|
||||
prettifyVariablesIcon.value = "check"
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
prettifyVariablesIcon.value = "info"
|
||||
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
||||
}
|
||||
setTimeout(() => (prettifyVariablesIcon.value = "wand"), 1000)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
headers.value = []
|
||||
clearBulkEditor()
|
||||
const clearGQLQuery = () => {
|
||||
gqlQueryString.value = ""
|
||||
}
|
||||
|
||||
const clearGQLVariables = () => {
|
||||
variableString.value = ""
|
||||
}
|
||||
|
||||
defineActionHandler("request.send-cancel", runQuery)
|
||||
defineActionHandler("request.save", saveRequest)
|
||||
defineActionHandler("request.reset", clearGQLQuery)
|
||||
</script>
|
||||
|
||||
@@ -1,48 +1,38 @@
|
||||
<template>
|
||||
<AppSection ref="response" label="response">
|
||||
<div>
|
||||
<div
|
||||
v-if="responseString === 'loading'"
|
||||
class="flex flex-col p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4"
|
||||
>
|
||||
<SmartSpinner class="my-4" />
|
||||
<span class="text-secondaryLight">{{ $t("state.loading") }}</span>
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
<div v-else-if="responseString">
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
pl-4
|
||||
top-0
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky top-0 z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ $t("response.title") }}
|
||||
{{ t("response.title") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('state.linewrap')"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': linewrapEnabled }"
|
||||
svg="corner-down-left"
|
||||
svg="wrap-text"
|
||||
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="downloadResponse"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.download_file')"
|
||||
:title="t('action.download_file')"
|
||||
:svg="downloadResponseIcon"
|
||||
@click.native="downloadResponse"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
ref="copyResponseButton"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.copy')"
|
||||
:title="t('action.copy')"
|
||||
:svg="copyResponseIcon"
|
||||
@click.native="copyResponse"
|
||||
/>
|
||||
@@ -52,21 +42,15 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
flex flex-col flex-1
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
class="flex flex-col items-center justify-center flex-1 p-4 text-secondaryLight"
|
||||
>
|
||||
<div class="flex space-x-2 pb-4 my-4">
|
||||
<div class="flex flex-col space-y-4 text-right items-end">
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ $t("shortcut.general.command_menu") }}
|
||||
<div class="flex pb-4 my-4 space-x-2">
|
||||
<div class="flex flex-col items-end space-y-4 text-right">
|
||||
<span class="flex items-center flex-1">
|
||||
{{ t("shortcut.general.command_menu") }}
|
||||
</span>
|
||||
<span class="flex flex-1 items-center">
|
||||
{{ $t("shortcut.general.help_menu") }}
|
||||
<span class="flex items-center flex-1">
|
||||
{{ t("shortcut.general.help_menu") }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4">
|
||||
@@ -79,29 +63,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io"
|
||||
:label="`${t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io/features/response"
|
||||
svg="external-link"
|
||||
blank
|
||||
outline
|
||||
reverse
|
||||
/>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { reactive, ref } from "@nuxtjs/composition-api"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
useReadonlyStream,
|
||||
useI18n,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import { gqlResponse$ } from "~/newstore/GQLSession"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const responseString = useReadonlyStream(gqlResponse$, "")
|
||||
|
||||
@@ -119,6 +105,7 @@ useCodemirror(
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -128,6 +115,7 @@ const copyResponseIcon = ref("copy")
|
||||
const copyResponse = () => {
|
||||
copyToClipboard(responseString.value!)
|
||||
copyResponseIcon.value = "check"
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
setTimeout(() => (copyResponseIcon.value = "copy"), 1000)
|
||||
}
|
||||
|
||||
@@ -141,9 +129,7 @@ const downloadResponse = () => {
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
downloadResponseIcon.value = "check"
|
||||
$toast.success(`${t("state.download_started")}`, {
|
||||
icon: "downloading",
|
||||
})
|
||||
toast.success(`${t("state.download_started")}`)
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
@@ -151,14 +137,3 @@ const downloadResponse = () => {
|
||||
}, 1000)
|
||||
}
|
||||
</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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<SmartTab
|
||||
:id="'history'"
|
||||
icon="clock"
|
||||
:label="`${$t('tab.history')}`"
|
||||
:label="`${t('tab.history')}`"
|
||||
:selected="true"
|
||||
>
|
||||
<History
|
||||
@@ -16,7 +16,7 @@
|
||||
<SmartTab
|
||||
:id="'collections'"
|
||||
icon="folder"
|
||||
:label="`${$t('tab.collections')}`"
|
||||
:label="`${t('tab.collections')}`"
|
||||
>
|
||||
<CollectionsGraphql />
|
||||
</SmartTab>
|
||||
@@ -24,229 +24,187 @@
|
||||
<SmartTab
|
||||
:id="'docs'"
|
||||
icon="book-open"
|
||||
:label="`${$t('tab.documentation')}`"
|
||||
:label="`${t('tab.documentation')}`"
|
||||
>
|
||||
<AppSection label="docs">
|
||||
<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
|
||||
"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_comment.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.documentation')"
|
||||
<div
|
||||
v-if="
|
||||
queryFields.length === 0 &&
|
||||
mutationFields.length === 0 &&
|
||||
subscriptionFields.length === 0 &&
|
||||
graphqlTypes.length === 0
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_comment.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.documentation')}`"
|
||||
/>
|
||||
<span class="mb-4 text-center">
|
||||
{{ t("empty.documentation") }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="sticky top-0 z-10 flex bg-primary">
|
||||
<input
|
||||
v-model="graphqlFieldsFilterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
class="flex w-full p-4 py-2 bg-transparent"
|
||||
/>
|
||||
<span class="text-center mb-4">
|
||||
{{ $t("empty.documentation") }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="bg-primary flex top-0 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-b border-dividerLight bg-primary sticky z-10 top-sidebarPrimaryStickyFold"
|
||||
>
|
||||
<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>
|
||||
</AppSection>
|
||||
</SmartTab>
|
||||
|
||||
<SmartTab :id="'schema'" icon="box" :label="`${$t('tab.schema')}`">
|
||||
<AppSection ref="schema" label="schema">
|
||||
<div
|
||||
v-if="schemaString"
|
||||
class="
|
||||
bg-primary
|
||||
flex flex-1
|
||||
top-0
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
border-b border-dividerLight
|
||||
"
|
||||
>
|
||||
<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')"
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('state.linewrap')"
|
||||
:class="{ '!text-accent': linewrapEnabled }"
|
||||
svg="corner-down-left"
|
||||
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
||||
/>
|
||||
<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>
|
||||
<div v-if="schemaString" ref="schemaEditor"></div>
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
<SmartTabs
|
||||
ref="gqlTabs"
|
||||
styles="border-t border-b border-dividerLight bg-primary sticky z-10 top-sidebarPrimaryStickyFold"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.schema')"
|
||||
<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>
|
||||
</SmartTab>
|
||||
|
||||
<SmartTab :id="'schema'" icon="box" :label="`${t('tab.schema')}`">
|
||||
<div
|
||||
v-if="schemaString"
|
||||
class="sticky top-0 z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight"
|
||||
>
|
||||
<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
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': linewrapEnabled }"
|
||||
svg="wrap-text"
|
||||
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<span class="text-center mb-4">
|
||||
{{ $t("empty.schema") }}
|
||||
</span>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
<div v-if="schemaString" ref="schemaEditor"></div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.schema')}`"
|
||||
/>
|
||||
<span class="mb-4 text-center">
|
||||
{{ t("empty.schema") }}
|
||||
</span>
|
||||
</div>
|
||||
</SmartTab>
|
||||
</SmartTabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
reactive,
|
||||
ref,
|
||||
useContext,
|
||||
} from "@nuxtjs/composition-api"
|
||||
import { computed, nextTick, reactive, ref } from "@nuxtjs/composition-api"
|
||||
import { GraphQLField, GraphQLType } from "graphql"
|
||||
import { map } from "rxjs/operators"
|
||||
import { GQLHeader } from "@hoppscotch/data"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||
import { GQLHeader } from "~/helpers/types/HoppGQLRequest"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
useReadonlyStream,
|
||||
useI18n,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import {
|
||||
setGQLHeaders,
|
||||
setGQLQuery,
|
||||
@@ -255,6 +213,8 @@ import {
|
||||
setGQLVariables,
|
||||
} from "~/newstore/GQLSession"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
function isTextFoundInGraphqlFieldObject(
|
||||
text: string,
|
||||
field: GraphQLField<any, any>
|
||||
@@ -321,11 +281,7 @@ const props = defineProps<{
|
||||
conn: GQLConnection
|
||||
}>()
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const toast = useToast()
|
||||
|
||||
const queryFields = useReadonlyStream(
|
||||
props.conn.queryFields$.pipe(map((x) => x ?? [])),
|
||||
@@ -408,12 +364,25 @@ const handleJumpToType = async (type: GraphQLType) => {
|
||||
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" })
|
||||
target.scrollIntoView({ block: "center", behavior: "smooth" })
|
||||
target.classList.add(
|
||||
"transition-all",
|
||||
"ring-inset",
|
||||
"ring-accentLight",
|
||||
"ring-4"
|
||||
)
|
||||
setTimeout(
|
||||
() =>
|
||||
target.classList.remove(
|
||||
"ring-inset",
|
||||
"ring-accentLight",
|
||||
"ring-4",
|
||||
"transition-all"
|
||||
),
|
||||
2000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,6 +405,7 @@ useCodemirror(
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -449,9 +419,7 @@ const downloadSchema = () => {
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
downloadSchemaIcon.value = "check"
|
||||
$toast.success(`${t("state.download_started")}`, {
|
||||
icon: "downloading",
|
||||
})
|
||||
toast.success(`${t("state.download_started")}`)
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<span v-else-if="isEnum" class="text-accent">enum </span>
|
||||
{{ gqlType.name }}
|
||||
</div>
|
||||
<div v-if="gqlType.description" class="text-secondaryLight py-2 type-desc">
|
||||
<div v-if="gqlType.description" class="py-2 text-secondaryLight type-desc">
|
||||
{{ gqlType.description }}
|
||||
</div>
|
||||
<div v-if="interfaces.length > 0">
|
||||
@@ -18,7 +18,7 @@
|
||||
<GraphqlTypeLink
|
||||
:gql-type="gqlInterface"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
class="border-divider border-l-2 pl-4"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
:key="`child-${index}`"
|
||||
:gql-type="child"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
class="border-divider border-l-2 pl-4"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="gqlType.getFields">
|
||||
@@ -37,7 +37,7 @@
|
||||
<GraphqlField
|
||||
v-for="(field, index) in gqlType.getFields()"
|
||||
:key="`field-${index}`"
|
||||
class="border-divider border-l-2 pl-4"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
:gql-field="field"
|
||||
:is-highlighted="isFieldHighlighted({ field })"
|
||||
:jump-type-callback="jumpTypeCallback"
|
||||
@@ -48,7 +48,7 @@
|
||||
<div
|
||||
v-for="(value, index) in gqlType.getValues()"
|
||||
:key="`value-${index}`"
|
||||
class="border-divider border-l-2 pl-4"
|
||||
class="pl-4 border-l-2 border-divider"
|
||||
v-text="value.name"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,19 @@
|
||||
<div class="flex flex-col group">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
pl-4
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="flex flex-1 min-w-0 py-2 pl-4 pr-2 transition cursor-pointer group-hover:text-secondaryDark"
|
||||
data-testid="restore_history_entry"
|
||||
@click="useEntry"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ entry.request.url }}
|
||||
</span>
|
||||
<tippy
|
||||
v-if="entry.updatedOn"
|
||||
theme="tooltip"
|
||||
:delay="[500, 20]"
|
||||
:content="`${new Date(entry.updatedOn).toLocaleString()}`"
|
||||
/>
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -45,11 +42,11 @@
|
||||
@click.native="$emit('toggle-star')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col text-tiny">
|
||||
<span
|
||||
v-for="(line, index) in query"
|
||||
:key="`line-${index}`"
|
||||
class="cursor-pointer text-secondaryLight px-4 whitespace-pre truncate"
|
||||
class="px-4 font-mono truncate whitespace-pre cursor-pointer text-secondaryLight"
|
||||
data-testid="restore_history_entry"
|
||||
@click="useEntry"
|
||||
>{{ line }}</span
|
||||
@@ -58,53 +55,39 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
PropType,
|
||||
ref,
|
||||
} from "@nuxtjs/composition-api"
|
||||
import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "@nuxtjs/composition-api"
|
||||
import { makeGQLRequest } from "@hoppscotch/data"
|
||||
import { setGQLSession } from "~/newstore/GQLSession"
|
||||
import { GQLHistoryEntry } from "~/newstore/history"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
entry: { type: Object as PropType<GQLHistoryEntry>, default: () => {} },
|
||||
showMore: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const expand = ref(false)
|
||||
const props = defineProps<{
|
||||
entry: GQLHistoryEntry
|
||||
showMore: Boolean
|
||||
}>()
|
||||
|
||||
const query = computed(() =>
|
||||
expand.value
|
||||
? (props.entry.request.query.split("\n") as string[])
|
||||
: (props.entry.request.query
|
||||
.split("\n")
|
||||
.slice(0, 2)
|
||||
.concat(["..."]) as string[])
|
||||
)
|
||||
const expand = ref(false)
|
||||
|
||||
const useEntry = () => {
|
||||
setGQLSession({
|
||||
request: makeGQLRequest({
|
||||
name: props.entry.request.name,
|
||||
url: props.entry.request.url,
|
||||
headers: props.entry.request.headers,
|
||||
query: props.entry.request.query,
|
||||
variables: props.entry.request.variables,
|
||||
}),
|
||||
schema: "",
|
||||
response: props.entry.response,
|
||||
})
|
||||
}
|
||||
const query = computed(() =>
|
||||
expand.value
|
||||
? (props.entry.request.query.split("\n") as string[])
|
||||
: (props.entry.request.query
|
||||
.split("\n")
|
||||
.slice(0, 2)
|
||||
.concat(["..."]) as string[])
|
||||
)
|
||||
|
||||
return {
|
||||
expand,
|
||||
query,
|
||||
useEntry,
|
||||
}
|
||||
},
|
||||
})
|
||||
const useEntry = () => {
|
||||
setGQLSession({
|
||||
request: makeGQLRequest({
|
||||
name: props.entry.request.name,
|
||||
url: props.entry.request.url,
|
||||
headers: props.entry.request.headers,
|
||||
query: props.entry.request.query,
|
||||
variables: props.entry.request.variables,
|
||||
}),
|
||||
schema: "",
|
||||
response: props.entry.response,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<AppSection label="history">
|
||||
<div class="bg-primary border-b border-dividerLight flex top-0 z-10 sticky">
|
||||
<div>
|
||||
<div class="sticky top-0 z-10 flex border-b bg-primary border-dividerLight">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="bg-transparent flex w-full p-4 py-2"
|
||||
:placeholder="`${$t('action.search')}`"
|
||||
class="flex w-full p-4 py-2 bg-transparent"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/history"
|
||||
blank
|
||||
:title="$t('app.wiki')"
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
@@ -21,67 +21,94 @@
|
||||
data-testid="clear_history"
|
||||
:disabled="history.length === 0"
|
||||
svg="trash-2"
|
||||
:title="$t('action.clear_all')"
|
||||
:title="t('action.clear_all')"
|
||||
@click.native="confirmRemove = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div v-for="(entry, index) in filteredHistory" :key="`entry-${index}`">
|
||||
<HistoryRestCard
|
||||
v-if="page == 'rest'"
|
||||
:id="index"
|
||||
:entry="entry"
|
||||
:show-more="showMore"
|
||||
@toggle-star="toggleStar(entry)"
|
||||
@delete-entry="deleteHistory(entry)"
|
||||
@use-entry="useHistory(entry)"
|
||||
/>
|
||||
<HistoryGraphqlCard
|
||||
v-if="page == 'graphql'"
|
||||
:entry="entry"
|
||||
:show-more="showMore"
|
||||
@toggle-star="toggleStar(entry)"
|
||||
@delete-entry="deleteHistory(entry)"
|
||||
@use-entry="useHistory(entry)"
|
||||
/>
|
||||
</div>
|
||||
<details
|
||||
v-for="(
|
||||
filteredHistoryGroup, filteredHistoryGroupIndex
|
||||
) in filteredHistoryGroups"
|
||||
:key="`filteredHistoryGroup-${filteredHistoryGroupIndex}`"
|
||||
class="flex flex-col"
|
||||
open
|
||||
>
|
||||
<summary
|
||||
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
||||
>
|
||||
<span
|
||||
class="px-4 py-2 truncate transition group-hover:text-secondary capitalize-first"
|
||||
>
|
||||
{{ filteredHistoryGroupIndex }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
svg="trash"
|
||||
color="red"
|
||||
:title="$t('action.remove')"
|
||||
class="hidden group-hover:inline-flex"
|
||||
@click.native="deleteBattleHistoryEntry(filteredHistoryGroup)"
|
||||
/>
|
||||
</summary>
|
||||
<div
|
||||
v-for="(entry, index) in filteredHistoryGroup"
|
||||
:key="`entry-${index}`"
|
||||
>
|
||||
<component
|
||||
:is="page == 'rest' ? 'HistoryRestCard' : 'HistoryGraphqlCard'"
|
||||
:id="index"
|
||||
:entry="entry"
|
||||
:show-more="showMore"
|
||||
@toggle-star="toggleStar(entry)"
|
||||
@delete-entry="deleteHistory(entry)"
|
||||
@use-entry="useHistory(entry)"
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div
|
||||
v-if="!(filteredHistory.length !== 0 || history.length === 0)"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<i class="opacity-75 pb-2 material-icons">manage_search</i>
|
||||
<span class="text-center">
|
||||
{{ $t("state.nothing_found") }} "{{ filterText }}"
|
||||
<i class="pb-2 opacity-75 material-icons">manage_search</i>
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="history.length === 0"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/history.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
:alt="$t('empty.history')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.history')}`"
|
||||
/>
|
||||
<span class="text-center mb-4">
|
||||
{{ $t("empty.history") }}
|
||||
<span class="mb-4 text-center">
|
||||
{{ t("empty.history") }}
|
||||
</span>
|
||||
</div>
|
||||
<SmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="`${$t('confirm.remove_history')}`"
|
||||
:title="`${t('confirm.remove_history')}`"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="clearHistory"
|
||||
/>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "@nuxtjs/composition-api"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "@nuxtjs/composition-api"
|
||||
import * as timeago from "timeago.js"
|
||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
useI18n,
|
||||
useReadonlyStream,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import {
|
||||
restHistory$,
|
||||
graphqlHistory$,
|
||||
@@ -94,70 +121,87 @@ import {
|
||||
RESTHistoryEntry,
|
||||
GQLHistoryEntry,
|
||||
} from "~/newstore/history"
|
||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
||||
import { getDefaultRESTRequest, setRESTRequest } from "~/newstore/RESTSession"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
page: { type: String as PropType<"rest" | "graphql">, default: null },
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
history: useReadonlyStream<RESTHistoryEntry[] | GQLHistoryEntry[]>(
|
||||
props.page === "rest" ? restHistory$ : graphqlHistory$,
|
||||
[]
|
||||
),
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filterText: "",
|
||||
showMore: false,
|
||||
confirmRemove: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredHistory(): any[] {
|
||||
const filteringHistory = this.history as Array<
|
||||
RESTHistoryEntry | GQLHistoryEntry
|
||||
>
|
||||
const props = defineProps<{
|
||||
page: "rest" | "graphql"
|
||||
}>()
|
||||
|
||||
return filteringHistory.filter(
|
||||
(entry: RESTHistoryEntry | GQLHistoryEntry) => {
|
||||
const filterText = this.filterText.toLowerCase()
|
||||
return Object.keys(entry).some((key) => {
|
||||
let value = entry[key as keyof typeof entry]
|
||||
if (value) {
|
||||
value = `${value}`
|
||||
return value.toLowerCase().includes(filterText)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearHistory() {
|
||||
if (this.page === "rest") clearRESTHistory()
|
||||
else clearGraphqlHistory()
|
||||
this.$toast.success(`${this.$t("state.history_deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
},
|
||||
useHistory(entry: any) {
|
||||
if (this.page === "rest") setRESTRequest(entry.request)
|
||||
},
|
||||
deleteHistory(entry: any) {
|
||||
if (this.page === "rest") deleteRESTHistoryEntry(entry)
|
||||
else deleteGraphqlHistoryEntry(entry)
|
||||
this.$toast.success(`${this.$t("state.deleted")}`, {
|
||||
icon: "delete",
|
||||
})
|
||||
},
|
||||
toggleStar(entry: any) {
|
||||
if (this.page === "rest") toggleRESTHistoryEntryStar(entry)
|
||||
else toggleGraphqlHistoryEntryStar(entry)
|
||||
},
|
||||
},
|
||||
const filterText = ref("")
|
||||
const showMore = ref(false)
|
||||
const confirmRemove = ref(false)
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const groupByDate = (array: any[], key: string) => {
|
||||
return array.reduce((rv: any, x: any) => {
|
||||
;(rv[timeago.format(x[key])] = rv[timeago.format(x[key])] || []).push(x)
|
||||
return rv
|
||||
}, {})
|
||||
}
|
||||
|
||||
const history = useReadonlyStream<RESTHistoryEntry[] | GQLHistoryEntry[]>(
|
||||
props.page === "rest" ? restHistory$ : graphqlHistory$,
|
||||
[]
|
||||
)
|
||||
|
||||
const filteredHistory = computed(() => {
|
||||
const regExp = new RegExp(filterText.value, "gi")
|
||||
const check = (obj: any) => {
|
||||
if (obj !== null && typeof obj === "object") {
|
||||
return Object.values(obj).some(check)
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.some(check)
|
||||
}
|
||||
return (
|
||||
(typeof obj === "string" || typeof obj === "number") &&
|
||||
regExp.test(obj as string)
|
||||
)
|
||||
}
|
||||
return (history.value as Array<RESTHistoryEntry | GQLHistoryEntry>).filter(
|
||||
check
|
||||
)
|
||||
})
|
||||
|
||||
const filteredHistoryGroups = computed(() =>
|
||||
groupByDate(filteredHistory.value, "updatedOn")
|
||||
)
|
||||
|
||||
const clearHistory = () => {
|
||||
if (props.page === "rest") clearRESTHistory()
|
||||
else clearGraphqlHistory()
|
||||
toast.success(`${t("state.history_deleted")}`)
|
||||
}
|
||||
|
||||
const useHistory = (entry: any) => {
|
||||
if (props.page === "rest")
|
||||
setRESTRequest(
|
||||
safelyExtractRESTRequest(entry.request, getDefaultRESTRequest())
|
||||
)
|
||||
}
|
||||
|
||||
const deleteBattleHistoryEntry = (entries: number[]) => {
|
||||
if (props.page === "rest") {
|
||||
entries.forEach((entry: any) => {
|
||||
deleteRESTHistoryEntry(entry)
|
||||
})
|
||||
} else {
|
||||
entries.forEach((entry: any) => {
|
||||
deleteGraphqlHistoryEntry(entry)
|
||||
})
|
||||
}
|
||||
toast.success(`${t("state.deleted")}`)
|
||||
}
|
||||
|
||||
const deleteHistory = (entry: any) => {
|
||||
if (props.page === "rest") deleteRESTHistoryEntry(entry)
|
||||
else deleteGraphqlHistoryEntry(entry)
|
||||
toast.success(`${t("state.deleted")}`)
|
||||
}
|
||||
|
||||
const toggleStar = (entry: any) => {
|
||||
if (props.page === "rest") toggleRESTHistoryEntryStar(entry)
|
||||
else toggleGraphqlHistoryEntryStar(entry)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="flex items-center group">
|
||||
<div class="flex items-stretch group">
|
||||
<span
|
||||
class="cursor-pointer flex px-2 w-16 justify-center items-center truncate"
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
|
||||
:class="entryStatus.className"
|
||||
data-testid="restore_history_entry"
|
||||
:title="`${duration}`"
|
||||
@@ -10,22 +11,19 @@
|
||||
{{ entry.request.method }}
|
||||
</span>
|
||||
<span
|
||||
class="
|
||||
cursor-pointer
|
||||
flex flex-1
|
||||
min-w-0
|
||||
py-2
|
||||
pr-2
|
||||
transition
|
||||
group-hover:text-secondaryDark
|
||||
"
|
||||
class="flex flex-1 min-w-0 py-2 pr-2 transition cursor-pointer group-hover:text-secondaryDark"
|
||||
data-testid="restore_history_entry"
|
||||
:title="`${duration}`"
|
||||
@click="$emit('use-entry')"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ entry.request.endpoint }}
|
||||
</span>
|
||||
<tippy
|
||||
v-if="entry.updatedOn"
|
||||
theme="tooltip"
|
||||
:delay="[500, 20]"
|
||||
:content="`${new Date(entry.updatedOn).toLocaleString()}`"
|
||||
/>
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -48,53 +46,36 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
PropType,
|
||||
useContext,
|
||||
} from "@nuxtjs/composition-api"
|
||||
<script setup lang="ts">
|
||||
import { computed } from "@nuxtjs/composition-api"
|
||||
import findStatusGroup from "~/helpers/findStatusGroup"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
import { RESTHistoryEntry } from "~/newstore/history"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
entry: { type: Object as PropType<RESTHistoryEntry>, default: () => {} },
|
||||
showMore: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const $t = i18n.t.bind(i18n)
|
||||
const props = defineProps<{
|
||||
entry: RESTHistoryEntry
|
||||
showMore: Boolean
|
||||
}>()
|
||||
|
||||
const duration = computed(() => {
|
||||
if (props.entry.responseMeta.duration) {
|
||||
const responseDuration = props.entry.responseMeta.duration
|
||||
if (!responseDuration) return ""
|
||||
const t = useI18n()
|
||||
|
||||
return responseDuration > 0
|
||||
? `${$t("request.duration")}: ${responseDuration}ms`
|
||||
: $t("error.no_duration")
|
||||
} else return $t("error.no_duration")
|
||||
})
|
||||
const duration = computed(() => {
|
||||
if (props.entry.responseMeta.duration) {
|
||||
const responseDuration = props.entry.responseMeta.duration
|
||||
if (!responseDuration) return ""
|
||||
|
||||
const entryStatus = computed(() => {
|
||||
const foundStatusGroup = findStatusGroup(
|
||||
props.entry.responseMeta.statusCode
|
||||
)
|
||||
return (
|
||||
foundStatusGroup || {
|
||||
className: "",
|
||||
}
|
||||
)
|
||||
})
|
||||
return responseDuration > 0
|
||||
? `${t("request.duration")}: ${responseDuration}ms`
|
||||
: t("error.no_duration")
|
||||
} else return t("error.no_duration")
|
||||
})
|
||||
|
||||
return {
|
||||
duration,
|
||||
entryStatus,
|
||||
const entryStatus = computed(() => {
|
||||
const foundStatusGroup = findStatusGroup(props.entry.responseMeta.statusCode)
|
||||
return (
|
||||
foundStatusGroup || {
|
||||
className: "",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperSecondaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
@@ -27,37 +17,84 @@
|
||||
<template #trigger>
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary
|
||||
class="rounded-none ml-2 pr-8"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
:label="authName"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<SmartItem
|
||||
label="None"
|
||||
:icon="
|
||||
authName === 'None'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="authName === 'None'"
|
||||
@click.native="
|
||||
authType = 'none'
|
||||
$refs.authTypeOptions.tippy().hide()
|
||||
() => {
|
||||
authType = 'none'
|
||||
authTypeOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="Basic Auth"
|
||||
:icon="
|
||||
authName === 'Basic Auth'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="authName === 'Basic Auth'"
|
||||
@click.native="
|
||||
authType = 'basic'
|
||||
$refs.authTypeOptions.tippy().hide()
|
||||
() => {
|
||||
authType = 'basic'
|
||||
authTypeOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="Bearer Token"
|
||||
:icon="
|
||||
authName === 'Bearer'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="authName === 'Bearer'"
|
||||
@click.native="
|
||||
authType = 'bearer'
|
||||
$refs.authTypeOptions.tippy().hide()
|
||||
() => {
|
||||
authType = 'bearer'
|
||||
authTypeOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="OAuth 2.0"
|
||||
:icon="
|
||||
authName === 'OAuth 2.0'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="authName === 'OAuth 2.0'"
|
||||
@click.native="
|
||||
authType = 'oauth-2'
|
||||
$refs.authTypeOptions.tippy().hide()
|
||||
() => {
|
||||
authType = 'oauth-2'
|
||||
authTypeOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
label="API key"
|
||||
:icon="
|
||||
authName === 'API key'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="authName === 'API key'"
|
||||
@click.native="
|
||||
() => {
|
||||
authType = 'api-key'
|
||||
authTypeOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</tippy>
|
||||
@@ -93,156 +130,157 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="authType === 'none'"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/login.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
:alt="$t('empty.authorization')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${$t('empty.authorization')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
<span class="pb-4 text-center">
|
||||
{{ $t("empty.authorization") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
outline
|
||||
:label="$t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io"
|
||||
to="https://docs.hoppscotch.io/features/authorization"
|
||||
blank
|
||||
svg="external-link"
|
||||
reverse
|
||||
class="mb-4"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="authType === 'basic'" class="border-b border-dividerLight flex">
|
||||
<div class="border-r border-dividerLight w-2/3">
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<SmartEnvInput
|
||||
v-model="basicUsername"
|
||||
:placeholder="$t('authorization.username')"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
<div v-else class="flex border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div v-if="authType === 'basic'">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicUsername"
|
||||
:placeholder="$t('authorization.username')"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicPassword"
|
||||
:placeholder="$t('authorization.password')"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<SmartEnvInput
|
||||
v-model="basicPassword"
|
||||
:placeholder="$t('authorization.password')"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
<div v-if="authType === 'bearer'">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="bearerToken"
|
||||
placeholder="Token"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'oauth-2'">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="oauth2Token"
|
||||
placeholder="Token"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization />
|
||||
</div>
|
||||
<div v-if="authType === 'api-key'">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="apiKey"
|
||||
placeholder="Key"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="apiValue"
|
||||
placeholder="Value"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<label class="ml-4 text-secondaryLight">
|
||||
{{ $t("authorization.pass_key_by") }}
|
||||
</label>
|
||||
<tippy
|
||||
ref="addToOptions"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
arrow
|
||||
>
|
||||
<template #trigger>
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary
|
||||
:label="addTo || $t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<SmartItem
|
||||
:icon="
|
||||
addTo === 'Headers'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="addTo === 'Headers'"
|
||||
:label="'Headers'"
|
||||
@click.native="
|
||||
() => {
|
||||
addTo = 'Headers'
|
||||
addToOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SmartItem
|
||||
:icon="
|
||||
addTo === 'Query params'
|
||||
? 'radio_button_checked'
|
||||
: 'radio_button_unchecked'
|
||||
"
|
||||
:active="addTo === 'Query params'"
|
||||
:label="'Query params'"
|
||||
@click.native="
|
||||
() => {
|
||||
addTo = 'Query params'
|
||||
addToOptions.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
h-full
|
||||
top-upperTertiaryStickyFold
|
||||
min-w-46
|
||||
max-w-1/3
|
||||
p-4
|
||||
z-9
|
||||
sticky
|
||||
overflow-auto
|
||||
"
|
||||
class="sticky h-full p-4 overflow-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||
>
|
||||
<div class="p-2">
|
||||
<div class="text-secondaryLight pb-2">
|
||||
{{ $t("helpers.authorization") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="`${$t('authorization.learn')} \xA0 →`"
|
||||
to="https://docs.hoppscotch.io/"
|
||||
blank
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'bearer'" class="border-b border-dividerLight flex">
|
||||
<div class="border-r border-dividerLight w-2/3">
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<SmartEnvInput
|
||||
v-model="bearerToken"
|
||||
placeholder="Token"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
h-full
|
||||
top-upperTertiaryStickyFold
|
||||
min-w-46
|
||||
max-w-1/3
|
||||
p-4
|
||||
z-9
|
||||
sticky
|
||||
overflow-auto
|
||||
"
|
||||
>
|
||||
<div class="p-2">
|
||||
<div class="text-secondaryLight pb-2">
|
||||
{{ $t("helpers.authorization") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="`${$t('authorization.learn')} \xA0 →`"
|
||||
to="https://docs.hoppscotch.io/"
|
||||
blank
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="authType === 'oauth-2'"
|
||||
class="border-b border-dividerLight flex"
|
||||
>
|
||||
<div class="border-r border-dividerLight w-2/3">
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<SmartEnvInput
|
||||
v-model="oauth2Token"
|
||||
placeholder="Token"
|
||||
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization />
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
h-full
|
||||
top-upperTertiaryStickyFold
|
||||
min-w-46
|
||||
max-w-1/3
|
||||
p-4
|
||||
z-9
|
||||
sticky
|
||||
overflow-auto
|
||||
"
|
||||
>
|
||||
<div class="p-2">
|
||||
<div class="text-secondaryLight pb-2">
|
||||
{{ $t("helpers.authorization") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="`${$t('authorization.learn')} \xA0 →`"
|
||||
to="https://docs.hoppscotch.io/"
|
||||
blank
|
||||
/>
|
||||
<div class="pb-2 text-secondaryLight">
|
||||
{{ $t("helpers.authorization") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
class="link"
|
||||
:label="`${$t('authorization.learn')} \xA0 →`"
|
||||
to="https://docs.hoppscotch.io/features/authorization"
|
||||
blank
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, Ref } from "@nuxtjs/composition-api"
|
||||
import { computed, defineComponent, ref, Ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
} from "~/helpers/types/HoppRESTAuth"
|
||||
HoppRESTAuthAPIKey,
|
||||
} from "@hoppscotch/data"
|
||||
import { pluckRef, useStream } from "~/helpers/utils/composables"
|
||||
import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
|
||||
import { useSetting } from "~/newstore/settings"
|
||||
@@ -259,6 +297,7 @@ export default defineComponent({
|
||||
if (authType.value === "basic") return "Basic Auth"
|
||||
else if (authType.value === "bearer") return "Bearer"
|
||||
else if (authType.value === "oauth-2") return "OAuth 2.0"
|
||||
else if (authType.value === "api-key") return "API key"
|
||||
else return "None"
|
||||
})
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
@@ -266,6 +305,15 @@ export default defineComponent({
|
||||
const basicPassword = pluckRef(auth as Ref<HoppRESTAuthBasic>, "password")
|
||||
const bearerToken = pluckRef(auth as Ref<HoppRESTAuthBearer>, "token")
|
||||
const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
|
||||
const apiKey = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "key")
|
||||
const apiValue = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "value")
|
||||
const addTo = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "addTo")
|
||||
if (typeof addTo.value === "undefined") {
|
||||
addTo.value = "Headers"
|
||||
apiKey.value = ""
|
||||
apiValue.value = ""
|
||||
}
|
||||
|
||||
const URLExcludes = useSetting("URL_EXCLUDES")
|
||||
const clearContent = () => {
|
||||
auth.value = {
|
||||
@@ -284,6 +332,11 @@ export default defineComponent({
|
||||
oauth2Token,
|
||||
URLExcludes,
|
||||
clearContent,
|
||||
apiKey,
|
||||
apiValue,
|
||||
addTo,
|
||||
authTypeOptions: ref<any | null>(null),
|
||||
addToOptions: ref<any | null>(null),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperSecondaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
@@ -28,7 +18,7 @@
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary
|
||||
:label="contentType || $t('state.none').toLowerCase()"
|
||||
class="rounded-none ml-2 pr-8"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
@@ -63,21 +53,21 @@
|
||||
<HttpRawBody v-else-if="contentType !== null" :content-type="contentType" />
|
||||
<div
|
||||
v-if="contentType == null"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/upload_single_file.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
:alt="$t('empty.body')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${$t('empty.body')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
<span class="pb-4 text-center">
|
||||
{{ $t("empty.body") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
outline
|
||||
:label="`${$t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io"
|
||||
to="https://docs.hoppscotch.io/features/body"
|
||||
blank
|
||||
svg="external-link"
|
||||
reverse
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<template>
|
||||
<AppSection label="bodyParameters">
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperTertiaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperTertiaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ $t("request.body") }}
|
||||
@@ -41,7 +31,7 @@
|
||||
<div
|
||||
v-for="(param, index) in bodyParams"
|
||||
:key="`param-${index}`"
|
||||
class="divide-x divide-dividerLight border-b border-dividerLight flex"
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
||||
>
|
||||
<SmartEnvInput
|
||||
v-model="param.key"
|
||||
@@ -64,13 +54,12 @@
|
||||
/>
|
||||
<div v-if="param.isFile" class="file-chips-container hide-scrollbar">
|
||||
<div class="space-x-2 file-chips-wrapper">
|
||||
<SmartDeletableChip
|
||||
<SmartFileChip
|
||||
v-for="(file, fileIndex) in param.value"
|
||||
:key="`param-${index}-file-${fileIndex}`"
|
||||
@chip-delete="chipDelete(index, fileIndex)"
|
||||
>
|
||||
{{ file.name }}
|
||||
</SmartDeletableChip>
|
||||
</SmartFileChip>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="flex flex-1">
|
||||
@@ -95,21 +84,17 @@
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<label for="attachment" class="p-0">
|
||||
<ButtonSecondary
|
||||
class="w-full"
|
||||
svg="paperclip"
|
||||
@click.native="$refs.attachment[index].click()"
|
||||
<label :for="`attachment${index}`" class="p-0">
|
||||
<input
|
||||
:id="`attachment${index}`"
|
||||
:ref="`attachment${index}`"
|
||||
:name="`attachment${index}`"
|
||||
type="file"
|
||||
multiple
|
||||
class="p-1 transition cursor-pointer file:transition file:cursor-pointer text-secondaryLight hover:text-secondaryDark file:mr-2 file:py-1 file:px-4 file:rounded file:border-0 file:text-tiny text-tiny file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||
@change="setRequestAttachment(index, param, $event)"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
ref="attachment"
|
||||
class="input"
|
||||
name="attachment"
|
||||
type="file"
|
||||
multiple
|
||||
@change="setRequestAttachment(index, param, $event)"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
@@ -151,15 +136,15 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="bodyParams.length === 0"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/upload_single_file.svg`"
|
||||
loading="lazy"
|
||||
class="flex-col my-4 object-contain object-center h-16 w-16 inline-flex"
|
||||
:alt="$t('empty.body')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${$t('empty.body')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
<span class="pb-4 text-center">
|
||||
{{ $t("empty.body") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
@@ -170,12 +155,12 @@
|
||||
@click.native="addBodyParam"
|
||||
/>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, Ref, watch } from "@nuxtjs/composition-api"
|
||||
import { FormDataKeyValue } from "~/helpers/types/HoppRESTRequest"
|
||||
<script setup lang="ts">
|
||||
import { onMounted, Ref, watch } from "@nuxtjs/composition-api"
|
||||
import { FormDataKeyValue } from "@hoppscotch/data"
|
||||
import { pluckRef } from "~/helpers/utils/composables"
|
||||
import {
|
||||
addFormDataEntry,
|
||||
@@ -185,87 +170,66 @@ import {
|
||||
useRESTRequestBody,
|
||||
} from "~/newstore/RESTSession"
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
|
||||
FormDataKeyValue[]
|
||||
>
|
||||
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
|
||||
FormDataKeyValue[]
|
||||
>
|
||||
|
||||
const addBodyParam = () => {
|
||||
addFormDataEntry({ key: "", value: "", active: true, isFile: false })
|
||||
}
|
||||
const addBodyParam = () => {
|
||||
addFormDataEntry({ key: "", value: "", active: true, isFile: false })
|
||||
}
|
||||
|
||||
const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
|
||||
updateFormDataEntry(index, entry)
|
||||
}
|
||||
const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
|
||||
updateFormDataEntry(index, entry)
|
||||
}
|
||||
|
||||
const deleteBodyParam = (index: number) => {
|
||||
deleteFormDataEntry(index)
|
||||
}
|
||||
const deleteBodyParam = (index: number) => {
|
||||
deleteFormDataEntry(index)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
deleteAllFormDataEntries()
|
||||
}
|
||||
const clearContent = () => {
|
||||
deleteAllFormDataEntries()
|
||||
}
|
||||
|
||||
const chipDelete = (paramIndex: number, fileIndex: number) => {
|
||||
const entry = bodyParams.value[paramIndex]
|
||||
if (entry.isFile) {
|
||||
entry.value.splice(fileIndex, 1)
|
||||
if (entry.value.length === 0) {
|
||||
updateFormDataEntry(paramIndex, {
|
||||
...entry,
|
||||
isFile: false,
|
||||
value: "",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updateFormDataEntry(paramIndex, entry)
|
||||
}
|
||||
|
||||
const setRequestAttachment = (
|
||||
index: number,
|
||||
entry: FormDataKeyValue,
|
||||
event: InputEvent
|
||||
) => {
|
||||
const fileEntry: FormDataKeyValue = {
|
||||
...entry,
|
||||
isFile: true,
|
||||
value: Array.from((event.target as HTMLInputElement).files!),
|
||||
}
|
||||
updateFormDataEntry(index, fileEntry)
|
||||
}
|
||||
|
||||
watch(
|
||||
bodyParams,
|
||||
() => {
|
||||
if (
|
||||
bodyParams.value.length > 0 &&
|
||||
(bodyParams.value[bodyParams.value.length - 1].key !== "" ||
|
||||
bodyParams.value[bodyParams.value.length - 1].value !== "")
|
||||
)
|
||||
addBodyParam()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (!bodyParams.value?.length) {
|
||||
addBodyParam()
|
||||
}
|
||||
const setRequestAttachment = (
|
||||
index: number,
|
||||
entry: FormDataKeyValue,
|
||||
event: InputEvent
|
||||
) => {
|
||||
// check if file exists or not
|
||||
if ((event.target as HTMLInputElement).files?.length === 0) {
|
||||
updateFormDataEntry(index, {
|
||||
...entry,
|
||||
isFile: false,
|
||||
value: "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
bodyParams,
|
||||
addBodyParam,
|
||||
updateBodyParam,
|
||||
deleteBodyParam,
|
||||
clearContent,
|
||||
setRequestAttachment,
|
||||
chipDelete,
|
||||
}
|
||||
const fileEntry: FormDataKeyValue = {
|
||||
...entry,
|
||||
isFile: true,
|
||||
value: Array.from((event.target as HTMLInputElement).files!),
|
||||
}
|
||||
updateFormDataEntry(index, fileEntry)
|
||||
}
|
||||
|
||||
watch(
|
||||
bodyParams,
|
||||
() => {
|
||||
if (
|
||||
bodyParams.value.length > 0 &&
|
||||
(bodyParams.value[bodyParams.value.length - 1].key !== "" ||
|
||||
bodyParams.value[bodyParams.value.length - 1].value !== "")
|
||||
)
|
||||
addBodyParam()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (!bodyParams.value?.length) {
|
||||
addBodyParam()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -278,8 +242,7 @@ export default defineComponent({
|
||||
|
||||
.file-chips-wrapper {
|
||||
@apply flex;
|
||||
@apply px-4;
|
||||
@apply py-1;
|
||||
@apply p-1;
|
||||
@apply w-0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,41 +7,62 @@
|
||||
<template #body>
|
||||
<div class="flex flex-col px-2">
|
||||
<label for="requestType" class="px-4 pb-4">
|
||||
{{ $t("request.choose_language") }}
|
||||
{{ t("request.choose_language") }}
|
||||
</label>
|
||||
<tippy ref="options" interactive trigger="click" theme="popover" arrow>
|
||||
<template #trigger>
|
||||
<span class="select-wrapper">
|
||||
<ButtonSecondary
|
||||
:label="codegens.find((x) => x.id === codegenType).name"
|
||||
:label="
|
||||
CodegenDefinitions.find((x) => x.name === codegenType).caption
|
||||
"
|
||||
outline
|
||||
class="flex-1 pr-8"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<SmartItem
|
||||
v-for="(gen, index) in codegens"
|
||||
:key="`gen-${index}`"
|
||||
:label="gen.name"
|
||||
:info-icon="gen.id === codegenType ? 'done' : ''"
|
||||
:active-info-icon="gen.id === codegenType"
|
||||
@click.native="
|
||||
() => {
|
||||
codegenType = gen.id
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="sticky top-0">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full p-4 py-2 !bg-popover input"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<SmartItem
|
||||
v-for="codegen in filteredCodegenDefinitions"
|
||||
:key="codegen.name"
|
||||
:label="codegen.caption"
|
||||
:info-icon="codegen.name === codegenType ? 'done' : ''"
|
||||
:active-info-icon="codegen.name === codegenType"
|
||||
@click.native="
|
||||
() => {
|
||||
codegenType = codegen.name
|
||||
options.tippy().hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</tippy>
|
||||
<div class="flex flex-1 justify-between">
|
||||
<div class="flex justify-between flex-1">
|
||||
<label for="generatedCode" class="p-4">
|
||||
{{ t("request.generated_code") }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="codegenType"
|
||||
v-if="errorState"
|
||||
class="bg-primaryLight rounded font-mono w-full py-2 px-4 text-red-400 overflow-auto whitespace-normal"
|
||||
>
|
||||
{{ t("error.something_went_wrong") }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="codegenType"
|
||||
ref="generatedCode"
|
||||
class="border border-dividerLight rounded"
|
||||
class="border rounded border-dividerLight"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -62,13 +83,25 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useContext, watch } from "@nuxtjs/composition-api"
|
||||
import { codegens, generateCodegenContext } from "~/helpers/codegen/codegen"
|
||||
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { makeRESTRequest } from "@hoppscotch/data"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { getEffectiveRESTRequest } from "~/helpers/utils/EffectiveURL"
|
||||
import { getCurrentEnvironment } from "~/newstore/environments"
|
||||
import {
|
||||
getEffectiveRESTRequest,
|
||||
resolvesEnvsInBody,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { Environment, getAggregateEnvs } from "~/newstore/environments"
|
||||
import { getRESTRequest } from "~/newstore/RESTSession"
|
||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||
import {
|
||||
CodegenDefinitions,
|
||||
CodegenName,
|
||||
generateCode,
|
||||
} from "~/helpers/new-codegen"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
@@ -78,27 +111,49 @@ const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const toast = useToast()
|
||||
|
||||
const options = ref<any | null>(null)
|
||||
|
||||
const request = ref(getRESTRequest())
|
||||
const codegenType = ref("curl")
|
||||
const codegenType = ref<CodegenName>("shell-curl")
|
||||
const copyIcon = ref("copy")
|
||||
const errorState = ref(false)
|
||||
|
||||
const requestCode = computed(() => {
|
||||
const effectiveRequest = getEffectiveRESTRequest(
|
||||
request.value as any,
|
||||
getCurrentEnvironment()
|
||||
const aggregateEnvs = getAggregateEnvs()
|
||||
const env: Environment = {
|
||||
name: "Env",
|
||||
variables: aggregateEnvs,
|
||||
}
|
||||
const effectiveRequest = getEffectiveRESTRequest(request.value, env)
|
||||
|
||||
if (!props.show) return ""
|
||||
|
||||
const result = generateCode(
|
||||
codegenType.value,
|
||||
makeRESTRequest({
|
||||
...effectiveRequest,
|
||||
body: resolvesEnvsInBody(effectiveRequest.body, env),
|
||||
headers: effectiveRequest.effectiveFinalHeaders.map((header) => ({
|
||||
...header,
|
||||
active: true,
|
||||
})),
|
||||
params: effectiveRequest.effectiveFinalParams.map((param) => ({
|
||||
...param,
|
||||
active: true,
|
||||
})),
|
||||
endpoint: effectiveRequest.effectiveFinalURL,
|
||||
})
|
||||
)
|
||||
|
||||
return codegens
|
||||
.find((x) => x.id === codegenType.value)!
|
||||
.generator(generateCodegenContext(effectiveRequest))
|
||||
if (O.isSome(result)) {
|
||||
errorState.value = false
|
||||
return result.value
|
||||
} else {
|
||||
errorState.value = true
|
||||
return ""
|
||||
}
|
||||
})
|
||||
|
||||
const generatedCode = ref<any | null>(null)
|
||||
@@ -110,6 +165,7 @@ useCodemirror(generatedCode, requestCode, {
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -126,9 +182,15 @@ const hideModal = () => emit("hide-modal")
|
||||
const copyRequestCode = () => {
|
||||
copyToClipboard(requestCode.value)
|
||||
copyIcon.value = "check"
|
||||
$toast.success(`${t("state.copied_to_clipboard")}`, {
|
||||
icon: "content_paste",
|
||||
})
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
||||
}
|
||||
|
||||
const searchQuery = ref("")
|
||||
|
||||
const filteredCodegenDefinitions = computed(() => {
|
||||
return CodegenDefinitions.filter((obj) =>
|
||||
Object.values(obj).some((val) => val.includes(searchQuery.value))
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
<template>
|
||||
<AppSection label="headers">
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperSecondaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ $t("request.header_list") }}
|
||||
{{ t("request.header_list") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/headers"
|
||||
blank
|
||||
:title="$t('app.wiki')"
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.clear_all')"
|
||||
:title="t('action.clear_all')"
|
||||
svg="trash-2"
|
||||
@click.native="bulkMode ? clearBulkEditor() : clearContent()"
|
||||
@click.native="clearContent()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('state.bulk_mode')"
|
||||
:title="t('state.bulk_mode')"
|
||||
svg="edit"
|
||||
:class="{ '!text-accent': bulkMode }"
|
||||
@click.native="bulkMode = !bulkMode"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('add.new')"
|
||||
:title="t('add.new')"
|
||||
svg="plus"
|
||||
:disabled="bulkMode"
|
||||
@click.native="addHeader"
|
||||
@@ -49,12 +39,12 @@
|
||||
<div v-if="bulkMode" ref="bulkEditor"></div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="(header, index) in headers$"
|
||||
v-for="(header, index) in workingHeaders"
|
||||
:key="`header-${index}`"
|
||||
class="divide-x divide-dividerLight border-b border-dividerLight flex"
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
||||
>
|
||||
<SmartAutoComplete
|
||||
:placeholder="`${$t('count.header', { count: index + 1 })}`"
|
||||
:placeholder="`${t('count.header', { count: index + 1 })}`"
|
||||
:source="commonHeaders"
|
||||
:spellcheck="false"
|
||||
:value="header.key"
|
||||
@@ -67,7 +57,7 @@
|
||||
px-4
|
||||
truncate
|
||||
"
|
||||
class="!flex flex-1"
|
||||
class="flex-1 !flex"
|
||||
@input="
|
||||
updateHeader(index, {
|
||||
key: $event,
|
||||
@@ -78,7 +68,7 @@
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="header.value"
|
||||
:placeholder="`${$t('count.value', { count: index + 1 })}`"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
styles="
|
||||
bg-transparent
|
||||
flex
|
||||
@@ -100,9 +90,9 @@
|
||||
:title="
|
||||
header.hasOwnProperty('active')
|
||||
? header.active
|
||||
? $t('action.turn_off')
|
||||
: $t('action.turn_on')
|
||||
: $t('action.turn_off')
|
||||
? t('action.turn_off')
|
||||
: t('action.turn_on')
|
||||
: t('action.turn_off')
|
||||
"
|
||||
:svg="
|
||||
header.hasOwnProperty('active')
|
||||
@@ -116,9 +106,7 @@
|
||||
updateHeader(index, {
|
||||
key: header.key,
|
||||
value: header.value,
|
||||
active: header.hasOwnProperty('active')
|
||||
? !header.active
|
||||
: false,
|
||||
active: !header.active,
|
||||
})
|
||||
"
|
||||
/>
|
||||
@@ -126,7 +114,7 @@
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.remove')"
|
||||
:title="t('action.remove')"
|
||||
svg="trash"
|
||||
color="red"
|
||||
@click.native="deleteHeader(index)"
|
||||
@@ -134,68 +122,49 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="headers$.length === 0"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
v-if="workingHeaders.length === 0"
|
||||
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_category.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.headers')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.headers')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
{{ $t("empty.headers") }}
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.headers") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
filled
|
||||
:label="`${$t('add.new')}`"
|
||||
:label="`${t('add.new')}`"
|
||||
svg="plus"
|
||||
class="mb-4"
|
||||
@click.native="addHeader"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUpdate, ref, useContext, watch } from "@nuxtjs/composition-api"
|
||||
import { Ref, ref, watch } from "@nuxtjs/composition-api"
|
||||
import isEqual from "lodash/isEqual"
|
||||
import clone from "lodash/clone"
|
||||
import { HoppRESTHeader } from "@hoppscotch/data"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import {
|
||||
addRESTHeader,
|
||||
deleteAllRESTHeaders,
|
||||
deleteRESTHeader,
|
||||
restHeaders$,
|
||||
setRESTHeaders,
|
||||
updateRESTHeader,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { restHeaders$, setRESTHeaders } from "~/newstore/RESTSession"
|
||||
import { commonHeaders } from "~/helpers/headers"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest"
|
||||
import { useI18n, useStream, useToast } from "~/helpers/utils/composables"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const bulkMode = ref(false)
|
||||
const bulkHeaders = ref("")
|
||||
const bulkEditor = ref<any | null>(null)
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
useCodemirror(bulkEditor, bulkHeaders, {
|
||||
extendedEditorConfig: {
|
||||
mode: "text/x-yaml",
|
||||
@@ -203,98 +172,168 @@ useCodemirror(bulkEditor, bulkHeaders, {
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: true,
|
||||
})
|
||||
|
||||
// The functional headers list (the headers actually in the system)
|
||||
const headers = useStream(restHeaders$, [], setRESTHeaders) as Ref<
|
||||
HoppRESTHeader[]
|
||||
>
|
||||
|
||||
// The UI representation of the headers list (has the empty end header)
|
||||
const workingHeaders = ref<HoppRESTHeader[]>([
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
|
||||
// Rule: Working Headers always have one empty header or the last element is always an empty header
|
||||
watch(workingHeaders, (headersList) => {
|
||||
if (
|
||||
headersList.length > 0 &&
|
||||
headersList[headersList.length - 1].key !== ""
|
||||
) {
|
||||
workingHeaders.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Sync logic between headers and working headers
|
||||
watch(
|
||||
headers,
|
||||
(newHeadersList) => {
|
||||
// Sync should overwrite working headers
|
||||
const filteredWorkingHeaders = workingHeaders.value.filter(
|
||||
(e) => e.key !== ""
|
||||
)
|
||||
|
||||
if (!isEqual(newHeadersList, filteredWorkingHeaders)) {
|
||||
workingHeaders.value = newHeadersList
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(workingHeaders, (newWorkingHeaders) => {
|
||||
const fixedHeaders = newWorkingHeaders.filter((e) => e.key !== "")
|
||||
if (!isEqual(headers.value, fixedHeaders)) {
|
||||
headers.value = fixedHeaders
|
||||
}
|
||||
})
|
||||
|
||||
// Bulk Editor Syncing with Working Headers
|
||||
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("//"),
|
||||
}))
|
||||
setRESTHeaders(transformation)
|
||||
const transformation = bulkHeaders.value
|
||||
.split("\n")
|
||||
.filter((x) => x.trim().length > 0 && x.includes(":"))
|
||||
.map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
const filteredHeaders = workingHeaders.value.filter((x) => x.key !== "")
|
||||
|
||||
if (!isEqual(filteredHeaders, transformation)) {
|
||||
workingHeaders.value = transformation
|
||||
}
|
||||
} catch (e) {
|
||||
$toast.error(`${t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
const headers$ = useReadonlyStream(restHeaders$, [])
|
||||
watch(workingHeaders, (newHeadersList) => {
|
||||
// If we are in bulk mode, don't apply direct changes
|
||||
if (bulkMode.value) return
|
||||
|
||||
watch(
|
||||
headers$,
|
||||
(newValue) => {
|
||||
if (
|
||||
(newValue[newValue.length - 1]?.key !== "" ||
|
||||
newValue[newValue.length - 1]?.value !== "") &&
|
||||
newValue.length
|
||||
)
|
||||
addHeader()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
try {
|
||||
const currentBulkHeaders = bulkHeaders.value.split("\n").map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
onBeforeUpdate(() => editBulkHeadersLine(-1, null))
|
||||
const filteredHeaders = newHeadersList.filter((x) => x.key !== "")
|
||||
|
||||
const editBulkHeadersLine = (index: number, item?: HoppRESTHeader | null) => {
|
||||
const headers = headers$.value
|
||||
|
||||
bulkHeaders.value = headers
|
||||
.reduce((all, header, pIndex) => {
|
||||
const current =
|
||||
index === pIndex && item != null
|
||||
? `${item.active ? "" : "//"}${item.key}: ${item.value}`
|
||||
: `${header.active ? "" : "//"}${header.key}: ${header.value}`
|
||||
return [...all, current]
|
||||
}, [])
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
const clearBulkEditor = () => {
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
if (!isEqual(currentBulkHeaders, filteredHeaders)) {
|
||||
bulkHeaders.value = filteredHeaders
|
||||
.map((header) => {
|
||||
return `${header.active ? "" : "#"}${header.key}: ${header.value}`
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
const addHeader = () => {
|
||||
const empty = { key: "", value: "", active: true }
|
||||
const index = headers$.value.length
|
||||
|
||||
addRESTHeader(empty)
|
||||
editBulkHeadersLine(index, empty)
|
||||
workingHeaders.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
const updateHeader = (index: number, item: HoppRESTHeader) => {
|
||||
updateRESTHeader(index, item)
|
||||
editBulkHeadersLine(index, item)
|
||||
const updateHeader = (index: number, header: HoppRESTHeader) => {
|
||||
workingHeaders.value = workingHeaders.value.map((h, i) =>
|
||||
i === index ? header : h
|
||||
)
|
||||
}
|
||||
|
||||
const deleteHeader = (index: number) => {
|
||||
const headersBeforeDeletion = headers$.value
|
||||
const headersBeforeDeletion = clone(workingHeaders.value)
|
||||
|
||||
deleteRESTHeader(index)
|
||||
editBulkHeadersLine(index, null)
|
||||
if (
|
||||
!(
|
||||
headersBeforeDeletion.length > 0 &&
|
||||
index === headersBeforeDeletion.length - 1
|
||||
)
|
||||
) {
|
||||
if (deletionToast.value) {
|
||||
deletionToast.value.goAway(0)
|
||||
deletionToast.value = null
|
||||
}
|
||||
|
||||
const deletedItem = headersBeforeDeletion[index]
|
||||
if (deletedItem.key || deletedItem.value) {
|
||||
$toast.success(t("state.deleted").toString(), {
|
||||
icon: "delete",
|
||||
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||
action: [
|
||||
{
|
||||
text: t("action.undo").toString(),
|
||||
text: `${t("action.undo")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setRESTHeaders(headersBeforeDeletion as HoppRESTHeader[])
|
||||
editBulkHeadersLine(index, deletedItem)
|
||||
workingHeaders.value = headersBeforeDeletion
|
||||
toastObject.goAway(0)
|
||||
deletionToast.value = null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onComplete: () => {
|
||||
deletionToast.value = null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
workingHeaders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
deleteAllRESTHeaders()
|
||||
clearBulkEditor()
|
||||
// set headers list to the initial state
|
||||
workingHeaders.value = [
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<template>
|
||||
<SmartModal v-if="show" :title="`${$t('import.curl')}`" @close="hideModal">
|
||||
<SmartModal v-if="show" :title="`${t('import.curl')}`" @close="hideModal">
|
||||
<template #body>
|
||||
<div class="flex flex-col px-2">
|
||||
<div ref="curlEditor" class="border border-dividerLight rounded"></div>
|
||||
<div class="px-2 h-46">
|
||||
<div
|
||||
ref="curlEditor"
|
||||
class="h-full border rounded border-dividerLight"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex">
|
||||
<ButtonPrimary
|
||||
:label="`${$t('import.title')}`"
|
||||
ref="importButton"
|
||||
:label="`${t('import.title')}`"
|
||||
@click.native="handleImport"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('action.cancel')}`"
|
||||
:label="`${t('action.cancel')}`"
|
||||
@click.native="hideModal"
|
||||
/>
|
||||
</span>
|
||||
@@ -21,26 +25,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useContext } from "@nuxtjs/composition-api"
|
||||
import parseCurlCommand from "~/helpers/curlparser"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { ref, watch } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
makeRESTRequest,
|
||||
} from "~/helpers/types/HoppRESTRequest"
|
||||
} from "@hoppscotch/data"
|
||||
import parseCurlCommand from "~/helpers/curlparser"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const curl = ref("")
|
||||
|
||||
const curlEditor = ref<any | null>(null)
|
||||
|
||||
const props = defineProps<{ show: boolean; text: string }>()
|
||||
|
||||
useCodemirror(curlEditor, curl, {
|
||||
extendedEditorConfig: {
|
||||
mode: "application/x-sh",
|
||||
@@ -48,9 +53,18 @@ useCodemirror(curlEditor, curl, {
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
|
||||
defineProps<{ show: boolean }>()
|
||||
watch(
|
||||
() => props.show,
|
||||
() => {
|
||||
if (props.show) {
|
||||
curl.value = props.text.toString()
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
@@ -70,6 +84,7 @@ const handleImport = () => {
|
||||
const endpoint = origin + pathname
|
||||
const headers: HoppRESTHeader[] = []
|
||||
const params: HoppRESTParam[] = []
|
||||
const body = parsedCurl.body
|
||||
if (parsedCurl.query) {
|
||||
for (const key of Object.keys(parsedCurl.query)) {
|
||||
const val = parsedCurl.query[key]!
|
||||
@@ -100,6 +115,7 @@ const handleImport = () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const method = parsedCurl.method.toUpperCase()
|
||||
|
||||
setRESTRequest(
|
||||
@@ -117,15 +133,13 @@ const handleImport = () => {
|
||||
},
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
body: "",
|
||||
body,
|
||||
},
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
$toast.error(`${t("error.curl_invalid_format")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
toast.error(`${t("error.curl_invalid_format")}`)
|
||||
}
|
||||
hideModal()
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<input
|
||||
id="oidcDiscoveryURL"
|
||||
v-model="oidcDiscoveryURL"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
placeholder="OpenID Connect Discovery URL"
|
||||
name="oidcDiscoveryURL"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<input
|
||||
id="authURL"
|
||||
v-model="authURL"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
placeholder="Authentication URL"
|
||||
name="authURL"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<input
|
||||
id="accessTokenURL"
|
||||
v-model="accessTokenURL"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
placeholder="Access Token URL"
|
||||
name="accessTokenURL"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<input
|
||||
id="clientID"
|
||||
v-model="clientID"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
placeholder="Client ID"
|
||||
name="clientID"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<input
|
||||
id="scope"
|
||||
v-model="scope"
|
||||
class="bg-transparent flex flex-1 py-2 px-4"
|
||||
class="flex flex-1 px-4 py-2 bg-transparent"
|
||||
placeholder="Scope"
|
||||
name="scope"
|
||||
/>
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="p-2">
|
||||
<ButtonSecondary
|
||||
filled
|
||||
:label="`${$t('authorization.generate_token')}`"
|
||||
:label="`${t('authorization.generate_token')}`"
|
||||
@click.native="handleAccessTokenRequest()"
|
||||
/>
|
||||
</div>
|
||||
@@ -56,19 +56,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { pluckRef, useStream } from "~/helpers/utils/composables"
|
||||
import { HoppRESTAuthOAuth2 } from "~/helpers/types/HoppRESTAuth"
|
||||
import { Ref } from "@nuxtjs/composition-api"
|
||||
import { HoppRESTAuthOAuth2 } from "@hoppscotch/data"
|
||||
import {
|
||||
pluckRef,
|
||||
useI18n,
|
||||
useStream,
|
||||
useToast,
|
||||
} from "~/helpers/utils/composables"
|
||||
import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
|
||||
import { tokenRequest } from "~/helpers/oauth"
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const $t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const auth = useStream(
|
||||
restAuth$,
|
||||
@@ -97,9 +99,7 @@ export default {
|
||||
oidcDiscoveryURL.value === "" &&
|
||||
(authURL.value === "" || accessTokenURL.value === "")
|
||||
) {
|
||||
$toast.error(`${$t("complete_config_urls")}`, {
|
||||
icon: "error",
|
||||
})
|
||||
toast.error(`${t("complete_config_urls")}`)
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -113,9 +113,7 @@ export default {
|
||||
}
|
||||
await tokenRequest(tokenReqParams)
|
||||
} catch (e) {
|
||||
$toast.error(`${e}`, {
|
||||
icon: "code",
|
||||
})
|
||||
toast.error(`${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +124,7 @@ export default {
|
||||
clientID,
|
||||
scope,
|
||||
handleAccessTokenRequest,
|
||||
t,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
<template>
|
||||
<AppSection label="parameters">
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperSecondaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ $t("request.parameter_list") }}
|
||||
{{ t("request.parameter_list") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/parameters"
|
||||
blank
|
||||
:title="$t('app.wiki')"
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.clear_all')"
|
||||
:title="t('action.clear_all')"
|
||||
svg="trash-2"
|
||||
@click.native="bulkMode ? clearBulkEditor() : clearContent()"
|
||||
@click.native="clearContent()"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('state.bulk_mode')"
|
||||
:title="t('state.bulk_mode')"
|
||||
svg="edit"
|
||||
:class="{ '!text-accent': bulkMode }"
|
||||
@click.native="bulkMode = !bulkMode"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('add.new')"
|
||||
:title="t('add.new')"
|
||||
svg="plus"
|
||||
:disabled="bulkMode"
|
||||
@click.native="addParam"
|
||||
@@ -49,13 +39,13 @@
|
||||
<div v-if="bulkMode" ref="bulkEditor"></div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="(param, index) in params$"
|
||||
v-for="(param, index) in workingParams"
|
||||
:key="`param-${index}`"
|
||||
class="divide-x divide-dividerLight border-b border-dividerLight flex"
|
||||
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
||||
>
|
||||
<SmartEnvInput
|
||||
v-model="param.key"
|
||||
:placeholder="`${$t('count.parameter', { count: index + 1 })}`"
|
||||
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
|
||||
styles="
|
||||
bg-transparent
|
||||
flex
|
||||
@@ -73,7 +63,7 @@
|
||||
/>
|
||||
<SmartEnvInput
|
||||
v-model="param.value"
|
||||
:placeholder="`${$t('count.value', { count: index + 1 })}`"
|
||||
:placeholder="`${t('count.value', { count: index + 1 })}`"
|
||||
styles="
|
||||
bg-transparent
|
||||
flex
|
||||
@@ -95,9 +85,9 @@
|
||||
:title="
|
||||
param.hasOwnProperty('active')
|
||||
? param.active
|
||||
? $t('action.turn_off')
|
||||
: $t('action.turn_on')
|
||||
: $t('action.turn_off')
|
||||
? t('action.turn_off')
|
||||
: t('action.turn_on')
|
||||
: t('action.turn_off')
|
||||
"
|
||||
:svg="
|
||||
param.hasOwnProperty('active')
|
||||
@@ -119,7 +109,7 @@
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.remove')"
|
||||
:title="t('action.remove')"
|
||||
svg="trash"
|
||||
color="red"
|
||||
@click.native="deleteParam(index)"
|
||||
@@ -127,33 +117,20 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="params$.length === 0"
|
||||
class="
|
||||
flex flex-col
|
||||
text-secondaryLight
|
||||
p-4
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
v-if="workingParams.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${$colorMode.value}/add_files.svg`"
|
||||
loading="lazy"
|
||||
class="
|
||||
flex-col
|
||||
my-4
|
||||
object-contain object-center
|
||||
h-16
|
||||
w-16
|
||||
inline-flex
|
||||
"
|
||||
:alt="$t('empty.parameters')"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||
:alt="`${t('empty.parameters')}`"
|
||||
/>
|
||||
<span class="text-center pb-4">
|
||||
{{ $t("empty.parameters") }}
|
||||
<span class="pb-4 text-center">
|
||||
{{ t("empty.parameters") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
:label="`${$t('add.new')}`"
|
||||
:label="`${t('add.new')}`"
|
||||
svg="plus"
|
||||
filled
|
||||
class="mb-4"
|
||||
@@ -161,47 +138,26 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useContext, watch, onBeforeUpdate } from "@nuxtjs/composition-api"
|
||||
import { ref, watch } from "@nuxtjs/composition-api"
|
||||
import { HoppRESTParam } from "@hoppscotch/data"
|
||||
import isEqual from "lodash/isEqual"
|
||||
import clone from "lodash/clone"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import { HoppRESTParam } from "~/helpers/types/HoppRESTRequest"
|
||||
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
restParams$,
|
||||
addRESTParam,
|
||||
updateRESTParam,
|
||||
deleteRESTParam,
|
||||
deleteAllRESTParams,
|
||||
setRESTParams,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { useI18n, useToast, useStream } from "~/helpers/utils/composables"
|
||||
import { restParams$, setRESTParams } from "~/newstore/RESTSession"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const bulkMode = ref(false)
|
||||
const bulkParams = ref("")
|
||||
|
||||
watch(bulkParams, () => {
|
||||
try {
|
||||
const transformation = bulkParams.value.split("\n").map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trim(),
|
||||
active: !item.trim().startsWith("//"),
|
||||
}))
|
||||
setRESTParams(transformation)
|
||||
} catch (e) {
|
||||
$toast.error(`${t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
const bulkEditor = ref<any | null>(null)
|
||||
|
||||
@@ -212,82 +168,163 @@ useCodemirror(bulkEditor, bulkParams, {
|
||||
},
|
||||
linter: null,
|
||||
completer: null,
|
||||
environmentHighlights: true,
|
||||
})
|
||||
|
||||
const params$ = useReadonlyStream(restParams$, [])
|
||||
// The functional parameters list (the parameters actually applied to the session)
|
||||
const params = useStream(restParams$, [], setRESTParams)
|
||||
|
||||
watch(
|
||||
params$,
|
||||
(newValue) => {
|
||||
if (
|
||||
(newValue[newValue.length - 1]?.key !== "" ||
|
||||
newValue[newValue.length - 1]?.value !== "") &&
|
||||
newValue.length
|
||||
)
|
||||
addParam()
|
||||
// The UI representation of the parameters list (has the empty end param)
|
||||
const workingParams = ref<HoppRESTParam[]>([
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
{ deep: true }
|
||||
])
|
||||
|
||||
// Rule: Working Params always have last element is always an empty param
|
||||
watch(workingParams, (paramsList) => {
|
||||
if (paramsList.length > 0 && paramsList[paramsList.length - 1].key !== "") {
|
||||
workingParams.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Sync logic between params and working params
|
||||
watch(
|
||||
params,
|
||||
(newParamsList) => {
|
||||
// Sync should overwrite working params
|
||||
const filteredWorkingParams = workingParams.value.filter(
|
||||
(e) => e.key !== ""
|
||||
)
|
||||
|
||||
if (!isEqual(newParamsList, filteredWorkingParams)) {
|
||||
workingParams.value = newParamsList
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onBeforeUpdate(() => editBulkParamsLine(-1, null))
|
||||
watch(workingParams, (newWorkingParams) => {
|
||||
const fixedParams = newWorkingParams.filter((e) => e.key !== "")
|
||||
if (!isEqual(params.value, fixedParams)) {
|
||||
params.value = fixedParams
|
||||
}
|
||||
})
|
||||
|
||||
const editBulkParamsLine = (index: number, item?: HoppRESTParam | null) => {
|
||||
const params = params$.value
|
||||
// Bulk Editor Syncing with Working Params
|
||||
watch(bulkParams, () => {
|
||||
try {
|
||||
const transformation = bulkParams.value
|
||||
.split("\n")
|
||||
.filter((x) => x.trim().length > 0 && x.includes(":"))
|
||||
.map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
bulkParams.value = params
|
||||
.reduce((all, param, pIndex) => {
|
||||
const current =
|
||||
index === pIndex && item != null
|
||||
? `${item.active ? "" : "//"}${item.key}: ${item.value}`
|
||||
: `${param.active ? "" : "//"}${param.key}: ${param.value}`
|
||||
return [...all, current]
|
||||
}, [])
|
||||
.join("\n")
|
||||
}
|
||||
const filteredParams = workingParams.value.filter((x) => x.key !== "")
|
||||
|
||||
const clearBulkEditor = () => {
|
||||
bulkParams.value = ""
|
||||
}
|
||||
if (!isEqual(filteredParams, transformation)) {
|
||||
workingParams.value = transformation
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
watch(workingParams, (newParamsList) => {
|
||||
// If we are in bulk mode, don't apply direct changes
|
||||
if (bulkMode.value) return
|
||||
|
||||
try {
|
||||
const currentBulkParams = bulkParams.value.split("\n").map((item) => ({
|
||||
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
|
||||
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||
active: !item.trim().startsWith("#"),
|
||||
}))
|
||||
|
||||
const filteredParams = newParamsList.filter((x) => x.key !== "")
|
||||
|
||||
if (!isEqual(currentBulkParams, filteredParams)) {
|
||||
bulkParams.value = filteredParams
|
||||
.map((param) => {
|
||||
return `${param.active ? "" : "#"}${param.key}: ${param.value}`
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
const addParam = () => {
|
||||
const empty = { key: "", value: "", active: true }
|
||||
const index = params$.value.length
|
||||
|
||||
addRESTParam(empty)
|
||||
editBulkParamsLine(index, empty)
|
||||
workingParams.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
})
|
||||
}
|
||||
|
||||
const updateParam = (index: number, item: HoppRESTParam) => {
|
||||
updateRESTParam(index, item)
|
||||
editBulkParamsLine(index, item)
|
||||
const updateParam = (index: number, param: HoppRESTParam) => {
|
||||
workingParams.value = workingParams.value.map((h, i) =>
|
||||
i === index ? param : h
|
||||
)
|
||||
}
|
||||
|
||||
const deleteParam = (index: number) => {
|
||||
const parametersBeforeDeletion = params$.value
|
||||
const paramsBeforeDeletion = clone(workingParams.value)
|
||||
|
||||
deleteRESTParam(index)
|
||||
editBulkParamsLine(index, null)
|
||||
if (
|
||||
!(
|
||||
paramsBeforeDeletion.length > 0 &&
|
||||
index === paramsBeforeDeletion.length - 1
|
||||
)
|
||||
) {
|
||||
if (deletionToast.value) {
|
||||
deletionToast.value.goAway(0)
|
||||
deletionToast.value = null
|
||||
}
|
||||
|
||||
const deletedItem = parametersBeforeDeletion[index]
|
||||
if (deletedItem.key || deletedItem.value) {
|
||||
$toast.success(t("state.deleted").toString(), {
|
||||
icon: "delete",
|
||||
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||
action: [
|
||||
{
|
||||
text: t("action.undo").toString(),
|
||||
text: `${t("action.undo")}`,
|
||||
onClick: (_, toastObject) => {
|
||||
setRESTParams(parametersBeforeDeletion as HoppRESTParam[])
|
||||
editBulkParamsLine(index, deletedItem)
|
||||
workingParams.value = paramsBeforeDeletion
|
||||
toastObject.goAway(0)
|
||||
deletionToast.value = null
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
onComplete: () => {
|
||||
deletionToast.value = null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
workingParams.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
deleteAllRESTParams()
|
||||
clearBulkEditor()
|
||||
// set params list to the initial state
|
||||
workingParams.value = [
|
||||
{
|
||||
key: "",
|
||||
value: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
bulkParams.value = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,71 +1,51 @@
|
||||
<template>
|
||||
<AppSection id="script" :label="`${$t('preRequest.script')}`">
|
||||
<div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
border-b border-dividerLight
|
||||
flex flex-1
|
||||
top-upperSecondaryStickyFold
|
||||
pl-4
|
||||
z-10
|
||||
sticky
|
||||
items-center
|
||||
justify-between
|
||||
"
|
||||
class="sticky z-10 flex items-center justify-between flex-1 pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ $t("preRequest.javascript_code") }}
|
||||
{{ t("preRequest.javascript_code") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/features/pre-request-script"
|
||||
blank
|
||||
:title="$t('app.wiki')"
|
||||
:title="t('app.wiki')"
|
||||
svg="help-circle"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('state.linewrap')"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': linewrapEnabled }"
|
||||
svg="corner-down-left"
|
||||
svg="wrap-text"
|
||||
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="$t('action.clear')"
|
||||
:title="t('action.clear')"
|
||||
svg="trash-2"
|
||||
@click.native="clearContent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-b border-dividerLight flex">
|
||||
<div class="border-r border-dividerLight w-2/3">
|
||||
<div ref="preRrequestEditor"></div>
|
||||
<div class="flex border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div ref="preRrequestEditor" class="h-full"></div>
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
bg-primary
|
||||
h-full
|
||||
top-upperTertiaryStickyFold
|
||||
min-w-46
|
||||
max-w-1/3
|
||||
p-4
|
||||
z-9
|
||||
sticky
|
||||
overflow-auto
|
||||
"
|
||||
class="sticky h-full p-4 overflow-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||
>
|
||||
<div class="text-secondaryLight pb-2">
|
||||
{{ $t("helpers.pre_request_script") }}
|
||||
<div class="pb-2 text-secondaryLight">
|
||||
{{ t("helpers.pre_request_script") }}
|
||||
</div>
|
||||
<SmartAnchor
|
||||
:label="`${$t('preRequest.learn')}`"
|
||||
:label="`${t('preRequest.learn')}`"
|
||||
to="https://docs.hoppscotch.io/features/pre-request-script"
|
||||
blank
|
||||
/>
|
||||
<h4 class="font-bold text-secondaryLight pt-6">
|
||||
{{ $t("preRequest.snippets") }}
|
||||
<h4 class="pt-6 font-bold text-secondaryLight">
|
||||
{{ t("preRequest.snippets") }}
|
||||
</h4>
|
||||
<div class="flex flex-col pt-4">
|
||||
<TabSecondary
|
||||
@@ -78,21 +58,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { reactive, ref } from "@nuxtjs/composition-api"
|
||||
import { usePreRequestScript } from "~/newstore/RESTSession"
|
||||
import snippets from "~/helpers/preRequestScriptSnippets"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
import linter from "~/helpers/editor/linting/preRequest"
|
||||
import completer from "~/helpers/editor/completion/preRequest"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
const {
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
const t = useI18n()
|
||||
|
||||
const preRequestScript = usePreRequestScript()
|
||||
|
||||
@@ -110,6 +88,7 @@ useCodemirror(
|
||||
},
|
||||
linter,
|
||||
completer,
|
||||
environmentHighlights: false,
|
||||
})
|
||||
)
|
||||
|
||||
|
||||