Merge branch 'master' into i18n

This commit is contained in:
Liyas Thomas
2020-02-25 09:10:20 +05:30
committed by GitHub
56 changed files with 2968 additions and 1461 deletions

6
.gitignore vendored
View File

@@ -98,3 +98,9 @@ sw.*
# File explorer
.directory
# Tests screenshots
tests/*/screenshots
# Tests videos
tests/*/videos

View File

@@ -40,6 +40,7 @@ install:
- "npm install"
before_script:
- "npm run build" # use nuxt build and start to run tests
- "npm run test"
script:

View File

@@ -1,5 +1,85 @@
# Changelog
## [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)
**Implemented enhancements:**
- Disable SSL cert for websockets [\#557](https://github.com/liyasthomas/postwoman/issues/557)
- Feature request: Keyboard shortcuts for folder creation [\#539](https://github.com/liyasthomas/postwoman/issues/539)
- Friendly minded GraphQL [\#468](https://github.com/liyasthomas/postwoman/issues/468)
- multipart/form-data support [\#434](https://github.com/liyasthomas/postwoman/issues/434)
- 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)
- POST request body editor reacts to the content type [\#594](https://github.com/liyasthomas/postwoman/pull/594) ([AndrewBastin](https://github.com/AndrewBastin))
- 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))
- Postman collection parsing [\#574](https://github.com/liyasthomas/postwoman/pull/574) ([JacobAnavisca](https://github.com/JacobAnavisca))
- 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))
- Updated GraphQL Query Variable Editor [\#534](https://github.com/liyasthomas/postwoman/pull/534) ([AndrewBastin](https://github.com/AndrewBastin))
**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)
- 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)
- Add max-height and overflow: auto to "parameter list" textarea [\#532](https://github.com/liyasthomas/postwoman/issues/532)
- IE Support [\#386](https://github.com/liyasthomas/postwoman/issues/386)
- ⏬ Import a Postman's Collection [\#333](https://github.com/liyasthomas/postwoman/issues/333)
**Merged pull requests:**
- Fix variablesJSONString store default for GraphQL page [\#593](https://github.com/liyasthomas/postwoman/pull/593) ([dmitryyankowski](https://github.com/dmitryyankowski))
- 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))
- 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))
- 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))
- Updating spanish translation [\#529](https://github.com/liyasthomas/postwoman/pull/529) ([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))
## [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)
@@ -11,9 +91,12 @@
- 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)
- Add DB cache [\#26](https://github.com/liyasthomas/postwoman/issues/26)
- Update asset/\*. js to asset/\*. ts. [\#517](https://github.com/liyasthomas/postwoman/pull/517) ([Sn005](https://github.com/Sn005))
- 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))
- GraphQL query validation based on schema [\#508](https://github.com/liyasthomas/postwoman/pull/508) ([AndrewBastin](https://github.com/AndrewBastin))
- Syntax Error marking in GraphQL query editor [\#505](https://github.com/liyasthomas/postwoman/pull/505) ([AndrewBastin](https://github.com/AndrewBastin))
@@ -46,8 +129,8 @@
- 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))
- even merge [\#528](https://github.com/liyasthomas/postwoman/pull/528) ([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))
@@ -69,7 +152,6 @@
- ⬆️ 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))
- OAuth 2.0/OIDC Access Token Retrieval Support [\#476](https://github.com/liyasthomas/postwoman/pull/476) ([reefqi037](https://github.com/reefqi037))
- ⬆️ 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))
## [v1.5.0](https://github.com/liyasthomas/postwoman/tree/v1.5.0) (2020-01-04)
@@ -79,7 +161,6 @@
- Can WSDL be implemented, similar to SoapUI? [\#461](https://github.com/liyasthomas/postwoman/issues/461)
- Raw Request Body should be supported to format the JSON string [\#446](https://github.com/liyasthomas/postwoman/issues/446)
- cookie not found support [\#443](https://github.com/liyasthomas/postwoman/issues/443)
- Ability to send Binary data using Postwoman [\#415](https://github.com/liyasthomas/postwoman/issues/415)
- Custom request method [\#398](https://github.com/liyasthomas/postwoman/issues/398)
- \[request\]: CLI possibilities [\#363](https://github.com/liyasthomas/postwoman/issues/363)
@@ -112,7 +193,6 @@
- Two Way Data Binding \(v-model\) to Ace Editor component [\#379](https://github.com/liyasthomas/postwoman/pull/379) ([AndrewBastin](https://github.com/AndrewBastin))
- 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))
- refactor: minor improvements [\#343](https://github.com/liyasthomas/postwoman/pull/343) ([jamesgeorge007](https://github.com/jamesgeorge007))
**Fixed bugs:**
@@ -174,6 +254,7 @@
- ⬆️ 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))
@@ -360,6 +441,7 @@
- ⬆️ 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))
- 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))

View File

@@ -3,7 +3,7 @@
<br>
<br>
<p>
<b>A free, fast & beautiful API request builder</b>
<b>A free, fast and beautiful API request builder</b>
</p>
<p>
<i>Web alternative to Postman - Helps you create requests faster, saving precious time on development - <a href="https://postwoman.launchaco.com">Subscribe</a></i>
@@ -204,7 +204,7 @@ _**All `i18n` contributions are welcome to `i18n` [branch](https://github.com/li
- **[CLI β](https://github.com/postwoman-io/postwoman-cli)** - A CLI solution for Postwoman
- **Browser Extensions** - Browser extensions that simplifies access to Postwoman
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/postwoman) ([GitHub](https://github.com/AndrewBastin/postwoman-firefox)) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld) ([GitHub](https://github.com/AndrewBastin/postwoman-chrome))
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/postwoman) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/postwoman-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld) ([GitHub](https://github.com/AndrewBastin/postwoman-extension))
>**Extensions fixes `CORS` issues.**
@@ -315,7 +315,10 @@ See the [CHANGELOG](CHANGELOG.md) file for details.
### Lead Developers
* **[Liyas Thomas](https://github.com/liyasthomas)** - *Author*
* **[Caneco](https://twitter.com/caneco)** - *Designer*
* **[John Harker](https://github.com/NBTX)** - *Lead developer*
* **[Andrew Bastin](https://github.com/andrewbastin)** - *Lead developer*
* **[James George](https://github.com/jamesgeorge007)** - *Lead maintainer*
* **[Caneco](https://twitter.com/caneco)** - *Logo and banner designer*
### Testing and Debugging

View File

@@ -6,7 +6,9 @@
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
font-display: swap;
// Do not use font-display: swap for the icon font - it looks really bad when the page
// loads.
font-display: block;
src: url("~static/fonts/material-icons-v48.woff2") format("woff2");
}

View File

@@ -22,13 +22,11 @@ $responsiveWidth: 768px;
::-webkit-scrollbar {
width: 4px;
height: 4px;
border-radius: 4px;
background-color: var(--bg-light-color);
background-color: var(--bg-dark-color);
}
::-webkit-scrollbar-thumb {
background-color: var(--fg-light-color);
border-radius: 8px;
&:hover {
background-color: var(--fg-color);
@@ -60,9 +58,9 @@ body {
padding: 0;
margin: 0;
scroll-behavior: smooth;
transition: all 0.2s ease-in-out;
}
// Make theme transition smoother.
body.afterLoad {
transition: background-color 0.2s ease-in-out;
}
@@ -76,6 +74,10 @@ a {
color: inherit;
text-decoration: none;
transition: all 0.2s ease-in-out;
&.link {
color: var(--ac-color);
}
}
header,
@@ -92,7 +94,7 @@ footer {
.wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
flex-flow: column nowrap;
}
.wrapper .page {
@@ -146,7 +148,8 @@ footer {
z-index: 1;
height: 100vh;
padding: 0 8px;
background-color: var(--bg-light-color);
background-color: var(--bg-dark-color);
transition: all 0.2s ease-in-out;
}
.main {
@@ -173,6 +176,7 @@ nav.primary-nav {
svg {
fill: var(--fg-light-color);
transition: all 0.2s ease-in-out;
}
a {
@@ -186,7 +190,7 @@ nav.primary-nav {
color: var(--fg-light-color);
fill: var(--fg-light-color);
margin: 8px 0;
transition: all 0.2s ease-in-out;
height: 52px;
&:hover {
color: var(--fg-color);
@@ -246,7 +250,7 @@ nav.secondary-nav {
}
.footer {
flex-direction: column;
flex-flow: column nowrap;
}
h1,
@@ -263,11 +267,16 @@ hr {
border-bottom: 1px dashed var(--brd-color);
}
p {
transition: all 0.2s ease-in-out;
}
.tooltip {
$bgcolor: var(--tt-color);
$fgcolor: var(--fg-color);
display: block !important;
z-index: 10000;
transition: all 0.2s ease-in-out;
.tooltip-inner {
background: $bgcolor;
@@ -422,7 +431,6 @@ button {
color: var(--act-color);
fill: var(--act-color);
box-shadow: inset 0 0 0 2px var(--fg-color);
transition: all 0.2s ease-in-out;
}
&.icon {
@@ -437,7 +445,6 @@ button {
color: var(--fg-color);
fill: var(--fg-color);
box-shadow: none;
transition: all 0.2s ease-in-out;
}
}
@@ -472,8 +479,19 @@ button {
fieldset {
margin: 16px 0;
border-radius: 16px;
transition: all 0.2s ease-in-out;
background-color: var(--bg-dark-color);
transition: all 0.2s ease-in-out;
}
fieldset:target,
section:target {
animation: highlight 2s ease;
}
@keyframes highlight {
50% {
box-shadow: 0 0 0 2px var(--ac-color);
}
}
legend {
@@ -483,6 +501,7 @@ legend {
color: var(--fg-color);
font-weight: 700;
cursor: pointer;
transition: all 0.2s ease-in-out;
* {
vertical-align: middle;
@@ -538,6 +557,7 @@ input[type="radio"],
display: none;
}
.method,
kbd,
select,
input,
@@ -564,7 +584,16 @@ code {
&:not([readonly]):not(.ace_editor):active,
&:not([readonly]):not(.ace_editor):focus {
box-shadow: inset 0 0 0 2px var(--fg-light-color);
transition: all 0.2s ease-in-out;
}
}
.method {
cursor: pointer;
&:hover,
&:active,
&:focus {
box-shadow: inset 0 0 0 2px var(--fg-light-color);
}
}
@@ -587,6 +616,14 @@ pre {
.select-wrapper {
position: relative;
input {
text-transform: uppercase;
}
.trigger {
width: 100%;
}
&:after {
display: inline-block;
position: absolute;
@@ -661,6 +698,7 @@ input[type="checkbox"] {
label {
padding: 4px;
color: var(--fg-light-color);
transition: all 0.2s ease-in-out;
}
ul,
@@ -679,7 +717,7 @@ ol {
ul li,
ol li {
display: inline-flex;
flex-direction: column;
flex-flow: column nowrap;
flex-grow: 1;
justify-content: center;
}
@@ -693,7 +731,7 @@ ol li {
* {
display: inline-flex;
flex-wrap: wrap;
flex-flow: row wrap;
align-items: center;
justify-content: center;
}
@@ -773,27 +811,19 @@ ol li {
}
#send {
// #hidden-message {
// display: none;
// }
&.show {
display: flex;
position: fixed;
top: 12px;
right: 12px;
z-index: 2;
// #hidden-message {
// display: block;
// margin-left: 4px;
// }
}
}
section {
display: flex;
flex-wrap: wrap;
border-radius: 16px;
}
.tab {
@@ -873,18 +903,19 @@ input[type="radio"]:checked + label + .tab {
padding: 0;
width: 100%;
background-color: var(--bg-color);
transition: all 0.2s ease-in-out;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.45);
}
nav.primary-nav {
flex-flow: row nowrap;
overflow: auto;
justify-content: space-around;
justify-content: space-between;
background-color: var(--bg-dark-color);
a {
background-color: transparent;
margin: 8px;
flex: 1;
&.nuxt-link-exact-active {
background-color: transparent;
@@ -908,7 +939,7 @@ input[type="radio"]:checked + label + .tab {
ul,
ol {
flex-direction: column;
flex-flow: column nowrap;
}
ul li,

View File

@@ -1,7 +1,23 @@
<template>
<pre ref="editor"></pre>
<div class="show-if-initialized" :class="{ initialized }">
<pre ref="editor"></pre>
</div>
</template>
<style lang="scss">
.show-if-initialized {
opacity: 0;
&.initialized {
opacity: 1;
}
& > * {
transition: none;
}
}
</style>
<script>
const DEFAULT_THEME = "twilight";
@@ -30,6 +46,7 @@ export default {
data() {
return {
initialized: false,
editor: null,
cacheValue: ""
};
@@ -43,7 +60,12 @@ export default {
}
},
theme() {
this.editor.setTheme("ace/theme/" + this.defineTheme());
this.initialized = false;
this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
},
lang(value) {
this.editor.getSession().setMode("ace/mode/" + value);
@@ -55,11 +77,17 @@ export default {
mounted() {
const editor = ace.edit(this.$refs.editor, {
theme: "ace/theme/" + this.defineTheme(),
mode: "ace/mode/" + this.lang,
mode: `ace/mode/${this.lang}`,
...this.options
});
// Set the theme and show the editor only after it's been set to prevent FOUC.
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
if (this.value) editor.setValue(this.value, 1);
this.editor = editor;
@@ -76,17 +104,15 @@ export default {
defineTheme() {
if (this.theme) {
return this.theme;
} else {
return (
this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
);
}
return (
this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
);
}
},
beforeDestroy() {
destroyed() {
this.editor.destroy();
this.editor.container.remove();
}
};
</script>

View File

@@ -21,6 +21,7 @@
type="text"
v-model="name"
:placeholder="$t('my_new_collection')"
@keyup.enter="addNewCollection"
/>
</li>
</ul>
@@ -42,6 +43,8 @@
</template>
<script>
import { fb } from "../../functions/fb";
export default {
props: {
show: Boolean
@@ -55,15 +58,25 @@ export default {
};
},
methods: {
syncCollections() {
if (fb.currentUser !== null) {
if (fb.currentSettings[0].value) {
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections))
);
}
}
},
addNewCollection() {
if (!this.$data.name) {
this.$toast.info("Please provide a valid name for the collection");
this.$toast.info($t("invalid_collection_name"));
return;
}
this.$store.commit("postwoman/addNewCollection", {
name: this.$data.name
});
this.$emit("hide-modal");
this.syncCollections();
},
hideModal() {
this.$emit("hide-modal");

View File

@@ -21,6 +21,7 @@
type="text"
v-model="name"
:placeholder="$t('my_new_folder')"
@keyup.enter="addNewFolder"
/>
</li>
</ul>

View File

@@ -60,7 +60,7 @@ export default {
methods: {
saveCollection() {
if (!this.$data.name) {
this.$toast.info("Please provide a valid name for the collection");
this.$toast.info($t("invalid_collection_name"));
return;
}
const collectionUpdated = {

View File

@@ -120,20 +120,54 @@ export default {
reader.onload = event => {
let content = event.target.result;
let collections = JSON.parse(content);
this.$store.commit("postwoman/replaceCollections", collections);
if (collections[0]) {
let [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 {
return this.failedImport();
}
this.$store.commit("postwoman/importCollections", collections);
this.fileImported();
};
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0]);
this.fileImported();
},
importFromJSON() {
let reader = new FileReader();
reader.onload = event => {
let content = event.target.result;
let collections = JSON.parse(content);
if (collections[0]) {
let [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 {
return this.failedImport();
}
this.$store.commit("postwoman/importCollections", collections);
this.fileImported();
};
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0]);
this.fileImported();
},
exportJSON() {
let text = this.collectionJson;
@@ -161,6 +195,129 @@ export default {
this.$toast.info(this.$t("file_imported"), {
icon: "folder_shared"
});
},
failedImport() {
this.$toast.error(this.$t("import_failed"), {
icon: "error"
});
},
parsePostmanCollection(collection, folders = true) {
let postwomanCollection = folders
? [
{
name: "",
folders: [],
requests: []
}
]
: {
name: "",
requests: []
};
for (let collectionItem of collection.item) {
if (collectionItem.request) {
if (postwomanCollection[0]) {
postwomanCollection[0].name = collection.info
? collection.info.name
: "";
postwomanCollection[0].requests.push(
this.parsePostmanRequest(collectionItem)
);
} else {
postwomanCollection.name = collection.name ? collection.name : "";
postwomanCollection.requests.push(
this.parsePostmanRequest(collectionItem)
);
}
} else if (collectionItem.item) {
if (collectionItem.item[0]) {
postwomanCollection[0].folders.push(
this.parsePostmanCollection(collectionItem, false)
);
}
}
}
return postwomanCollection;
},
parsePostmanRequest(requestObject) {
let pwRequest = {
url: "",
path: "",
method: "",
auth: "",
httpUser: "",
httpPassword: "",
passwordFieldType: "password",
bearerToken: "",
headers: [],
params: [],
bodyParams: [],
rawParams: "",
rawInput: false,
contentType: "",
requestType: "",
name: ""
};
pwRequest.name = requestObject.name;
let requestObjectUrl = requestObject.request.url.raw.match(
/^(.+:\/\/[^\/]+|{[^\/]+})(\/[^\?]+|).*$/
);
pwRequest.url = requestObjectUrl[1];
pwRequest.path = requestObjectUrl[2] ? requestObjectUrl[2] : "";
pwRequest.method = requestObject.request.method;
let itemAuth = requestObject.request.auth
? requestObject.request.auth
: "";
let 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;
}
let requestObjectHeaders = requestObject.request.header;
if (requestObjectHeaders) {
pwRequest.headers = requestObjectHeaders;
for (let header of pwRequest.headers) {
delete header.name;
delete header.type;
}
}
let requestObjectParams = requestObject.request.url.query;
if (requestObjectParams) {
pwRequest.params = requestObjectParams;
for (let param of pwRequest.params) {
delete param.disabled;
}
}
if (requestObject.request.body) {
if (requestObject.request.body.mode === "urlencoded") {
let params = requestObject.request.body.urlencoded;
pwRequest.bodyParams = params ? params : [];
for (let param of pwRequest.bodyParams) {
delete param.type;
}
} else if (requestObject.request.body.mode === "raw") {
pwRequest.rawInput = true;
pwRequest.rawParams = requestObject.request.body.raw;
}
}
return pwRequest;
}
}
};

View File

@@ -4,7 +4,7 @@ TODO:
-->
<template>
<div class="collections-wrapper">
<pw-section class="yellow" :label="$t('collections')" ref="collections">
<addCollection :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
<editCollection
:show="showModalEdit"
@@ -92,7 +92,7 @@ TODO:
<span>{{ $t("generate_docs") }}</span>
</button>
</nuxt-link>
</div>
</pw-section>
</template>
<style scoped lang="scss">
@@ -113,6 +113,7 @@ import { fb } from "../../functions/fb";
export default {
components: {
collection,
"pw-section": () => import("../section"),
addCollection: () => import("./addCollection"),
addFolder: () => import("./addFolder"),
editCollection: () => import("./editCollection"),
@@ -142,6 +143,15 @@ export default {
return this.$store.state.postwoman.collections;
}
},
async mounted() {
this._keyListener = function(e) {
if (e.key === "Escape") {
e.preventDefault();
this.showModalAdd = this.showModalEdit = this.showModalImportExport = this.showModalAddFolder = this.showModalEditFolder = this.showModalEditRequest = false;
}
};
document.addEventListener("keydown", this._keyListener.bind(this));
},
methods: {
displayModalAdd(shouldDisplay) {
this.showModalAdd = shouldDisplay;
@@ -216,6 +226,9 @@ export default {
}
}
}
},
beforeDestroy() {
document.removeEventListener("keydown", this._keyListener);
}
};
</script>

View File

@@ -104,6 +104,8 @@
</template>
<script>
import { fb } from "../../functions/fb";
export default {
props: {
show: Boolean,
@@ -180,6 +182,15 @@ export default {
}
},
methods: {
syncCollections() {
if (fb.currentUser !== null) {
if (fb.currentSettings[0].value) {
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections))
);
}
}
},
saveRequestAs() {
const userDidntSpecifyCollection =
this.$data.requestData.collectionIndex === undefined;
@@ -204,6 +215,7 @@ export default {
});
this.hideModal();
this.syncCollections();
},
hideModal() {
this.$emit("hide-modal");

View File

@@ -0,0 +1,94 @@
<template>
<modal v-if="show" @close="hideModal">
<div slot="header">
<ul>
<li>
<div class="flex-wrap">
<h3 class="title">{{ $t("new_environment") }}</h3>
<div>
<button class="icon" @click="hideModal">
<i class="material-icons">close</i>
</button>
</div>
</div>
</li>
</ul>
</div>
<div slot="body">
<ul>
<li>
<input
type="text"
v-model="name"
:placeholder="$t('my_new_environment')"
@keyup.enter="addNewEnvironment"
/>
</li>
</ul>
</div>
<div slot="footer">
<div class="flex-wrap">
<span></span>
<span>
<button class="icon" @click="hideModal">
{{ $t("cancel") }}
</button>
<button class="icon primary" @click="addNewEnvironment">
{{ $t("save") }}
</button>
</span>
</div>
</div>
</modal>
</template>
<script>
import { fb } from "../../functions/fb";
export default {
props: {
show: Boolean
},
components: {
modal: () => import("../../components/modal")
},
data() {
return {
name: undefined
};
},
methods: {
syncEnvironments() {
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
);
}
}
},
addNewEnvironment() {
if (!this.$data.name) {
this.$toast.info(this.$t("invalid_environment_name"));
return;
}
let newEnvironment = [
{
name: this.$data.name,
variables: []
}
];
this.$store.commit("postwoman/importAddEnvironments", {
environments: newEnvironment,
confirmation: "Environment added"
});
this.$emit("hide-modal");
this.syncEnvironments();
},
hideModal() {
this.$data.name = undefined;
this.$emit("hide-modal");
}
}
};
</script>

View File

@@ -0,0 +1,221 @@
<template>
<modal v-if="show" @close="hideModal">
<div slot="header">
<ul>
<li>
<div class="flex-wrap">
<h3 class="title">{{ $t("edit_environment") }}</h3>
<div>
<button class="icon" @click="hideModal">
<i class="material-icons">close</i>
</button>
</div>
</div>
</li>
</ul>
</div>
<div slot="body">
<ul>
<li>
<input
type="text"
v-model="name"
:placeholder="editingEnvironment.name"
@keyup.enter="saveEnvironment"
/>
</li>
</ul>
<ul>
<li>
<div class="flex-wrap">
<label for="variableList">{{ $t("env_variable_list") }}</label>
<div>
<button
class="icon"
@click="clearContent($event)"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
<textarea
id="variableList"
readonly
v-textarea-auto-height="variableString"
v-model="variableString"
:placeholder="$t('add_one_variable')"
rows="1"
></textarea>
</li>
</ul>
<ul
v-for="(variable, index) in this.editingEnvCopy.variables"
:key="index"
>
<li>
<input
:placeholder="$t('parameter_count', { count: index + 1 })"
:name="'param' + index"
:value="variable.key"
@change="
$store.commit('postwoman/setVariableKey', {
index,
value: $event.target.value
})
"
autofocus
/>
</li>
<li>
<input
:placeholder="$t('value_count', { count: index + 1 })"
:name="'value' + index"
:value="
typeof variable.value === 'string'
? variable.value
: JSON.stringify(variable.value)
"
@change="
$store.commit('postwoman/setVariableValue', {
index,
value: $event.target.value
})
"
/>
</li>
<div>
<li>
<button
class="icon"
@click="removeEnvironmentVariable(index)"
v-tooltip.bottom="$t('delete')"
id="variable"
>
<i class="material-icons">delete</i>
</button>
</li>
</div>
</ul>
<ul>
<li>
<button class="icon" @click="addEnvironmentVariable">
<i class="material-icons">add</i>
<span>{{ $t("add_new") }}</span>
</button>
</li>
</ul>
</div>
<div slot="footer">
<div class="flex-wrap">
<span></span>
<span>
<button class="icon" @click="hideModal">
{{ $t("cancel") }}
</button>
<button class="icon primary" @click="saveEnvironment">
{{ $t("save") }}
</button>
</span>
</div>
</div>
</modal>
</template>
<script>
import textareaAutoHeight from "../../directives/textareaAutoHeight";
export default {
directives: {
textareaAutoHeight
},
props: {
show: Boolean,
editingEnvironment: Object,
editingEnvironmentIndex: Number
},
components: {
modal: () => import("../../components/modal")
},
data() {
return {
name: undefined
};
},
watch: {
editingEnvironment: function(update) {
this.name = this.$props.editingEnvironment && this.$props.editingEnvironment.name
? this.$props.editingEnvironment.name
: undefined
this.$store.commit(
"postwoman/setEditingEnvironment",
this.$props.editingEnvironment
);
}
},
computed: {
editingEnvCopy() {
return this.$store.state.postwoman.editingEnvironment;
},
variableString() {
const result = this.editingEnvCopy.variables;
return result === "" ? "" : JSON.stringify(result);
}
},
methods: {
clearContent(e) {
this.$store.commit("postwoman/removeVariables", []);
e.target.innerHTML = this.doneButton;
this.$toast.info(this.$t("cleared"), {
icon: "clear_all"
});
setTimeout(
() => (e.target.innerHTML = '<i class="material-icons">clear_all</i>'),
1000
);
},
addEnvironmentVariable() {
let value = { key: "", value: "" };
this.$store.commit("postwoman/addVariable", value);
},
removeEnvironmentVariable(index) {
let variableIndex = index;
const oldVariables = this.editingEnvCopy.variables.slice();
const newVariables = this.editingEnvCopy.variables.filter(
(variable, index) => variableIndex !== index
);
this.$store.commit("postwoman/removeVariable", newVariables);
this.$toast.error(this.$t("deleted"), {
icon: "delete",
action: {
text: this.$t("undo"),
onClick: (e, toastObject) => {
this.$store.commit("postwoman/removeVariable", oldVariables);
toastObject.remove();
}
}
});
},
saveEnvironment() {
if (!this.$data.name) {
this.$toast.info(this.$t("invalid_environment_name"));
return;
}
const environmentUpdated = {
...this.editingEnvCopy,
name: this.$data.name
};
this.$store.commit("postwoman/saveEnvironment", {
environment: environmentUpdated,
environmentIndex: this.$props.editingEnvironmentIndex
});
this.$emit("hide-modal");
},
hideModal() {
this.$data.name = undefined;
this.$emit("hide-modal");
}
}
};
</script>

View File

@@ -0,0 +1,65 @@
<template>
<div class="flex-wrap">
<div>
<button
class="icon"
@click="$emit('select-environment')"
v-tooltip="$t('use_environment')"
>
<i class="material-icons">insert_drive_file</i>
<span>{{ environment.name }}</span>
</button>
</div>
<v-popover>
<button class="tooltip-target icon" v-tooltip="$t('more')">
<i class="material-icons">more_vert</i>
</button>
<template slot="popover">
<div>
<button
class="icon"
@click="$emit('edit-environment')"
v-close-popover
>
<i class="material-icons">create</i>
<span>{{ $t("edit") }}</span>
</button>
</div>
<div>
<button class="icon" @click="removeEnvironment" v-close-popover>
<i class="material-icons">delete</i>
<span>{{ $t("delete") }}</span>
</button>
</div>
</template>
</v-popover>
</div>
</template>
<style scoped lang="scss">
ul {
display: flex;
flex-direction: column;
}
ul li {
display: flex;
padding-left: 16px;
border-left: 1px solid var(--brd-color);
}
</style>
<script>
export default {
props: {
environment: Object,
environmentIndex: Number
},
methods: {
removeEnvironment() {
if (!confirm("Are you sure you want to remove this environment?")) return;
this.$store.commit("postwoman/removeEnvironment", this.environmentIndex);
}
}
};
</script>

View File

@@ -0,0 +1,173 @@
<template>
<modal v-if="show" @close="hideModal">
<div slot="header">
<ul>
<li>
<div class="flex-wrap">
<h3 class="title">Import / Export Environment</h3>
<div>
<button class="icon" @click="hideModal">
<i class="material-icons">close</i>
</button>
</div>
</div>
<div class="flex-wrap">
<span
v-tooltip="{
content: !fb.currentUser
? $t('login_first')
: $t('replace_current')
}"
>
<button
:disabled="!fb.currentUser"
class="icon"
@click="syncEnvironments"
>
<i class="material-icons">folder_shared</i>
<span>{{ $t("import_from_sync") }}</span>
</button>
</span>
<button
class="icon"
@click="openDialogChooseFileToReplaceWith"
v-tooltip="$t('replace_current')"
>
<i class="material-icons">create_new_folder</i>
<span>{{ $t("replace_json") }}</span>
<input
type="file"
@change="replaceWithJSON"
style="display: none;"
ref="inputChooseFileToReplaceWith"
accept="application/json"
/>
</button>
<button
class="icon"
@click="openDialogChooseFileToImportFrom"
v-tooltip="$t('preserve_current')"
>
<i class="material-icons">folder_special</i>
<span>{{ $t("import_json") }}</span>
<input
type="file"
@change="importFromJSON"
style="display: none;"
ref="inputChooseFileToImportFrom"
accept="application/json"
/>
</button>
</div>
</li>
</ul>
</div>
<div slot="body">
<textarea v-model="environmentJson" rows="8"></textarea>
</div>
<div slot="footer">
<div class="flex-wrap">
<span></span>
<span>
<button class="icon" @click="hideModal">
{{ $t("cancel") }}
</button>
<button
class="icon primary"
@click="exportJSON"
v-tooltip="$t('download_file')"
>
{{ $t("export") }}
</button>
</span>
</div>
</div>
</modal>
</template>
<script>
import { fb } from "../../functions/fb";
export default {
data() {
return {
fb
};
},
props: {
show: Boolean
},
components: {
modal: () => import("../../components/modal")
},
computed: {
environmentJson() {
return JSON.stringify(this.$store.state.postwoman.environments, null, 2);
}
},
methods: {
hideModal() {
this.$emit("hide-modal");
},
openDialogChooseFileToReplaceWith() {
this.$refs.inputChooseFileToReplaceWith.click();
},
openDialogChooseFileToImportFrom() {
this.$refs.inputChooseFileToImportFrom.click();
},
replaceWithJSON() {
let reader = new FileReader();
reader.onload = event => {
let content = event.target.result;
let environments = JSON.parse(content);
this.$store.commit("postwoman/replaceEnvironments", environments);
};
reader.readAsText(this.$refs.inputChooseFileToReplaceWith.files[0]);
this.fileImported();
},
importFromJSON() {
let reader = new FileReader();
reader.onload = event => {
let content = event.target.result;
let environments = JSON.parse(content);
let confirmation = this.$t("file_imported")
this.$store.commit("postwoman/importAddEnvironments", {
environments,
confirmation
});
};
reader.readAsText(this.$refs.inputChooseFileToImportFrom.files[0]);
},
exportJSON() {
let text = this.environmentJson;
text = text.replace(/\n/g, "\r\n");
let blob = new Blob([text], {
type: "text/json"
});
let anchor = document.createElement("a");
anchor.download = "postwoman-environment.json";
anchor.href = window.URL.createObjectURL(blob);
anchor.target = "_blank";
anchor.style.display = "none";
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
this.$toast.success(this.$t("download_started"), {
icon: "done"
});
},
syncEnvironments() {
this.$store.commit(
"postwoman/replaceEnvironments",
fb.currentEnvironments
);
this.fileImported();
},
fileImported() {
this.$toast.info(this.$t("file_imported"), {
icon: "folder_shared"
});
}
}
};
</script>

View File

@@ -0,0 +1,147 @@
<template>
<pw-section
class="green"
icon="history"
:label="$t('environment')"
ref="environment"
>
<addEnvironment :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
<editEnvironment
:show="showModalEdit"
:editingEnvironment="editingEnvironment"
:editingEnvironmentIndex="editingEnvironmentIndex"
@hide-modal="displayModalEdit(false)"
/>
<importExportEnvironment
:show="showModalImportExport"
@hide-modal="displayModalImportExport(false)"
/>
<div class="flex-wrap">
<div>
<button class="icon" @click="displayModalAdd(true)">
<i class="material-icons">add</i>
<span>{{ $t("new") }}</span>
</button>
</div>
<div>
<button class="icon" @click="displayModalImportExport(true)">
{{ $t("import_export") }}
</button>
</div>
</div>
<p v-if="environments.length === 0" class="info">
Create new environment
</p>
<virtual-list
class="virtual-list"
:class="{ filled: environments.length }"
:size="152"
:remain="Math.min(5, environments.length)"
>
<ul>
<li
v-for="(environment, index) in environments"
:key="environment.name"
>
<environment
:environmentIndex="index"
:environment="environment"
@edit-environment="editEnvironment(environment, index)"
@select-environment="$emit('use-environment', environment)"
/>
</li>
<li v-if="environments.length === 0">
<label>Environments are empty</label>
</li>
</ul>
</virtual-list>
</pw-section>
</template>
<style scoped lang="scss">
.virtual-list {
max-height: calc(100vh - 276px);
}
ul {
display: flex;
flex-direction: column;
}
</style>
<script>
import environment from "./environment";
import { fb } from "../../functions/fb";
const updateOnLocalStorage = (propertyName, property) =>
window.localStorage.setItem(propertyName, JSON.stringify(property));
export default {
components: {
environment,
"pw-section": () => import("../section"),
addEnvironment: () => import("./addEnvironment"),
editEnvironment: () => import("./editEnvironment"),
importExportEnvironment: () => import("./importExportEnvironment"),
VirtualList: () => import("vue-virtual-scroll-list")
},
data() {
return {
showModalImportExport: false,
showModalAdd: false,
showModalEdit: false,
editingEnvironment: undefined,
editingEnvironmentIndex: undefined
};
},
computed: {
environments() {
return this.$store.state.postwoman.environments;
}
},
async mounted() {
this._keyListener = function(e) {
if (e.key === "Escape") {
e.preventDefault();
this.showModalImportExport = false;
}
};
document.addEventListener("keydown", this._keyListener.bind(this));
},
methods: {
displayModalAdd(shouldDisplay) {
this.showModalAdd = shouldDisplay;
},
displayModalEdit(shouldDisplay) {
this.showModalEdit = shouldDisplay;
if (!shouldDisplay) this.resetSelectedData();
},
displayModalImportExport(shouldDisplay) {
this.showModalImportExport = shouldDisplay;
},
editEnvironment(environment, environmentIndex) {
this.$data.editingEnvironment = environment;
this.$data.editingEnvironmentIndex = environmentIndex;
this.displayModalEdit(true);
this.syncEnvironments;
},
resetSelectedData() {
this.$data.editingEnvironment = undefined;
this.$data.editingEnvironmentIndex = undefined;
},
syncEnvironments() {
if (fb.currentUser !== null) {
if (fb.currentSettings[1].value) {
fb.writeEnvironments(
JSON.parse(JSON.stringify(this.$store.state.postwoman.environments))
);
}
}
}
},
beforeDestroy() {
document.removeEventListener("keydown", this._keyListener);
}
};
</script>

View File

@@ -3,28 +3,25 @@
v-if="fb.currentFeeds.length !== 0"
class="virtual-list"
:class="{ filled: fb.currentFeeds.length }"
:size="56"
:remain="Math.min(8, fb.currentFeeds.length)"
:size="90"
:remain="Math.min(5, fb.currentFeeds.length)"
>
<ul v-for="feed in fb.currentFeeds" :key="feed.id">
<ul v-for="feed in fb.currentFeeds" :key="feed.id" class="entry">
<div class="show-on-large-screen">
<li>
<input
:aria-label="$t('label')"
type="text"
readonly
:value="feed.label"
:placeholder="$t('no_label')"
class="bg-color"
/>
<li class="info">
<label>
{{ feed.label || $t("no_label") }}
</label>
</li>
<button class="icon" @click="saveFeed(feed)">
<i class="material-icons">get_app</i>
</button>
<button class="icon" @click="deleteFeed(feed)">
<i class="material-icons">delete</i>
</button>
</div>
<div class="show-on-large-screen">
<li class="info clamb-3">
<label>{{ feed.message || $t("empty") }}</label>
</li>
</div>
</ul>
</virtual-list>
<ul v-else>
@@ -38,6 +35,23 @@
.virtual-list {
max-height: calc(100vh - 288px);
}
ul,
ol {
flex-direction: column;
}
.entry {
border-bottom: 1px dashed var(--brd-color);
padding: 0 0 8px;
}
.clamb-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
<script>
@@ -58,23 +72,6 @@ export default {
this.$toast.error(this.$t("deleted"), {
icon: "delete"
});
},
saveFeed(feed) {
const dataToWrite = JSON.stringify(feed.message, null, 2);
const file = new Blob([dataToWrite], { type: "application/json" });
const a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = (feed.label + " on " + Date()).replace(/\./g, "[dot]");
document.body.appendChild(a);
a.click();
this.$toast.success(this.$t("download_started"), {
icon: "done"
});
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 1000);
}
}
};

View File

@@ -1,39 +1,50 @@
<template>
<div>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="message"
:placeholder="$t('paste_a_collection')"
/>
</li>
</ul>
<ul>
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="label"
:placeholder="$t('label')"
/>
</li>
<button
class="icon"
:disabled="!(this.message && this.label)"
value="Save"
@click="formPost"
>
<i class="material-icons">add</i>
<span>Add</span>
</button>
<div class="show-on-large-screen">
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="message"
:placeholder="$t('paste_a_note')"
@keyup.enter="formPost"
/>
</li>
</div>
<div class="show-on-large-screen">
<li>
<input
:aria-label="$t('label')"
type="text"
autofocus
v-model="label"
:placeholder="$t('label')"
@keyup.enter="formPost"
/>
</li>
<button
class="icon"
:disabled="!(this.message || this.label)"
value="Save"
@click="formPost"
>
<i class="material-icons">add</i>
<span>Add</span>
</button>
</div>
</ul>
</div>
</template>
<style scoped lang="scss">
ul,
ol {
flex-direction: column;
}
</style>
<script>
import { fb } from "../../functions/fb";
@@ -46,6 +57,9 @@ export default {
},
methods: {
formPost() {
if (!(this.message || this.label)) {
return;
}
fb.writeFeeds(this.message, this.label);
this.message = null;
this.label = null;

View File

@@ -57,9 +57,9 @@ export default {
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
.then(({ additionalUserInfo }) => {
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
@@ -86,9 +86,9 @@ export default {
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
.then(({ additionalUserInfo }) => {
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,

View File

@@ -8,7 +8,7 @@
</span>
</template>
<style></style>
<style scoped lang="scss"></style>
<script>
import typelink from "./typelink";

View File

@@ -28,7 +28,7 @@
</div>
</template>
<style>
<style scoped lang="scss">
.field-box {
padding: 16px;
margin: 4px;

View File

@@ -1,13 +1,31 @@
<template>
<pre ref="editor"></pre>
<div class="show-if-initialized" :class="{ initialized }">
<pre ref="editor"></pre>
</div>
</template>
<style lang="scss">
.show-if-initialized {
opacity: 0;
&.initialized {
opacity: 1;
}
& > * {
transition: none;
}
}
</style>
<script>
const DEFAULT_THEME = "twilight";
import ace from "ace-builds";
import * as gql from "graphql";
import { getAutocompleteSuggestions } from "graphql-language-service-interface";
import "ace-builds/webpack-resolver";
import "ace-builds/src-noconflict/ext-language_tools";
import debounce from "../../functions/utils/debounce";
export default {
@@ -32,6 +50,7 @@ export default {
data() {
return {
initialized: false,
editor: null,
cacheValue: "",
validationSchema: null
@@ -46,10 +65,15 @@ export default {
}
},
theme() {
this.editor.setTheme("ace/theme/" + this.defineTheme());
this.initialized = false;
this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
},
lang(value) {
this.editor.getSession().setMode("ace/mode/" + value);
this.editor.getSession().setMode(`ace/mode/${value}`);
},
options(value) {
this.editor.setOptions(value);
@@ -57,12 +81,54 @@ export default {
},
mounted() {
let langTools = ace.require("ace/ext/language_tools");
const editor = ace.edit(this.$refs.editor, {
theme: "ace/theme/" + this.defineTheme(),
mode: "ace/mode/" + this.lang,
mode: `ace/mode/${this.lang}`,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
...this.options
});
// Set the theme and show the editor only after it's been set to prevent FOUC.
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
const completer = {
getCompletions: (
editor,
_session,
{ row, column },
_prefix,
callback
) => {
if (this.validationSchema) {
const completions = getAutocompleteSuggestions(
this.validationSchema,
editor.getValue(),
{ line: row, character: column }
);
callback(
null,
completions.map(({ label, detail }) => ({
name: label,
value: label,
score: 1.0,
meta: detail
}))
);
} else {
callback(null, []);
}
}
};
langTools.setCompleters([completer]);
if (this.value) editor.setValue(this.value, 1);
this.editor = editor;
@@ -101,14 +167,14 @@ export default {
if (this.validationSchema) {
this.editor.session.setAnnotations(
gql.validate(this.validationSchema, doc).map(err => {
return {
row: err.locations[0].line - 1,
column: err.locations[0].column - 1,
text: err.message,
gql
.validate(this.validationSchema, doc)
.map(({ locations, message }) => ({
row: locations[0].line - 1,
column: locations[0].column - 1,
text: message,
type: "error"
};
})
}))
);
}
} catch (e) {
@@ -129,7 +195,6 @@ export default {
beforeDestroy() {
this.editor.destroy();
this.editor.container.remove();
}
};
</script>

View File

@@ -14,7 +14,7 @@
</div>
</template>
<style>
<style scoped lang="scss">
.type-box {
padding: 16px;
margin: 4px 0;

View File

@@ -2,7 +2,7 @@
<span class="typelink" @click="jumpToType">{{ typeString }}</span>
</template>
<style>
<style scoped lang="scss">
.typelink {
color: var(--ac-color);
font-family: "Roboto Mono", monospace;

View File

@@ -1,14 +1,19 @@
<template>
<pw-section class="green" icon="history" :label="$t('history')" ref="history">
<ul>
<li id="filter-history">
<input
aria-label="Search"
type="text"
:placeholder="$t('search_history')"
v-model="filterText"
/>
</li>
<div class="show-on-large-screen">
<li id="filter-history">
<input
aria-label="Search"
type="search"
:placeholder="$t('search')"
v-model="filterText"
/>
</li>
<button class="icon">
<i class="material-icons">search</i>
</button>
</div>
</ul>
<virtual-list
class="virtual-list"
@@ -289,11 +294,6 @@ ol {
flex-direction: column;
}
ul li,
ol li {
display: flex;
}
.method-list-item {
position: relative;

View File

@@ -25,7 +25,7 @@
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.87);
background-color: rgba(0, 0, 0, 0.32);
display: flex;
align-items: center;
justify-content: center;

View File

@@ -15,7 +15,7 @@
</fieldset>
</template>
<style>
<style scoped lang="scss">
fieldset.no-colored-frames legend {
color: var(--fg-color);
}

View File

@@ -4,10 +4,9 @@
:data-color="color"
:class="{ active: active }"
v-tooltip="{ content: name || color }"
:style="{ backgroundColor: color }"
>
<span :style="{ backgroundColor: color }" class="preview">
<i v-if="active" class="material-icons activeTick">done</i>
</span>
<i v-if="active" class="material-icons activeTick">done</i>
</div>
</template>
@@ -17,9 +16,11 @@
align-items: center;
justify-content: center;
margin: 8px;
padding: 16px;
border-radius: 100%;
border: 3px solid var(--bg-dark-color);
cursor: pointer;
transition: all 0.2s ease-in-out;
&.fg {
color: var(--act-color);
@@ -33,19 +34,8 @@
border: 3px solid var(--fg-color);
}
.preview {
vertical-align: middle;
display: inline-block;
border-radius: 100%;
padding: 16px;
position: relative;
.activeTick {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.activeTick {
position: absolute;
}
}
</style>

11
functions/editorutils.js Normal file
View File

@@ -0,0 +1,11 @@
const mimeToMode = {
"text/plain": "plain_text",
"text/html": "html",
"application/xml": "xml",
"application/hal+json": "json",
"application/json": "json"
}
export function getEditorLangForMimeType(mimeType) {
return mimeToMode[mimeType] || "plain_text";
}

View File

@@ -26,6 +26,7 @@ export const fb = {
currentSettings: [],
currentHistory: [],
currentCollections: [],
currentEnvironments: [],
writeFeeds: async (message, label) => {
const dt = {
createdOn: new Date(),
@@ -112,6 +113,21 @@ export const fb = {
.doc("sync")
.set(cl)
.catch(e => console.error("error updating", cl, e));
},
writeEnvironments: async environment => {
const ev = {
updatedOn: new Date(),
author: fb.currentUser.uid,
author_name: fb.currentUser.displayName,
author_image: fb.currentUser.photoURL,
environment: environment
};
usersCollection
.doc(fb.currentUser.uid)
.collection("environments")
.doc("sync")
.set(ev)
.catch(e => console.error("error updating", ev, e));
}
};
@@ -186,6 +202,19 @@ firebase.auth().onAuthStateChanged(user => {
});
fb.currentCollections = collections[0].collection;
});
usersCollection
.doc(fb.currentUser.uid)
.collection("environments")
.onSnapshot(environmentsRef => {
const environments = [];
environmentsRef.forEach(doc => {
const environment = doc.data();
environment.id = doc.id;
environments.push(environment);
});
fb.currentEnvironments = environments[0].environment;
});
} else {
fb.currentUser = null;
}

124
functions/headers.js Normal file
View File

@@ -0,0 +1,124 @@
export const commonHeaders = [
"WWW-Authenticate",
"Authorization",
"Proxy-Authenticate",
"Proxy-Authorization",
"Age",
"Cache-Control",
"Clear-Site-Data",
"Expires",
"Pragma",
"Warning",
"Accept-CH",
"Accept-CH-Lifetime",
"Early-Data",
"Content-DPR",
"DPR",
"Device-Memory",
"Save-Data",
"Viewport-Width",
"Width",
"Last-Modified",
"ETag",
"If-Match",
"If-None-Match",
"If-Modified-Since",
"If-Unmodified-Since",
"Vary",
"Connection",
"Keep-Alive",
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Expect",
"Max-Forwards",
"Cookie",
"Set-Cookie",
"Cookie2",
"Set-Cookie2",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Access-Control-Allow-Methods",
"Access-Control-Expose-Headers",
"Access-Control-Max-Age",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Origin",
"Service-Worker-Allowed",
"Timing-Allow-Origin",
"X-Permitted-Cross-Domain-Policies",
"DNT",
"Tk",
"Content-Disposition",
"Content-Length",
"Content-Type",
"Content-Encoding",
"Content-Language",
"Content-Location",
"Forwarded",
"X-Forwarded-For",
"X-Forwarded-Host",
"X-Forwarded-Proto",
"Via",
"Location",
"From",
"Host",
"Referer",
"Referrer-Policy",
"User-Agent",
"Allow",
"Server",
"Accept-Ranges",
"Range",
"If-Range",
"Content-Range",
"Cross-Origin-Opener-Policy",
"Cross-Origin-Resource-Policy",
"Content-Security-Policy",
"Content-Security-Policy-Report-Only",
"Expect-CT",
"Feature-Policy",
"Public-Key-Pins",
"Public-Key-Pins-Report-Only",
"Strict-Transport-Security",
"Upgrade-Insecure-Requests",
"X-Content-Type-Options",
"X-Download-Options",
"X-Frame-Options",
"X-Powered-By",
"X-XSS-Protection",
"Last-Event-ID",
"NEL",
"Ping-From",
"Ping-To",
"Report-To",
"Transfer-Encoding",
"TE",
"Trailer",
"Sec-WebSocket-Key",
"Sec-WebSocket-Extensions",
"Sec-WebSocket-Accept",
"Sec-WebSocket-Protocol",
"Sec-WebSocket-Version",
"Accept-Push-Policy",
"Accept-Signature",
"Alt-Svc",
"Date",
"Large-Allocation",
"Link",
"Push-Policy",
"Retry-After",
"Signature",
"Signed-Headers",
"Server-Timing",
"SourceMap",
"Upgrade",
"X-DNS-Prefetch-Control",
"X-Firefox-Spdy",
"X-Pingback",
"X-Requested-With",
"X-Robots-Tag",
"X-UA-Compatible"
];

View File

@@ -1,24 +1,42 @@
import AxiosStrategy from "./strategies/AxiosStrategy";
import ExtensionStrategy, {
hasExtensionInstalled
} from "./strategies/ExtensionStrategy";
import FirefoxStrategy from "./strategies/FirefoxStrategy";
import ChromeStrategy, { hasChromeExtensionInstalled } from "./strategies/ChromeStrategy";
import ChromeStrategy, {
hasChromeExtensionInstalled
} from "./strategies/ChromeStrategy";
const isExtensionsAllowed = ({ state }) =>
typeof state.postwoman.settings.EXTENSIONS_ENABLED === "undefined" ||
state.postwoman.settings.EXTENSIONS_ENABLED;
const runAppropriateStrategy = (req, store) => {
// Chrome Provides a chrome object for scripts to access
// Check its availability to say whether you are in Google Chrome
if (window.chrome && hasChromeExtensionInstalled()) {
if (isExtensionsAllowed(store)) {
if (hasExtensionInstalled()) {
return ExtensionStrategy(req, store);
}
// The following strategies are deprecated and kept to support older version of the extensions
// Chrome Provides a chrome object for scripts to access
// Check its availability to say whether you are in Google Chrome
if (window.chrome && hasChromeExtensionInstalled()) {
return ChromeStrategy(req, store);
}
// The firefox plugin injects a function to send requests through it
// If that is available, then we can use the FirefoxStrategy
if (window.firefoxExtSendRequest) {
return FirefoxStrategy(req, store);
}
// The firefox plugin injects a function to send requests through it
// If that is available, then we can use the FirefoxStrategy
if (window.firefoxExtSendRequest) {
return FirefoxStrategy(req, store);
}
}
return AxiosStrategy(req, store);
}
};
const sendNetworkRequest = (req, store) =>
runAppropriateStrategy(req, store)
.finally(() => window.$nuxt.$loading.finish());
const sendNetworkRequest = (req, store) =>
runAppropriateStrategy(req, store).finally(() =>
window.$nuxt.$loading.finish()
);
export { sendNetworkRequest };

View File

@@ -3,47 +3,54 @@ const EXTENSION_ID = "amknoiejhlmhancpahfcfcfhllgkpbld";
// Check if the Chrome Extension is present
// The Chrome extension injects an empty span to help detection.
// Also check for the presence of window.chrome object to confirm smooth operations
export const hasChromeExtensionInstalled = () => {
return document.getElementById("chromePWExtensionDetect") !== null;
}
export const hasChromeExtensionInstalled = () =>
document.getElementById("chromePWExtensionDetect") !== null;
const chromeWithoutProxy = (req, _store) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: req
}
}, (message) => {
if (message.data.error) {
reject(message.data.error);
} else {
resolve(message.data.response);
}
}
);
});
const chromeWithProxy = (req, { state }) => new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID, {
messageType: "send-req",
data: {
config: {
method: "post",
url: state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
const chromeWithoutProxy = (req, _store) =>
new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID,
{
messageType: "send-req",
data: {
config: req
}
},
({ data }) => {
if (data.error) {
reject(data.error);
} else {
resolve(data.response);
}
}
}, (message) => {
if (message.data.error) {
reject(error);
} else {
resolve(message.data.response.data);
);
});
const chromeWithProxy = (req, { state }) =>
new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
EXTENSION_ID,
{
messageType: "send-req",
data: {
config: {
method: "post",
url:
state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/",
data: req
}
}
},
({ data }) => {
if (data.error) {
reject(error);
} else {
resolve(data.response.data);
}
}
}
)
});
);
});
const chromeStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
@@ -51,6 +58,6 @@ const chromeStrategy = (req, store) => {
} else {
return chromeWithoutProxy(req, store);
}
}
};
export default chromeStrategy;

View File

@@ -0,0 +1,26 @@
export const hasExtensionInstalled = () =>
typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined";
const extensionWithProxy = async (req, { state }) => {
const { data } = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({
method: "post",
url:
state.postwoman.settings.PROXY_URL || "https://postwoman.apollotv.xyz/",
data: req
});
return data;
};
const extensionWithoutProxy = async (req, _store) => {
const res = await window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest(req);
return res;
};
const extensionStrategy = (req, store) => {
if (store.state.postwoman.settings.PROXY_ENABLED) {
return extensionWithProxy(req, store);
}
return extensionWithoutProxy(req, store);
};
export default extensionStrategy;

View File

@@ -27,7 +27,6 @@ export default {
method: "Method",
path: "Path",
label: "Label",
again: "Again",
content_type: "Content Type",
raw_input: "Raw input",
parameter_list: "Parameter List",
@@ -46,6 +45,14 @@ export default {
preview_html: "Preview HTML",
history: "History",
collections: "Collections",
environment: "Environment",
new_environment: "New Environment",
my_new_environment: "My New Environment",
edit_environment: "Edit Environment",
env_variable_list: "Variable List",
invalid_environment_name: "Please provide a valid name for the environment",
use_environment: "Use Environment",
add_one_variable: "(add at least one variable)",
import_curl: "Import cURL",
import: "Import",
generate_code: "Generate code",
@@ -111,7 +118,7 @@ export default {
no_label: "No label",
prerequest_script: "Pre-Request Script",
no_prerequest_script: "No pre-request script",
search_history: "search history",
search: "Search",
history_empty: "History is empty",
history_deleted: "History Deleted",
clear: "Clear",
@@ -141,6 +148,7 @@ export default {
deleted: "Deleted",
undo: "Undo",
collection_empty: "Collection is empty",
invalid_collection_name: "Please provide a valid name for the collection",
new_collection: "New Collection",
my_new_collection: "My New Collection",
edit_collection: "Edit Collection",
@@ -163,7 +171,7 @@ export default {
kinda_dark: "Kinda Dark",
clearly_white: "Clearly White",
just_black: "Just Black",
auto_system: "Auth (system)",
auto_system: "Auto (system)",
green: "Green",
yellow: "Yellow",
pink: "Pink",
@@ -222,6 +230,7 @@ export default {
payload: "Payload",
choose_file: "Choose a file",
file_imported: "File imported",
import_failed: "Import failed",
f12_details: "(F12 for details)",
we_use_cookies: "We use cookies",
copied_to_clipboard: "Copied to clipboard",
@@ -247,6 +256,8 @@ export default {
enter_curl: "Enter cURL",
empty: "Empty",
extensions: "Extensions",
extensions_use_toggle:
"Use the browser extension to send requests (if present)",
extensions_info1: "Browser extension that simplifies access to Postwoman",
extensions_info2: "Get Postwoman browser extension!",
installed: "Installed",
@@ -254,11 +265,14 @@ export default {
logged_out: "Logged out",
logout: "Logout",
account: "Account",
scrollInto_use_toggle: "Auto scroll",
sync: "Sync",
syncHistory: "History",
syncCollections: "Collections",
syncEnvironments: "Environments",
turn_on: "Turn on",
login_first: "Login first",
paste_a_collection: "Paste a Collection",
import_from_sync: "Import from Sync"
paste_a_note: "Paste a note",
import_from_sync: "Import from Sync",
notes: "Notes"
};

View File

@@ -111,7 +111,7 @@ export default {
no_label: "Sin etiqueta",
prerequest_script: "Pre-Request Script",
no_prerequest_script: "Script sin pre-requisito",
search_history: "buscar historial",
search: "buscar historial",
history_empty: "Historial vacío",
history_deleted: "Historial borrado",
clear: "Limpiar",

View File

@@ -111,7 +111,7 @@ export default {
no_label: "Aucun label",
prerequest_script: "Pre-Request Script",
no_prerequest_script: "No pre-request script",
search_history: "Historique de la recherche",
search: "Historique de la recherche",
history_empty: "L'historique est vide",
history_deleted: "Historique supprimé",
clear: "Nettoyer",

View File

@@ -111,7 +111,7 @@ export default {
no_label: "ラベル無し",
prerequest_script: "プレリクエストスクリプト",
no_prerequest_script: "プレリクエストスクリプト無し",
search_history: "検索履歴",
search: "検索履歴",
history_empty: "履歴が空です",
history_deleted: "履歴が削除された",
clear: "クリア",

View File

@@ -247,6 +247,11 @@
<i class="material-icons">brush</i>
</a>
</li>
<li>
<a href="#extensions" v-tooltip.right="$t('extensions')">
<i class="material-icons">extensions</i>
</a>
</li>
<li>
<a href="#proxy" v-tooltip.right="$t('proxy')">
<i class="material-icons">public</i>
@@ -293,8 +298,8 @@
>
<i class="material-icons">offline_bolt</i>
</button>
<login v-if="!fb.currentUser" />
<span v-if="fb.currentUser">
<login v-if="fb.currentUser === null" />
<span v-else>
<v-popover>
<button
class="icon"
@@ -373,7 +378,7 @@
<div>
<button
class="icon"
onClick="window.open('https://twitter.com/share?text=👽 Postwoman • API request builder - Helps you create your requests faster, saving you precious time on your development&url=https://postwoman.io&hashtags=postwoman&via=liyasthomas');"
onClick="window.open('https://twitter.com/share?text=👽 Postwoman • A free, fast and beautiful API request builder - Web alternative to Postman - Helps you create requests faster, saving precious time on development.&url=https://postwoman.io&hashtags=postwoman&via=liyasthomas');"
v-close-popover
>
<svg
@@ -388,6 +393,15 @@
</svg>
<span>{{ $t("tweet") }}</span>
</button>
<button
v-if="navigatorShare"
class="icon"
@click="nativeShare"
v-close-popover
v-tooltip="$t('more')"
>
<i class="material-icons">share</i>
</button>
</div>
</template>
</v-popover>
@@ -399,7 +413,7 @@
<div class="flex-wrap">
<span v-if="version.name" class="mono">
<a
class="link"
class="footer-link"
:href="
'https://github.com/liyasthomas/postwoman/releases/tag/' +
version.name
@@ -411,7 +425,7 @@
{{ version.name }}
</a>
<a
class="link"
class="footer-link hide-on-small-screen"
href="https://www.netlify.com"
target="_blank"
rel="noopener"
@@ -566,19 +580,19 @@
<div slot="body">
<div>
<label>{{ $t("send_request") }}</label>
<kbd> G</kbd>
<kbd>{{ getSpecialKey() }} G</kbd>
</div>
<div>
<label>{{ $t("save_to_collections") }}</label>
<kbd> S</kbd>
<kbd>{{ getSpecialKey() }} S</kbd>
</div>
<div>
<label>{{ $t("copy_request_link") }}</label>
<kbd> K</kbd>
<kbd>{{ getSpecialKey() }} K</kbd>
</div>
<div>
<label>{{ $t("reset_request") }}</label>
<kbd> L</kbd>
<kbd>{{ getSpecialKey() }} L</kbd>
</div>
</div>
<div slot="footer"></div>
@@ -651,21 +665,15 @@
</template>
<style scoped lang="scss">
.link {
.footer-link {
margin: 8px 16px;
}
@media (max-width: 768px) {
.link {
display: flex;
flex: 1;
}
}
</style>
<script>
import intializePwa from "../assets/js/pwa";
import * as version from "../.postwoman/version.json";
import { hasChromeExtensionInstalled } from "../functions/strategies/ChromeStrategy";
import { hasExtensionInstalled } from "../functions/strategies/ExtensionStrategy";
import firebase from "firebase/app";
import { fb } from "../functions/fb";
@@ -677,13 +685,15 @@ export default {
},
methods: {
getSpecialKey() {
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl";
},
linkActive(path) {
return {
"nuxt-link-exact-active": this.$route.path === path,
"nuxt-link-active": this.$route.path === path
};
},
logout() {
fb.currentUser = null;
firebase
@@ -697,6 +707,21 @@ export default {
this.$toast.info(this.$t("logged_out"), {
icon: "vpn_key"
});
},
nativeShare() {
if (navigator.share) {
navigator
.share({
title: "Postwoman",
text:
"Postwoman • A free, fast and beautiful API request builder - Web alternative to Postman - Helps you create requests faster, saving precious time on development.",
url: "https://postwoman.io/"
})
.then(() => {})
.catch(console.error);
} else {
// fallback
}
}
},
@@ -710,9 +735,9 @@ export default {
showExtensions: false,
showShortcuts: false,
showSupport: false,
firefoxExtInstalled: window.firefoxExtSendRequest,
chromeExtInstalled: window.chrome && hasChromeExtensionInstalled(),
fb
extensionInstalled: hasExtensionInstalled(),
fb,
navigatorShare: navigator.share
};
},
@@ -773,8 +798,7 @@ export default {
let showExtensionsToast =
localStorage.getItem("showExtensionsToast") === "yes";
if (
!this.firefoxExtInstalled &&
!this.chromeExtInstalled &&
!this.extensionInstalled &&
!showExtensionsToast
) {
setTimeout(() => {
@@ -801,6 +825,14 @@ export default {
});
}, 15000);
}
this._keyListener = function(e) {
if (e.key === "Escape") {
e.preventDefault();
this.showExtensions = this.showShortcuts = this.showSupport = false;
}
};
document.addEventListener("keydown", this._keyListener.bind(this));
})();
window.addEventListener("scroll", event => {
@@ -841,6 +873,10 @@ export default {
availableLocales() {
return this.$i18n.locales.filter(i => i.code !== this.$i18n.locale);
}
},
beforeDestroy() {
document.removeEventListener("keydown", this._keyListener);
}
};
</script>

View File

@@ -2,9 +2,9 @@
// TODO: Use these when rendering the pages (rather than just for head/meta tags...)
export const meta = {
name: "Postwoman",
shortDescription: "API request builder",
shortDescription: "A free, fast and beautiful API request builder",
description:
"The Postwoman API request builder helps you create your requests faster, saving you precious time on your development."
"Web alternative to Postman - Helps you create requests faster, saving precious time on development."
};
// Sets the base path for the router.
// Important for deploying to GitHub pages.
@@ -73,7 +73,7 @@ export default {
},
{
itemprop: "image",
content: `${routerBase.router.base}logo.jpg`
content: `https://postwoman.io/logo.jpg`
},
// Add to homescreen for Chrome on Android. Fallback for PWA (handled by nuxt)
{
@@ -129,7 +129,7 @@ export default {
},
{
property: "og:image",
content: `${routerBase.router.base}logo.jpg`
content: `https://postwoman.io/logo.jpg`
},
// Twitter
{

1204
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "postwoman",
"version": "1.5.0",
"version": "1.9.0",
"description": "A free, fast and beautiful API request builder",
"author": "liyasthomas",
"private": true,
@@ -14,33 +14,34 @@
"generate": "nuxt generate",
"e2e": "cypress run",
"e2e:open": "cypress open",
"dev:e2e": "start-server-and-test dev http://localhost:3000 e2e:open",
"test": "start-server-and-test dev http://localhost:3000 e2e"
"dev:e2e": "server-test dev :3000 e2e:open",
"test": "start-server-and-test start http-get://localhost:3000 e2e"
},
"dependencies": {
"@nuxtjs/axios": "^5.9.3",
"@nuxtjs/axios": "^5.9.5",
"@nuxtjs/google-analytics": "^2.2.3",
"@nuxtjs/google-tag-manager": "^2.3.1",
"@nuxtjs/pwa": "^3.0.0-beta.19",
"@nuxtjs/pwa": "^3.0.0-beta.20",
"@nuxtjs/robots": "^2.4.2",
"@nuxtjs/sitemap": "^2.0.1",
"@nuxtjs/toast": "^3.3.0",
"ace-builds": "^1.4.8",
"firebase": "^7.7.0",
"graphql": "^14.5.8",
"firebase": "^7.9.1",
"graphql": "^14.6.0",
"graphql-language-service-interface": "^2.3.3",
"nuxt": "^2.11.0",
"nuxt-i18n": "^6.5.0",
"v-tooltip": "^2.0.3",
"vue-virtual-scroll-list": "^1.4.4",
"vue-virtual-scroll-list": "^1.4.6",
"vuefire": "^2.2.1",
"vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.2.0",
"yargs-parser": "^16.1.0"
"yargs-parser": "^17.0.0"
},
"devDependencies": {
"cypress": "^3.8.3",
"cypress": "^4.0.2",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2",
"start-server-and-test": "^1.10.6"
"start-server-and-test": "^1.10.8"
}
}

View File

@@ -363,8 +363,8 @@ export default {
let file = this.$refs.collectionUpload.files[0];
if (file !== undefined && file !== null) {
let reader = new FileReader();
reader.onload = e => {
this.collectionJSON = e.target.result;
reader.onload = ({ target }) => {
this.collectionJSON = target.result;
};
reader.readAsText(file);
this.$toast.info(this.$t("file_imported"), {

View File

@@ -187,67 +187,21 @@
useWorker: false
}"
/>
<div class="flex-wrap">
<label>{{ $t("query_variables") }}</label>
<div>
<button
class="icon"
@click="variables = []"
v-tooltip.bottom="$t('clear')"
>
<i class="material-icons">clear_all</i>
</button>
</div>
</div>
<ul v-for="(variable, index) in variables" :key="index">
<li>
<input
:placeholder="$t('variable_count', { count: index + 1 })"
:name="'variable_key_' + index"
:value="variable.key"
@change="
$store.commit('setGQLVariableKey', {
index,
value: $event.target.value
})
"
autofocus
/>
</li>
<li>
<input
:placeholder="$t('value_count', { count: index + 1 })"
:name="'variable_value_' + index"
:value="variable.value"
@change="
$store.commit('setGQLVariableValue', {
index,
value: $event.target.value
})
"
autofocus
/>
</li>
<div>
<li>
<button
class="icon"
@click="removeQueryVariable(index)"
v-tooltip.bottom="$t('delete')"
>
<i class="material-icons">delete</i>
</button>
</li>
</div>
</ul>
<ul>
<li>
<button class="icon" @click="addQueryVariable">
<i class="material-icons">add</i>
<span>{{ $t("add_new") }}</span>
</button>
</li>
</ul>
</pw-section>
<pw-section class="yellow" label="Variables" ref="variables">
<Editor
v-model="variableString"
:lang="'json'"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
fontSize: '16px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false
}"
/>
</pw-section>
<pw-section class="purple" label="Response" ref="response">
@@ -391,6 +345,7 @@
import axios from "axios";
import * as gql from "graphql";
import textareaAutoHeight from "../directives/textareaAutoHeight";
import { commonHeaders } from "../functions/headers";
import AceEditor from "../components/ace-editor";
import QueryEditor from "../components/graphql/queryeditor";
import { sendNetworkRequest } from "../functions/network";
@@ -410,130 +365,7 @@ export default {
data() {
return {
schemaString: "",
commonHeaders: [
"WWW-Authenticate",
"Authorization",
"Proxy-Authenticate",
"Proxy-Authorization",
"Age",
"Cache-Control",
"Clear-Site-Data",
"Expires",
"Pragma",
"Warning",
"Accept-CH",
"Accept-CH-Lifetime",
"Early-Data",
"Content-DPR",
"DPR",
"Device-Memory",
"Save-Data",
"Viewport-Width",
"Width",
"Last-Modified",
"ETag",
"If-Match",
"If-None-Match",
"If-Modified-Since",
"If-Unmodified-Since",
"Vary",
"Connection",
"Keep-Alive",
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Expect",
"Max-Forwards",
"Cookie",
"Set-Cookie",
"Cookie2",
"Set-Cookie2",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Access-Control-Allow-Methods",
"Access-Control-Expose-Headers",
"Access-Control-Max-Age",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Origin",
"Service-Worker-Allowed",
"Timing-Allow-Origin",
"X-Permitted-Cross-Domain-Policies",
"DNT",
"Tk",
"Content-Disposition",
"Content-Length",
"Content-Type",
"Content-Encoding",
"Content-Language",
"Content-Location",
"Forwarded",
"X-Forwarded-For",
"X-Forwarded-Host",
"X-Forwarded-Proto",
"Via",
"Location",
"From",
"Host",
"Referer",
"Referrer-Policy",
"User-Agent",
"Allow",
"Server",
"Accept-Ranges",
"Range",
"If-Range",
"Content-Range",
"Cross-Origin-Opener-Policy",
"Cross-Origin-Resource-Policy",
"Content-Security-Policy",
"Content-Security-Policy-Report-Only",
"Expect-CT",
"Feature-Policy",
"Public-Key-Pins",
"Public-Key-Pins-Report-Only",
"Strict-Transport-Security",
"Upgrade-Insecure-Requests",
"X-Content-Type-Options",
"X-Download-Options",
"X-Frame-Options",
"X-Powered-By",
"X-XSS-Protection",
"Last-Event-ID",
"NEL",
"Ping-From",
"Ping-To",
"Report-To",
"Transfer-Encoding",
"TE",
"Trailer",
"Sec-WebSocket-Key",
"Sec-WebSocket-Extensions",
"Sec-WebSocket-Accept",
"Sec-WebSocket-Protocol",
"Sec-WebSocket-Version",
"Accept-Push-Policy",
"Accept-Signature",
"Alt-Svc",
"Date",
"Large-Allocation",
"Link",
"Push-Policy",
"Retry-After",
"Signature",
"Signed-Headers",
"Server-Timing",
"SourceMap",
"Upgrade",
"X-DNS-Prefetch-Control",
"X-Firefox-Spdy",
"X-Pingback",
"X-Requested-With",
"X-Robots-Tag",
"X-UA-Compatible"
],
commonHeaders,
queryFields: [],
mutationFields: [],
subscriptionFields: [],
@@ -564,14 +396,6 @@ export default {
this.$store.commit("setGQLState", { value, attribute: "headers" });
}
},
variables: {
get() {
return this.$store.state.gql.variables;
},
set(value) {
this.$store.commit("setGQLState", { value, attribute: "variables" });
}
},
gqlQueryString: {
get() {
return this.$store.state.gql.query;
@@ -580,6 +404,17 @@ export default {
this.$store.commit("setGQLState", { value, attribute: "query" });
}
},
variableString: {
get() {
return this.$store.state.gql.variablesJSONString;
},
set(value) {
this.$store.commit("setGQLState", {
value,
attribute: "variablesJSONString"
});
}
},
headerString() {
const result = this.headers
.filter(({ key }) => !!key)
@@ -596,7 +431,7 @@ export default {
const rootTypeName = this.resolveRootType(type).name;
const target = document.getElementById(`type_${rootTypeName}`);
if (target) {
if (target && this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED) {
target.scrollIntoView({
behavior: "smooth"
});
@@ -659,7 +494,8 @@ export default {
const startTime = Date.now();
this.$nuxt.$loading.start();
this.scrollInto("response");
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED &&
this.scrollInto("response");
try {
let headers = {};
@@ -667,18 +503,9 @@ export default {
headers[header.key] = header.value;
});
let variables = {};
let variables = JSON.parse(this.variableString);
const gqlQueryString = this.gqlQueryString;
this.variables.forEach(variable => {
// todo: better variable type validation
if (gqlQueryString.indexOf(`\$${variable.key}: Int`) > -1) {
variables[variable.key] = parseInt(variable.value);
} else if (gqlQueryString.indexOf(`\$${variable.key}: Float`) > -1) {
variables[variable.key] = parseFloat(variable.value);
} else {
variables[variable.key] = variable.value;
}
});
const reqOptions = {
method: "post",
@@ -711,7 +538,8 @@ export default {
async getSchema() {
const startTime = Date.now();
this.schemaString = this.$t("loading");
this.scrollInto("schema");
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED &&
this.scrollInto("schema");
// Start showing the loading bar as soon as possible.
// The nuxt axios module will hide it when the request is made.
@@ -835,13 +663,10 @@ export default {
downloadResponse() {
const dataToWrite = JSON.stringify(this.schemaString, null, 2);
const file = new Blob([dataToWrite], { type: "application/json" });
const a = document.createElement("a"),
url = URL.createObjectURL(file);
const a = document.createElement("a");
const url = URL.createObjectURL(file);
a.href = url;
a.download = (this.url + " on " + Date() + ".graphql").replace(
/\./g,
"[dot]"
);
a.download = `${this.url} on ${Date()}.graphql`.replace(/\./g, "[dot]");
document.body.appendChild(a);
a.click();
this.$refs.downloadResponse.innerHTML = this.doneButton;
@@ -879,30 +704,6 @@ export default {
});
// console.log(oldHeaders);
},
addQueryVariable(index) {
this.$store.commit("addGQLVariable", {
key: "",
value: ""
});
return false;
},
removeQueryVariable(index) {
const oldVariables = this.variables.slice();
this.$store.commit("removeGQLVariable", index);
this.$toast.error(this.$t("deleted"), {
icon: "delete",
action: {
text: this.$t("undo"),
duration: 4000,
onClick: (e, toastObject) => {
this.variables = oldVariables;
toastObject.remove();
}
}
});
// console.log(oldVariables);
},
scrollInto(view) {
this.$refs[view].$el.scrollIntoView({
behavior: "smooth"

File diff suppressed because it is too large Load Diff

View File

@@ -211,24 +211,20 @@ export default {
urlValid() {
const protocol = "^(wss?:\\/\\/)?";
const validIP = new RegExp(
protocol +
"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
`${protocol}(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`
);
const validHostname = new RegExp(
protocol +
"(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9/])$"
`${protocol}(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9/])$`
);
return validIP.test(this.url) || validHostname.test(this.url);
},
serverValid() {
const protocol = "^(https?:\\/\\/)?";
const validIP = new RegExp(
protocol +
"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
`${protocol}(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`
);
const validHostname = new RegExp(
protocol +
"(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9/])$"
`${protocol}(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9/])$`
);
return validIP.test(this.server) || validHostname.test(this.server);
}

View File

@@ -12,14 +12,14 @@
/>
<i v-else class="material-icons">account_circle</i>
<span>
{{ fb.currentUser.displayName || "Name not found" }}
{{ fb.currentUser.displayName || $t("nothing_found") }}
</span>
</button>
<br />
<button class="icon">
<i class="material-icons">email</i>
<span>
{{ fb.currentUser.email || "Email not found" }}
{{ fb.currentUser.email || $t("nothing_found") }}
</span>
</button>
<br />
@@ -38,7 +38,7 @@
{{ setting.value ? $t("enabled") : $t("disabled") }}
</pw-toggle>
</p>
<p v-if="fb.currentSettings.length == 0">
<p v-if="fb.currentSettings.length !== 3">
<button class="" @click="initSettings">
<i class="material-icons">sync</i>
<span>{{ $t("turn_on") + " " + $t("sync") }}</span>
@@ -99,7 +99,7 @@
:color="theme.color"
:name="theme.name"
class="bg"
></swatch>
/>
</span>
</div>
</li>
@@ -110,7 +110,7 @@
<div class="colors">
<span
:key="entry.color"
@click.prevent="setActiveColor(entry.color, entry.vibrant)"
@click="setActiveColor(entry.color, entry.vibrant)"
v-for="entry in colors"
>
<swatch
@@ -139,6 +139,36 @@
</span>
</li>
</ul>
<ul>
<li>
<span>
<pw-toggle
:on="settings.SCROLL_INTO_ENABLED"
@change="toggleSetting('SCROLL_INTO_ENABLED')"
>
{{ $t("scrollInto_use_toggle") }}
{{
settings.SCROLL_INTO_ENABLED ? $t("enabled") : $t("disabled")
}}
</pw-toggle>
</span>
</li>
</ul>
</pw-section>
<pw-section class="purple" :label="$t('extensions')" ref="extensions">
<ul>
<li>
<div class="flex-wrap">
<pw-toggle
:on="settings.EXTENSIONS_ENABLED"
@change="toggleSetting('EXTENSIONS_ENABLED')"
>
{{ $t("extensions_use_toggle") }}
</pw-toggle>
</div>
</li>
</ul>
</pw-section>
<pw-section class="blue" :label="$t('proxy')" ref="proxy">
@@ -192,7 +222,12 @@
{{ $t("postwoman_official_proxy_hosting") }}
<br />
{{ $t("read_the") }}
<a href="https://apollotv.xyz/legal" target="_blank" rel="noopener">
<a
class="link"
href="https://apollotv.xyz/legal"
target="_blank"
rel="noopener"
>
{{ $t("apollotv_privacy_policy") }} </a
>.
</p>
@@ -256,7 +291,7 @@ export default {
aceEditor: "vibrant_ink"
},
{
color: "var(--bg-color)",
color: "var(--ac-color)",
name: this.$t("auto_system"),
vibrant: window.matchMedia("(prefers-color-scheme: light)").matches,
class: "auto",
@@ -311,7 +346,13 @@ export default {
],
settings: {
THEME_CLASS: this.$store.state.postwoman.settings.THEME_CLASS || "",
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !==
"undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
THEME_CLASS: "",
THEME_COLOR: "",
THEME_TAB_COLOR: "",
THEME_COLOR_VIBRANT: true,
@@ -323,7 +364,13 @@ export default {
PROXY_URL:
this.$store.state.postwoman.settings.PROXY_URL ||
"https://postwoman.apollotv.xyz/",
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ""
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || "",
EXTENSIONS_ENABLED:
typeof this.$store.state.postwoman.settings.EXTENSIONS_ENABLED !==
"undefined"
? this.$store.state.postwoman.settings.EXTENSIONS_ENABLED
: true
},
doneButton: '<i class="material-icons">done</i>',
@@ -398,9 +445,9 @@ export default {
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
.then(({ additionalUserInfo }) => {
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
@@ -408,7 +455,8 @@ export default {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
fb.writeSettings("syncCollections", true);
fb.writeSettings("syncEnvironments", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
@@ -427,9 +475,9 @@ export default {
firebase
.auth()
.signInWithPopup(provider)
.then(res => {
if (res.additionalUserInfo.isNewUser) {
this.$toast.info(this.$t("turn_on") + " " + this.$t("sync"), {
.then(({ additionalUserInfo }) => {
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
@@ -437,7 +485,8 @@ export default {
text: this.$t("yes"),
onClick: (e, toastObject) => {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
fb.writeSettings("syncCollections", true);
fb.writeSettings("syncEnvironments", true);
this.$router.push({ path: "/settings" });
toastObject.remove();
}
@@ -456,16 +505,17 @@ export default {
},
initSettings() {
fb.writeSettings("syncHistory", true);
fb.writeSettings("syncCollections", false);
fb.writeSettings("syncCollections", true);
fb.writeSettings("syncEnvironments", true);
},
resetProxy(e) {
resetProxy({ target }) {
this.settings.PROXY_URL = `https://postwoman.apollotv.xyz/`;
e.target.innerHTML = this.doneButton;
target.innerHTML = this.doneButton;
this.$toast.info(this.$t("cleared"), {
icon: "clear_all"
});
setTimeout(
() => (e.target.innerHTML = '<i class="material-icons">clear_all</i>'),
() => (target.innerHTML = '<i class="material-icons">clear_all</i>'),
1000
);
}

View File

@@ -23,22 +23,6 @@ export default {
gql.headers[index].value = value;
},
addGQLVariable({ gql }, object) {
gql.variables.push(object);
},
removeGQLVariable({ gql }, index) {
gql.variables.splice(index, 1);
},
setGQLVariableKey({ gql }, { index, value }) {
gql.variables[index].key = value;
},
setGQLVariableValue({ gql }, { index, value }) {
gql.variables[index].value = value;
},
addHeaders({ request }, value) {
request.headers.push(value);
},

View File

@@ -1,6 +1,12 @@
import Vue from "vue";
export const SETTINGS_KEYS = [
/**
* Whether or not to enable scrolling to a specified element, when certain
* actions are triggered.
*/
"SCROLL_INTO_ENABLED",
/**
* The CSS class that should be applied to the root element.
* Essentially, the name of the background theme.
@@ -56,7 +62,13 @@ export const SETTINGS_KEYS = [
* An array of properties to exclude from the URL.
* e.g. 'auth'
*/
"URL_EXCLUDES"
"URL_EXCLUDES",
/**
* A boolean value indicating whether to use the browser extensions
* to run the requests
*/
"EXTENSIONS_ENABLED"
];
export const state = () => ({
@@ -68,6 +80,13 @@ export const state = () => ({
requests: []
}
],
environments: [
{
name: "My Environment Variables",
variables: []
}
],
editingEnvironment: {},
selectedRequest: {},
editingRequest: {}
});
@@ -96,6 +115,80 @@ export const mutations = {
settings[key] = value;
},
removeVariables({ editingEnvironment }, value) {
editingEnvironment.variables = value;
},
setEditingEnvironment(state, value) {
state.editingEnvironment = { ...value };
},
setVariableKey({ editingEnvironment }, { index, value }) {
editingEnvironment.variables[index].key = value;
},
setVariableValue({ editingEnvironment }, { index, value }) {
editingEnvironment.variables[index].value = testValue(value);
},
removeVariable({ editingEnvironment }, variables) {
editingEnvironment.variables = variables;
},
addVariable({ editingEnvironment }, value) {
editingEnvironment.variables.push(value);
},
replaceEnvironments(state, environments) {
state.environments = environments;
},
importAddEnvironments(state, { environments, confirmation }) {
const duplicateEnvironment = environments.some(
item => {
return state.environments.some(
item2 => {
return item.name.toLowerCase() === item2.name.toLowerCase();
});
}
);
if (duplicateEnvironment) {
this.$toast.info("Duplicate environment");
return;
};
state.environments = [...state.environments, ...environments];
let index = 0;
for (let environment of state.environments) {
environment.environmentIndex = index;
index += 1;
}
this.$toast.info(confirmation, {
icon: "folder_shared"
});
},
removeEnvironment({ environments }, environmentIndex) {
environments.splice(environmentIndex, 1);
},
saveEnvironment({ environments }, payload) {
const { environment, environmentIndex } = payload;
const { name } = environment;
const duplicateEnvironment = environments.length === 1
? false
: environments.some(
item =>
item.environmentIndex !== environmentIndex &&
item.name.toLowerCase() === name.toLowerCase()
);
if (duplicateEnvironment) {
this.$toast.info("Duplicate environment");
return;
}
environments[environmentIndex] = environment;
},
replaceCollections(state, collections) {
state.collections = collections;
},
@@ -112,7 +205,9 @@ export const mutations = {
addNewCollection({ collections }, collection) {
const { name } = collection;
const duplicateCollection = collections.some(item => item.name === name);
const duplicateCollection = collections.some(
item => item.name.toLowerCase() === name.toLowerCase()
);
if (duplicateCollection) {
this.$toast.info("Duplicate collection");
return;
@@ -131,9 +226,13 @@ export const mutations = {
},
editCollection({ collections }, payload) {
const { collection, collectionIndex } = payload;
const { name } = collection;
const duplicateCollection = collections.some(item => item.name === name);
const {
collection: { name },
collectionIndex
} = payload;
const duplicateCollection = collections.some(
item => item.name.toLowerCase() === name.toLowerCase()
);
if (duplicateCollection) {
this.$toast.info("Duplicate collection");
return;
@@ -317,3 +416,12 @@ export const mutations = {
state.selectedRequest = Object.assign({}, request);
}
};
function testValue(myValue) {
try {
return JSON.parse(myValue);
} catch(ex) {
// Now we know it's a string just leave it as a string value.
return myValue;
}
}

View File

@@ -1,8 +1,8 @@
export default () => ({
request: {
method: "GET",
url: "https://reqres.in",
path: "/api/users",
url: "https://httpbin.org",
path: "/get",
label: "",
auth: "None",
httpUser: "",
@@ -20,7 +20,7 @@ export default () => ({
gql: {
url: "https://rickandmortyapi.com/graphql",
headers: [],
variables: [],
variablesJSONString: "{}",
query: ""
},
oauth2: {

View File

@@ -1,7 +1,7 @@
describe('Visit home', () => {
it('Have a page title with "Postwoman"', () => {
cy.visit('/')
.get('title')
cy.visit('/', { retryOnStatusCodeFailure: true })
.get('title')
.should('contain','Postwoman')
})
})

View File

@@ -1,57 +1,34 @@
describe('Methods', () => {
const methods = [ 'POST', 'HEAD', 'POST', 'PUT', 'DELETE','OPTIONS', 'PATCH']
methods.forEach((method) => {
it(`Change the default method GET to ${method} with url query`, () => {
cy.visit(`/?method=${method}`)
.get('#method').should('have.value', method)
})
})
})
describe('Url and path', () => {
it('Change default url with query and reset default path to empty string and make a request to cat api', () => {
cy.seedAndVisit('catapi', '/?url=https://api.thecatapi.com&path=')
.get('#url').then((el) => expect(el.val() === 'https://api.thecatapi.com').to.equal(true))
.get("#path").then((el) => expect(el.val() === '').to.equal(true))
.get('#response-details-wrapper').should($wrapper => {
expect($wrapper).to.contain('FAKE Cat API')
})
})
})
describe('Authentication', () => {
it(`Change default auth 'None' to 'Basic' and set httpUser and httpPassword with url query`, () => {
it(`Change default auth user and pass with url`, () => {
cy.visit(`?&auth=Basic Auth&httpUser=foo&httpPassword=bar`, { retryOnStatusCodeFailure: true })
.get('#authentication').contains('Authentication').click()
.then(() => {
cy.get('input[name="http_basic_user"]', { timeout: 500 })
.invoke('val')
.then((user) => {
expect(user === 'foo').to.equal(true)
cy.log('Success! user === foo')
})
.get('input[name="http_basic_user"]', { timeout: 500 })
.invoke('val')
.then((user) => {
expect(user === 'foo').to.equal(true)
})
cy.get('input[name="http_basic_passwd"]')
.invoke('val')
.then((user) => {
expect(user === 'bar').to.equal(true)
cy.log('Success! password === bar')
})
})
.get('input[name="http_basic_passwd"]')
.invoke('val')
.then((pass) => {
expect(pass === 'bar').to.equal(true)
})
})
const base64Tkn = encodeURI(btoa('{"alg":"HS256", "typ": "JWT"}'))
it(`Change default auth 'None' to 'Bearer token' and set bearerToken with url query`, () => {
cy.visit(`/?auth=Bearer Token&bearerToken=${base64Tkn}`, { retryOnStatusCodeFailure: true })
.get('#authentication').contains('Authentication').click()
.then(() => {
cy.get('input[name="bearer_token"]', { timeout: 500 })
.invoke('val')
.then((tkn) => {
expect(tkn === base64Tkn).to.equal(true)
cy.log(`Success! input[name="bearer_token"] === ${base64Tkn}`)
})
})
})
it('Enable user and pass at url with toggler', () => {
cy.visit('/', { retryOnStatusCodeFailure: true })
.get('#auth')
.select('Basic Auth')
.get('input[name="http_basic_user"]', { timeout: 500 })
.type('foo')
.get('input[name="http_basic_passwd"]', { timeout: 500 })
.type('bar')
.url()
.should('not.contain', 'foo')
.should('not.contain', 'bar')
.get('.toggle')
.click()
.url()
.should('contain', 'foo')
.should('contain', 'bar')
})
})

View File

@@ -0,0 +1,21 @@
describe('Proxy disabled - local request', () => {
it('Change default url with query and make a request to local cat api', () => {
cy.seedAndVisit('catapi', '/?url=https://api.thecatapi.com&path=')
.get('#url').then((el) => expect(el.val() === 'https://api.thecatapi.com').to.equal(true))
.get("#path").then((el) => expect(el.val() === '').to.equal(true))
.get('#response-details-wrapper').should($wrapper => {
expect($wrapper).to.contain('FAKE Cat API')
})
})
})
describe('Proxy enabled - external request', () => {
it('Enable the proxy and make a request to the real cat api', () => {
cy.enableProxy('/?url=https://api.thecatapi.com&path=')
.get('#send').click()
.wait(500)
.get('#response-details-wrapper').should($wrapper => {
expect($wrapper).to.contain('Cat API')
})
})
})

View File

@@ -7,10 +7,23 @@
*/
Cypress.Commands.add('seedAndVisit', (seedData, path = '/', method = 'GET') => {
cy.server()
.route(method, 'https://api.thecatapi.com/', `fixture:${seedData}`).as(
'load'
)
.route(method, 'https://api.thecatapi.com/', `fixture:${seedData}`).as('load')
cy.visit(path)
.get('#send').click()
.wait('@load')
.wait('@load')
})
/**
* Creates cy.enableProxy() function
* This function will enable the proxy and navigate back to a given path
* @param { String } goBackPath The page go back
*/
Cypress.Commands.add('enableProxy', (goBackPath) => {
cy.visit('/settings')
.get('#proxy')
.find('.toggle')
.click( { force: true } )
.should('have.class', 'on')
.visit(goBackPath)
})