Merge remote-tracking branch 'origin/master'
@@ -16,7 +16,7 @@ node_js:
|
||||
- "12"
|
||||
|
||||
env:
|
||||
- DEPLOY_ENV=GH_PAGES
|
||||
- DEPLOY_ENV=POSTWOMAN_IO
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
47
README.md
@@ -23,8 +23,8 @@ When I wrote this, only God and I understood what I was doing. Now, only God kno
|
||||
|
||||
<div align="center">
|
||||
<br>
|
||||
<img src="static/screely.png" alt="postwoman" width="100%">
|
||||
<img src="static/screely2.png" alt="postwoman" width="100%">
|
||||
<img src="static/screenshot.gif" alt="postwoman" width="100%">
|
||||
<img src="static/screenshot2.png" alt="postwoman" width="100%">
|
||||
<br>
|
||||
</div>
|
||||
|
||||
@@ -63,6 +63,15 @@ _Customized themes are also synced with local session storage_
|
||||
- Offline support
|
||||
- Low RAM/memory and CPU usage
|
||||
|
||||
:rocket: **Request**: Retrieve data from a URL without having to do a full page refresh
|
||||
|
||||
- Choose `method`
|
||||
- Enter `URL`
|
||||
- Enter `Path`
|
||||
- Copy public "Share URL"
|
||||
- Generate request code for JavaScript XHR, Fetch, cURL
|
||||
- Copy generated request code to clipboard
|
||||
|
||||
:electric_plug: **Web Socket**: Establish full-duplex communication channels over a single TCP connection
|
||||
|
||||
- Send and receive data
|
||||
@@ -86,6 +95,9 @@ _Customized themes are also synced with local session storage_
|
||||
|
||||
:wave: **Responses**: Contains the status line, headers and the message/response body
|
||||
|
||||
- Copy response to clipboard
|
||||
- View preview for HTML responses
|
||||
|
||||
_HTML responses have "Preview HTML" feature_
|
||||
|
||||
:alarm_clock: **History**: Request entries are synced with local session storage to reuse with a single click
|
||||
@@ -103,7 +115,9 @@ _History entries can be deleted one-by-one or all together_
|
||||
|
||||
## Demo
|
||||
|
||||
[https://liyasthomas.github.io/postwoman](https://liyasthomas.github.io/postwoman)
|
||||
[https://postwoman.io](https://postwoman.io)
|
||||
|
||||
## Usage
|
||||
|
||||
1. Specify your request method
|
||||
2. Type in your API URL
|
||||
@@ -177,20 +191,27 @@ See the [CHANGELOG](CHANGELOG.md) file for details.
|
||||
* ([contributors](https://github.com/liyasthomas/postwoman/graphs/contributors))
|
||||
|
||||
### Contributors
|
||||
* [John Harker](https://github.com/NBTX)
|
||||
* [Andrew Bastin](https://github.com/AndrewBastin)
|
||||
* [Nick Palenchar](https://github.com/nickpalenchar)
|
||||
* [Abraham Williams](https://github.com/abraham)
|
||||
* [Nicholas La Roux](https://github.com/larouxn)
|
||||
* [RifqiAlAbqary](https://github.com/reefqi037)
|
||||
* [izerozlu](https://github.com/izerozlu)
|
||||
* [Thomas Yuba](https://github.com/yubathom)
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/NBTX"><img src="https://github.com/NBTX.png?size=100" width="100px;" alt="John Harker"/><br /><sub><b>John Harker</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=NBTX" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/izerozlu"><img src="https://github.com/izerozlu.png?size=100" width="100px;" alt="izerozlu"/><br /><sub><b>izerozlu</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=izerozlu" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/AndrewBastin"><img src="https://github.com/AndrewBastin.png?size=100" width="100px;" alt="Andrew Bastin"/><br /><sub><b>Andrew Bastin</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=AndrewBastin" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nickpalenchar"><img src="https://github.com/nickpalenchar.png?size=100" width="100px;" alt="Nick Palenchar"/><br /><sub><b>Nick Palenchar</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=nickpalenchar" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/yubathom"><img src="https://github.com/yubathom.png?size=100" width="100px;" alt="Thomas Yuba"/><br /><sub><b>Thomas Yuba</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=yubathom" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/larouxn"><img src="https://github.com/larouxn.png?size=100" width="100px;" alt="Nicholas La Roux"/><br /><sub><b>Nicholas La Roux</b></sub></a><br /><a href="https://github.com/liyasthomas/postwoman/commits?author=larouxn" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
See the list of [contributors](https://github.com/liyasthomas/postwoman/graphs/contributors) who participated in this project.
|
||||
|
||||
### Thanks
|
||||
* [Dribbble](https://dribbble.com)
|
||||
|
||||
See the list of [contributors](https://github.com/liyasthomas/postwoman/graphs/contributors) who participated in this project.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
@@ -10,7 +10,11 @@ $responsiveWidth: 720px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #4a4a4a;
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -78,6 +82,9 @@ body.sticky-footer footer {
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 4px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
@@ -88,9 +95,19 @@ button {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&.icon {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--ac-color);
|
||||
fill: var(--ac-color);
|
||||
|
||||
span {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&:not([disabled]):hover,
|
||||
&:not(.disabled):focus {
|
||||
background-color: transparent;
|
||||
background-color: var(--bg-color);
|
||||
box-shadow: inset 0 0 0 2px var(--ac-color);
|
||||
color: var(--ac-color);
|
||||
}
|
||||
@@ -122,51 +139,59 @@ fieldset.blue legend {
|
||||
}
|
||||
|
||||
fieldset.gray {
|
||||
border-color: #9B9B9B;
|
||||
border-color: #BCC2CD;
|
||||
}
|
||||
|
||||
fieldset.gray legend {
|
||||
color: #9B9B9B;
|
||||
color: #BCC2CD;
|
||||
}
|
||||
|
||||
fieldset.green {
|
||||
border-color: #B8E986;
|
||||
border-color: #50fa7b;
|
||||
}
|
||||
|
||||
fieldset.green legend {
|
||||
color: #B8E986;
|
||||
color: #50fa7b;
|
||||
}
|
||||
|
||||
fieldset.cyan {
|
||||
border-color: #50E3C2;
|
||||
border-color: #8be9fd;
|
||||
}
|
||||
|
||||
fieldset.cyan legend {
|
||||
color: #50E3C2;
|
||||
}
|
||||
|
||||
fieldset.blue-dark {
|
||||
border-color: #4A90E2;
|
||||
}
|
||||
|
||||
fieldset.blue-dark legend {
|
||||
color: #4A90E2;
|
||||
color: #8be9fd;
|
||||
}
|
||||
|
||||
fieldset.purple {
|
||||
border-color: #C198FB;
|
||||
border-color: #bd93f9;
|
||||
}
|
||||
|
||||
fieldset.purple legend {
|
||||
color: #C198FB;
|
||||
color: #bd93f9;
|
||||
}
|
||||
|
||||
fieldset.orange {
|
||||
border-color: #F5A623;
|
||||
border-color: #ffb86c;
|
||||
}
|
||||
|
||||
fieldset.orange legend {
|
||||
color: #F5A623;
|
||||
color: #ffb86c;
|
||||
}
|
||||
|
||||
fieldset.pink {
|
||||
border-color: #ff79c6;
|
||||
}
|
||||
|
||||
fieldset.pink legend {
|
||||
color: #ff79c6;
|
||||
}
|
||||
|
||||
fieldset.red {
|
||||
border-color: #ff5555;
|
||||
}
|
||||
|
||||
fieldset.red legend {
|
||||
color: #ff5555;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
@@ -187,12 +212,19 @@ pre {
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-family: monospace;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
select,
|
||||
input,
|
||||
option {
|
||||
height: 41px;
|
||||
|
||||
&:not([readonly]):hover,
|
||||
&:not([readonly]):focus {
|
||||
background-color: var(--bg-color);
|
||||
box-shadow: inset 0 0 0 2px var(--ac-color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
@@ -231,6 +263,11 @@ input[type="checkbox"] {
|
||||
background-color: var(--err-color);
|
||||
color: #b2b2b2;
|
||||
cursor: default;
|
||||
|
||||
&.icon {
|
||||
color: #b2b2b2;
|
||||
fill: #b2b2b2;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
@@ -258,6 +295,10 @@ ol li {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.show-on-small-screen {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (max-width: $responsiveWidth) {
|
||||
header div {
|
||||
display: flex;
|
||||
@@ -283,6 +324,10 @@ ol li {
|
||||
.hide-on-small-screen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-on-small-screen {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
#installPWA {
|
||||
@@ -349,11 +394,8 @@ fieldset#history {
|
||||
margin: 4px;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#response-details {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.covers-response {
|
||||
|
||||
@@ -7,37 +7,37 @@
|
||||
|
||||
// Dark is the default theme variant.
|
||||
:root {
|
||||
--bg-dark-color: #000000;
|
||||
--bg-dark-color: #44475a;
|
||||
// Background color
|
||||
--bg-color: #121212;
|
||||
// Auto-complete color
|
||||
--atc-color: #212121;
|
||||
--bg-color: #282a36;
|
||||
// Auto-complete color
|
||||
--atc-color: #3C4556;
|
||||
// Text color
|
||||
--fg-color: #FFF;
|
||||
--fg-color: #f8f8f2;
|
||||
|
||||
// Error color
|
||||
--err-color: #393939;
|
||||
--err-color: #3C4556;
|
||||
|
||||
// Active color
|
||||
--ac-color: #51FF0D;
|
||||
--ac-color: #50fa7b;
|
||||
// Active text color
|
||||
--act-color: #121212;
|
||||
--act-color: #282a36;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
--bg-dark-color: #ffffff;
|
||||
--bg-dark-color: #e1e4eb;
|
||||
// Background color
|
||||
--bg-color: #F6F8FA;
|
||||
// Auto-complete color
|
||||
--atc-color: #F1F1F1;
|
||||
--bg-color: #ebeef5;
|
||||
// Auto-complete color
|
||||
--atc-color: #e1e4eb;
|
||||
// Text color
|
||||
--fg-color: #121212;
|
||||
--fg-color: #5d5d5f;
|
||||
|
||||
// Error color
|
||||
--err-color: invert(#393939, 1);
|
||||
--err-color: invert(#3C4556, 1);
|
||||
|
||||
// Active color
|
||||
--ac-color: #51FF0D;
|
||||
--ac-color: #57b5f9;
|
||||
// Active text color
|
||||
--act-color: #121212;
|
||||
--act-color: #ebeef5;
|
||||
}
|
||||
|
||||
@@ -1,190 +1,186 @@
|
||||
<template>
|
||||
<div class="autocomplete-wrapper">
|
||||
<label>
|
||||
<slot />
|
||||
<input type="text"
|
||||
:placeholder="placeholder"
|
||||
v-model="value"
|
||||
@input="updateSuggestions"
|
||||
@keyup="updateSuggestions"
|
||||
@click="updateSuggestions"
|
||||
@keydown="handleKeystroke"
|
||||
ref="acInput"
|
||||
:spellcheck="spellcheck"
|
||||
:autocapitalize="spellcheck"
|
||||
:autocorrect="spellcheck">
|
||||
<div class="autocomplete-wrapper">
|
||||
<label>
|
||||
<slot />
|
||||
<input type="text" :placeholder="placeholder" v-model="value" @input="updateSuggestions" @keyup="updateSuggestions" @click="updateSuggestions" @keydown="handleKeystroke" ref="acInput" :spellcheck="spellcheck" :autocapitalize="spellcheck" :autocorrect="spellcheck">
|
||||
|
||||
<ul class="suggestions" v-if="suggestions.length > 0 && suggestionsVisible" :style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }">
|
||||
<li v-for="(suggestion, index) in suggestions" @click.prevent="forceSuggestion(suggestion)" :class="{ active: currentSuggestionIndex === index }">{{ suggestion }}</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
<ul class="suggestions" v-if="suggestions.length > 0 && suggestionsVisible" :style="{ transform: `translate(${suggestionsOffsetLeft}px, 0)` }">
|
||||
<li v-for="(suggestion, index) in suggestions" @click.prevent="forceSuggestion(suggestion)" :class="{ active: currentSuggestionIndex === index }">{{ suggestion }}</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.autocomplete-wrapper {
|
||||
.autocomplete-wrapper {
|
||||
|
||||
position: relative;
|
||||
position: relative;
|
||||
|
||||
input:focus + ul.suggestions, ul.suggestions:hover {
|
||||
display: block;
|
||||
}
|
||||
input:focus+ul.suggestions,
|
||||
ul.suggestions:hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul.suggestions {
|
||||
display: none;
|
||||
background-color: var(--atc-color);
|
||||
position: absolute;
|
||||
top: 90%;
|
||||
margin: 0 4px;
|
||||
left: 0;
|
||||
ul.suggestions {
|
||||
display: none;
|
||||
background-color: var(--atc-color);
|
||||
position: absolute;
|
||||
top: 90%;
|
||||
margin: 0 4px;
|
||||
left: 0;
|
||||
|
||||
padding: 0;
|
||||
border-radius: 0 0 4px 4px;
|
||||
z-index: 9999;
|
||||
transition: transform 200ms ease-out;
|
||||
padding: 0;
|
||||
border-radius: 0 0 4px 4px;
|
||||
z-index: 9999;
|
||||
transition: transform 200ms ease-out;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
padding: 10px 10px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
li {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
|
||||
&:hover, &.active {
|
||||
background-color: var(--ac-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--ac-color);
|
||||
color: var(--act-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const KEY_TAB = 9;
|
||||
const KEY_ESC = 27;
|
||||
const KEY_TAB = 9;
|
||||
const KEY_ESC = 27;
|
||||
|
||||
const KEY_ARROW_UP = 38;
|
||||
const KEY_ARROW_DOWN = 40;
|
||||
const KEY_ARROW_UP = 38;
|
||||
const KEY_ARROW_DOWN = 40;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
spellcheck: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false
|
||||
},
|
||||
export default {
|
||||
props: {
|
||||
spellcheck: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Start typing...',
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Start typing...',
|
||||
required: false
|
||||
},
|
||||
|
||||
source: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
|
||||
value: {}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value () {
|
||||
this.$emit('input', this.value);
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
value: "",
|
||||
|
||||
selectionStart: 0,
|
||||
suggestionsOffsetLeft: 0,
|
||||
currentSuggestionIndex: -1,
|
||||
suggestionsVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateSuggestions (event) {
|
||||
// Hide suggestions if ESC pressed.
|
||||
if(event.which && event.which === KEY_ESC){
|
||||
event.preventDefault();
|
||||
this.suggestionsVisible = false;
|
||||
this.currentSuggestionIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// As suggestions is a reactive property, this implicitly
|
||||
// causes suggestions to update.
|
||||
this.selectionStart = this.$refs.acInput.selectionStart;
|
||||
this.suggestionsOffsetLeft = (12 * this.selectionStart);
|
||||
this.suggestionsVisible = true;
|
||||
},
|
||||
|
||||
forceSuggestion (text) {
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
this.value = input + text;
|
||||
|
||||
this.selectionStart = this.value.length;
|
||||
this.suggestionsVisible = true;
|
||||
this.currentSuggestionIndex = -1;
|
||||
},
|
||||
|
||||
handleKeystroke (event) {
|
||||
if(event.which === KEY_ARROW_UP){
|
||||
event.preventDefault();
|
||||
|
||||
this.currentSuggestionIndex = this.currentSuggestionIndex - 1 >= 0
|
||||
? this.currentSuggestionIndex - 1
|
||||
: 0;
|
||||
}else if(event.which === KEY_ARROW_DOWN){
|
||||
event.preventDefault();
|
||||
|
||||
this.currentSuggestionIndex = this.currentSuggestionIndex < this.suggestions.length - 1
|
||||
? this.currentSuggestionIndex + 1
|
||||
: this.suggestions.length - 1;
|
||||
}
|
||||
|
||||
if(event.which === KEY_TAB){
|
||||
event.preventDefault();
|
||||
|
||||
let activeSuggestion = this.suggestions[this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0];
|
||||
if(activeSuggestion){
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
this.value = input + activeSuggestion;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Gets the suggestions list to be displayed under the input box.
|
||||
*
|
||||
* @returns {default.props.source|{type, required}}
|
||||
*/
|
||||
suggestions () {
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
|
||||
return this.source.filter((entry) => {
|
||||
return entry.toLowerCase().startsWith(input.toLowerCase())
|
||||
&& input.toLowerCase() !== entry.toLowerCase();
|
||||
})
|
||||
// Cut off the part that's already been typed.
|
||||
.map((entry) => entry.substring(this.selectionStart))
|
||||
// We only want the top 3 suggestions.
|
||||
.slice(0, 3);
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.updateSuggestions({
|
||||
target: this.$refs.acInput
|
||||
});
|
||||
source: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.$emit('input', this.value);
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: "application/json",
|
||||
selectionStart: 0,
|
||||
suggestionsOffsetLeft: 0,
|
||||
currentSuggestionIndex: -1,
|
||||
suggestionsVisible: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateSuggestions(event) {
|
||||
// Hide suggestions if ESC pressed.
|
||||
if (event.which && event.which === KEY_ESC) {
|
||||
event.preventDefault();
|
||||
this.suggestionsVisible = false;
|
||||
this.currentSuggestionIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// As suggestions is a reactive property, this implicitly
|
||||
// causes suggestions to update.
|
||||
this.selectionStart = this.$refs.acInput.selectionStart;
|
||||
this.suggestionsOffsetLeft = (12 * this.selectionStart);
|
||||
this.suggestionsVisible = true;
|
||||
},
|
||||
|
||||
forceSuggestion(text) {
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
this.value = input + text;
|
||||
|
||||
this.selectionStart = this.value.length;
|
||||
this.suggestionsVisible = true;
|
||||
this.currentSuggestionIndex = -1;
|
||||
},
|
||||
|
||||
handleKeystroke(event) {
|
||||
if (event.which === KEY_ARROW_UP) {
|
||||
event.preventDefault();
|
||||
|
||||
this.currentSuggestionIndex = this.currentSuggestionIndex - 1 >= 0 ?
|
||||
this.currentSuggestionIndex - 1 :
|
||||
0;
|
||||
} else if (event.which === KEY_ARROW_DOWN) {
|
||||
event.preventDefault();
|
||||
|
||||
this.currentSuggestionIndex = this.currentSuggestionIndex < this.suggestions.length - 1 ?
|
||||
this.currentSuggestionIndex + 1 :
|
||||
this.suggestions.length - 1;
|
||||
}
|
||||
|
||||
if (event.which === KEY_TAB) {
|
||||
event.preventDefault();
|
||||
|
||||
let activeSuggestion = this.suggestions[this.currentSuggestionIndex >= 0 ? this.currentSuggestionIndex : 0];
|
||||
if (activeSuggestion) {
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
this.value = input + activeSuggestion;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Gets the suggestions list to be displayed under the input box.
|
||||
*
|
||||
* @returns {default.props.source|{type, required}}
|
||||
*/
|
||||
suggestions() {
|
||||
let input = this.value.substring(0, this.selectionStart);
|
||||
|
||||
return this.source.filter((entry) => {
|
||||
return entry.toLowerCase().startsWith(input.toLowerCase()) &&
|
||||
input.toLowerCase() !== entry.toLowerCase();
|
||||
})
|
||||
// Cut off the part that's already been typed.
|
||||
.map((entry) => entry.substring(this.selectionStart))
|
||||
// We only want the top 3 suggestions.
|
||||
.slice(0, 3);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateSuggestions({
|
||||
target: this.$refs.acInput
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ul>
|
||||
<li id="filter-history">
|
||||
<label for="filter-history-input">Search History</label>
|
||||
<input id="filter-history-input" type="text" :disabled="history.length === 0 || isClearingHistory" v-model="filterText">
|
||||
<input id="filter-history-input" type="text" :readonly="history.length === 0 || isClearingHistory" v-model="filterText">
|
||||
</li>
|
||||
</ul>
|
||||
<virtual-list class="virtual-list" :class="{filled: filteredHistory.length}" :size="89" :remain="Math.min(5, filteredHistory.length)">
|
||||
@@ -25,18 +25,24 @@
|
||||
<label :for="'path#'+index">Path</label>
|
||||
<input :id="'path#'+index" type="text" readonly :value="entry.path">
|
||||
</li>
|
||||
<li>
|
||||
<label :for="'delete-button#'+index" class="hide-on-small-screen"> </label>
|
||||
<button :id="'delete-button#'+index" :disabled="isClearingHistory" @click="deleteHistory(entry)">
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label :for="'use-button#'+index" class="hide-on-small-screen"> </label>
|
||||
<button :id="'use-button#'+index" :disabled="isClearingHistory" @click="useHistory(entry)">
|
||||
Use
|
||||
</button>
|
||||
</li>
|
||||
<div class="show-on-small-screen">
|
||||
<li>
|
||||
<label :for="'delete-button#'+index" class="hide-on-small-screen"> </label>
|
||||
<button class="icon" :id="'delete-button#'+index" :disabled="isClearingHistory" @click="deleteHistory(entry)">
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
|
||||
<path d="M5.662 23l-5.369-5.365c-.195-.195-.293-.45-.293-.707 0-.256.098-.512.293-.707l14.929-14.928c.195-.194.451-.293.707-.293.255 0 .512.099.707.293l7.071 7.073c.196.195.293.451.293.708 0 .256-.097.511-.293.707l-11.216 11.219h5.514v2h-12.343zm3.657-2l-5.486-5.486-1.419 1.414 4.076 4.072h2.829zm6.605-17.581l-10.677 10.68 5.658 5.659 10.676-10.682-5.657-5.657z" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label :for="'use-button#'+index" class="hide-on-small-screen"> </label>
|
||||
<button class="icon" :id="'use-button#'+index" :disabled="isClearingHistory" @click="useHistory(entry)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M14.078 7.061l2.861 2.862-10.799 10.798-3.584.723.724-3.585 10.798-10.798zm0-2.829l-12.64 12.64-1.438 7.128 7.127-1.438 12.642-12.64-5.691-5.69zm7.105 4.277l2.817-2.82-5.691-5.689-2.816 2.817 5.69 5.692z" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</virtual-list>
|
||||
<ul :class="{hidden: filteredHistory.length != 0 || history.length === 0 }">
|
||||
@@ -47,7 +53,7 @@
|
||||
<ul>
|
||||
<li v-if="!isClearingHistory">
|
||||
<button id="clear-history-button" :disabled="history.length === 0" @click="enableHistoryClearing">
|
||||
Clear History
|
||||
Clear all
|
||||
</button>
|
||||
</li>
|
||||
<li v-else>
|
||||
@@ -147,7 +153,7 @@
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.virtual-list.filled {
|
||||
min-height: 430px;
|
||||
min-height: 380px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label.caption {
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label.toggle {
|
||||
@@ -46,6 +48,7 @@
|
||||
box-sizing: initial;
|
||||
padding: 0;
|
||||
margin: 10px 5px;
|
||||
cursor: pointer;
|
||||
|
||||
.handle {
|
||||
position: absolute;
|
||||
@@ -62,8 +65,6 @@
|
||||
|
||||
pointer-events: none;
|
||||
transition: $transition;
|
||||
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
&.on {
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
Install PWA
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bottom section of footer: version/author information -->
|
||||
<p class="align-center">
|
||||
<span v-if="version.name">{{ version.name }}
|
||||
<span v-if="version.hash">- {{ version.hash }}</span>
|
||||
<span v-if="version.name">
|
||||
<a v-bind:href="'https://github.com/liyasthomas/postwoman/releases/tag/' + version.name" target="_blank">{{version.name }}</a>
|
||||
<span v-if="version.hash">
|
||||
- <a v-bind:href="'https://github.com/liyasthomas/postwoman/commit/' + version.hash" target="_blank">{{ version.hash }}</a>
|
||||
</span>
|
||||
<span v-if="version.variant"> ({{ version.variant }})</span>
|
||||
• </span>by <a href="https://liyasthomas.web.app" target="_blank">Liyas Thomas 🦄</a>
|
||||
</p>
|
||||
@@ -158,7 +160,7 @@
|
||||
// Apply theme from settings.
|
||||
document.documentElement.className = this.$store.state.postwoman.settings.THEME_CLASS || '';
|
||||
// Load theme color data from settings, or use default color.
|
||||
let color = this.$store.state.postwoman.settings.THEME_COLOR || '#51FF0D';
|
||||
let color = this.$store.state.postwoman.settings.THEME_COLOR || '#50fa7b';
|
||||
let vibrant = this.$store.state.postwoman.settings.THEME_COLOR_VIBRANT;
|
||||
if (vibrant == null) vibrant = true;
|
||||
document.documentElement.style.setProperty('--ac-color', color);
|
||||
|
||||
250
nuxt.config.js
@@ -1,4 +1,3 @@
|
||||
|
||||
// Some helpful application constants.
|
||||
// TODO: Use these when rendering the pages (rather than just for head/meta tags...)
|
||||
export const meta = {
|
||||
@@ -6,10 +5,8 @@ export const meta = {
|
||||
shortDescription: "API request builder",
|
||||
description: "The Postwoman API request builder helps you create your requests faster, saving you precious time on your development."
|
||||
};
|
||||
|
||||
// Sets the base path for the router.
|
||||
// Important for deploying to GitHub pages.
|
||||
|
||||
// -- Travis includes the author in the repo slug,
|
||||
// so if there's a /, we need to get everything after it.
|
||||
let repoName = (process.env.TRAVIS_REPO_SLUG || '').split('/').pop();
|
||||
@@ -22,94 +19,195 @@ export const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? {
|
||||
base: '/'
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
mode: 'spa',
|
||||
/*
|
||||
** Headers of the page
|
||||
*/
|
||||
** Headers of the page
|
||||
*/
|
||||
head: {
|
||||
title: `${meta.name} \u2022 ${meta.shortDescription}`,
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, minimum-scale=1, shrink-to-fit=no, minimal-ui' },
|
||||
{ hid: 'description', name: 'description', content: meta.description || '' },
|
||||
{ name: 'keywords', content: 'postwoman, api, request, testing, tool, rest, websocket'},
|
||||
|
||||
{ name: 'X-UA-Compatible', content: "IE=edge, chrome=1" },
|
||||
{ itemprop: "name", content: `${meta.name} \u2022 ${meta.shortDescription}` },
|
||||
{ itemprop: "description", content: meta.description },
|
||||
{ itemprop: "image", content: `${routerBase.router.base}icons/icon-192x192.png` },
|
||||
|
||||
{
|
||||
charset: 'utf-8'
|
||||
},
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1, minimum-scale=1, shrink-to-fit=no, minimal-ui'
|
||||
},
|
||||
{
|
||||
hid: 'description',
|
||||
name: 'description',
|
||||
content: meta.description || ''
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content: 'postwoman, postwoman chrome, postwoman online, postwoman for mac, postwoman app, postwoman for windows, postwoman google chrome, postwoman chrome app, get postwoman, postwoman web, postwoman android, postwoman app for chrome, postwoman mobile app, postwoman web app, api, request, testing, tool, rest, websocket'
|
||||
},
|
||||
{
|
||||
name: 'X-UA-Compatible',
|
||||
content: "IE=edge, chrome=1"
|
||||
},
|
||||
{
|
||||
itemprop: "name",
|
||||
content: `${meta.name} \u2022 ${meta.shortDescription}`
|
||||
},
|
||||
{
|
||||
itemprop: "description",
|
||||
content: meta.description
|
||||
},
|
||||
{
|
||||
itemprop: "image",
|
||||
content: `${routerBase.router.base}icons/icon-192x192.png`
|
||||
},
|
||||
// Add to homescreen for Chrome on Android. Fallback for PWA (handled by nuxt)
|
||||
{ name: 'application-name', content: meta.name },
|
||||
|
||||
{
|
||||
name: 'application-name',
|
||||
content: meta.name
|
||||
},
|
||||
// Add to homescreen for Safari on iOS
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'apple-mobile-web-app-title', content: meta.name },
|
||||
|
||||
{
|
||||
name: 'apple-mobile-web-app-capable',
|
||||
content: 'yes'
|
||||
},
|
||||
{
|
||||
name: 'apple-mobile-web-app-status-bar-style',
|
||||
content: 'black-translucent'
|
||||
},
|
||||
{
|
||||
name: 'apple-mobile-web-app-title',
|
||||
content: meta.name
|
||||
},
|
||||
// Windows phone tile icon
|
||||
{ name: 'msapplication-TileImage', content: `${routerBase.router.base}icons/icon-144x144.png` },
|
||||
{ name: 'msapplication-TileColor', content: '#121212' },
|
||||
{ name: 'msapplication-tap-highlight', content: 'no' },
|
||||
|
||||
{
|
||||
name: 'msapplication-TileImage',
|
||||
content: `${routerBase.router.base}icons/icon-144x144.png`
|
||||
},
|
||||
{
|
||||
name: 'msapplication-TileColor',
|
||||
content: '#282a36'
|
||||
},
|
||||
{
|
||||
name: 'msapplication-tap-highlight',
|
||||
content: 'no'
|
||||
},
|
||||
// OpenGraph
|
||||
{ property: 'og:site_name', content: meta.name },
|
||||
{ property: 'og:url', content: 'https://liyasthomas.github.io/postwoman' },
|
||||
{ property: 'og:type', content: 'website' },
|
||||
{ property: 'og:title', content: `${meta.name} \u2022 ${meta.shortDescription}` },
|
||||
{ property: 'og:description', content: meta.description },
|
||||
{ property: 'og:image', content: `${routerBase.router.base}icons/icon-144x144.png` },
|
||||
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: meta.name
|
||||
},
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://postwoman.io'
|
||||
},
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website'
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: `${meta.name} \u2022 ${meta.shortDescription}`
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
content: meta.description
|
||||
},
|
||||
{
|
||||
property: 'og:image',
|
||||
content: `${routerBase.router.base}icons/icon-144x144.png`
|
||||
},
|
||||
// Twitter
|
||||
{ name: 'twitter:card', content: "summary" },
|
||||
{ name: 'twitter:site', content: "@liyasthomas" },
|
||||
{ name: 'twitter:creator', content: "@liyasthomas" },
|
||||
{ name: 'twitter:url', content: "https://liyasthomas.github.io/postwoman" },
|
||||
{ name: 'twitter:title', content: meta.name },
|
||||
{ name: 'twitter:description', content: meta.shortDescription },
|
||||
{ name: 'twitter:image', content: `${routerBase.router.base}icons/icon-144x144.png` },
|
||||
|
||||
{
|
||||
name: 'twitter:card',
|
||||
content: "summary"
|
||||
},
|
||||
{
|
||||
name: 'twitter:site',
|
||||
content: "@liyasthomas"
|
||||
},
|
||||
{
|
||||
name: 'twitter:creator',
|
||||
content: "@liyasthomas"
|
||||
},
|
||||
{
|
||||
name: 'twitter:url',
|
||||
content: "https://postwoman.io"
|
||||
},
|
||||
{
|
||||
name: 'twitter:title',
|
||||
content: meta.name
|
||||
},
|
||||
{
|
||||
name: 'twitter:description',
|
||||
content: meta.shortDescription
|
||||
},
|
||||
{
|
||||
name: 'twitter:image',
|
||||
content: `${routerBase.router.base}icons/icon-144x144.png`
|
||||
},
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: `${routerBase.router.base}favicon.ico`
|
||||
},
|
||||
// Home-screen icons (iOS)
|
||||
{ rel: 'apple-touch-icon', href: `${routerBase.router.base}icons/icon-48x48.png` },
|
||||
{ rel: 'apple-touch-icon', sizes: '72x72', href: `${routerBase.router.base}icons/icon-72x72.png` },
|
||||
{ rel: 'apple-touch-icon', sizes: '96x96', href: `${routerBase.router.base}icons/icon-96x96.png` },
|
||||
{ rel: 'apple-touch-icon', sizes: '144x144', href: `${routerBase.router.base}icons/icon-144x144.png` },
|
||||
{ rel: 'apple-touch-icon', sizes: '192x192', href: `${routerBase.router.base}icons/icon-192x192.png` },
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
href: `${routerBase.router.base}icons/icon-48x48.png`
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '72x72',
|
||||
href: `${routerBase.router.base}icons/icon-72x72.png`
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '96x96',
|
||||
href: `${routerBase.router.base}icons/icon-96x96.png`
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '144x144',
|
||||
href: `${routerBase.router.base}icons/icon-144x144.png`
|
||||
},
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
sizes: '192x192',
|
||||
href: `${routerBase.router.base}icons/icon-192x192.png`
|
||||
},
|
||||
]
|
||||
},
|
||||
/*
|
||||
** Customize the progress-bar color
|
||||
*/
|
||||
loading: { color: 'var(--ac-color)' },
|
||||
|
||||
** Customize the progress-bar color
|
||||
*/
|
||||
loading: {
|
||||
color: 'var(--ac-color)'
|
||||
},
|
||||
/*
|
||||
** Global CSS
|
||||
*/
|
||||
** Global CSS
|
||||
*/
|
||||
css: [
|
||||
'@/assets/css/themes.scss',
|
||||
'@/assets/css/fonts.scss',
|
||||
'@/assets/css/styles.scss'
|
||||
],
|
||||
/*
|
||||
** Plugins to load before mounting the App
|
||||
*/
|
||||
** Plugins to load before mounting the App
|
||||
*/
|
||||
plugins: [
|
||||
{ src: '~/plugins/vuex-persist' }
|
||||
{
|
||||
src: '~/plugins/vuex-persist'
|
||||
}
|
||||
],
|
||||
/*
|
||||
** Nuxt.js dev-modules
|
||||
*/
|
||||
** Nuxt.js dev-modules
|
||||
*/
|
||||
buildModules: [
|
||||
],
|
||||
/*
|
||||
** Nuxt.js modules
|
||||
*/
|
||||
** Nuxt.js modules
|
||||
*/
|
||||
modules: [
|
||||
// See https://goo.gl/OOhYW5
|
||||
['@nuxtjs/pwa', {
|
||||
@@ -117,48 +215,40 @@ export default {
|
||||
name: meta.name,
|
||||
short_name: meta.name,
|
||||
description: meta.shortDescription,
|
||||
|
||||
display: "standalone",
|
||||
theme_color: "#121212",
|
||||
background_color: "#121212",
|
||||
|
||||
theme_color: "#282a36",
|
||||
background_color: "#282a36",
|
||||
start_url: `${routerBase.router.base}`,
|
||||
icons: ((sizes) => {
|
||||
let icons = [];
|
||||
|
||||
for(let size of sizes){
|
||||
for (let size of sizes) {
|
||||
icons.push({
|
||||
"src": `${routerBase.router.base}icons/icon-${size}x${size}.png`,
|
||||
"type": "image/png",
|
||||
"sizes": `${size}x${size}`
|
||||
});
|
||||
}
|
||||
|
||||
return icons;
|
||||
})([48, 72, 96, 144, 192, 512])
|
||||
}
|
||||
}],
|
||||
|
||||
['@nuxtjs/axios']
|
||||
],
|
||||
|
||||
/*
|
||||
** Build configuration
|
||||
*/
|
||||
** Build configuration
|
||||
*/
|
||||
build: {
|
||||
/*
|
||||
** You can extend webpack config here
|
||||
*/
|
||||
extend (config, ctx) {
|
||||
}
|
||||
** You can extend webpack config here
|
||||
*/
|
||||
extend(config, ctx) {}
|
||||
},
|
||||
|
||||
/*
|
||||
** Generate configuration
|
||||
*/
|
||||
** Generate configuration
|
||||
*/
|
||||
generate: {
|
||||
fallback: true
|
||||
},
|
||||
|
||||
/*
|
||||
** Router configuration
|
||||
*/
|
||||
|
||||
224
pages/index.vue
@@ -37,6 +37,27 @@
|
||||
<label for="path">Path</label>
|
||||
<input @keyup.enter="isValidURL ? sendRequest() : null" id="path" v-model="path">
|
||||
</li>
|
||||
<li>
|
||||
<label class="hide-on-small-screen" for="copyRequest"> </label>
|
||||
<button class="icon" @click="copyRequest" id="copyRequest" ref="copyRequest" :disabled="!isValidURL">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
|
||||
<path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
|
||||
</svg>
|
||||
<span>Share URL</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label class="hide-on-small-screen" for="code"> </label>
|
||||
<button class="icon" id="code" name="code" v-on:click="isHidden = !isHidden" :disabled="!isValidURL">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="isHidden">
|
||||
<path d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 5c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2zm0-2c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="!isHidden">
|
||||
<path d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z" />
|
||||
</svg>
|
||||
<span>{{ isHidden ? 'Show Code' : 'Hide Code' }}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label class="hide-on-small-screen" for="action"> </label>
|
||||
<button :disabled="!isValidURL" @click="sendRequest" class="show" id="action" name="action" ref="sendButton">
|
||||
@@ -45,7 +66,35 @@
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
<pw-section class="blue-dark" label="Request Body" v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
|
||||
<pw-section class="blue" label="Request Code" ref="requestCode" v-if="!isHidden">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="requestType">Request Type</label>
|
||||
<select name="requestType" v-model="requestType">
|
||||
<option>JavaScript XHR</option>
|
||||
<option>Fetch</option>
|
||||
<option>cURL</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<div class="flex-wrap">
|
||||
<label for="generatedCode">Generated Code</label>
|
||||
<div>
|
||||
<button class="icon" @click="copyRequestCode" name="copyRequestCode" ref="copyRequestCode">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
|
||||
<path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
|
||||
</svg>
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea ref="generatedCode" name="generatedCode" style="font-family: monospace;" rows="16">{{requestCode}}</textarea>
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
<pw-section class="blue" label="Request Body" v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
|
||||
<ul>
|
||||
<li>
|
||||
<autocomplete :source="validContentTypes" :spellcheck="false" v-model="contentType">Content Type
|
||||
@@ -61,11 +110,11 @@
|
||||
<ol v-for="(param, index) in bodyParams">
|
||||
<li>
|
||||
<label :for="'bparam'+index">Key {{index + 1}}</label>
|
||||
<input :name="'bparam'+index" v-model="param.key">
|
||||
<input :name="'bparam'+index" v-model="param.key" @keyup.prevent="setRouteQueryState">
|
||||
</li>
|
||||
<li>
|
||||
<label :for="'bvalue'+index">Value {{index + 1}}</label>
|
||||
<input :name="'bvalue'+index" v-model="param.value">
|
||||
<input :name="'bvalue'+index" v-model="param.value" @keyup.prevent="setRouteQueryState">
|
||||
</li>
|
||||
<li>
|
||||
<label class="hide-on-small-screen" for="request"> </label>
|
||||
@@ -107,16 +156,28 @@
|
||||
<div class="flex-wrap">
|
||||
<label for="body">response</label>
|
||||
<div>
|
||||
<button class="block" @click="copyRequest" name="copyRequest" v-if="isValidURL">Copy Request URL</button>
|
||||
<button @click="copyResponse" name="copyResponse" v-if="response.body">Copy Response</button>
|
||||
<button class="icon" @click="copyResponse" name="copyResponse" ref="copyResponse" v-if="response.body">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
|
||||
<path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" />
|
||||
</svg>
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="response-details-wrapper">
|
||||
<textarea id="response-details" name="body" readonly rows="16">{{response.body || '(waiting to send request)'}}</textarea>
|
||||
<textarea ref="responseBody" name="body" readonly rows="16">{{response.body || '(waiting to send request)'}}</textarea>
|
||||
<iframe :class="{hidden: !previewEnabled}" class="covers-response" ref="previewFrame" src="about:blank"></iframe>
|
||||
</div>
|
||||
<div class="align-right" v-if="response.body && responseType === 'text/html'">
|
||||
<button @click.prevent="togglePreview">{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</button>
|
||||
<button class="icon" @click.prevent="togglePreview">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="!previewEnabled">
|
||||
<path d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 5c1.103 0 2 .897 2 2s-.897 2-2 2-2-.897-2-2 .897-2 2-2zm0-2c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" v-if="previewEnabled">
|
||||
<path d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z" />
|
||||
</svg>
|
||||
<span>{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -153,11 +214,11 @@
|
||||
<ol v-for="(header, index) in headers">
|
||||
<li>
|
||||
<label :for="'header'+index">Key {{index + 1}}</label>
|
||||
<input :name="'header'+index" v-model="header.key">
|
||||
<input :name="'header'+index" v-model="header.key" @keyup.prevent="setRouteQueryState">
|
||||
</li>
|
||||
<li>
|
||||
<label :for="'value'+index">Value {{index + 1}}</label>
|
||||
<input :name="'value'+index" v-model="header.value">
|
||||
<input :name="'value'+index" v-model="header.value" @keyup.prevent="setRouteQueryState">
|
||||
</li>
|
||||
<li>
|
||||
<label class="hide-on-small-screen" for="header"> </label>
|
||||
@@ -177,7 +238,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
<pw-section class="cyan" collapsed label="Parameters">
|
||||
<pw-section class="pink" collapsed label="Parameters">
|
||||
<ol v-for="(param, index) in params">
|
||||
<li>
|
||||
<label :for="'param'+index">Key {{index + 1}}</label>
|
||||
@@ -278,6 +339,8 @@
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
copyButton: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z" /></svg>',
|
||||
copiedButton: '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path d="M22 2v20h-20v-20h20zm2-2h-24v24h24v-24zm-5.541 8.409l-1.422-1.409-7.021 7.183-3.08-2.937-1.395 1.435 4.5 4.319 8.418-8.591z"/></svg>',
|
||||
method: 'GET',
|
||||
url: 'https://reqres.in',
|
||||
auth: 'None',
|
||||
@@ -291,6 +354,8 @@
|
||||
rawParams: '',
|
||||
rawInput: false,
|
||||
contentType: 'application/json',
|
||||
requestType: 'JavaScript XHR',
|
||||
isHidden: true,
|
||||
response: {
|
||||
status: '',
|
||||
headers: '',
|
||||
@@ -323,6 +388,10 @@
|
||||
watch: {
|
||||
contentType(val) {
|
||||
this.rawInput = !this.knownContentTypes.includes(val);
|
||||
},
|
||||
rawInput (status) {
|
||||
if (status && this.rawParams === '') this.rawParams = '{}'
|
||||
else this.setRouteQueryState()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -386,6 +455,89 @@
|
||||
},
|
||||
responseType() {
|
||||
return (this.response.headers['content-type'] || '').split(';')[0].toLowerCase();
|
||||
},
|
||||
requestCode() {
|
||||
if (this.requestType == 'JavaScript XHR') {
|
||||
var requestString = []
|
||||
requestString.push('const xhr = new XMLHttpRequest()');
|
||||
const user = this.auth === 'Basic' ? this.httpUser : null
|
||||
const pswd = this.auth === 'Basic' ? this.httpPassword : null
|
||||
requestString.push('xhr.open(' + this.method + ', ' + this.url + this.path + this.queryString + ', true, ' + user + ', ' + pswd + ')');
|
||||
if (this.auth === 'Bearer Token') {
|
||||
requestString.push("xhr.setRequestHeader('Authorization', 'Bearer ' + " + this.bearerToken + ")");
|
||||
}
|
||||
if (this.headers) {
|
||||
this.headers.forEach(function(element) {
|
||||
requestString.push('xhr.setRequestHeader(' + element.key + ', ' + element.value + ')');
|
||||
})
|
||||
}
|
||||
if (this.method === 'POST' || this.method === 'PUT') {
|
||||
const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
|
||||
requestString.push("xhr.setRequestHeader('Content-Length', " + requestBody.length + ")")
|
||||
requestString.push("xhr.setRequestHeader('Content-Type', `" + this.contentType + "; charset=utf-8`)")
|
||||
requestString.push("xhr.send(" + requestBody + ")")
|
||||
} else {
|
||||
requestString.push('xhr.send()')
|
||||
}
|
||||
return requestString.join('\n');
|
||||
} else if (this.requestType == 'Fetch') {
|
||||
var requestString = [];
|
||||
var headers = [];
|
||||
requestString.push('fetch(' + this.url + this.path + this.queryString + ', {\n')
|
||||
requestString.push(' method: "' + this.method + '",\n')
|
||||
if (this.auth === 'Basic') {
|
||||
var basic = this.httpUser + ':' + this.httpPassword;
|
||||
headers.push(' "Authorization": "Basic ' + window.btoa(unescape(encodeURIComponent(basic))) + ',\n')
|
||||
} else if (this.auth === 'Bearer Token') {
|
||||
headers.push(' "Authorization": "Bearer Token ' + this.bearerToken + ',\n')
|
||||
}
|
||||
if (this.method === 'POST' || this.method === 'PUT') {
|
||||
const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
|
||||
requestString.push(' body: ' + requestBody + ',\n')
|
||||
headers.push(' "Content-Length": ' + requestBody.length + ',\n')
|
||||
headers.push(' "Content-Type": "' + this.contentType + '; charset=utf-8",\n')
|
||||
}
|
||||
if (this.headers) {
|
||||
this.headers.forEach(function(element) {
|
||||
headers.push(' "' + element.key + '": "' + element.value + '",\n');
|
||||
})
|
||||
}
|
||||
headers = headers.join('').slice(0, -3);
|
||||
requestString.push(' headers: {\n' + headers + '\n },\n')
|
||||
requestString.push(' credentials: "same-origin"\n')
|
||||
requestString.push(')}).then(function(response) {\n')
|
||||
requestString.push(' response.status\n')
|
||||
requestString.push(' response.statusText\n')
|
||||
requestString.push(' response.headers\n')
|
||||
requestString.push(' response.url\n\n')
|
||||
requestString.push(' return response.text()\n')
|
||||
requestString.push(')}, function(error) {\n')
|
||||
requestString.push(' error.message\n')
|
||||
requestString.push(')}')
|
||||
return requestString.join('');
|
||||
} else if (this.requestType == 'cURL') {
|
||||
var requestString = [];
|
||||
requestString.push('curl -X ' + this.method + ' \\\n')
|
||||
requestString.push(" '" + this.url + this.path + this.queryString + "' \\\n")
|
||||
if (this.auth === 'Basic') {
|
||||
var basic = this.httpUser + ':' + this.httpPassword;
|
||||
requestString.push(" -H 'Authorization: Basic " + window.btoa(unescape(encodeURIComponent(basic))) + "' \\\n")
|
||||
} else if (this.auth === 'Bearer Token') {
|
||||
requestString.push(" -H 'Authorization: Bearer Token " + this.bearerToken + "' \\\n")
|
||||
}
|
||||
if (this.headers) {
|
||||
this.headers.forEach(function(element) {
|
||||
requestString.push(" -H '" + element.key + ": " + element.value + "' \\\n");
|
||||
})
|
||||
}
|
||||
if (this.method === 'POST' || this.method === 'PUT') {
|
||||
const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
|
||||
requestString.push(" -H 'Content-Length: " + requestBody.length + "' \\\n")
|
||||
requestString.push(" -H 'Content-Type: " + this.contentType + "; charset=utf-8' \\\n")
|
||||
requestString.push(" -d '" + requestBody + "' \\\n")
|
||||
}
|
||||
return requestString.join('').slice(0, -4);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -561,17 +713,38 @@
|
||||
}
|
||||
},
|
||||
copyRequest() {
|
||||
var dummy = document.createElement('input');
|
||||
document.body.appendChild(dummy);
|
||||
dummy.value = window.location.href;
|
||||
dummy.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(dummy);
|
||||
if (navigator.share) {
|
||||
let time = new Date().toLocaleTimeString();
|
||||
let date = new Date().toLocaleDateString();
|
||||
navigator.share({
|
||||
text: `Postwoman • API request builder at ${time} on ${date}`,
|
||||
url: window.location.href
|
||||
}).then(() => {
|
||||
// console.log('Thanks for sharing!');
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
this.$refs.copyRequest.innerHTML = this.copiedButton + '<span>Copied</span>';
|
||||
var dummy = document.createElement('input');
|
||||
document.body.appendChild(dummy);
|
||||
dummy.value = window.location.href;
|
||||
dummy.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(dummy);
|
||||
setTimeout(() => this.$refs.copyRequest.innerHTML = this.copyButton + '<span>Share URL</span>', 1500)
|
||||
}
|
||||
},
|
||||
copyRequestCode() {
|
||||
this.$refs.copyRequestCode.innerHTML = this.copiedButton + '<span>Copied</span>';
|
||||
this.$refs.generatedCode.select();
|
||||
document.execCommand("copy");
|
||||
setTimeout(() => this.$refs.copyRequestCode.innerHTML = this.copyButton + '<span>Copy</span>', 1500)
|
||||
},
|
||||
copyResponse() {
|
||||
var copyText = document.getElementById("response-details");
|
||||
copyText.select();
|
||||
this.$refs.copyResponse.innerHTML = this.copiedButton + '<span>Copied</span>';
|
||||
this.$refs.responseBody.select();
|
||||
document.execCommand("copy");
|
||||
setTimeout(() => this.$refs.copyResponse.innerHTML = this.copyButton + '<span>Copy</span>', 1500)
|
||||
},
|
||||
togglePreview() {
|
||||
this.previewEnabled = !this.previewEnabled;
|
||||
@@ -601,14 +774,19 @@
|
||||
} else return ''
|
||||
}
|
||||
let flats = ['method', 'url', 'path', 'auth', 'httpUser', 'httpPassword', 'bearerToken', 'contentType'].map(item => flat(item))
|
||||
let deeps = ['headers', 'params', 'bodyParams'].map(item => deep(item))
|
||||
this.$router.replace('/?' + flats.concat(deeps).join('').slice(0, -1))
|
||||
let deeps = ['headers', 'params'].map(item => deep(item))
|
||||
let bodyParams = this.rawInput ? [flat('rawParams')] : [deep('bodyParams')];
|
||||
|
||||
this.$router.replace('/?' + flats.concat(deeps, bodyParams).join('').slice(0, -1))
|
||||
},
|
||||
setRouteQueries(queries) {
|
||||
if (typeof(queries) !== 'object') throw new Error('Route query parameters must be a Object')
|
||||
for (const key in queries) {
|
||||
if (key === 'headers' || key === 'params' || key === 'bodyParams') this[key] = JSON.parse(queries[key])
|
||||
else if (typeof(this[key]) === 'string') this[key] = queries[key];
|
||||
if (key === 'rawParams') {
|
||||
this.rawInput = true
|
||||
this.rawParams = queries['rawParams']
|
||||
} else if (typeof(this[key]) === 'string') this[key] = queries[key]
|
||||
}
|
||||
},
|
||||
observeRequestButton() {
|
||||
@@ -665,11 +843,11 @@
|
||||
vm.headers,
|
||||
vm.params,
|
||||
vm.bodyParams,
|
||||
vm.contentType
|
||||
vm.contentType,
|
||||
vm.rawParams
|
||||
], val => {
|
||||
this.setRouteQueryState()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<pw-section class="blue" label="Theme">
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Background</h3>
|
||||
<div class="backgrounds">
|
||||
<div class="page">
|
||||
<pw-section class="cyan" label="Theme">
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Background</h3>
|
||||
<div class="backgrounds">
|
||||
<span :key="theme.class" @click="applyTheme(theme.class)" v-for="theme in themes">
|
||||
<swatch :active="settings.THEME_CLASS === theme.class" :class="{ vibrant: theme.vibrant }" :color="theme.color" :name="theme.name"></swatch>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Color</h3>
|
||||
<div class="colors">
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Color</h3>
|
||||
<div class="colors">
|
||||
<span :key="entry.color" @click.prevent="setActiveColor(entry.color, entry.vibrant)" v-for="entry in colors">
|
||||
<swatch :active="settings.THEME_COLOR === entry.color.toUpperCase()" :class="{ vibrant: entry.vibrant }" :color="entry.color" :name="entry.name" />
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Frames</h3>
|
||||
<pw-toggle :on="!settings.DISABLE_FRAME_COLORS" @change="toggleSetting('DISABLE_FRAME_COLORS')">
|
||||
Multi-color {{ settings.DISABLE_FRAME_COLORS ? "disabled" : "enabled" }}
|
||||
</pw-toggle>
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<h3 class="title">Frames</h3>
|
||||
<span>
|
||||
<pw-toggle :on="!settings.DISABLE_FRAME_COLORS" @change="toggleSetting('DISABLE_FRAME_COLORS')">
|
||||
Multi-color {{ settings.DISABLE_FRAME_COLORS ? "disabled" : "enabled" }}
|
||||
</pw-toggle>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</pw-section>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
PROXY SETTINGS
|
||||
--------------
|
||||
This feature is currently not finished.
|
||||
@@ -57,141 +59,151 @@
|
||||
</ul>
|
||||
</pw-section>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import section from "../components/section";
|
||||
import swatch from "../components/settings/swatch";
|
||||
import toggle from "../components/toggle";
|
||||
import section from "../components/section";
|
||||
import swatch from "../components/settings/swatch";
|
||||
import toggle from "../components/toggle";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'pw-section': section,
|
||||
'pw-toggle': toggle,
|
||||
'swatch': swatch
|
||||
},
|
||||
export default {
|
||||
components: {
|
||||
'pw-section': section,
|
||||
'pw-toggle': toggle,
|
||||
'swatch': swatch
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// NOTE:: You need to first set the CSS for your theme in /assets/css/themes.scss
|
||||
// You should copy the existing light theme as a template and then just
|
||||
// set the relevant values.
|
||||
themes: [{
|
||||
"color": "#121212",
|
||||
"name": "Dark (Default)",
|
||||
"class": ""
|
||||
},
|
||||
{
|
||||
"color": "#DFDFDF",
|
||||
"name": "Light",
|
||||
"vibrant": true,
|
||||
"class": "light"
|
||||
}
|
||||
],
|
||||
// You can define a new color here! It will simply store the color value.
|
||||
colors: [
|
||||
// If the color is vibrant, black is used as the active foreground color.
|
||||
{
|
||||
"color": "#51ff0d",
|
||||
"name": "Lime (Default)",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#FFC107",
|
||||
"name": "Yellow",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#E91E63",
|
||||
"name": "Pink",
|
||||
"vibrant": false
|
||||
},
|
||||
{
|
||||
"color": "#e74c3c",
|
||||
"name": "Red",
|
||||
"vibrant": false
|
||||
},
|
||||
{
|
||||
"color": "#9b59b6",
|
||||
"name": "Purple",
|
||||
"vibrant": false
|
||||
},
|
||||
{
|
||||
"color": "#2980b9",
|
||||
"name": "Blue",
|
||||
"vibrant": false
|
||||
},
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
// NOTE:: You need to first set the CSS for your theme in /assets/css/themes.scss
|
||||
// You should copy the existing light theme as a template and then just
|
||||
// set the relevant values.
|
||||
themes: [{
|
||||
"color": "#282a36",
|
||||
"name": "Dark (Default)",
|
||||
"class": ""
|
||||
},
|
||||
{
|
||||
"color": "#ebeef5",
|
||||
"name": "Light",
|
||||
"vibrant": true,
|
||||
"class": "light"
|
||||
}
|
||||
],
|
||||
// You can define a new color here! It will simply store the color value.
|
||||
colors: [
|
||||
// If the color is vibrant, black is used as the active foreground color.
|
||||
{
|
||||
"color": "#50fa7b",
|
||||
"name": "Green (Default)",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#f1fa8c",
|
||||
"name": "Yellow",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#ff79c6",
|
||||
"name": "Pink",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#ff5555",
|
||||
"name": "Red",
|
||||
"vibrant": false
|
||||
},
|
||||
{
|
||||
"color": "#bd93f9",
|
||||
"name": "Purple",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#ffb86c",
|
||||
"name": "Orange",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#8be9fd",
|
||||
"name": "Cyan",
|
||||
"vibrant": true
|
||||
},
|
||||
{
|
||||
"color": "#57b5f9",
|
||||
"name": "Blue",
|
||||
"vibrant": false
|
||||
},
|
||||
],
|
||||
|
||||
settings: {
|
||||
THEME_CLASS: this.$store.state.postwoman.settings.THEME_CLASS || '',
|
||||
THEME_COLOR: '',
|
||||
THEME_COLOR_VIBRANT: true,
|
||||
settings: {
|
||||
THEME_CLASS: this.$store.state.postwoman.settings.THEME_CLASS || '',
|
||||
THEME_COLOR: '',
|
||||
THEME_COLOR_VIBRANT: true,
|
||||
|
||||
DISABLE_FRAME_COLORS: this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false,
|
||||
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
|
||||
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || '',
|
||||
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
proxySettings: {
|
||||
deep: true,
|
||||
handler (value) {
|
||||
this.applySetting('PROXY_URL', value.url);
|
||||
this.applySetting('PROXY_KEY', value.key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
applyTheme(name) {
|
||||
this.applySetting('THEME_CLASS', name);
|
||||
document.documentElement.className = name;
|
||||
let imgGitHub = document.getElementById("imgGitHub");
|
||||
imgGitHub.style['filter'] = "";
|
||||
imgGitHub.style['webkit-filter'] = "invert(100%)";
|
||||
if (name.includes("light")){
|
||||
imgGitHub.style['filter'] = "invert(100%)";
|
||||
imgGitHub.style['webkit-filter'] = "invert(100%)";
|
||||
}
|
||||
},
|
||||
setActiveColor(color, vibrant) {
|
||||
// By default, the color is vibrant.
|
||||
if (vibrant == null) vibrant = true;
|
||||
document.documentElement.style.setProperty('--ac-color', color);
|
||||
document.documentElement.style.setProperty('--act-color', vibrant ? '#121212' : '#fff');
|
||||
this.applySetting('THEME_COLOR', color.toUpperCase());
|
||||
this.applySetting('THEME_COLOR_VIBRANT', vibrant);
|
||||
},
|
||||
getActiveColor() {
|
||||
// This strips extra spaces and # signs from the strings.
|
||||
const strip = (str) => str.replace(/#/g, '').replace(/ /g, '');
|
||||
return `#${strip(window.getComputedStyle(document.documentElement).getPropertyValue('--ac-color')).toUpperCase()}`;
|
||||
},
|
||||
applySetting(key, value) {
|
||||
this.settings[key] = value;
|
||||
this.$store.commit('postwoman/applySetting', [key, value]);
|
||||
},
|
||||
toggleSetting(key) {
|
||||
this.settings[key] = !this.settings[key];
|
||||
this.$store.commit('postwoman/applySetting', [key, this.settings[key]]);
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.settings.THEME_COLOR = this.getActiveColor();
|
||||
},
|
||||
|
||||
computed: {
|
||||
proxySettings () {
|
||||
return {
|
||||
url: this.settings.PROXY_URL,
|
||||
key: this.settings.PROXY_KEY
|
||||
}
|
||||
}
|
||||
DISABLE_FRAME_COLORS: this.$store.state.postwoman.settings.DISABLE_FRAME_COLORS || false,
|
||||
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
|
||||
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || '',
|
||||
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
proxySettings: {
|
||||
deep: true,
|
||||
handler(value) {
|
||||
this.applySetting('PROXY_URL', value.url);
|
||||
this.applySetting('PROXY_KEY', value.key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
applyTheme(name) {
|
||||
this.applySetting('THEME_CLASS', name);
|
||||
document.documentElement.className = name;
|
||||
let imgGitHub = document.getElementById("imgGitHub");
|
||||
imgGitHub.style['filter'] = "";
|
||||
imgGitHub.style['webkit-filter'] = "invert(100%)";
|
||||
if (name.includes("light")) {
|
||||
imgGitHub.style['filter'] = "invert(100%)";
|
||||
imgGitHub.style['webkit-filter'] = "invert(100%)";
|
||||
}
|
||||
},
|
||||
setActiveColor(color, vibrant) {
|
||||
// By default, the color is vibrant.
|
||||
if (vibrant == null) vibrant = true;
|
||||
document.documentElement.style.setProperty('--ac-color', color);
|
||||
document.documentElement.style.setProperty('--act-color', vibrant ? '#282a36' : '#f8f8f2');
|
||||
this.applySetting('THEME_COLOR', color.toUpperCase());
|
||||
this.applySetting('THEME_COLOR_VIBRANT', vibrant);
|
||||
},
|
||||
getActiveColor() {
|
||||
// This strips extra spaces and # signs from the strings.
|
||||
const strip = (str) => str.replace(/#/g, '').replace(/ /g, '');
|
||||
return `#${strip(window.getComputedStyle(document.documentElement).getPropertyValue('--ac-color')).toUpperCase()}`;
|
||||
},
|
||||
applySetting(key, value) {
|
||||
this.settings[key] = value;
|
||||
this.$store.commit('postwoman/applySetting', [key, value]);
|
||||
},
|
||||
toggleSetting(key) {
|
||||
this.settings[key] = !this.settings[key];
|
||||
this.$store.commit('postwoman/applySetting', [key, this.settings[key]]);
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.settings.THEME_COLOR = this.getActiveColor();
|
||||
},
|
||||
|
||||
computed: {
|
||||
proxySettings() {
|
||||
return {
|
||||
url: this.settings.PROXY_URL,
|
||||
key: this.settings.PROXY_KEY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<pw-section class="blue" label="Request" ref="request">
|
||||
<pw-section class="cyan" label="Request" ref="request">
|
||||
<ul>
|
||||
<li>
|
||||
<label for="url">URL</label>
|
||||
@@ -27,7 +27,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<label for="message">Message</label>
|
||||
<input id="message" name="message" type="text" v-model="communication.input" :disabled="!connectionState" @keyup.enter="connectionState ? sendMessage() : null">
|
||||
<input id="message" name="message" type="text" v-model="communication.input" :readonly="!connectionState" @keyup.enter="connectionState ? sendMessage() : null">
|
||||
</li>
|
||||
<li>
|
||||
<label for="send" class="hide-on-small-screen"> </label>
|
||||
@@ -104,7 +104,7 @@
|
||||
this.communication.log = [{
|
||||
payload: `Connecting to ${this.url}...`,
|
||||
source: 'info',
|
||||
color: 'lime'
|
||||
color: 'var(--ac-color)'
|
||||
}];
|
||||
try {
|
||||
this.socket = new WebSocket(this.url);
|
||||
@@ -113,7 +113,7 @@
|
||||
this.communication.log = [{
|
||||
payload: `Connected to ${this.url}.`,
|
||||
source: 'info',
|
||||
color: 'lime',
|
||||
color: 'var(--ac-color)',
|
||||
ts: (new Date()).toLocaleTimeString()
|
||||
}];
|
||||
};
|
||||
@@ -125,7 +125,7 @@
|
||||
this.communication.log.push({
|
||||
payload: `Disconnected from ${this.url}.`,
|
||||
source: 'info',
|
||||
color: 'red',
|
||||
color: '#ff5555',
|
||||
ts: (new Date()).toLocaleTimeString()
|
||||
});
|
||||
};
|
||||
|
||||
1
static/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
postwoman.io
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
static/icon.png
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -59,4 +59,4 @@
|
||||
data-original="#121212"
|
||||
d="M 64.601,236.822 C 64.601,394.256 192.786,612 306.001,612 412.582,612 547.4,394.256 547.4,236.822 547.4,79.388 439.322,0 306,0 172.678,0 64.601,79.388 64.601,236.822 Z m 304.12,116.415 c 29.475,-29.475 70.598,-40.195 108.552,-32.173 8.021,37.954 -2.698,79.077 -32.173,108.552 -29.475,29.475 -70.598,40.195 -108.552,32.173 -8.022,-37.955 2.698,-79.078 32.173,-108.552 z M 134.727,321.063 c 37.954,-8.021 79.077,2.698 108.552,32.173 29.475,29.475 40.195,70.598 32.173,108.552 -37.954,8.021 -79.077,-2.698 -108.552,-32.173 -29.475,-29.476 -40.194,-70.598 -32.173,-108.552 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#51ff0d" /></g></g></g></svg>
|
||||
style="fill:#50fa7b" /></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
static/screenshot.gif
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
static/screenshot2.png
Normal file
|
After Width: | Height: | Size: 173 KiB |