Merge branch 'master' into feature/fast-url

This commit is contained in:
Liyas Thomas
2020-02-26 13:33:26 +05:30
committed by GitHub
13 changed files with 286 additions and 219 deletions

View File

@@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "npm run pretty-quick"
}
}

View File

@@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"semi": false,
"singleQuote": false,
"printWidth": 100
}

View File

@@ -5,26 +5,26 @@
</template>
<style lang="scss">
.show-if-initialized {
opacity: 0;
.show-if-initialized {
opacity: 0;
&.initialized {
opacity: 1;
}
& > * {
transition: none;
}
&.initialized {
opacity: 1;
}
& > * {
transition: none;
}
}
</style>
<script>
const DEFAULT_THEME = "twilight"
import ace from "ace-builds";
import "ace-builds/webpack-resolver";
import jsonParse from '../functions/jsonParse';
import debounce from '../functions/utils/debounce';
import ace from "ace-builds"
import "ace-builds/webpack-resolver"
import jsonParse from "../functions/jsonParse"
import debounce from "../functions/utils/debounce"
export default {
props: {
@@ -40,6 +40,11 @@ export default {
type: String,
default: "json",
},
lint: {
type: Boolean,
default: true,
required: false,
},
options: {
type: Object,
default: {},
@@ -57,19 +62,18 @@ export default {
watch: {
value(value) {
if (value !== this.cacheValue) {
this.editor.session.setValue(value, 1);
this.cacheValue = value;
this.provideLinting(value);
this.editor.session.setValue(value, 1)
this.cacheValue = value
if (this.lint) this.provideLinting(value)
}
},
theme() {
this.initialized = false;
this.initialized = false
this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
this.initialized = true
})
})
},
lang(value) {
this.editor.getSession().setMode("ace/mode/" + value)
@@ -88,23 +92,24 @@ export default {
// Set the theme and show the editor only after it's been set to prevent FOUC.
editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
this.$nextTick().then(() => {
this.initialized = true;
});
});
this.initialized = true
})
})
if (this.value) editor.setValue(this.value, 1);
if (this.value) editor.setValue(this.value, 1)
this.editor = editor
this.cacheValue = this.value
editor.on("change", () => {
const content = editor.getValue();
this.$emit("input", content);
this.cacheValue = content;
this.provideLinting(content);
});
const content = editor.getValue()
this.$emit("input", content)
this.cacheValue = content
if (this.lint) this.provideLinting(content)
})
this.provideLinting(this.value);
// Disable linting, if lint prop is false
if (this.lint) this.provideLinting(this.value)
},
methods: {
@@ -112,33 +117,31 @@ export default {
if (this.theme) {
return this.theme
}
return (
this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
);
return this.$store.state.postwoman.settings.THEME_ACE_EDITOR || DEFAULT_THEME
},
provideLinting: debounce(function (code) {
provideLinting: debounce(function(code) {
if (this.lang === "json") {
try {
jsonParse(code);
this.editor.session.setAnnotations([]);
jsonParse(code)
this.editor.session.setAnnotations([])
} catch (e) {
const pos = this.editor.session.getDocument().indexToPosition(e.start, 0);
const pos = this.editor.session.getDocument().indexToPosition(e.start, 0)
this.editor.session.setAnnotations([
{
row: pos.row,
column: pos.column,
text: e.message,
type: "error"
}
]);
type: "error",
},
])
}
}
}, 2000)
}, 2000),
},
destroyed() {
this.editor.destroy();
}
};
this.editor.destroy()
},
}
</script>

View File

@@ -3,10 +3,10 @@
<legend @click.prevent="collapse">
<span>{{ label }}</span>
<i class="material-icons">
{{ isCollapsed ? "expand_more" : "expand_less" }}
{{ isCollapsed(label) ? "expand_more" : "expand_less" }}
</i>
</legend>
<div class="collapsible" :class="{ hidden: collapsed }">
<div class="collapsible" :class="{ hidden: isCollapsed(label.toLowerCase()) }">
<slot />
</div>
</fieldset>
@@ -24,12 +24,9 @@ export default {
frameColorsEnabled() {
return this.$store.state.postwoman.settings.FRAME_COLORS_ENABLED || false
},
},
data() {
return {
isCollapsed: false,
}
sectionString() {
return `${this.$route.path.replace(/\/+$/, "")}/${this.label}`
},
},
props: {
@@ -46,7 +43,12 @@ export default {
collapse({ target }) {
const parent = target.parentNode.parentNode
parent.querySelector(".collapsible").classList.toggle("hidden")
this.isCollapsed = !this.isCollapsed
// Save collapsed section into the collapsedSections array
this.$store.commit("setCollapsedSection", this.sectionString)
},
isCollapsed(label) {
return this.$store.state.theme.collapsedSections.includes(this.sectionString) || false
},
},
}

View File

@@ -20,152 +20,152 @@
*
*/
export default function jsonParse(str) {
string = str;
strLen = str.length;
start = end = lastEnd = -1;
ch();
lex();
const ast = parseObj();
expect('EOF');
return ast;
string = str
strLen = str.length
start = end = lastEnd = -1
ch()
lex()
const ast = parseObj()
expect("EOF")
return ast
}
let string;
let strLen;
let start;
let end;
let lastEnd;
let code;
let kind;
let string
let strLen
let start
let end
let lastEnd
let code
let kind
function parseObj() {
const nodeStart = start;
const members = [];
expect('{');
if (!skip('}')) {
const nodeStart = start
const members = []
expect("{")
if (!skip("}")) {
do {
members.push(parseMember());
} while (skip(','));
expect('}');
members.push(parseMember())
} while (skip(","))
expect("}")
}
return {
kind: 'Object',
kind: "Object",
start: nodeStart,
end: lastEnd,
members,
};
}
}
function parseMember() {
const nodeStart = start;
const key = kind === 'String' ? curToken() : null;
expect('String');
expect(':');
const value = parseVal();
const nodeStart = start
const key = kind === "String" ? curToken() : null
expect("String")
expect(":")
const value = parseVal()
return {
kind: 'Member',
kind: "Member",
start: nodeStart,
end: lastEnd,
key,
value,
};
}
}
function parseArr() {
const nodeStart = start;
const values = [];
expect('[');
if (!skip(']')) {
const nodeStart = start
const values = []
expect("[")
if (!skip("]")) {
do {
values.push(parseVal());
} while (skip(','));
expect(']');
values.push(parseVal())
} while (skip(","))
expect("]")
}
return {
kind: 'Array',
kind: "Array",
start: nodeStart,
end: lastEnd,
values,
};
}
}
function parseVal() {
switch (kind) {
case '[':
return parseArr();
case '{':
return parseObj();
case 'String':
case 'Number':
case 'Boolean':
case 'Null':
const token = curToken();
lex();
return token;
case "[":
return parseArr()
case "{":
return parseObj()
case "String":
case "Number":
case "Boolean":
case "Null":
const token = curToken()
lex()
return token
}
return expect('Value');
return expect("Value")
}
function curToken() {
return { kind, start, end, value: JSON.parse(string.slice(start, end)) };
return { kind, start, end, value: JSON.parse(string.slice(start, end)) }
}
function expect(str) {
if (kind === str) {
lex();
return;
lex()
return
}
let found;
if (kind === 'EOF') {
found = '[end of file]';
let found
if (kind === "EOF") {
found = "[end of file]"
} else if (end - start > 1) {
found = '`' + string.slice(start, end) + '`';
found = "`" + string.slice(start, end) + "`"
} else {
const match = string.slice(start).match(/^.+?\b/);
found = '`' + (match ? match[0] : string[start]) + '`';
const match = string.slice(start).match(/^.+?\b/)
found = "`" + (match ? match[0] : string[start]) + "`"
}
throw syntaxError(`Expected ${str} but found ${found}.`);
throw syntaxError(`Expected ${str} but found ${found}.`)
}
function syntaxError(message) {
return { message, start, end };
return { message, start, end }
}
function skip(k) {
if (kind === k) {
lex();
return true;
lex()
return true
}
}
function ch() {
if (end < strLen) {
end++;
code = end === strLen ? 0 : string.charCodeAt(end);
end++
code = end === strLen ? 0 : string.charCodeAt(end)
}
}
function lex() {
lastEnd = end;
lastEnd = end
while (code === 9 || code === 10 || code === 13 || code === 32) {
ch();
ch()
}
if (code === 0) {
kind = 'EOF';
return;
kind = "EOF"
return
}
start = end;
start = end
switch (code) {
// "
case 34:
kind = 'String';
return readString();
kind = "String"
return readString()
// -, 0-9
case 45:
case 48:
@@ -178,50 +178,50 @@ function lex() {
case 55:
case 56:
case 57:
kind = 'Number';
return readNumber();
kind = "Number"
return readNumber()
// f
case 102:
if (string.slice(start, start + 5) !== 'false') {
break;
if (string.slice(start, start + 5) !== "false") {
break
}
end += 4;
ch();
end += 4
ch()
kind = 'Boolean';
return;
kind = "Boolean"
return
// n
case 110:
if (string.slice(start, start + 4) !== 'null') {
break;
if (string.slice(start, start + 4) !== "null") {
break
}
end += 3;
ch();
end += 3
ch()
kind = 'Null';
return;
kind = "Null"
return
// t
case 116:
if (string.slice(start, start + 4) !== 'true') {
break;
if (string.slice(start, start + 4) !== "true") {
break
}
end += 3;
ch();
end += 3
ch()
kind = 'Boolean';
return;
kind = "Boolean"
return
}
kind = string[start];
ch();
kind = string[start]
ch()
}
function readString() {
ch();
ch()
while (code !== 34 && code > 31) {
if (code === 92) {
// \
ch();
ch()
switch (code) {
case 34: // "
case 47: // /
@@ -231,31 +231,31 @@ function readString() {
case 110: // n
case 114: // r
case 116: // t
ch();
break;
ch()
break
case 117: // u
ch();
readHex();
readHex();
readHex();
readHex();
break;
ch()
readHex()
readHex()
readHex()
readHex()
break
default:
throw syntaxError('Bad character escape sequence.');
throw syntaxError("Bad character escape sequence.")
}
} else if (end === strLen) {
throw syntaxError('Unterminated string.');
throw syntaxError("Unterminated string.")
} else {
ch();
ch()
}
}
if (code === 34) {
ch();
return;
ch()
return
}
throw syntaxError('Unterminated string.');
throw syntaxError("Unterminated string.")
}
function readHex() {
@@ -264,47 +264,47 @@ function readHex() {
(code >= 65 && code <= 70) || // A-F
(code >= 97 && code <= 102) // a-f
) {
return ch();
return ch()
}
throw syntaxError('Expected hexadecimal digit.');
throw syntaxError("Expected hexadecimal digit.")
}
function readNumber() {
if (code === 45) {
// -
ch();
ch()
}
if (code === 48) {
// 0
ch();
ch()
} else {
readDigits();
readDigits()
}
if (code === 46) {
// .
ch();
readDigits();
ch()
readDigits()
}
if (code === 69 || code === 101) {
// E e
ch();
ch()
if (code === 43 || code === 45) {
// + -
ch();
ch()
}
readDigits();
readDigits()
}
}
function readDigits() {
if (code < 48 || code > 57) {
// 0 - 9
throw syntaxError('Expected decimal digit.');
throw syntaxError("Expected decimal digit.")
}
do {
ch();
} while (code >= 48 && code <= 57); // 0 - 9
ch()
} while (code >= 48 && code <= 57) // 0 - 9
}

View File

@@ -758,7 +758,11 @@ export default {
})
}
let showExtensionsToast = localStorage.getItem("showExtensionsToast") === "yes"
if (!this.extensionInstalled && !showExtensionsToast) {
// Just return if showExtensionsToast is "no"
if (!showExtensionsToast) return
if (!this.extensionInstalled) {
setTimeout(() => {
this.$toast.show(this.$t("extensions_info2"), {
icon: "extension",
@@ -776,6 +780,11 @@ export default {
{
text: this.$t("no"),
onClick: (e, toastObject) => {
this.$store.commit("setMiscState", {
value: false,
attribute: "showExtensionsToast",
})
localStorage.setItem("showExtensionsToast", "no")
toastObject.goAway(0)
},
},

6
package-lock.json generated
View File

@@ -7344,9 +7344,9 @@
"dev": true
},
"lint-staged": {
"version": "10.0.7",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.7.tgz",
"integrity": "sha512-Byj0F4l7GYUpYYHEqyFH69NiI6ICTg0CeCKbhRorL+ickbzILKUlZLiyCkljZV02wnoh7yH7PmFyYm9PRNwk9g==",
"version": "10.0.8",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.8.tgz",
"integrity": "sha512-Oa9eS4DJqvQMVdywXfEor6F4vP+21fPHF8LUXgBbVWUSWBddjqsvO6Bv1LwMChmgQZZqwUvgJSHlu8HFHAPZmA==",
"dev": true,
"requires": {
"chalk": "^3.0.0",

View File

@@ -18,6 +18,17 @@
"pretty-quick": "pretty-quick --pattern \"**/*.*(html|js|json|vue)\"",
"test": "start-server-and-test start http-get://localhost:3000 e2e"
},
"husky": {
"hooks": {
"pre-commit": "npm run pretty-quick"
}
},
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": false,
"printWidth": 100
},
"dependencies": {
"@nuxtjs/axios": "^5.9.5",
"@nuxtjs/google-analytics": "^2.2.3",
@@ -42,7 +53,7 @@
"devDependencies": {
"cypress": "^4.0.2",
"husky": "^4.2.3",
"lint-staged": "^10.0.7",
"lint-staged": "^10.0.8",
"node-sass": "^4.13.1",
"prettier": "^1.19.1",
"pretty-quick": "^2.0.1",

View File

@@ -6,7 +6,13 @@
<ul>
<li>
<label for="url">{{ $t("url") }}</label>
<input id="url" type="url" v-model="url" @keyup.enter="getSchema()" />
<input
id="url"
type="url"
v-model="url"
spellcheck="false"
@keyup.enter="getSchema()"
/>
</li>
<div>
<li>
@@ -129,7 +135,7 @@
</div>
</div>
<Editor
:value="schemaString"
:value="schema"
:lang="'graphqlschema'"
:options="{
maxLines: responseBodyMaxLines,
@@ -144,10 +150,10 @@
</pw-section>
<pw-section class="cyan" :label="$t('query')" ref="query">
<div class="flex-wrap">
<div class="flex-wrap gqlRunQuery">
<label for="gqlQuery">{{ $t("query") }}</label>
<div>
<button class="icon" @click="runQuery()" v-tooltip.bottom="$t('run_query')">
<button @click="runQuery()" v-tooltip.bottom="$t('run_query')">
<i class="material-icons">play_arrow</i>
</button>
<button
@@ -204,8 +210,9 @@
</div>
</div>
<Editor
:value="responseString"
:value="response"
:lang="'json'"
:lint="false"
:options="{
maxLines: responseBodyMaxLines,
minLines: '16',
@@ -308,6 +315,9 @@
max-height: calc(100vh - 186px);
overflow: auto;
}
.gqlRunQuery {
margin-bottom: 12px;
}
</style>
<script>
@@ -333,18 +343,23 @@ export default {
},
data() {
return {
schemaString: "",
commonHeaders,
queryFields: [],
mutationFields: [],
subscriptionFields: [],
gqlTypes: [],
responseString: "",
copyButton: '<i class="material-icons">file_copy</i>',
downloadButton: '<i class="material-icons">get_app</i>',
doneButton: '<i class="material-icons">done</i>',
expandResponse: false,
responseBodyMaxLines: 16,
settings: {
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
},
}
},
@@ -373,6 +388,22 @@ export default {
this.$store.commit("setGQLState", { value, attribute: "query" })
},
},
response: {
get() {
return this.$store.state.gql.response
},
set(value) {
this.$store.commit("setGQLState", { value, attribute: "response" })
},
},
schema: {
get() {
return this.$store.state.gql.schema
},
set(value) {
this.$store.commit("setGQLState", { value, attribute: "schema" })
},
},
variableString: {
get() {
return this.$store.state.gql.variablesJSONString
@@ -400,7 +431,7 @@ export default {
const rootTypeName = this.resolveRootType(type).name
const target = document.getElementById(`type_${rootTypeName}`)
if (target && this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED) {
if (target && this.settings.SCROLL_INTO_ENABLED) {
target.scrollIntoView({
behavior: "smooth",
})
@@ -414,7 +445,7 @@ export default {
copySchema() {
this.$refs.copySchemaCode.innerHTML = this.doneButton
const aux = document.createElement("textarea")
aux.innerText = this.schemaString
aux.innerText = this.schema
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
@@ -440,7 +471,7 @@ export default {
copyResponse() {
this.$refs.copyResponseButton.innerHTML = this.doneButton
const aux = document.createElement("textarea")
aux.innerText = this.responseString
aux.innerText = this.response
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
@@ -453,8 +484,12 @@ export default {
async runQuery() {
const startTime = Date.now()
// Start showing the loading bar as soon as possible.
// The nuxt axios module will hide it when the request is made.
this.$nuxt.$loading.start()
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED && this.scrollInto("response")
this.response = this.$t("loading")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
try {
let headers = {}
@@ -477,8 +512,7 @@ export default {
}
const data = await sendNetworkRequest(reqOptions, this.$store)
this.responseString = JSON.stringify(data.data, null, 2)
this.response = JSON.stringify(data.data, null, 2)
this.$nuxt.$loading.finish()
const duration = Date.now() - startTime
@@ -486,6 +520,7 @@ export default {
icon: "done",
})
} catch (error) {
this.response = `${error}. ${this.$t("check_console_details")}`
this.$nuxt.$loading.finish()
this.$toast.error(`${error} ${this.$t("f12_details")}`, {
@@ -496,13 +531,14 @@ export default {
},
async getSchema() {
const startTime = Date.now()
this.schemaString = this.$t("loading")
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED && this.scrollInto("schema")
// Start showing the loading bar as soon as possible.
// The nuxt axios module will hide it when the request is made.
this.$nuxt.$loading.start()
this.schema = this.$t("loading")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("schema")
try {
const query = JSON.stringify({
query: gql.getIntrospectionQuery(),
@@ -523,8 +559,6 @@ export default {
data: query,
}
// console.log(reqOptions);
const reqConfig = this.$store.state.postwoman.settings.PROXY_ENABLED
? {
method: "post",
@@ -537,9 +571,8 @@ export default {
const res = await axios(reqConfig)
const data = this.$store.state.postwoman.settings.PROXY_ENABLED ? res.data : res
const schema = gql.buildClientSchema(data.data.data)
this.schemaString = gql.printSchema(schema, {
this.schema = gql.printSchema(schema, {
commentDescriptions: true,
})
@@ -597,7 +630,8 @@ export default {
})
} catch (error) {
this.$nuxt.$loading.finish()
this.schemaString = `${error}. ${this.$t("check_console_details")}`
this.schema = `${error}. ${this.$t("check_console_details")}`
this.$toast.error(`${error} ${this.$t("f12_details")}`, {
icon: "error",
})
@@ -609,7 +643,7 @@ export default {
this.responseBodyMaxLines = this.responseBodyMaxLines == Infinity ? 16 : Infinity
},
downloadResponse() {
const dataToWrite = JSON.stringify(this.schemaString, null, 2)
const dataToWrite = JSON.stringify(this.schema, null, 2)
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
@@ -650,7 +684,6 @@ export default {
},
},
})
// console.log(oldHeaders);
},
scrollInto(view) {
this.$refs[view].$el.scrollIntoView({

View File

@@ -185,6 +185,7 @@
name="url"
type="url"
v-model="uri"
spellcheck="false"
/>
</li>
<div>
@@ -1424,6 +1425,13 @@ export default {
files: [],
filenames: "",
navigatorShare: navigator.share,
settings: {
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
},
}
},
watch: {
@@ -1980,7 +1988,7 @@ export default {
this.path = path
this.showPreRequestScript = usesScripts
this.preRequestScript = preRequestScript
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED && this.scrollInto("request")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("request")
},
getVariablesFromPreRequestScript() {
if (!this.preRequestScript) {
@@ -2015,7 +2023,7 @@ export default {
},
async sendRequest() {
this.$toast.clear()
this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED && this.scrollInto("response")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
if (!this.isValidURL) {
this.$toast.error(this.$t("url_invalid_format"), {
icon: "error",

View File

@@ -11,6 +11,7 @@
<input
id="url"
type="url"
spellcheck="false"
:class="{ error: !urlValid }"
v-model="url"
@keyup.enter="urlValid ? toggleConnection() : null"

View File

@@ -7,6 +7,12 @@ export default {
gql[attribute] = value
},
setCollapsedSection({ theme }, value) {
theme.collapsedSections.includes(value)
? (theme.collapsedSections = theme.collapsedSections.filter(section => section !== value))
: theme.collapsedSections.push(value)
},
addGQLHeader({ gql }, object) {
gql.headers.push(object)
},

View File

@@ -21,8 +21,13 @@ export default () => ({
gql: {
url: "https://rickandmortyapi.com/graphql",
headers: [],
schema: "",
variablesJSONString: "{}",
query: "",
response: "",
},
theme: {
collapsedSections: [],
},
oauth2: {
tokens: [],