Fix and Revert "Revert "⌨️Add autocomplete widget with command line completion""
This reverts commit dd5dfdbabd.
This commit is contained in:
@@ -86,17 +86,17 @@ button {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all .2s;
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
&[disabled],
|
&[disabled], &.disabled {
|
||||||
&.disabled {
|
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
// Only show hover and focus if the button is *not*
|
||||||
&:focus {
|
// disabled.
|
||||||
background-color: var(--act-color);
|
&:not([disabled]):hover, &:not(.disabled):focus {
|
||||||
|
background-color: transparent;
|
||||||
box-shadow: inset 0 0 0 2px var(--ac-color);
|
box-shadow: inset 0 0 0 2px var(--ac-color);
|
||||||
color: var(--ac-color);
|
color: var(--ac-color);
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,8 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.disabled,
|
.disabled,
|
||||||
input[disabled] {
|
input[disabled],
|
||||||
|
button[disabled] {
|
||||||
background-color: var(--err-color);
|
background-color: var(--err-color);
|
||||||
color: #b2b2b2;
|
color: #b2b2b2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
--bg-dark-color: #000000;
|
--bg-dark-color: #000000;
|
||||||
// Background color
|
// Background color
|
||||||
--bg-color: #121212;
|
--bg-color: #121212;
|
||||||
|
// Auto-complete color
|
||||||
|
--atc-color: #212121;
|
||||||
// Text color
|
// Text color
|
||||||
--fg-color: #FFF;
|
--fg-color: #FFF;
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
--bg-dark-color: #ffffff;
|
--bg-dark-color: #ffffff;
|
||||||
// Background color
|
// Background color
|
||||||
--bg-color: #F6F8FA;
|
--bg-color: #F6F8FA;
|
||||||
|
// Auto-complete color
|
||||||
|
--atc-color: #F1F1F1;
|
||||||
// Text color
|
// Text color
|
||||||
--fg-color: #121212;
|
--fg-color: #121212;
|
||||||
|
|
||||||
|
|||||||
190
components/autocomplete.vue
Normal file
190
components/autocomplete.vue
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<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">
|
||||||
|
|
||||||
|
<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 {
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
background-color: var(--ac-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const KEY_TAB = 9;
|
||||||
|
const KEY_ESC = 27;
|
||||||
|
|
||||||
|
const KEY_ARROW_UP = 38;
|
||||||
|
const KEY_ARROW_DOWN = 40;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
spellcheck: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="!isClearingHistory">
|
<li v-if="!isClearingHistory">
|
||||||
<button id="clear-history-button" :class="{ disabled: history.length === 0 }" @click="enableHistoryClearing">
|
<button id="clear-history-button" :disabled="history.length === 0" @click="enableHistoryClearing">
|
||||||
Clear History
|
Clear History
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -10128,6 +10128,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
|
||||||
"integrity": "sha512-jcXl1cYDxGZX+aF9vsUauXWnUkXm8oQxnvLTJ8UMTmMxwzbmlHX7vs0xGDdEej91vJpBNrdNNseWPxboTvI+UA=="
|
"integrity": "sha512-jcXl1cYDxGZX+aF9vsUauXWnUkXm8oQxnvLTJ8UMTmMxwzbmlHX7vs0xGDdEej91vJpBNrdNNseWPxboTvI+UA=="
|
||||||
},
|
},
|
||||||
|
"vuejs-auto-complete": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuejs-auto-complete/-/vuejs-auto-complete-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-7UV3s9bXdnsbGARhHcOuDAszGUsz7JpsFKBfHQuQvo4rfH0yQIru2Rb/x2bWU+m+VW4fS9DKDSYi6tY511QSIA=="
|
||||||
|
},
|
||||||
"vuex": {
|
"vuex": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@nuxtjs/pwa": "^3.0.0-0",
|
"@nuxtjs/pwa": "^3.0.0-0",
|
||||||
"nuxt": "^2.9.2",
|
"nuxt": "^2.9.2",
|
||||||
"vue-virtual-scroll-list": "^1.4.2",
|
"vue-virtual-scroll-list": "^1.4.2",
|
||||||
|
"vuejs-auto-complete": "^0.9.0",
|
||||||
"vuex-persist": "^2.1.0"
|
"vuex-persist": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
199
pages/index.vue
199
pages/index.vue
@@ -16,29 +16,31 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="url">URL</label>
|
<label for="url">URL</label>
|
||||||
<input id="url" type="url" :class="{ error: !isValidURL }" v-model="url" @keyup.enter="isValidURL ? sendRequest() : null">
|
<input :class="{ error: !isValidURL }" @keyup.enter="isValidURL ? sendRequest() : null" id="url" type="url"
|
||||||
|
v-model="url">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="path">Path</label>
|
<label for="path">Path</label>
|
||||||
<input id="path" v-model="path" @keyup.enter="isValidURL ? sendRequest() : null">
|
<input @keyup.enter="isValidURL ? sendRequest() : null" id="path" v-model="path">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="action" class="hide-on-small-screen"> </label>
|
<label class="hide-on-small-screen" for="action"> </label>
|
||||||
<button id="action" class="show" name="action" @click="sendRequest" :disabled="!isValidURL" ref="sendButton">Send <span id="hidden-message">Again</span></button>
|
<button :disabled="!isValidURL" @click="sendRequest" class="show" id="action" name="action" ref="sendButton">
|
||||||
|
Send <span id="hidden-message">Again</span></button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
<pw-section class="blue-dark" label="Request Body" v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
|
<pw-section class="blue-dark" label="Request Body"
|
||||||
|
v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label>Content Type</label>
|
<autocomplete :source="validContentTypes" :spellcheck="false" v-model="contentType">Content Type
|
||||||
<select v-model="contentType">
|
</autocomplete>
|
||||||
<option>application/json</option>
|
|
||||||
<option>www-form/urlencoded</option>
|
|
||||||
</select>
|
|
||||||
<span>
|
<span>
|
||||||
<input v-model="rawInput" style="cursor: pointer;" type="checkbox" id="rawInput">
|
<pw-toggle :on="rawInput" @change="rawInput = !rawInput">
|
||||||
<label for="rawInput" style="cursor: pointer;">Raw Input</label>
|
Raw input {{ rawInput ? "enabled" : "disabled" }}
|
||||||
|
</pw-toggle>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -53,57 +55,29 @@
|
|||||||
<input :name="'bvalue'+index" v-model="param.value">
|
<input :name="'bvalue'+index" v-model="param.value">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="request" class="hide-on-small-screen"> </label>
|
<label class="hide-on-small-screen" for="request"> </label>
|
||||||
<button name="request" @click="removeRequestBodyParam(index)">Remove</button>
|
<button @click="removeRequestBodyParam(index)" name="request">Remove</button>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="addrequest">Action</label>
|
<label for="addrequest">Action</label>
|
||||||
<button name="addrequest" @click="addRequestBodyParam">Add</button>
|
<button @click="addRequestBodyParam" name="addrequest">Add</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="request">Parameter List</label>
|
<label for="request">Parameter List</label>
|
||||||
<textarea name="request" rows="1" v-textarea-auto-height="rawRequestBody" readonly>{{rawRequestBody || '(add at least one parameter)'}}</textarea>
|
<textarea name="request" readonly rows="1" v-textarea-auto-height="rawRequestBody">{{rawRequestBody || '(add at least one parameter)'}}</textarea>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<textarea v-model="rawParams" v-textarea-auto-height="rawParams" style="font-family: monospace;" rows="16" @keydown="formatRawParams"></textarea>
|
<textarea @keydown="formatRawParams" rows="16" style="font-family: monospace;" v-model="rawParams"
|
||||||
|
v-textarea-auto-height="rawParams"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
<pw-section class="purple" label="Response" id="response" ref="response">
|
<pw-section class="green" collapsed label="Authentication">
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<label for="status">status</label>
|
|
||||||
<input name="status" type="text" readonly :value="response.status || '(waiting to send request)'" :class="statusCategory ? statusCategory.className : ''">
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-for="(value, key) in response.headers">
|
|
||||||
<li>
|
|
||||||
<label for="value">{{key}}</label>
|
|
||||||
<input name="value" :value="value" readonly>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<div class="flex-wrap">
|
|
||||||
<label for="body">response</label>
|
|
||||||
<button v-if="response.body" name="action" @click="copyResponse">Copy Response</button>
|
|
||||||
</div>
|
|
||||||
<div id="response-details-wrapper">
|
|
||||||
<textarea name="body" rows="16" id="response-details" readonly>{{response.body || '(waiting to send request)'}}</textarea>
|
|
||||||
<iframe src="about:blank" class="covers-response" ref="previewFrame" :class="{hidden: !previewEnabled}"></iframe>
|
|
||||||
</div>
|
|
||||||
<div v-if="response.body && responseType === 'text/html'" class="align-right">
|
|
||||||
<button @click.prevent="togglePreview">{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</button>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</pw-section>
|
|
||||||
<pw-section class="green" label="Authentication" collapsed>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="auth">Authentication Type</label>
|
<label for="auth">Authentication Type</label>
|
||||||
@@ -121,7 +95,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="http_basic_passwd">Password</label>
|
<label for="http_basic_passwd">Password</label>
|
||||||
<input v-model="httpPassword" type="password">
|
<input type="password" v-model="httpPassword">
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="auth === 'Bearer Token'">
|
<ul v-if="auth === 'Bearer Token'">
|
||||||
@@ -131,7 +105,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
<pw-section class="orange" label="Headers" collapsed>
|
<pw-section class="orange" collapsed label="Headers">
|
||||||
<ol v-for="(header, index) in headers">
|
<ol v-for="(header, index) in headers">
|
||||||
<li>
|
<li>
|
||||||
<label :for="'header'+index">Key {{index + 1}}</label>
|
<label :for="'header'+index">Key {{index + 1}}</label>
|
||||||
@@ -142,24 +116,24 @@
|
|||||||
<input :name="'value'+index" v-model="header.value">
|
<input :name="'value'+index" v-model="header.value">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="header" class="hide-on-small-screen"> </label>
|
<label class="hide-on-small-screen" for="header"> </label>
|
||||||
<button name="header" @click="removeRequestHeader(index)">Remove</button>
|
<button @click="removeRequestHeader(index)" name="header">Remove</button>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="add">Action</label>
|
<label for="add">Action</label>
|
||||||
<button name="add" @click="addRequestHeader">Add</button>
|
<button @click="addRequestHeader" name="add">Add</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="request">Header List</label>
|
<label for="request">Header List</label>
|
||||||
<textarea v-textarea-auto-height="headerString" name="request" rows="1" readonly ref="requestTextarea">{{headerString || '(add at least one header)'}}</textarea>
|
<textarea name="request" readonly ref="requestTextarea" rows="1" v-textarea-auto-height="headerString">{{headerString || '(add at least one header)'}}</textarea>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
<pw-section class="cyan" label="Parameters" collapsed>
|
<pw-section class="cyan" collapsed label="Parameters">
|
||||||
<ol v-for="(param, index) in params">
|
<ol v-for="(param, index) in params">
|
||||||
<li>
|
<li>
|
||||||
<label :for="'param'+index">Key {{index + 1}}</label>
|
<label :for="'param'+index">Key {{index + 1}}</label>
|
||||||
@@ -170,30 +144,65 @@
|
|||||||
<input :name="'value'+index" v-model="param.value">
|
<input :name="'value'+index" v-model="param.value">
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label for="param" class="hide-on-small-screen"> </label>
|
<label class="hide-on-small-screen" for="param"> </label>
|
||||||
<button name="param" @click="removeRequestParam(index)">Remove</button>
|
<button @click="removeRequestParam(index)" name="param">Remove</button>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="add">Action</label>
|
<label for="add">Action</label>
|
||||||
<button name="add" @click="addRequestParam">Add</button>
|
<button @click="addRequestParam" name="add">Add</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="request">Parameter List</label>
|
<label for="request">Parameter List</label>
|
||||||
<textarea name="request" v-textarea-auto-height="queryString" rows="1" readonly>{{queryString || '(add at least one parameter)'}}</textarea>
|
<textarea name="request" readonly rows="1" v-textarea-auto-height="queryString">{{queryString || '(add at least one parameter)'}}</textarea>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</pw-section>
|
</pw-section>
|
||||||
<history @useHistory="handleUseHistory" ref="historyComponent" />
|
<pw-section class="purple" id="response" label="Response" ref="response">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for="status">status</label>
|
||||||
|
<input :class="statusCategory ? statusCategory.className : ''"
|
||||||
|
:value="response.status || '(waiting to send request)'" name="status" readonly type="text">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul v-for="(value, key) in response.headers">
|
||||||
|
<li>
|
||||||
|
<label for="value">{{key}}</label>
|
||||||
|
<input :value="value" name="value" readonly>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul v-if="response.body">
|
||||||
|
<li>
|
||||||
|
<div class="flex-wrap">
|
||||||
|
<label for="body">response</label>
|
||||||
|
<button @click="copyResponse" name="action" v-if="response.body">Copy Response</button>
|
||||||
|
</div>
|
||||||
|
<div id="response-details-wrapper">
|
||||||
|
<textarea id="response-details" 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>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
</pw-section>
|
||||||
|
<history @useHistory="handleUseHistory" ref="historyComponent"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import autocomplete from '../components/autocomplete';
|
||||||
import history from "../components/history";
|
import history from "../components/history";
|
||||||
import section from "../components/section";
|
import section from "../components/section";
|
||||||
import textareaAutoHeight from "../directives/textareaAutoHeight";
|
import textareaAutoHeight from "../directives/textareaAutoHeight";
|
||||||
|
import toggle from "../components/toggle";
|
||||||
|
|
||||||
const statusCategories = [{
|
const statusCategories = [{
|
||||||
name: 'informational',
|
name: 'informational',
|
||||||
statusCodeRegex: new RegExp(/[1][0-9]+/),
|
statusCodeRegex: new RegExp(/[1][0-9]+/),
|
||||||
@@ -236,19 +245,24 @@
|
|||||||
headerMap[header] = value
|
headerMap[header] = value
|
||||||
});
|
});
|
||||||
return headerMap
|
return headerMap
|
||||||
|
|
||||||
};
|
};
|
||||||
export const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
|
export const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middleware: 'parsedefaulturl', // calls middleware before loading the page
|
|
||||||
directives: {
|
directives: {
|
||||||
textareaAutoHeight
|
textareaAutoHeight
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
'pw-section': section,
|
'pw-section': section,
|
||||||
history
|
'pw-toggle': toggle,
|
||||||
|
history,
|
||||||
|
autocomplete
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: 'https://reqres.in',
|
url: 'https://reqres.in',
|
||||||
auth: 'None',
|
auth: 'None',
|
||||||
@@ -267,7 +281,33 @@
|
|||||||
headers: '',
|
headers: '',
|
||||||
body: ''
|
body: ''
|
||||||
},
|
},
|
||||||
previewEnabled: false
|
previewEnabled: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are content types that can be automatically
|
||||||
|
* serialized by postwoman.
|
||||||
|
*/
|
||||||
|
knownContentTypes: [
|
||||||
|
'application/json',
|
||||||
|
'application/x-www-form-urlencoded'
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are a list of Content Types known to Postwoman.
|
||||||
|
*/
|
||||||
|
validContentTypes: [
|
||||||
|
'application/json',
|
||||||
|
'application/hal+json',
|
||||||
|
'application/xml',
|
||||||
|
'application/x-www-form-urlencoded',
|
||||||
|
'text/html',
|
||||||
|
'text/plain'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
contentType(val) {
|
||||||
|
this.rawInput = !this.knownContentTypes.includes(val);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -289,7 +329,7 @@
|
|||||||
} = this
|
} = this
|
||||||
if (this.contentType === 'application/json') {
|
if (this.contentType === 'application/json') {
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(`{${bodyParams.filter(({ key }) => !!key).map(({ key, value }) => `
|
const obj = JSON.parse(`{${bodyParams.filter(({key}) => !!key).map(({key, value}) => `
|
||||||
"${key}": "${value}"
|
"${key}": "${value}"
|
||||||
`).join()}}`)
|
`).join()}}`)
|
||||||
return JSON.stringify(obj)
|
return JSON.stringify(obj)
|
||||||
@@ -351,59 +391,73 @@
|
|||||||
alert('Please check the formatting of the URL.');
|
alert('Please check the formatting of the URL.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start showing the loading bar as soon as possible.
|
// Start showing the loading bar as soon as possible.
|
||||||
// The nuxt axios module will hide it when the request is made.
|
// The nuxt axios module will hide it when the request is made.
|
||||||
this.$nuxt.$loading.start();
|
this.$nuxt.$loading.start();
|
||||||
|
|
||||||
if (this.$refs.response.$el.classList.contains('hidden')) {
|
if (this.$refs.response.$el.classList.contains('hidden')) {
|
||||||
this.$refs.response.$el.classList.toggle('hidden')
|
this.$refs.response.$el.classList.toggle('hidden')
|
||||||
}
|
}
|
||||||
this.$refs.request.$el.scrollIntoView({
|
this.$refs.response.$el.scrollIntoView({
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
this.previewEnabled = false;
|
this.previewEnabled = false;
|
||||||
this.response.status = 'Fetching...';
|
this.response.status = 'Fetching...';
|
||||||
this.response.body = 'Loading...';
|
this.response.body = 'Loading...';
|
||||||
|
|
||||||
const auth = this.auth === 'Basic' ? {
|
const auth = this.auth === 'Basic' ? {
|
||||||
username: this.httpUser,
|
username: this.httpUser,
|
||||||
password: this.httpPassword
|
password: this.httpPassword
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
let headers = {};
|
let headers = {};
|
||||||
|
|
||||||
// If the request has a request body, we want to ensure Content-Length and
|
// If the request has a request body, we want to ensure Content-Length and
|
||||||
// Content-Type are sent.
|
// Content-Type are sent.
|
||||||
let requestBody;
|
let requestBody;
|
||||||
if (this.hasRequestBody) {
|
if (this.hasRequestBody) {
|
||||||
requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
|
requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
|
||||||
|
|
||||||
Object.assign(headers, {
|
Object.assign(headers, {
|
||||||
'Content-Length': requestBody.length,
|
//'Content-Length': requestBody.length,
|
||||||
'Content-Type': `${this.contentType}; charset=utf-8`
|
'Content-Type': `${this.contentType}; charset=utf-8`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request uses a token for auth, we want to make sure it's sent here.
|
// If the request uses a token for auth, we want to make sure it's sent here.
|
||||||
if (this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`;
|
if (this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`;
|
||||||
|
|
||||||
headers = Object.assign(
|
headers = Object.assign(
|
||||||
// Clone the app headers object first, we don't want to
|
// Clone the app headers object first, we don't want to
|
||||||
// mutate it with the request headers added by default.
|
// mutate it with the request headers added by default.
|
||||||
Object.assign({}, this.headers),
|
Object.assign({}, this.headers),
|
||||||
|
|
||||||
// We make our temporary headers object the source so
|
// We make our temporary headers object the source so
|
||||||
// that you can override the added headers if you
|
// that you can override the added headers if you
|
||||||
// specify them.
|
// specify them.
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await this.$axios({
|
const payload = await this.$axios({
|
||||||
method: this.method,
|
method: this.method,
|
||||||
url: this.url + this.path + this.queryString,
|
url: this.url + this.path + this.queryString,
|
||||||
auth,
|
auth,
|
||||||
headers,
|
headers,
|
||||||
data: requestBody
|
data: requestBody ? requestBody.toString() : null
|
||||||
});
|
});
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const status = this.response.status = payload.status;
|
const status = this.response.status = payload.status;
|
||||||
const headers = this.response.headers = payload.headers;
|
const headers = this.response.headers = payload.headers;
|
||||||
|
|
||||||
// We don't need to bother parsing JSON, axios already handles it for us!
|
// We don't need to bother parsing JSON, axios already handles it for us!
|
||||||
const body = this.response.body = payload.data;
|
const body = this.response.body = payload.data;
|
||||||
|
|
||||||
const date = new Date().toLocaleDateString();
|
const date = new Date().toLocaleDateString();
|
||||||
const time = new Date().toLocaleTimeString();
|
const time = new Date().toLocaleTimeString();
|
||||||
|
|
||||||
// Addition of an entry to the history component.
|
// Addition of an entry to the history component.
|
||||||
const entry = {
|
const entry = {
|
||||||
status,
|
status,
|
||||||
@@ -420,6 +474,7 @@
|
|||||||
this.response.headers = error.response.headers;
|
this.response.headers = error.response.headers;
|
||||||
this.response.status = error.response.status;
|
this.response.status = error.response.status;
|
||||||
this.response.body = error.response.data;
|
this.response.body = error.response.data;
|
||||||
|
|
||||||
// Addition of an entry to the history component.
|
// Addition of an entry to the history component.
|
||||||
const entry = {
|
const entry = {
|
||||||
status: this.response.status,
|
status: this.response.status,
|
||||||
@@ -432,10 +487,12 @@
|
|||||||
this.$refs.historyComponent.addEntry(entry);
|
this.$refs.historyComponent.addEntry(entry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.response.status = error.message;
|
this.response.status = error.message;
|
||||||
this.response.body = "See JavaScript console (F12) for details.";
|
this.response.body = "See JavaScript console (F12) for details.";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addRequestHeader() {
|
addRequestHeader() {
|
||||||
this.headers.push({
|
this.headers.push({
|
||||||
key: '',
|
key: '',
|
||||||
@@ -525,10 +582,10 @@
|
|||||||
this.$router.replace('/?' + flats.concat(deeps).join('').slice(0, -1))
|
this.$router.replace('/?' + flats.concat(deeps).join('').slice(0, -1))
|
||||||
},
|
},
|
||||||
setRouteQueries(queries) {
|
setRouteQueries(queries) {
|
||||||
if (typeof(queries) !== 'object') throw new Error('Route query parameters must be a Object')
|
if (typeof (queries) !== 'object') throw new Error('Route query parameters must be a Object')
|
||||||
for (const key in queries) {
|
for (const key in queries) {
|
||||||
if (key === 'headers' || key === 'params' || key === 'bodyParams') this[key] = JSON.parse(queries[key])
|
if (key === 'headers' || key === 'params' || key === 'bodyParams') this[key] = JSON.parse(queries[key])
|
||||||
else if (typeof(this[key]) === 'string') this[key] = queries[key];
|
else if (typeof (this[key]) === 'string') this[key] = queries[key];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
observeRequestButton() {
|
observeRequestButton() {
|
||||||
@@ -538,9 +595,8 @@
|
|||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
sendButtonElement.classList.toggle('show');
|
sendButtonElement.classList.toggle('show');
|
||||||
});
|
});
|
||||||
}, {
|
}, {threshold: 1});
|
||||||
threshold: 1
|
|
||||||
});
|
|
||||||
observer.observe(requestElement);
|
observer.observe(requestElement);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -564,6 +620,7 @@
|
|||||||
], val => {
|
], val => {
|
||||||
this.setRouteQueryState()
|
this.setRouteQueryState()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user