refactor: merge branch 'main' into refactor/monorepo
This commit is contained in:
@@ -195,7 +195,7 @@ export class GQLConnection {
|
||||
method: "post",
|
||||
url,
|
||||
headers: {
|
||||
...headers,
|
||||
...finalHeaders,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
data: JSON.stringify({
|
||||
|
||||
@@ -15,16 +15,16 @@ describe("getEditorLangForMimeType", () => {
|
||||
expect(getEditorLangForMimeType("text/html")).toMatch("html")
|
||||
})
|
||||
|
||||
test("returns 'plain_text' for plain text mime", () => {
|
||||
expect(getEditorLangForMimeType("text/plain")).toMatch("plain_text")
|
||||
test("returns 'text/x-yaml' for plain text mime", () => {
|
||||
expect(getEditorLangForMimeType("text/plain")).toMatch("text/x-yaml")
|
||||
})
|
||||
|
||||
test("returns 'plain_text' for unimplemented mimes", () => {
|
||||
expect(getEditorLangForMimeType("image/gif")).toMatch("plain_text")
|
||||
test("returns 'text/x-yaml' for unimplemented mimes", () => {
|
||||
expect(getEditorLangForMimeType("image/gif")).toMatch("text/x-yaml")
|
||||
})
|
||||
|
||||
test("returns 'plain_text' for null/undefined mimes", () => {
|
||||
expect(getEditorLangForMimeType(null)).toMatch("plain_text")
|
||||
expect(getEditorLangForMimeType(undefined)).toMatch("plain_text")
|
||||
test("returns 'text/x-yaml' for null/undefined mimes", () => {
|
||||
expect(getEditorLangForMimeType(null)).toMatch("text/x-yaml")
|
||||
expect(getEditorLangForMimeType(undefined)).toMatch("text/x-yaml")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -150,8 +150,6 @@ function getCodegenGeneralRESTInfo(
|
||||
.map((x) => ({ ...x, active: true }))
|
||||
: request.effectiveFinalHeaders.map((x) => ({ ...x, active: true }))
|
||||
|
||||
console.log(finalHeaders)
|
||||
|
||||
return {
|
||||
name: request.name,
|
||||
uri: request.effectiveFinalURL,
|
||||
|
||||
@@ -24,7 +24,7 @@ export const CLibcurlCodegen = {
|
||||
`curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "${method}");`
|
||||
)
|
||||
requestString.push(
|
||||
`curl_easy_setopt(hnd, CURLOPT_URL, "${url}${pathName}${queryString}");`
|
||||
`curl_easy_setopt(hnd, CURLOPT_URL, "${url}${pathName}?${queryString}");`
|
||||
)
|
||||
requestString.push(`struct curl_slist *headers = NULL;`)
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export const CsRestsharpCodegen = {
|
||||
// create client and request
|
||||
requestString.push(`var client = new RestClient("${url}");\n\n`)
|
||||
requestString.push(
|
||||
`var request = new RestRequest("${pathName}${queryString}", ${requestDataFormat});\n\n`
|
||||
`var request = new RestRequest("${pathName}?${queryString}", ${requestDataFormat});\n\n`
|
||||
)
|
||||
|
||||
// authentification
|
||||
|
||||
@@ -19,7 +19,7 @@ export const CurlCodegen = {
|
||||
}) => {
|
||||
const requestString = []
|
||||
requestString.push(`curl -X ${method}`)
|
||||
requestString.push(` '${url}${pathName}${queryString}'`)
|
||||
requestString.push(` '${url}${pathName}?${queryString}'`)
|
||||
if (auth === "Basic Auth") {
|
||||
const basic = `${httpUser}:${httpPassword}`
|
||||
requestString.push(
|
||||
|
||||
@@ -25,7 +25,7 @@ export const GoNativeCodegen = {
|
||||
const requestBody = rawInput ? rawParams : rawRequestBody
|
||||
if (method === "GET") {
|
||||
requestString.push(
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}${queryString}")\n`
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}")\n`
|
||||
)
|
||||
}
|
||||
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
||||
@@ -33,11 +33,11 @@ export const GoNativeCodegen = {
|
||||
if (isJSONContentType(contentType)) {
|
||||
requestString.push(`var reqBody = []byte(\`${requestBody}\`)\n\n`)
|
||||
requestString.push(
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}${queryString}", bytes.NewBuffer(reqBody))\n`
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", bytes.NewBuffer(reqBody))\n`
|
||||
)
|
||||
} else if (contentType.includes("x-www-form-urlencoded")) {
|
||||
requestString.push(
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}${queryString}", strings.NewReader("${requestBody}"))\n`
|
||||
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", strings.NewReader("${requestBody}"))\n`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const JavaOkhttpCodegen = {
|
||||
}
|
||||
|
||||
requestString.push("Request request = new Request.Builder()")
|
||||
requestString.push(`.url("${url}${pathName}${queryString}")`)
|
||||
requestString.push(`.url("${url}${pathName}?${queryString}")`)
|
||||
|
||||
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
||||
requestString.push(`.method("${method}", body)`)
|
||||
|
||||
@@ -32,7 +32,7 @@ export const JavaUnirestCodegen = {
|
||||
// create client and request
|
||||
const verb = verbs.find((v) => v.verb === method)
|
||||
requestString.push(
|
||||
`HttpResponse<String> response = Unirest.${verb.unirestMethod}("${url}${pathName}${queryString}")\n`
|
||||
`HttpResponse<String> response = Unirest.${verb.unirestMethod}("${url}${pathName}?${queryString}")\n`
|
||||
)
|
||||
if (auth === "Basic Auth") {
|
||||
const basic = `${httpUser}:${httpPassword}`
|
||||
|
||||
@@ -21,7 +21,7 @@ export const JavascriptFetchCodegen = {
|
||||
}) => {
|
||||
const requestString = []
|
||||
let genHeaders = []
|
||||
requestString.push(`fetch("${url}${pathName}${queryString}", {\n`)
|
||||
requestString.push(`fetch("${url}${pathName}?${queryString}", {\n`)
|
||||
requestString.push(` method: "${method}",\n`)
|
||||
if (auth === "Basic Auth") {
|
||||
const basic = `${httpUser}:${httpPassword}`
|
||||
|
||||
@@ -21,7 +21,7 @@ export const JavascriptJqueryCodegen = {
|
||||
const genHeaders = []
|
||||
|
||||
requestString.push(
|
||||
`jQuery.ajax({\n url: "${url}${pathName}${queryString}"`
|
||||
`jQuery.ajax({\n url: "${url}${pathName}?${queryString}"`
|
||||
)
|
||||
requestString.push(`,\n method: "${method.toUpperCase()}"`)
|
||||
const requestBody = rawInput ? rawParams : rawRequestBody
|
||||
|
||||
@@ -25,7 +25,7 @@ export const JavascriptXhrCodegen = {
|
||||
const user = auth === "Basic Auth" ? `'${httpUser}'` : null
|
||||
const password = auth === "Basic Auth" ? `'${httpPassword}'` : null
|
||||
requestString.push(
|
||||
`xhr.open('${method}', '${url}${pathName}${queryString}', true, ${user}, ${password})`
|
||||
`xhr.open('${method}', '${url}${pathName}?${queryString}', true, ${user}, ${password})`
|
||||
)
|
||||
if (auth === "Bearer Token" || auth === "OAuth 2.0") {
|
||||
requestString.push(
|
||||
|
||||
@@ -22,7 +22,7 @@ export const NodejsAxiosCodegen = {
|
||||
const requestBody = rawInput ? rawParams : rawRequestBody
|
||||
|
||||
requestString.push(
|
||||
`axios.${method.toLowerCase()}('${url}${pathName}${queryString}'`
|
||||
`axios.${method.toLowerCase()}('${url}${pathName}?${queryString}'`
|
||||
)
|
||||
if (requestBody.length !== 0) {
|
||||
requestString.push(", ")
|
||||
|
||||
@@ -24,7 +24,7 @@ export const NodejsNativeCodegen = {
|
||||
|
||||
requestString.push(`const http = require('http');\n\n`)
|
||||
|
||||
requestString.push(`const url = '${url}${pathName}${queryString}';\n`)
|
||||
requestString.push(`const url = '${url}${pathName}?${queryString}';\n`)
|
||||
|
||||
requestString.push(`const options = {\n`)
|
||||
requestString.push(` method: '${method}',\n`)
|
||||
|
||||
@@ -25,7 +25,7 @@ export const NodejsRequestCodegen = {
|
||||
requestString.push(`const request = require('request');\n`)
|
||||
requestString.push(`const options = {\n`)
|
||||
requestString.push(` method: '${method.toLowerCase()}',\n`)
|
||||
requestString.push(` url: '${url}${pathName}${queryString}'`)
|
||||
requestString.push(` url: '${url}${pathName}?${queryString}'`)
|
||||
|
||||
if (auth === "Basic Auth") {
|
||||
const basic = `${httpUser}:${httpPassword}`
|
||||
|
||||
@@ -25,7 +25,7 @@ export const NodejsUnirestCodegen = {
|
||||
requestString.push(`const unirest = require('unirest');\n`)
|
||||
requestString.push(`const req = unirest(\n`)
|
||||
requestString.push(
|
||||
`'${method.toLowerCase()}', '${url}${pathName}${queryString}')\n`
|
||||
`'${method.toLowerCase()}', '${url}${pathName}?${queryString}')\n`
|
||||
)
|
||||
|
||||
if (auth === "Basic Auth") {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const PhpCurlCodegen = {
|
||||
requestString.push(`<?php\n`)
|
||||
requestString.push(`$curl = curl_init();\n`)
|
||||
requestString.push(`curl_setopt_array($curl, array(\n`)
|
||||
requestString.push(` CURLOPT_URL => "${url}${pathName}${queryString}",\n`)
|
||||
requestString.push(` CURLOPT_URL => "${url}${pathName}?${queryString}",\n`)
|
||||
requestString.push(` CURLOPT_RETURNTRANSFER => true,\n`)
|
||||
requestString.push(` CURLOPT_ENCODING => "",\n`)
|
||||
requestString.push(` CURLOPT_MAXREDIRS => 10,\n`)
|
||||
|
||||
@@ -26,7 +26,7 @@ export const PowershellRestmethodCodegen = {
|
||||
let variables = ""
|
||||
|
||||
requestString.push(
|
||||
`Invoke-RestMethod -Method '${formattedMethod}' -Uri '${url}${pathName}${queryString}'`
|
||||
`Invoke-RestMethod -Method '${formattedMethod}' -Uri '${url}${pathName}?${queryString}'`
|
||||
)
|
||||
const requestBody = rawInput ? rawParams : rawRequestBody
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export const PythonHttpClientCodegen = {
|
||||
}
|
||||
}
|
||||
requestString.push(
|
||||
`conn.request("${method}", "${pathName}${queryString}", payload, headers)\n`
|
||||
`conn.request("${method}", "${pathName}?${queryString}", payload, headers)\n`
|
||||
)
|
||||
requestString.push(`res = conn.getresponse()\n`)
|
||||
requestString.push(`data = res.read()\n`)
|
||||
|
||||
@@ -31,7 +31,7 @@ export const PythonRequestsCodegen = {
|
||||
const genHeaders = []
|
||||
|
||||
requestString.push(`import requests\n\n`)
|
||||
requestString.push(`url = '${url}${pathName}${queryString}'\n`)
|
||||
requestString.push(`url = '${url}${pathName}?${queryString}'\n`)
|
||||
|
||||
// auth headers
|
||||
if (auth === "Basic Auth") {
|
||||
@@ -58,7 +58,7 @@ export const PythonRequestsCodegen = {
|
||||
requestString.push(...printHeaders(genHeaders))
|
||||
requestString.push(`response = requests.request(\n`)
|
||||
requestString.push(` '${method}',\n`)
|
||||
requestString.push(` '${url}${pathName}${queryString}',\n`)
|
||||
requestString.push(` '${url}${pathName}?${queryString}',\n`)
|
||||
}
|
||||
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
||||
genHeaders.push(`'Content-Type': '${contentType}'`)
|
||||
@@ -83,7 +83,7 @@ export const PythonRequestsCodegen = {
|
||||
}
|
||||
requestString.push(`response = requests.request(\n`)
|
||||
requestString.push(` '${method}',\n`)
|
||||
requestString.push(` '${url}${pathName}${queryString}',\n`)
|
||||
requestString.push(` '${url}${pathName}?${queryString}',\n`)
|
||||
requestString.push(` data=data,\n`)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export const RubyNetHttpCodeGen = {
|
||||
|
||||
// create URI and request
|
||||
const verb = verbs.find((v) => v.verb === method)
|
||||
requestString.push(`uri = URI.parse('${url}${pathName}${queryString}')\n`)
|
||||
requestString.push(`uri = URI.parse('${url}${pathName}?${queryString}')\n`)
|
||||
requestString.push(`request = Net::HTTP::${verb.rbMethod}.new(uri)`)
|
||||
|
||||
// content type
|
||||
|
||||
@@ -30,7 +30,7 @@ export const SalesforceApexCodegen = {
|
||||
requestString.push(`HttpRequest request = new HttpRequest();\n`)
|
||||
requestString.push(`request.setMethod('${method}');\n`)
|
||||
requestString.push(
|
||||
`request.setEndpoint('${url}${pathName}${queryString}');\n\n`
|
||||
`request.setEndpoint('${url}${pathName}?${queryString}');\n\n`
|
||||
)
|
||||
|
||||
// authentification
|
||||
|
||||
@@ -37,7 +37,7 @@ export const ShellHttpieCodegen = {
|
||||
}
|
||||
|
||||
// URL
|
||||
let escapedUrl = `${url}${pathName}${queryString}`
|
||||
let escapedUrl = `${url}${pathName}?${queryString}`
|
||||
escapedUrl = escapedUrl.replace(/'/g, "\\'")
|
||||
requestString.push(` ${method} $'${escapedUrl}'`)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export const ShellWgetCodegen = {
|
||||
}) => {
|
||||
const requestString = []
|
||||
requestString.push(`wget -O - --method=${method}`)
|
||||
requestString.push(` '${url}${pathName}${queryString}'`)
|
||||
requestString.push(` '${url}${pathName}?${queryString}'`)
|
||||
if (auth === "Basic Auth") {
|
||||
const basic = `${httpUser}:${httpPassword}`
|
||||
requestString.push(
|
||||
|
||||
215
packages/hoppscotch-app/helpers/editor/codemirror.ts
Normal file
215
packages/hoppscotch-app/helpers/editor/codemirror.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import CodeMirror from "codemirror"
|
||||
|
||||
import "codemirror-theme-github/theme/github.css"
|
||||
import "codemirror/theme/base16-dark.css"
|
||||
import "codemirror/theme/tomorrow-night-bright.css"
|
||||
|
||||
import "codemirror/lib/codemirror.css"
|
||||
import "codemirror/addon/lint/lint.css"
|
||||
import "codemirror/addon/dialog/dialog.css"
|
||||
import "codemirror/addon/hint/show-hint.css"
|
||||
|
||||
import "codemirror/addon/fold/foldgutter.css"
|
||||
import "codemirror/addon/fold/foldgutter"
|
||||
import "codemirror/addon/fold/brace-fold"
|
||||
import "codemirror/addon/fold/comment-fold"
|
||||
import "codemirror/addon/fold/indent-fold"
|
||||
import "codemirror/addon/display/autorefresh"
|
||||
import "codemirror/addon/lint/lint"
|
||||
import "codemirror/addon/hint/show-hint"
|
||||
import "codemirror/addon/display/placeholder"
|
||||
import "codemirror/addon/edit/closebrackets"
|
||||
import "codemirror/addon/search/search"
|
||||
import "codemirror/addon/search/searchcursor"
|
||||
import "codemirror/addon/search/jump-to-line"
|
||||
import "codemirror/addon/dialog/dialog"
|
||||
import "codemirror/addon/selection/active-line"
|
||||
|
||||
import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api"
|
||||
import { LinterDefinition } from "./linting/linter"
|
||||
import { Completer } from "./completion"
|
||||
|
||||
type CodeMirrorOptions = {
|
||||
extendedEditorConfig: Omit<CodeMirror.EditorConfiguration, "value">
|
||||
linter: LinterDefinition | null
|
||||
completer: Completer | null
|
||||
}
|
||||
|
||||
const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = {
|
||||
autoRefresh: true,
|
||||
lineNumbers: true,
|
||||
foldGutter: true,
|
||||
autoCloseBrackets: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
extraKeys: {
|
||||
"Ctrl-Space": "autocomplete",
|
||||
},
|
||||
viewportMargin: Infinity,
|
||||
styleActiveLine: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue composable to mount and use Codemirror
|
||||
*
|
||||
* NOTE: Make sure to import all the necessary Codemirror modules,
|
||||
* as this function doesn't import any other than the core
|
||||
* @param el Reference to the dom node to attach to
|
||||
* @param value Reference to value to read/write to
|
||||
* @param options CodeMirror options to pass
|
||||
*/
|
||||
export function useCodemirror(
|
||||
el: Ref<any | null>,
|
||||
value: Ref<string>,
|
||||
options: CodeMirrorOptions
|
||||
): { cm: Ref<CodeMirror.Position | null>; cursor: Ref<CodeMirror.Position> } {
|
||||
const { $colorMode } = useContext() as any
|
||||
|
||||
const cm = ref<CodeMirror.Editor | null>(null)
|
||||
const cursor = ref<CodeMirror.Position>({ line: 0, ch: 0 })
|
||||
|
||||
const updateEditorConfig = () => {
|
||||
Object.keys(options.extendedEditorConfig).forEach((key) => {
|
||||
// Only update options which need updating
|
||||
if (
|
||||
cm.value &&
|
||||
cm.value?.getOption(key as any) !==
|
||||
(options.extendedEditorConfig as any)[key]
|
||||
) {
|
||||
cm.value?.setOption(
|
||||
key as any,
|
||||
(options.extendedEditorConfig as any)[key]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateLinterConfig = () => {
|
||||
if (options.linter) {
|
||||
cm.value?.setOption("lint", options.linter)
|
||||
}
|
||||
}
|
||||
|
||||
const updateCompleterConfig = () => {
|
||||
if (options.completer) {
|
||||
cm.value?.setOption("hintOptions", {
|
||||
completeSingle: false,
|
||||
hint: async (editor: CodeMirror.Editor) => {
|
||||
const pos = editor.getCursor()
|
||||
const text = editor.getValue()
|
||||
|
||||
const token = editor.getTokenAt(pos)
|
||||
// It's not a word token, so, just increment to skip to next
|
||||
if (token.string.toUpperCase() === token.string.toLowerCase())
|
||||
token.start += 1
|
||||
|
||||
const result = await options.completer!(text, pos)
|
||||
|
||||
if (!result) return null
|
||||
|
||||
return <CodeMirror.Hints>{
|
||||
from: { line: pos.line, ch: token.start },
|
||||
to: { line: pos.line, ch: token.end },
|
||||
list: result.completions
|
||||
.sort((a, b) => a.score - b.score)
|
||||
.map((x) => x.text),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const initialize = () => {
|
||||
if (!el.value) return
|
||||
|
||||
cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG)
|
||||
|
||||
cm.value.setValue(value.value)
|
||||
|
||||
setTheme()
|
||||
updateEditorConfig()
|
||||
updateLinterConfig()
|
||||
updateCompleterConfig()
|
||||
|
||||
cm.value.on("change", (instance) => {
|
||||
// External update propagation (via watchers) should be ignored
|
||||
if (instance.getValue() !== value.value) {
|
||||
value.value = instance.getValue()
|
||||
}
|
||||
})
|
||||
|
||||
cm.value.on("cursorActivity", (instance) => {
|
||||
cursor.value = instance.getCursor()
|
||||
})
|
||||
}
|
||||
|
||||
// Boot-up CodeMirror, set the value and listeners
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
})
|
||||
|
||||
// Reinitialize if the target ref updates
|
||||
watch(el, () => {
|
||||
if (cm.value) {
|
||||
const parent = cm.value.getWrapperElement()
|
||||
parent.remove()
|
||||
cm.value = null
|
||||
}
|
||||
initialize()
|
||||
})
|
||||
|
||||
const setTheme = () => {
|
||||
if (cm.value) {
|
||||
cm.value?.setOption("theme", getThemeName($colorMode.value))
|
||||
}
|
||||
}
|
||||
|
||||
const getThemeName = (mode: string) => {
|
||||
switch (mode) {
|
||||
case "system":
|
||||
return "default"
|
||||
case "light":
|
||||
return "github"
|
||||
case "dark":
|
||||
return "base16-dark"
|
||||
case "black":
|
||||
return "tomorrow-night-bright"
|
||||
default:
|
||||
return "default"
|
||||
}
|
||||
}
|
||||
|
||||
// If the editor properties are reactive, watch for updates
|
||||
watch(() => options.extendedEditorConfig, updateEditorConfig, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
watch(() => options.linter, updateLinterConfig, { immediate: true })
|
||||
watch(() => options.completer, updateCompleterConfig, { immediate: true })
|
||||
|
||||
// Watch value updates
|
||||
watch(value, (newVal) => {
|
||||
// Check if we are mounted
|
||||
if (cm.value) {
|
||||
// Don't do anything on internal updates
|
||||
if (cm.value.getValue() !== newVal) {
|
||||
cm.value.setValue(newVal)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Push cursor updates
|
||||
watch(cursor, (value) => {
|
||||
if (value !== cm.value?.getCursor()) {
|
||||
cm.value?.focus()
|
||||
cm.value?.setCursor(value)
|
||||
}
|
||||
})
|
||||
|
||||
// Watch color mode updates and update theme
|
||||
watch(() => $colorMode.value, setTheme)
|
||||
|
||||
return {
|
||||
cm,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Ref } from "@nuxtjs/composition-api"
|
||||
import { GraphQLSchema } from "graphql"
|
||||
import { getAutocompleteSuggestions } from "graphql-language-service-interface"
|
||||
import { Completer, CompleterResult, CompletionEntry } from "."
|
||||
|
||||
const completer: (schemaRef: Ref<GraphQLSchema | null>) => Completer =
|
||||
(schemaRef: Ref<GraphQLSchema | null>) => (text, completePos) => {
|
||||
if (!schemaRef.value) return Promise.resolve(null)
|
||||
|
||||
const completions = getAutocompleteSuggestions(schemaRef.value, text, {
|
||||
line: completePos.line,
|
||||
character: completePos.ch,
|
||||
} as any)
|
||||
|
||||
return Promise.resolve(<CompleterResult>{
|
||||
completions: completions.map(
|
||||
(x, i) =>
|
||||
<CompletionEntry>{
|
||||
text: x.label!,
|
||||
meta: x.detail!,
|
||||
score: completions.length - i,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
export default completer
|
||||
23
packages/hoppscotch-app/helpers/editor/completion/index.ts
Normal file
23
packages/hoppscotch-app/helpers/editor/completion/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export type CompletionEntry = {
|
||||
text: string
|
||||
meta: string
|
||||
score: number
|
||||
}
|
||||
|
||||
export type CompleterResult = {
|
||||
/**
|
||||
* List of completions to display
|
||||
*/
|
||||
completions: CompletionEntry[]
|
||||
}
|
||||
|
||||
export type Completer = (
|
||||
/**
|
||||
* The contents of the editor
|
||||
*/
|
||||
text: string,
|
||||
/**
|
||||
* Position where the completer is fired
|
||||
*/
|
||||
completePos: { line: number; ch: number }
|
||||
) => Promise<CompleterResult | null>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Completer, CompletionEntry } from "."
|
||||
import { getPreRequestScriptCompletions } from "~/helpers/tern"
|
||||
|
||||
const completer: Completer = async (text, completePos) => {
|
||||
const results = await getPreRequestScriptCompletions(
|
||||
text,
|
||||
completePos.line,
|
||||
completePos.ch
|
||||
)
|
||||
|
||||
const completions = results.completions.map((completion: any, i: number) => {
|
||||
return <CompletionEntry>{
|
||||
text: completion.name,
|
||||
meta: completion.isKeyword ? "keyword" : completion.type,
|
||||
score: results.completions.length - i,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
completions,
|
||||
}
|
||||
}
|
||||
|
||||
export default completer
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Completer, CompletionEntry } from "."
|
||||
import { getTestScriptCompletions } from "~/helpers/tern"
|
||||
|
||||
export const completer: Completer = async (text, completePos) => {
|
||||
const results = await getTestScriptCompletions(
|
||||
text,
|
||||
completePos.line,
|
||||
completePos.ch
|
||||
)
|
||||
|
||||
const completions = results.completions.map((completion: any, i: number) => {
|
||||
return <CompletionEntry>{
|
||||
text: completion.name,
|
||||
meta: completion.isKeyword ? "keyword" : completion.type,
|
||||
score: results.completions.length - i,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
completions,
|
||||
}
|
||||
}
|
||||
|
||||
export default completer
|
||||
58
packages/hoppscotch-app/helpers/editor/linting/gqlQuery.ts
Normal file
58
packages/hoppscotch-app/helpers/editor/linting/gqlQuery.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Ref } from "@nuxtjs/composition-api"
|
||||
import {
|
||||
GraphQLError,
|
||||
GraphQLSchema,
|
||||
parse as gqlParse,
|
||||
validate as gqlValidate,
|
||||
} from "graphql"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
|
||||
/**
|
||||
* Creates a Linter function that can lint a GQL query against a given
|
||||
* schema
|
||||
*/
|
||||
export const createGQLQueryLinter: (
|
||||
schema: Ref<GraphQLSchema | null>
|
||||
) => LinterDefinition = (schema: Ref<GraphQLSchema | null>) => (text) => {
|
||||
if (text === "") return Promise.resolve([])
|
||||
if (!schema.value) return Promise.resolve([])
|
||||
|
||||
try {
|
||||
const doc = gqlParse(text)
|
||||
|
||||
const results = gqlValidate(schema.value, doc).map(
|
||||
({ locations, message }) =>
|
||||
<LinterResult>{
|
||||
from: {
|
||||
line: locations![0].line - 1,
|
||||
ch: locations![0].column - 1,
|
||||
},
|
||||
to: {
|
||||
line: locations![0].line - 1,
|
||||
ch: locations![0].column,
|
||||
},
|
||||
message,
|
||||
severity: "error",
|
||||
}
|
||||
)
|
||||
|
||||
return Promise.resolve(results)
|
||||
} catch (e) {
|
||||
const err = e as GraphQLError
|
||||
|
||||
return Promise.resolve([
|
||||
<LinterResult>{
|
||||
from: {
|
||||
line: err.locations![0].line - 1,
|
||||
ch: err.locations![0].column - 1,
|
||||
},
|
||||
to: {
|
||||
line: err.locations![0].line - 1,
|
||||
ch: err.locations![0].column,
|
||||
},
|
||||
message: err.message,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
21
packages/hoppscotch-app/helpers/editor/linting/json.ts
Normal file
21
packages/hoppscotch-app/helpers/editor/linting/json.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { convertIndexToLineCh } from "../utils"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import jsonParse from "~/helpers/jsonParse"
|
||||
|
||||
const linter: LinterDefinition = (text) => {
|
||||
try {
|
||||
jsonParse(text)
|
||||
return Promise.resolve([])
|
||||
} catch (e: any) {
|
||||
return Promise.resolve([
|
||||
<LinterResult>{
|
||||
from: convertIndexToLineCh(text, e.start),
|
||||
to: convertIndexToLineCh(text, e.end),
|
||||
message: e.message,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export default linter
|
||||
7
packages/hoppscotch-app/helpers/editor/linting/linter.ts
Normal file
7
packages/hoppscotch-app/helpers/editor/linting/linter.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type LinterResult = {
|
||||
message: string
|
||||
severity: "warning" | "error"
|
||||
from: { line: number; ch: number }
|
||||
to: { line: number; ch: number }
|
||||
}
|
||||
export type LinterDefinition = (text: string) => Promise<LinterResult[]>
|
||||
69
packages/hoppscotch-app/helpers/editor/linting/preRequest.ts
Normal file
69
packages/hoppscotch-app/helpers/editor/linting/preRequest.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as esprima from "esprima"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import { performPreRequestLinting } from "~/helpers/tern"
|
||||
|
||||
const linter: LinterDefinition = async (text) => {
|
||||
let results: LinterResult[] = []
|
||||
|
||||
// Semantic linting
|
||||
const semanticLints = await performPreRequestLinting(text)
|
||||
|
||||
results = results.concat(
|
||||
semanticLints.map((lint: any) => ({
|
||||
from: lint.from,
|
||||
to: lint.to,
|
||||
severity: "error",
|
||||
message: `[semantic] ${lint.message}`,
|
||||
}))
|
||||
)
|
||||
|
||||
// Syntax linting
|
||||
try {
|
||||
const res: any = esprima.parseScript(text, { tolerant: true })
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
results = results.concat(
|
||||
res.errors.map((err: any) => {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber - 1,
|
||||
ch: err.column - 1,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber - 1,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
return <LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${err.description}`,
|
||||
severity: "error",
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber - 1,
|
||||
ch: e.column - 1,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber - 1,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
results = results.concat([
|
||||
<LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${e.description}`,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export default linter
|
||||
69
packages/hoppscotch-app/helpers/editor/linting/testScript.ts
Normal file
69
packages/hoppscotch-app/helpers/editor/linting/testScript.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as esprima from "esprima"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import { performTestLinting } from "~/helpers/tern"
|
||||
|
||||
const linter: LinterDefinition = async (text) => {
|
||||
let results: LinterResult[] = []
|
||||
|
||||
// Semantic linting
|
||||
const semanticLints = await performTestLinting(text)
|
||||
|
||||
results = results.concat(
|
||||
semanticLints.map((lint: any) => ({
|
||||
from: lint.from,
|
||||
to: lint.to,
|
||||
severity: "error",
|
||||
message: `[semantic] ${lint.message}`,
|
||||
}))
|
||||
)
|
||||
|
||||
// Syntax linting
|
||||
try {
|
||||
const res: any = esprima.parseScript(text, { tolerant: true })
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
results = results.concat(
|
||||
res.errors.map((err: any) => {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber - 1,
|
||||
ch: err.column - 1,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber - 1,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
return <LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${err.description}`,
|
||||
severity: "error",
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber - 1,
|
||||
ch: e.column - 1,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber - 1,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
results = results.concat([
|
||||
<LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${e.description}`,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export default linter
|
||||
80
packages/hoppscotch-app/helpers/editor/modes/graphql.ts
Normal file
80
packages/hoppscotch-app/helpers/editor/modes/graphql.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2021 GraphQL Contributors
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
import CodeMirror from "codemirror"
|
||||
import {
|
||||
LexRules,
|
||||
ParseRules,
|
||||
isIgnored,
|
||||
onlineParser,
|
||||
State,
|
||||
} from "graphql-language-service-parser"
|
||||
|
||||
/**
|
||||
* The GraphQL mode is defined as a tokenizer along with a list of rules, each
|
||||
* of which is either a function or an array.
|
||||
*
|
||||
* * Function: Provided a token and the stream, returns an expected next step.
|
||||
* * Array: A list of steps to take in order.
|
||||
*
|
||||
* A step is either another rule, or a terminal description of a token. If it
|
||||
* is a rule, that rule is pushed onto the stack and the parsing continues from
|
||||
* that point.
|
||||
*
|
||||
* If it is a terminal description, the token is checked against it using a
|
||||
* `match` function. If the match is successful, the token is colored and the
|
||||
* rule is stepped forward. If the match is unsuccessful, the remainder of the
|
||||
* rule is skipped and the previous rule is advanced.
|
||||
*
|
||||
* This parsing algorithm allows for incremental online parsing within various
|
||||
* levels of the syntax tree and results in a structured `state` linked-list
|
||||
* which contains the relevant information to produce valuable typeaheads.
|
||||
*/
|
||||
CodeMirror.defineMode("graphql", (config) => {
|
||||
const parser = onlineParser({
|
||||
eatWhitespace: (stream) => stream.eatWhile(isIgnored),
|
||||
lexRules: LexRules,
|
||||
parseRules: ParseRules,
|
||||
editorConfig: { tabSize: 2 },
|
||||
})
|
||||
|
||||
return {
|
||||
config,
|
||||
startState: parser.startState,
|
||||
token: parser.token as unknown as CodeMirror.Mode<any>["token"], // TODO: Check if the types are indeed compatible
|
||||
indent,
|
||||
electricInput: /^\s*[})\]]/,
|
||||
fold: "brace",
|
||||
lineComment: "#",
|
||||
closeBrackets: {
|
||||
pairs: '()[]{}""',
|
||||
explode: "()[]{}",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Seems the electricInput type in @types/codemirror is wrong (i.e it is written as electricinput instead of electricInput)
|
||||
function indent(
|
||||
this: CodeMirror.Mode<any> & {
|
||||
electricInput?: RegExp
|
||||
config?: CodeMirror.EditorConfiguration
|
||||
},
|
||||
state: State,
|
||||
textAfter: string
|
||||
) {
|
||||
const levels = state.levels
|
||||
// If there is no stack of levels, use the current level.
|
||||
// Otherwise, use the top level, pre-emptively dedenting for close braces.
|
||||
const level =
|
||||
!levels || levels.length === 0
|
||||
? state.indentLevel
|
||||
: levels[levels.length - 1] -
|
||||
(this.electricInput?.test(textAfter) ? 1 : 0)
|
||||
return (level || 0) * (this.config?.indentUnit || 0)
|
||||
}
|
||||
38
packages/hoppscotch-app/helpers/editor/utils.ts
Normal file
38
packages/hoppscotch-app/helpers/editor/utils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export function convertIndexToLineCh(
|
||||
text: string,
|
||||
i: number
|
||||
): { line: number; ch: number } {
|
||||
const lines = text.split("\n")
|
||||
|
||||
let line = 0
|
||||
let counter = 0
|
||||
|
||||
while (line < lines.length) {
|
||||
if (i > lines[line].length + counter) {
|
||||
counter += lines[line].length + 1
|
||||
line++
|
||||
} else {
|
||||
return {
|
||||
line: line + 1,
|
||||
ch: i - counter + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Invalid input")
|
||||
}
|
||||
|
||||
export function convertLineChToIndex(
|
||||
text: string,
|
||||
lineCh: { line: number; ch: number }
|
||||
): number {
|
||||
const textSplit = text.split("\n")
|
||||
|
||||
if (textSplit.length < lineCh.line) throw new Error("Invalid position")
|
||||
|
||||
const tillLineIndex = textSplit
|
||||
.slice(0, lineCh.line)
|
||||
.reduce((acc, line) => acc + line.length + 1, 0)
|
||||
|
||||
return tillLineIndex + lineCh.ch
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
const mimeToMode = {
|
||||
"text/plain": "plain_text",
|
||||
"text/html": "html",
|
||||
"application/xml": "xml",
|
||||
"application/hal+json": "json",
|
||||
"application/vnd.api+json": "json",
|
||||
"application/json": "json",
|
||||
"text/plain": "text/x-yaml",
|
||||
"text/html": "htmlmixed",
|
||||
"application/xml": "application/xml",
|
||||
"application/hal+json": "application/ld+json",
|
||||
"application/vnd.api+json": "application/ld+json",
|
||||
"application/json": "application/ld+json",
|
||||
}
|
||||
|
||||
export function getEditorLangForMimeType(mimeType) {
|
||||
return mimeToMode[mimeType] || "plain_text"
|
||||
return mimeToMode[mimeType] || "text/x-yaml"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,75 @@
|
||||
* - end: int - the end exclusive offset of the syntax error
|
||||
*
|
||||
*/
|
||||
export default function jsonParse(str) {
|
||||
type JSONEOFValue = {
|
||||
kind: "EOF"
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
type JSONNullValue = {
|
||||
kind: "Null"
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
type JSONNumberValue = {
|
||||
kind: "Number"
|
||||
start: number
|
||||
end: number
|
||||
value: number
|
||||
}
|
||||
|
||||
type JSONStringValue = {
|
||||
kind: "String"
|
||||
start: number
|
||||
end: number
|
||||
value: string
|
||||
}
|
||||
|
||||
type JSONBooleanValue = {
|
||||
kind: "Boolean"
|
||||
start: number
|
||||
end: number
|
||||
value: boolean
|
||||
}
|
||||
|
||||
type JSONPrimitiveValue =
|
||||
| JSONNullValue
|
||||
| JSONEOFValue
|
||||
| JSONStringValue
|
||||
| JSONNumberValue
|
||||
| JSONBooleanValue
|
||||
|
||||
export type JSONObjectValue = {
|
||||
kind: "Object"
|
||||
start: number
|
||||
end: number
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
members: JSONObjectMember[]
|
||||
}
|
||||
|
||||
export type JSONArrayValue = {
|
||||
kind: "Array"
|
||||
start: number
|
||||
end: number
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
values: JSONValue[]
|
||||
}
|
||||
|
||||
export type JSONValue = JSONObjectValue | JSONArrayValue | JSONPrimitiveValue
|
||||
|
||||
export type JSONObjectMember = {
|
||||
kind: "Member"
|
||||
start: number
|
||||
end: number
|
||||
key: JSONStringValue
|
||||
value: JSONValue
|
||||
}
|
||||
|
||||
export default function jsonParse(
|
||||
str: string
|
||||
): JSONObjectValue | JSONArrayValue {
|
||||
string = str
|
||||
strLen = str.length
|
||||
start = end = lastEnd = -1
|
||||
@@ -37,15 +105,15 @@ export default function jsonParse(str) {
|
||||
}
|
||||
}
|
||||
|
||||
let string
|
||||
let strLen
|
||||
let start
|
||||
let end
|
||||
let lastEnd
|
||||
let code
|
||||
let kind
|
||||
let string: string
|
||||
let strLen: number
|
||||
let start: number
|
||||
let end: number
|
||||
let lastEnd: number
|
||||
let code: number
|
||||
let kind: string
|
||||
|
||||
function parseObj() {
|
||||
function parseObj(): JSONObjectValue {
|
||||
const nodeStart = start
|
||||
const members = []
|
||||
expect("{")
|
||||
@@ -63,9 +131,9 @@ function parseObj() {
|
||||
}
|
||||
}
|
||||
|
||||
function parseMember() {
|
||||
function parseMember(): JSONObjectMember {
|
||||
const nodeStart = start
|
||||
const key = kind === "String" ? curToken() : null
|
||||
const key = kind === "String" ? (curToken() as JSONStringValue) : null
|
||||
expect("String")
|
||||
expect(":")
|
||||
const value = parseVal()
|
||||
@@ -73,14 +141,14 @@ function parseMember() {
|
||||
kind: "Member",
|
||||
start: nodeStart,
|
||||
end: lastEnd,
|
||||
key,
|
||||
key: key!,
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
function parseArr() {
|
||||
function parseArr(): JSONArrayValue {
|
||||
const nodeStart = start
|
||||
const values = []
|
||||
const values: JSONValue[] = []
|
||||
expect("[")
|
||||
if (!skip("]")) {
|
||||
do {
|
||||
@@ -96,7 +164,7 @@ function parseArr() {
|
||||
}
|
||||
}
|
||||
|
||||
function parseVal() {
|
||||
function parseVal(): JSONValue {
|
||||
switch (kind) {
|
||||
case "[":
|
||||
return parseArr()
|
||||
@@ -111,14 +179,19 @@ function parseVal() {
|
||||
lex()
|
||||
return token
|
||||
}
|
||||
return expect("Value")
|
||||
return expect("Value") as never
|
||||
}
|
||||
|
||||
function curToken() {
|
||||
return { kind, start, end, value: JSON.parse(string.slice(start, end)) }
|
||||
function curToken(): JSONPrimitiveValue {
|
||||
return {
|
||||
kind: kind as any,
|
||||
start,
|
||||
end,
|
||||
value: JSON.parse(string.slice(start, end)),
|
||||
}
|
||||
}
|
||||
|
||||
function expect(str) {
|
||||
function expect(str: string) {
|
||||
if (kind === str) {
|
||||
lex()
|
||||
return
|
||||
@@ -137,11 +210,17 @@ function expect(str) {
|
||||
throw syntaxError(`Expected ${str} but found ${found}.`)
|
||||
}
|
||||
|
||||
function syntaxError(message) {
|
||||
type SyntaxError = {
|
||||
message: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
function syntaxError(message: string): SyntaxError {
|
||||
return { message, start, end }
|
||||
}
|
||||
|
||||
function skip(k) {
|
||||
function skip(k: string) {
|
||||
if (kind === k) {
|
||||
lex()
|
||||
return true
|
||||
@@ -227,7 +306,7 @@ function lex() {
|
||||
function readString() {
|
||||
ch()
|
||||
while (code !== 34 && code > 31) {
|
||||
if (code === 92) {
|
||||
if (code === (92 as any)) {
|
||||
// \
|
||||
ch()
|
||||
switch (code) {
|
||||
@@ -299,7 +378,7 @@ function readNumber() {
|
||||
if (code === 69 || code === 101) {
|
||||
// E e
|
||||
ch()
|
||||
if (code === 43 || code === 45) {
|
||||
if (code === (43 as any) || code === (45 as any)) {
|
||||
// + -
|
||||
ch()
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
const htmlLens = {
|
||||
import { Lens } from "./lenses"
|
||||
|
||||
const htmlLens: Lens = {
|
||||
lensName: "response.html",
|
||||
isSupportedContentType: (contentType) =>
|
||||
/\btext\/html|application\/xhtml\+xml\b/i.test(contentType),
|
||||
renderer: "htmlres",
|
||||
rendererImport: () =>
|
||||
import("~/components/lenses/renderers/HTMLLensRenderer"),
|
||||
import("~/components/lenses/renderers/HTMLLensRenderer.vue"),
|
||||
}
|
||||
|
||||
export default htmlLens
|
||||
@@ -1,4 +1,6 @@
|
||||
const imageLens = {
|
||||
import { Lens } from "./lenses"
|
||||
|
||||
const imageLens: Lens = {
|
||||
lensName: "response.image",
|
||||
isSupportedContentType: (contentType) =>
|
||||
/\bimage\/(?:gif|jpeg|png|bmp|svg\+xml|x-icon|vnd\.microsoft\.icon)\b/i.test(
|
||||
@@ -6,7 +8,7 @@ const imageLens = {
|
||||
),
|
||||
renderer: "imageres",
|
||||
rendererImport: () =>
|
||||
import("~/components/lenses/renderers/ImageLensRenderer"),
|
||||
import("~/components/lenses/renderers/ImageLensRenderer.vue"),
|
||||
}
|
||||
|
||||
export default imageLens
|
||||
@@ -1,11 +1,12 @@
|
||||
import { isJSONContentType } from "../utils/contenttypes"
|
||||
import { Lens } from "./lenses"
|
||||
|
||||
const jsonLens = {
|
||||
const jsonLens: Lens = {
|
||||
lensName: "response.json",
|
||||
isSupportedContentType: isJSONContentType,
|
||||
renderer: "json",
|
||||
rendererImport: () =>
|
||||
import("~/components/lenses/renderers/JSONLensRenderer"),
|
||||
import("~/components/lenses/renderers/JSONLensRenderer.vue"),
|
||||
}
|
||||
|
||||
export default jsonLens
|
||||
@@ -1,28 +0,0 @@
|
||||
import jsonLens from "./jsonLens"
|
||||
import rawLens from "./rawLens"
|
||||
import imageLens from "./imageLens"
|
||||
import htmlLens from "./htmlLens"
|
||||
import xmlLens from "./xmlLens"
|
||||
|
||||
export const lenses = [jsonLens, imageLens, htmlLens, xmlLens, rawLens]
|
||||
|
||||
export function getSuitableLenses(response) {
|
||||
const contentType = response.headers.find((h) => h.key === "content-type")
|
||||
|
||||
if (!contentType) return [rawLens]
|
||||
|
||||
const result = []
|
||||
for (const lens of lenses) {
|
||||
if (lens.isSupportedContentType(contentType.value)) result.push(lens)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function getLensRenderers() {
|
||||
const response = {}
|
||||
for (const lens of lenses) {
|
||||
response[lens.renderer] = lens.rendererImport
|
||||
}
|
||||
return response
|
||||
}
|
||||
42
packages/hoppscotch-app/helpers/lenses/lenses.ts
Normal file
42
packages/hoppscotch-app/helpers/lenses/lenses.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||
import jsonLens from "./jsonLens"
|
||||
import rawLens from "./rawLens"
|
||||
import imageLens from "./imageLens"
|
||||
import htmlLens from "./htmlLens"
|
||||
import xmlLens from "./xmlLens"
|
||||
|
||||
export type Lens = {
|
||||
lensName: string
|
||||
isSupportedContentType: (contentType: string) => boolean
|
||||
renderer: string
|
||||
rendererImport: () => Promise<typeof import("*.vue")>
|
||||
}
|
||||
|
||||
export const lenses: Lens[] = [jsonLens, imageLens, htmlLens, xmlLens, rawLens]
|
||||
|
||||
export function getSuitableLenses(response: HoppRESTResponse): Lens[] {
|
||||
// return empty array if response is loading or error
|
||||
if (response.type === "loading" || response.type === "network_fail") return []
|
||||
|
||||
const contentType = response.headers.find((h) => h.key === "content-type")
|
||||
|
||||
if (!contentType) return [rawLens]
|
||||
|
||||
const result = []
|
||||
for (const lens of lenses) {
|
||||
if (lens.isSupportedContentType(contentType.value)) result.push(lens)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type LensRenderers = {
|
||||
[key: string]: Lens["rendererImport"]
|
||||
}
|
||||
|
||||
export function getLensRenderers(): LensRenderers {
|
||||
const response: LensRenderers = {}
|
||||
for (const lens of lenses) {
|
||||
response[lens.renderer] = lens.rendererImport
|
||||
}
|
||||
return response
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
const rawLens = {
|
||||
lensName: "response.raw",
|
||||
isSupportedContentType: () => true,
|
||||
renderer: "raw",
|
||||
rendererImport: () => import("~/components/lenses/renderers/RawLensRenderer"),
|
||||
}
|
||||
|
||||
export default rawLens
|
||||
11
packages/hoppscotch-app/helpers/lenses/rawLens.ts
Normal file
11
packages/hoppscotch-app/helpers/lenses/rawLens.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Lens } from "./lenses"
|
||||
|
||||
const rawLens: Lens = {
|
||||
lensName: "response.raw",
|
||||
isSupportedContentType: () => true,
|
||||
renderer: "raw",
|
||||
rendererImport: () =>
|
||||
import("~/components/lenses/renderers/RawLensRenderer.vue"),
|
||||
}
|
||||
|
||||
export default rawLens
|
||||
@@ -1,8 +1,11 @@
|
||||
const xmlLens = {
|
||||
import { Lens } from "./lenses"
|
||||
|
||||
const xmlLens: Lens = {
|
||||
lensName: "response.xml",
|
||||
isSupportedContentType: (contentType) => /\bxml\b/i.test(contentType),
|
||||
renderer: "xmlres",
|
||||
rendererImport: () => import("~/components/lenses/renderers/XMLLensRenderer"),
|
||||
rendererImport: () =>
|
||||
import("~/components/lenses/renderers/XMLLensRenderer.vue"),
|
||||
}
|
||||
|
||||
export default xmlLens
|
||||
100
packages/hoppscotch-app/helpers/newOutline.ts
Normal file
100
packages/hoppscotch-app/helpers/newOutline.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
JSONArrayValue,
|
||||
JSONObjectMember,
|
||||
JSONObjectValue,
|
||||
JSONValue,
|
||||
} from "./jsonParse"
|
||||
|
||||
type RootEntry =
|
||||
| {
|
||||
kind: "RootObject"
|
||||
astValue: JSONObjectValue
|
||||
}
|
||||
| {
|
||||
kind: "RootArray"
|
||||
astValue: JSONArrayValue
|
||||
}
|
||||
|
||||
type ObjectMemberEntry = {
|
||||
kind: "ObjectMember"
|
||||
name: string
|
||||
astValue: JSONObjectMember
|
||||
astParent: JSONObjectValue
|
||||
}
|
||||
|
||||
type ArrayMemberEntry = {
|
||||
kind: "ArrayMember"
|
||||
index: number
|
||||
astValue: JSONValue
|
||||
astParent: JSONArrayValue
|
||||
}
|
||||
|
||||
type PathEntry = RootEntry | ObjectMemberEntry | ArrayMemberEntry
|
||||
|
||||
export function getJSONOutlineAtPos(
|
||||
jsonRootAst: JSONObjectValue | JSONArrayValue,
|
||||
posIndex: number
|
||||
): PathEntry[] | null {
|
||||
try {
|
||||
const rootObj = jsonRootAst
|
||||
|
||||
if (posIndex > rootObj.end || posIndex < rootObj.start)
|
||||
throw new Error("Invalid position")
|
||||
|
||||
let current: JSONValue = rootObj
|
||||
|
||||
const path: PathEntry[] = []
|
||||
|
||||
if (rootObj.kind === "Object") {
|
||||
path.push({
|
||||
kind: "RootObject",
|
||||
astValue: rootObj,
|
||||
})
|
||||
} else {
|
||||
path.push({
|
||||
kind: "RootArray",
|
||||
astValue: rootObj,
|
||||
})
|
||||
}
|
||||
|
||||
while (current.kind === "Object" || current.kind === "Array") {
|
||||
if (current.kind === "Object") {
|
||||
const next: JSONObjectMember | undefined = current.members.find(
|
||||
(member) => member.start <= posIndex && member.end >= posIndex
|
||||
)
|
||||
|
||||
if (!next) throw new Error("Couldn't find child")
|
||||
|
||||
path.push({
|
||||
kind: "ObjectMember",
|
||||
name: next.key.value,
|
||||
astValue: next,
|
||||
astParent: current,
|
||||
})
|
||||
|
||||
current = next.value
|
||||
} else {
|
||||
const nextIndex = current.values.findIndex(
|
||||
(value) => value.start <= posIndex && value.end >= posIndex
|
||||
)
|
||||
|
||||
if (nextIndex < 0) throw new Error("Couldn't find child")
|
||||
|
||||
const next: JSONValue = current.values[nextIndex]
|
||||
|
||||
path.push({
|
||||
kind: "ArrayMember",
|
||||
index: nextIndex,
|
||||
astValue: next,
|
||||
astParent: current,
|
||||
})
|
||||
|
||||
current = next
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
} catch (e: any) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import jsonParse from "./jsonParse"
|
||||
|
||||
export default () => {
|
||||
let jsonAST = {}
|
||||
let path = []
|
||||
|
||||
const init = (jsonStr) => {
|
||||
jsonAST = jsonParse(jsonStr)
|
||||
linkParents(jsonAST)
|
||||
}
|
||||
|
||||
const setNewText = (jsonStr) => {
|
||||
init(jsonStr)
|
||||
path = []
|
||||
}
|
||||
|
||||
const linkParents = (node) => {
|
||||
if (node.kind === "Object") {
|
||||
if (node.members) {
|
||||
node.members.forEach((m) => {
|
||||
m.parent = node
|
||||
linkParents(m)
|
||||
})
|
||||
}
|
||||
} else if (node.kind === "Array") {
|
||||
if (node.values) {
|
||||
node.values.forEach((v) => {
|
||||
v.parent = node
|
||||
linkParents(v)
|
||||
})
|
||||
}
|
||||
} else if (node.kind === "Member") {
|
||||
if (node.value) {
|
||||
node.value.parent = node
|
||||
linkParents(node.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const genPath = (index) => {
|
||||
let output = {}
|
||||
path = []
|
||||
let current = jsonAST
|
||||
if (current.kind === "Object") {
|
||||
path.push({ label: "{}", obj: "root" })
|
||||
} else if (current.kind === "Array") {
|
||||
path.push({ label: "[]", obj: "root" })
|
||||
}
|
||||
let over = false
|
||||
|
||||
try {
|
||||
while (!over) {
|
||||
if (current.kind === "Object") {
|
||||
let i = 0
|
||||
let found = false
|
||||
while (i < current.members.length) {
|
||||
const m = current.members[i]
|
||||
if (m.start <= index && m.end >= index) {
|
||||
path.push({ label: m.key.value, obj: m })
|
||||
current = current.members[i]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (!found) over = true
|
||||
} else if (current.kind === "Array") {
|
||||
if (current.values) {
|
||||
let i = 0
|
||||
let found = false
|
||||
while (i < current.values.length) {
|
||||
const m = current.values[i]
|
||||
if (m.start <= index && m.end >= index) {
|
||||
path.push({ label: `[${i.toString()}]`, obj: m })
|
||||
current = current.values[i]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (!found) over = true
|
||||
} else over = true
|
||||
} else if (current.kind === "Member") {
|
||||
if (current.value) {
|
||||
if (current.value.start <= index && current.value.end >= index) {
|
||||
current = current.value
|
||||
} else over = true
|
||||
} else over = true
|
||||
} else if (
|
||||
current.kind === "String" ||
|
||||
current.kind === "Number" ||
|
||||
current.kind === "Boolean" ||
|
||||
current.kind === "Null"
|
||||
) {
|
||||
if (current.start <= index && current.end >= index) {
|
||||
path.push({ label: `${current.value}`, obj: current })
|
||||
}
|
||||
over = true
|
||||
}
|
||||
}
|
||||
output = { success: true, res: path.map((p) => p.label) }
|
||||
} catch (e) {
|
||||
output = { success: false, res: e }
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
const getSiblings = (index) => {
|
||||
const parent = path[index]?.obj?.parent
|
||||
if (!parent) return []
|
||||
else if (parent.kind === "Object") {
|
||||
return parent.members
|
||||
} else if (parent.kind === "Array") {
|
||||
return parent.values
|
||||
} else return []
|
||||
}
|
||||
|
||||
return {
|
||||
init,
|
||||
genPath,
|
||||
getSiblings,
|
||||
setNewText,
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,12 @@ export type HoppRequestSaveContext =
|
||||
* ID of the request in the team
|
||||
*/
|
||||
requestID: string
|
||||
/**
|
||||
* ID of the team
|
||||
*/
|
||||
teamID?: string
|
||||
/**
|
||||
* ID of the collection loaded
|
||||
*/
|
||||
collectionID?: string
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { map } from "rxjs/operators"
|
||||
*
|
||||
* @returns The constructed object observable
|
||||
*/
|
||||
export function constructFromStreams<T>(
|
||||
streamObj: { [key in keyof T]: Observable<T[key]> }
|
||||
): Observable<T> {
|
||||
export function constructFromStreams<T>(streamObj: {
|
||||
[key in keyof T]: Observable<T[key]>
|
||||
}): Observable<T> {
|
||||
return combineLatest(Object.values<Observable<T[keyof T]>>(streamObj)).pipe(
|
||||
map((streams) => {
|
||||
const keys = Object.keys(streamObj) as (keyof T)[]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const decodeB64StringToArrayBuffer = (input) => {
|
||||
export function decodeB64StringToArrayBuffer(input: string): ArrayBuffer {
|
||||
const bytes = Math.floor((input.length / 4) * 3)
|
||||
const ab = new ArrayBuffer(bytes)
|
||||
const uarray = new Uint8Array(ab)
|
||||
@@ -1,12 +0,0 @@
|
||||
export function getSourcePrefix(source) {
|
||||
const sourceEmojis = {
|
||||
// Source used for info messages.
|
||||
info: "\tℹ️ [INFO]:\t",
|
||||
// Source used for client to server messages.
|
||||
client: "\t⬅️ [SENT]:\t",
|
||||
// Source used for server to client messages.
|
||||
server: "\t➡️ [RECEIVED]:\t",
|
||||
}
|
||||
if (Object.keys(sourceEmojis).includes(source)) return sourceEmojis[source]
|
||||
return ""
|
||||
}
|
||||
12
packages/hoppscotch-app/helpers/utils/string.ts
Normal file
12
packages/hoppscotch-app/helpers/utils/string.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const sourceEmojis = {
|
||||
// Source used for info messages.
|
||||
info: "\tℹ️ [INFO]:\t",
|
||||
// Source used for client to server messages.
|
||||
client: "\t⬅️ [SENT]:\t",
|
||||
// Source used for server to client messages.
|
||||
server: "\t➡️ [RECEIVED]:\t",
|
||||
}
|
||||
|
||||
export function getSourcePrefix(source: keyof typeof sourceEmojis) {
|
||||
return sourceEmojis[source]
|
||||
}
|
||||
Reference in New Issue
Block a user