Merge pull request #123 from NBTX/master

⌨️Add autocomplete widget with command line completion
This commit is contained in:
Liyas Thomas 2019-08-31 22:18:07 +05:30 committed by GitHub
commit 7c49f58565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 810 additions and 583 deletions

View File

@ -10,6 +10,8 @@
--bg-dark-color: #000000;
// Background color
--bg-color: #121212;
// Auto-complete color
--atc-color: #212121;
// Text color
--fg-color: #FFF;
@ -26,6 +28,8 @@
--bg-dark-color: #ffffff;
// Background color
--bg-color: #F6F8FA;
// Auto-complete color
--atc-color: #F1F1F1;
// Text color
--fg-color: #121212;

190
components/autocomplete.vue Normal file
View 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>

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "postwoman",
"version": "1.0.0",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -10128,6 +10128,11 @@
"resolved": "https://registry.npmjs.org/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.2.tgz",
"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": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",

View File

@ -7,11 +7,9 @@
"scripts": {
"predev": "node build.js --dev",
"dev": "nuxt",
"prebuild": "node build.js",
"build": "nuxt build",
"start": "nuxt start",
"pregenerate": "node build.js",
"generate": "nuxt generate"
},
@ -20,6 +18,7 @@
"@nuxtjs/pwa": "^3.0.0-0",
"nuxt": "^2.9.2",
"vue-virtual-scroll-list": "^1.4.2",
"vuejs-auto-complete": "^0.9.0",
"vuex-persist": "^2.1.0"
},
"devDependencies": {

View File

@ -31,14 +31,12 @@
<pw-section class="blue-dark" label="Request Body" v-if="method === 'POST' || method === 'PUT' || method === 'PATCH'">
<ul>
<li>
<label>Content Type</label>
<select v-model="contentType">
<option>application/json</option>
<option>www-form/urlencoded</option>
</select>
<autocomplete v-model="contentType" :source="validContentTypes" :spellcheck="false">Content Type</autocomplete>
<span>
<input v-model="rawInput" style="cursor: pointer;" type="checkbox" id="rawInput">
<label for="rawInput" style="cursor: pointer;">Raw Input</label>
<pw-toggle :on="rawInput" @change="rawInput = !rawInput">
Raw input {{ rawInput ? "enabled" : "disabled" }}
</pw-toggle>
</span>
</li>
</ul>
@ -192,9 +190,11 @@
</template>
<script>
import autocomplete from '../components/autocomplete';
import history from "../components/history";
import section from "../components/section";
import textareaAutoHeight from "../directives/textareaAutoHeight";
import toggle from "../components/toggle";
const statusCategories = [{
name: 'informational',
@ -248,10 +248,13 @@
},
components: {
'pw-section': section,
history
'pw-toggle': toggle,
history,
autocomplete
},
data() {
return {
method: 'GET',
url: 'https://reqres.in',
auth: 'None',
@ -270,7 +273,33 @@
headers: '',
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: {
@ -383,7 +412,7 @@
requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
Object.assign(headers, {
'Content-Length': requestBody.length,
//'Content-Length': requestBody.length,
'Content-Type': `${this.contentType}; charset=utf-8`
});
}
@ -408,7 +437,7 @@
url: this.url + this.path + this.queryString,
auth,
headers,
data: requestBody
data: requestBody.toString()
});
(() => {
@ -538,8 +567,7 @@
const haveItems = [...this[key]].length
if (haveItems && this[key]['value'] !== '') {
return `${key}=${JSON.stringify(this[key])}&`
}
else return ''
} 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))
@ -584,6 +612,7 @@
], val => {
this.setRouteQueryState()
})
}
}