Merge remote-tracking branch 'upstream/master'

This commit is contained in:
izerozlu
2019-08-26 18:25:53 +03:00
8 changed files with 181 additions and 104 deletions

View File

@@ -35,10 +35,10 @@ notifications:
deploy:
provider: pages
skip_cleanup: true
skip-cleanup: true
# Refer to: https://docs.travis-ci.com/user/deployment/pages/#Setting-the-GitHub-token
github_token: $GITHUB_ACCESS_TOKEN
github-token: $GITHUB_ACCESS_TOKEN
target-branch: gh-pages
local_dir: dist
local-dir: dist
on:
branch: master
branch: master

View File

@@ -13,7 +13,7 @@ When I wrote this, only God and I understood what I was doing. Now, only God kno
---
[![Build Status](https://travis-ci.org/liyasthomas/postwoman.svg?branch=master)](https://travis-ci.org/liyasthomas/postwoman) [![GitHub release](https://img.shields.io/github/release/liyasthomas/postwoman/all.svg)](https://github.com/liyasthomas/postwoman/releases/latest) [![repo size](https://img.shields.io/github/repo-size/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/archive/master.zip) [![license](https://img.shields.io/github/license/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/blob/master/LICENSE) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/liyasthomas/postwoman/issues) [![Website](https://img.shields.io/website-up-down-green-red/https/shields.io.svg?label=website)](https://liyas-thomas.firebaseapp.com) [![Donate](https://img.shields.io/badge/$-donate-ff69b4.svg)](https://www.paypal.me/liyascthomas) [![Buy me a coffee](https://img.shields.io/badge/$-BuyMeACoffee-orange.svg)](https://www.buymeacoffee.com/liyasthomas)
[![Build Status](https://travis-ci.com/liyasthomas/postwoman.svg?branch=master)](https://travis-ci.com/liyasthomas/postwoman) [![GitHub release](https://img.shields.io/github/release/liyasthomas/postwoman/all.svg)](https://github.com/liyasthomas/postwoman/releases/latest) [![repo size](https://img.shields.io/github/repo-size/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/archive/master.zip) [![license](https://img.shields.io/github/license/liyasthomas/postwoman.svg)](https://github.com/liyasthomas/postwoman/blob/master/LICENSE) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/liyasthomas/postwoman/issues) [![Website](https://img.shields.io/website-up-down-green-red/https/shields.io.svg?label=website)](https://liyas-thomas.firebaseapp.com) [![Donate](https://img.shields.io/badge/$-donate-ff69b4.svg)](https://www.paypal.me/liyascthomas) [![Buy me a coffee](https://img.shields.io/badge/$-BuyMeACoffee-orange.svg)](https://www.buymeacoffee.com/liyasthomas)
# <img src="static/icon.png" alt="postwoman" width="32"> Postwoman
@@ -88,7 +88,7 @@ Please read [CONTRIBUTING](CONTRIBUTING.md) for details on our [CODE OF CONDUCT]
## Continuous Integration
We use [Travis CI](https://travis-ci.com) for continuous integration. Check out our [Travis CI Status](https://travis-ci.org/liyasthomas/postwoman).
We use [Travis CI](https://travis-ci.com) for continuous integration. Check out our [Travis CI Status](https://travis-ci.com/liyasthomas/postwoman).
---
@@ -113,8 +113,13 @@ See the [CHANGELOG](CHANGELOG.md) file for details.
* [Liyas Thomas](https://github.com/liyasthomas)
### Contributors
* [NBTX](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)
### Thanks
* [Dribbble](https://dribbble.com)

View File

@@ -234,6 +234,16 @@ ol li {
flex-direction: column;
flex-grow: 1;
}
.flex-wrap{
display: flex;
justify-content: space-between;
}
.btn-copy{
padding: 6px 14px;
font-size: 11px;
margin-right: 15px;
}
@media (max-width: $responsiveWidth) {
ul,
@@ -252,21 +262,34 @@ ol li {
}
.info-response {
background-color: #ffeb3b;
background-color: #FFEB3B;
}
.success-response {
background-color: #66BB6A;
background-color: #4BB543;
}
.redir-response {
background-color: #ff5722;
background-color: #FF5722;
}
.cl-error-response {
background-color: #ef5350;
background-color: #A63232;
}
.sv-error-response {
background-color: #b71c1c;
}
background-color: #B71C1C;
}
fieldset#history {
.method-list-item {
position: relative;
span {
position: absolute;
top: 44px;
right: 20px;
font-family: monospace, monospace;
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<fieldset :class="{ 'no-colored-frames': noFrameColors }">
<fieldset :id="label.toLowerCase()" :class="{ 'no-colored-frames': noFrameColors }">
<legend @click.prevent="collapse">{{ label }} </legend>
<div class="collapsible" :class="{ hidden: collapsed }">
<slot />
@@ -44,4 +44,4 @@
}
}
</script>
</script>

View File

@@ -5,7 +5,7 @@
<nuxt-link to="/">
<h1 class="logo"><logo alt="" style="height: 24px; margin-right: 16px"/>Postwoman</h1>
</nuxt-link>
<h3>Lightweight API request builder</h3>
<h3>API request builder</h3>
</div>
<nav>

View File

@@ -3,7 +3,7 @@
// TODO: Use these when rendering the pages (rather than just for head/meta tags...)
export const meta = {
name: "Postwoman",
shortDescription: "Lightweight API request builder",
shortDescription: "API request builder",
description: "The Postwoman API request builder helps you create your requests faster, saving you precious time on your development."
};
@@ -34,6 +34,7 @@ export default {
{ 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}` },

View File

@@ -36,34 +36,42 @@
<option>application/json</option>
<option>www-form/urlencoded</option>
</select>
<span>
<input v-model="rawInput" style="cursor: pointer;" type="checkbox" id="rawInput">
<label for="rawInput" style="cursor: pointer;">Raw Input</label>
</span>
</li>
</ul>
<ol v-for="(param, index) in bodyParams">
<li>
<label :for="'bparam'+index">Key {{index + 1}}</label>
<input :name="'bparam'+index" v-model="param.key">
</li>
<li>
<label :for="'bvalue'+index">Value {{index + 1}}</label>
<input :name="'bvalue'+index" v-model="param.value">
</li>
<li>
<label for="request">&nbsp;</label>
<button name="request" @click="removeRequestBodyParam(index)">Remove</button>
</li>
</ol>
<ul>
<li>
<label for="addrequest">Action</label>
<button name="addrequest" @click="addRequestBodyParam">Add</button>
</li>
</ul>
<ul>
<li>
<label for="request">Parameter List</label>
<textarea name="request" rows="1" readonly>{{rawRequestBody || '(add at least one parameter)'}}</textarea>
</li>
</ul>
<div v-if="!rawInput">
<ol v-for="(param, index) in bodyParams">
<li>
<label :for="'bparam'+index">Key {{index + 1}}</label>
<input :name="'bparam'+index" v-model="param.key">
</li>
<li>
<label :for="'bvalue'+index">Value {{index + 1}}</label>
<input :name="'bvalue'+index" v-model="param.value">
</li>
<li>
<label for="request">&nbsp;</label>
<button name="request" @click="removeRequestBodyParam(index)">Remove</button>
</li>
</ol>
<ul>
<li>
<label for="addrequest">Action</label>
<button name="addrequest" @click="addRequestBodyParam">Add</button>
</li>
</ul>
<ul>
<li>
<label for="request">Parameter List</label>
<textarea name="request" rows="1" readonly>{{rawRequestBody || '(add at least one parameter)'}}</textarea>
</li>
</ul>
</div><div v-else>
<textarea v-model="rawParams" style="font-family: monospace;" rows="16" @keydown="formatRawParams"></textarea>
</div>
</pw-section>
<pw-section class="green" label="Authentication" collapsed>
@@ -138,9 +146,12 @@
</li>
</ul>
<ul>
<li>
<li>
<div class="flex-wrap">
<label for="body">response</label>
<textarea name="body" rows="10" readonly>{{response.body || '(waiting to send request)'}}</textarea>
<button v-if="response.body" name="action" class="btn-copy" @click="copyResponse">Copy Response</button>
</div>
<textarea name="body" rows="10" id="response-details" readonly>{{response.body || '(waiting to send request)'}}</textarea>
</li>
</ul>
</pw-section>
@@ -156,17 +167,19 @@
<label for="time">Time</label>
<input name="time" type="text" readonly :value="entry.time">
</li>
<li>
<label for="name">Method</label>
<input name="name" type="text" readonly :value="entry.method">
<li class="method-list-item">
<label for="method">Method</label>
<input name="method" type="text" readonly
:value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">
<span class="entry-status-code">{{entry.status}}</span>
</li>
<li>
<label for="name">URL</label>
<input name="name" type="text" readonly :value="entry.url">
<label for="url">URL</label>
<input name="url" type="text" readonly :value="entry.url">
</li>
<li>
<label for="name">Path</label>
<input name="name" type="text" readonly :value="entry.path">
<label for="path">Path</label>
<input name="path" type="text" readonly :value="entry.path">
</li>
<li>
<label for="delete">&nbsp;</label>
@@ -183,22 +196,33 @@
</template>
<script>
const parseHeaders = xhr => {
const headers = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
const headerMap = {};
headers.forEach(line => {
const parts = line.split(': ');
const header = parts.shift().toLowerCase();
const value = parts.join(': ');
headerMap[header] = value
});
return headerMap
};
const statusCategories = [
{name: 'informational', statusCodeRegex: new RegExp(/[1][0-9]+/), className: 'info-response'},
{name: 'successful', statusCodeRegex: new RegExp(/[2][0-9]+/), className: 'success-response'},
{name: 'redirection', statusCodeRegex: new RegExp(/[3][0-9]+/), className: 'redir-response'},
{name: 'client error', statusCodeRegex: new RegExp(/[4][0-9]+/), className: 'cl-error-response'},
{name: 'server error', statusCodeRegex: new RegExp(/[5][0-9]+/), className: 'sv-error-response'},
];
import section from "../components/section";
export default {
components: {
const parseHeaders = xhr => {
const headers = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
const headerMap = {};
headers.forEach(line => {
const parts = line.split(': ');
const header = parts.shift().toLowerCase();
const value = parts.join(': ');
headerMap[header] = value
});
return headerMap
};
const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
import section from "../components/section";
export default {
components: {
'pw-section': section
},
@@ -213,6 +237,8 @@
bearerToken: '',
params: [],
bodyParams: [],
rawParams: '',
rawInput: false,
contentType: 'application/json',
response: {
status: '',
@@ -224,13 +250,7 @@
},
computed: {
statusCategory(){
return [
{name: 'informational', statusCodeRegex: new RegExp(/[1][0-9]+/), className: 'info-response'},
{name: 'successful', statusCodeRegex: new RegExp(/[2][0-9]+/), className: 'success-response'},
{name: 'redirection', statusCodeRegex: new RegExp(/[3][0-9]+/), className: 'redir-response'},
{name: 'client error', statusCodeRegex: new RegExp(/[4][0-9]+/), className: 'cl-error-response'},
{name: 'server error', statusCodeRegex: new RegExp(/[5][0-9]+/), className: 'sv-error-response'},
].find(status => status.statusCodeRegex.test(this.response.status));
return findStatusGroup(this.response.status);
},
noHistoryToClear() {
return this.history.length === 0;
@@ -280,6 +300,9 @@
}
},
methods: {
findEntryStatus(entry){
return findStatusGroup(entry.status);
},
deleteHistory(entry) {
this.history.splice(this.history.indexOf(entry), 1)
window.localStorage.setItem('history', JSON.stringify(this.history))
@@ -301,17 +324,6 @@
})
},
sendRequest() {
if (!this.isValidURL) {
alert('Please check the formatting of the URL');
return
}
const n = new Date().toLocaleTimeString()
this.history = [{
time: n,
method: this.method,
url: this.url,
path: this.path
}, ...this.history]
window.localStorage.setItem('history', JSON.stringify(this.history))
if (this.$refs.response.$el.classList.contains('hidden')) {
this.$refs.response.$el.classList.toggle('hidden')
@@ -329,7 +341,7 @@
xhr.setRequestHeader('Authorization', 'Bearer ' + this.bearerToken);
}
if (this.method === 'POST' || this.method === 'PUT') {
const requestBody = this.rawRequestBody
const requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
xhr.setRequestHeader('Content-Length', requestBody.length)
xhr.setRequestHeader('Content-Type', `${this.contentType}; charset=utf-8`)
xhr.send(requestBody)
@@ -337,13 +349,26 @@
xhr.send()
}
xhr.onload = e => {
this.response.status = xhr.status
const headers = this.response.headers = parseHeaders(xhr)
if ((headers['content-type'] || '').startsWith('application/json')) {
this.response.body = JSON.stringify(JSON.parse(xhr.responseText), null, 2)
} else {
this.response.body = xhr.responseText
}
this.response.status = xhr.status
const headers = this.response.headers = parseHeaders(xhr)
if ((headers['content-type'] || '').startsWith('application/json')) {
this.response.body = JSON.stringify(JSON.parse(xhr.responseText), null, 2)
} else {
this.response.body = xhr.responseText
}
if (!this.isValidURL) {
alert('Please check the formatting of the URL');
return
}
const n = new Date().toLocaleTimeString()
this.history = [{
status: xhr.status,
time: n,
method: this.method,
url: this.url,
path: this.path
}, ...this.history]
}
xhr.onerror = e => {
this.response.status = xhr.status
@@ -369,6 +394,36 @@
},
removeRequestBodyParam(index) {
this.bodyParams.splice(index, 1)
},
formatRawParams(event) {
if ((event.which !== 13 && event.which !== 9)) {
return;
}
const textBody = event.target.value;
const textBeforeCursor = textBody.substring(0, event.target.selectionStart);
const textAfterCursor = textBody.substring(event.target.selectionEnd);
if (event.which === 13) {
event.preventDefault();
const oldSelectionStart = event.target.selectionStart;
const lastLine = textBeforeCursor.split('\n').slice(-1)[0];
const rightPadding = lastLine.match(/([\s\t]*).*/)[1] || "";
event.target.value = textBeforeCursor + '\n' + rightPadding + textAfterCursor;
setTimeout(() => event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + rightPadding.length + 1, 1);
}
else if (event.which === 9) {
event.preventDefault();
const oldSelectionStart = event.target.selectionStart;
event.target.value = textBeforeCursor + '\xa0\xa0' + textAfterCursor;
event.target.selectionStart = event.target.selectionEnd = oldSelectionStart + 2;
return false;
}
},
copyResponse() {
var copyText = document.getElementById("response-details");
copyText.select();
document.execCommand("copy");
}
}
}

View File

@@ -7,9 +7,9 @@
<label for="url">URL</label>
<input id="url" type="url" :class="{ error: !urlValid }" v-model="url" @keyup.enter="toggleConnection">
</li>
<li class="no-grow">
<li>
<label>&nbsp;</label>
<button class="action" :class="{ disabled: !urlValid }" name="action" @click="toggleConnection">{{ toggleConnectionVerb }}</button>
<button :class="{ disabled: !urlValid }" name="action" @click="toggleConnection">{{ toggleConnectionVerb }}</button>
</li>
</ul>
</pw-section>
@@ -18,7 +18,7 @@
<ul>
<li>
<label for="log">Log</label>
<div id="log" name="log" class="log" readonly>
<div id="log" name="log" class="log">
<span v-if="communication.log">
<span v-for="logEntry in communication.log" :style="{ color: logEntry.color }">{{ getSourcePrefix(logEntry.source) }} {{ logEntry.payload }}</span>
</span>
@@ -33,9 +33,9 @@
<input id="message" name="message" type="text" v-model="communication.input" :readonly="!connectionState" @keyup.enter="sendMessage">
</li>
<li class="no-grow">
<li>
<label>&nbsp;</label>
<button class="action" name="send" :class="{ disabled: !connectionState }" @click="sendMessage">Send</button>
<button name="send" :class="{ disabled: !connectionState }" @click="sendMessage">Send</button>
</li>
</ul>
</pw-section>
@@ -44,13 +44,6 @@
</template>
<style lang="scss">
.no-grow { flex-grow: 0; }
.action {
padding-left: 30px;
padding-right: 30px;
width: 150px;
}
div.log {
margin: 4px;
padding: 8px 16px;
@@ -58,7 +51,7 @@
border-radius: 4px;
background-color: var(--bg-dark-color);
color: var(--fg-color);
height: 300px;
height: 256px;
overflow: auto;
&, span {