diff --git a/components/lenses/renderers/ImageLensRenderer.vue b/components/lenses/renderers/ImageLensRenderer.vue
index a2ee880443f0451aa2ed8d867710f397409da3e0..230406d0bf8c6dff02f26c8ef0afd51954ef6a53 100644
--- a/components/lenses/renderers/ImageLensRenderer.vue
+++ b/components/lenses/renderers/ImageLensRenderer.vue
@@ -27,13 +27,12 @@ @click.native="downloadResponse"
diff --git a/components/lenses/renderers/JSONLensRenderer.vue b/components/lenses/renderers/JSONLensRenderer.vue
index fc6bc2f1245656ffb4d0674a744026123ae9acf9..79652d54f1fa7618ad204ad01ef83974a2d2ba3a 100644
--- a/components/lenses/renderers/JSONLensRenderer.vue
+++ b/components/lenses/renderers/JSONLensRenderer.vue
@@ -14,12 +14,20 @@ justify-between
diff --git a/components/lenses/renderers/RawLensRenderer.vue b/components/lenses/renderers/RawLensRenderer.vue
index 563f9de1fc080d8a4157a8c096b14e74999bb89b..9203bddc20959671b4a758c23aeb3e78052b1388 100644
--- a/components/lenses/renderers/RawLensRenderer.vue
+++ b/components/lenses/renderers/RawLensRenderer.vue
@@ -20,6 +20,15 @@
<ButtonSecondary
v-if="response.body"
<div>
+ <div
+ :title="$t('state.linewrap')"
+ :class="{ '!text-accent': linewrapEnabled }"
+ svg="corner-down-left"
+ @click.native.prevent="linewrapEnabled = !linewrapEnabled"
+ />
+ <ButtonSecondary
+ v-if="response.body"
+ <div>
<div>
v-tippy="{ theme: 'tooltip' }"
:title="$t('action.download_file')"
@@ -36,91 +45,150 @@ @click.native="copyResponse"
/>
</div>
</div>
+ <div ref="rawResponse"></div>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, useContext, computed, reactive } from "@nuxtjs/composition-api"
+import { useCodemirror } from "~/helpers/editor/codemirror"
+import { copyToClipboard } from "~/helpers/utils/clipboard"
+import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
+
+const props = defineProps<{
+ sticky
<div
+ sticky
class="
+
+const {
+ $toast,
+ app: { i18n },
+} = useContext()
+const t = i18n.t.bind(i18n)
+
+const responseBodyText = computed(() => {
+ if (
+ props.response.type === "loading" ||
+ items-center
<div
+ )
+ items-center
bg-primary
+ if (typeof props.response.body === "string") return props.response.body
+ else {
+ const res = new TextDecoder("utf-8").decode(props.response.body)
+ // HACK: Temporary trailing null character issue from the extension fix
+ return res.replace(/\0+$/, "")
+ }
+})
+
+const downloadIcon = ref("download")
+const copyIcon = ref("copy")
+
+const responseType = computed(() => {
+ return (
+<template>
:value="responseBodyText"
+<template>
:lang="'plain_text'"
+ )
+<template>
:options="{
+<template>
maxLines: Infinity,
+})
+
+<template>
minLines: 16,
+<template>
autoScrollEditorIntoView: true,
+
+<template>
readOnly: true,
+<template>
showPrintMargin: false,
+<template>
useWorker: false,
+<template>
}"
+<template>
styles="border-b border-dividerLight"
+<template>
/>
- </div>
+<template>
</div>
+<template>
</template>
+ },
+<template>
+<template>
<script>
+<template>
import { defineComponent } from "@nuxtjs/composition-api"
+<template>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
bg-primary
- class="
+<template>
bg-primary
+ class="
+<template>
export default defineComponent({
+<template>
mixins: [TextContentRendererMixin],
+<template>
props: {
+<template>
response: { type: Object, default: () => {} },
+<template>
},
+<template>
data() {
+<template>
return {
+<template>
downloadIcon: "download",
+<template>
copyIcon: "copy",
+<template>
}
- },
+<template>
computed: {
+<template>
responseType() {
+ })
+<template>
return (
+<template>
this.response.headers.find(
+<template>
(h) => h.key.toLowerCase() === "content-type"
+<template>
).value || ""
+<template>
)
+<template>
.split(";")[0]
+
+<template>
.toLowerCase()
+<template>
},
- },
+<template>
methods: {
+<template>
downloadResponse() {
- flex flex-1
+<template>
flex flex-1
flex flex-1
- top-lowerSecondaryStickyFold
- const a = document.createElement("a")
- const url = URL.createObjectURL(file)
- top-lowerSecondaryStickyFold
<template>
- top-lowerSecondaryStickyFold
+ bg-primary
<div>
- a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}`
- document.body.appendChild(a)
- a.click()
- this.downloadIcon = "check"
- this.$toast.success(this.$t("state.download_started"), {
- icon: "downloading",
- })
- setTimeout(() => {
- pl-4
<template>
- URL.revokeObjectURL(url)
- this.downloadIcon = "download"
- }, 1000)
- },
- copyResponse() {
- copyToClipboard(this.responseBodyText)
- pl-4
flex flex-1
- pl-4
top-lowerSecondaryStickyFold
- icon: "content_paste",
- })
<template>
flex flex-1
- class="
- },
-})
+ <div>
</script>
diff --git a/components/lenses/renderers/XMLLensRenderer.vue b/components/lenses/renderers/XMLLensRenderer.vue
index eb5340f41c7573dd01ecbf0073ae16b7e1b59cf0..25d173c9119dbc7fc8e1ed6f2100883256ce1259 100644
--- a/components/lenses/renderers/XMLLensRenderer.vue
+++ b/components/lenses/renderers/XMLLensRenderer.vue
@@ -20,6 +20,15 @@
<ButtonSecondary
v-if="response.body"
<div>
+ <div
+ :title="$t('state.linewrap')"
+ :class="{ '!text-accent': linewrapEnabled }"
+ svg="corner-down-left"
+ @click.native.prevent="linewrapEnabled = !linewrapEnabled"
+ />
+ <ButtonSecondary
+ v-if="response.body"
+ <div>
<div>
v-tippy="{ theme: 'tooltip' }"
:title="$t('action.download_file')"
@@ -36,140 +45,162 @@ @click.native="copyResponse"
/>
</div>
</div>
- <div
+ <div ref="xmlResponse"></div>
class="
+ top-lowerSecondaryStickyFold
- <div
+</template>
bg-primary
+<script setup lang="ts">
+import { computed, ref, useContext, reactive } from "@nuxtjs/composition-api"
+import { useCodemirror } from "~/helpers/editor/codemirror"
+import { copyToClipboard } from "~/helpers/utils/clipboard"
+import "codemirror/mode/xml/xml"
+import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
+
+ sticky
<div
+ response: HoppRESTResponse
+}>()
+
+ sticky
border-b border-dividerLight
- <div
+ sticky
flex flex-1
- <div
+ sticky
top-lowerSecondaryStickyFold
- <div
+ sticky
pl-4
- class="
+ items-center
- class="
+
+ items-center
<template>
- class="
+ items-center
<div>
- class="
+ items-center
<div
- class="
+ items-center
class="
- class="
+ items-center
bg-primary
- class="
+ items-center
border-b border-dividerLight
- class="
+ items-center
flex flex-1
- </div>
- class="
+ items-center
top-lowerSecondaryStickyFold
- class="
+ items-center
pl-4
- bg-primary
+ justify-between
- bg-primary
+ justify-between
<template>
- bg-primary
+ justify-between
<div>
+})
bg-primary
+ justify-between
<div
- bg-primary
+ justify-between
class="
- bg-primary
+ justify-between
bg-primary
- bg-primary
+ justify-between
border-b border-dividerLight
- bg-primary
+ justify-between
flex flex-1
- bg-primary
+ justify-between
top-lowerSecondaryStickyFold
+ items-center
bg-primary
+ justify-between
pl-4
- border-b border-dividerLight
+ "
- border-b border-dividerLight
+ z-10
<template>
- copyIcon: "copy",
+
- downloadIcon: "download",
+const xmlResponse = ref<any | null>(null)
- border-b border-dividerLight
+<template>
class="
+ <div>
bg-primary
- pl-4
- border-b border-dividerLight
+useCodemirror(
+ xmlResponse,
+ "
bg-primary
- border-b border-dividerLight
+ "
border-b border-dividerLight
- border-b border-dividerLight
+ "
flex flex-1
- border-b border-dividerLight
+ "
top-lowerSecondaryStickyFold
- border-b border-dividerLight
+ "
pl-4
- flex flex-1
+ >
flex flex-1
+ class="
+ >
<template>
- flex flex-1
+ >
<div>
- flex flex-1
+ >
<div
- flex flex-1
+ >
class="
bg-primary
- pl-4
- flex flex-1
+ >
bg-primary
- flex flex-1
+ >
border-b border-dividerLight
- flex flex-1
+ >
flex flex-1
- flex flex-1
+ >
top-lowerSecondaryStickyFold
- flex flex-1
+ >
pl-4
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
<template>
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
<div>
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
<div
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
class="
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
bg-primary
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
border-b border-dividerLight
- top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
flex flex-1
- icon: "downloading",
+ })
+ <label class="font-semibold text-secondaryLight">
top-lowerSecondaryStickyFold
+ <label class="font-semibold text-secondaryLight">
pl-4
- pl-4
+ {{ $t("response.body") }}
- pl-4
+ {{ $t("response.body") }}
<template>
- pl-4
+ {{ $t("response.body") }}
<div>
- pl-4
+ {{ $t("response.body") }}
<div
- }, 1000)
+
+<template>
},
- pl-4
+ {{ $t("response.body") }}
bg-primary
- pl-4
+ {{ $t("response.body") }}
border-b border-dividerLight
- pl-4
+<template>
flex flex-1
- this.$toast.success(this.$t("state.copied_to_clipboard"), {
- icon: "content_paste",
+ flex flex-1
+ {{ $t("response.body") }}
top-lowerSecondaryStickyFold
- pl-4
<template>
+import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
+<template>
flex flex-1
- class="
- bg-primary
pl-4
<template>
-<template>
+ .toLowerCase()
</script>
diff --git a/components/smart/AceEditor.vue b/components/smart/AceEditor.vue
deleted file mode 100644
index 299c0f7f69caf872ae051c69951ca41aa414d1d0..0000000000000000000000000000000000000000
--- a/components/smart/AceEditor.vue
+++ /dev/null
@@ -1,282 +0,0 @@
-<template>
- <div class="show-if-initialized" :class="{ initialized }">
- <pre ref="editor" :class="styles"></pre>
- <div
- v-if="provideOutline"
- class="
- bg-primaryLight
- border-t border-divider
- flex flex-nowrap flex-1
- py-1
- px-4
- bottom-0
- z-10
- sticky
- overflow-auto
- hide-scrollbar
- "
- >
- <div
- v-for="(p, index) in currentPath"
- :key="`p-${index}`"
- class="
- cursor-pointer
- flex-grow-0 flex-shrink-0
- text-secondaryLight
- inline-flex
- items-center
- hover:text-secondary
- "
- >
- <span @click="onBlockClick(index)">
- {{ p }}
- </span>
- <i v-if="index + 1 !== currentPath.length" class="mx-2 material-icons">
- chevron_right
- </i>
- <tippy
- v-if="siblingDropDownIndex == index"
- ref="options"
- interactive
- trigger="click"
- theme="popover"
- arrow
- >
- <SmartItem
- v-for="(sibling, siblingIndex) in currentSibling"
- :key="`p-${index}-sibling-${siblingIndex}`"
- :label="sibling.key ? sibling.key.value : i"
- @click.native="goToSibling(sibling)"
- />
- </tippy>
- </div>
- </div>
- </div>
-</template>
-
-<script>
-import ace from "ace-builds"
-import "ace-builds/webpack-resolver"
-import { defineComponent } from "@nuxtjs/composition-api"
-import jsonParse from "~/helpers/jsonParse"
-import debounce from "~/helpers/utils/debounce"
-import outline from "~/helpers/outline"
-
-export default defineComponent({
- props: {
- provideOutline: {
- type: Boolean,
- default: false,
- required: false,
- },
- value: {
- type: String,
- default: "",
- },
- theme: {
- type: String,
- required: false,
- default: null,
- },
- lang: {
- type: String,
- default: "json",
- },
- lint: {
- type: Boolean,
- default: true,
- required: false,
- },
- options: {
- type: Object,
- default: () => {},
- },
- styles: {
- type: String,
- default: "",
- },
- },
-
- data() {
- return {
- initialized: false,
- editor: null,
- cacheValue: "",
- outline: outline(),
- currentPath: [],
- currentSibling: [],
- siblingDropDownIndex: null,
- }
- },
-
- computed: {
- appFontSize() {
- return getComputedStyle(document.documentElement).getPropertyValue(
- "--body-font-size"
- )
- },
- },
-
- watch: {
- value(value) {
- if (value !== this.cacheValue) {
- this.editor.session.setValue(value, 1)
- this.cacheValue = value
- if (this.lint) this.provideLinting(value)
- }
- },
- theme() {
- this.initialized = false
- this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
- this.$nextTick().then(() => {
- this.initialized = true
- })
- })
- },
- lang(value) {
- this.editor.getSession().setMode(`ace/mode/${value}`)
- },
- options(value) {
- this.editor.setOptions(value)
- },
- },
-
- mounted() {
- const editor = ace.edit(this.$refs.editor, {
- mode: `ace/mode/${this.lang}`,
- ...this.options,
- })
-
- // 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
- })
- })
-
- editor.setFontSize(this.appFontSize)
-
- if (this.value) editor.setValue(this.value, 1)
-
- this.editor = editor
- this.cacheValue = this.value
-
- if (this.lang === "json" && this.provideOutline)
- this.initOutline(this.value)
-
- editor.on("change", () => {
- const content = editor.getValue()
- this.$emit("input", content)
- this.cacheValue = content
-
- if (this.provideOutline) debounce(this.initOutline(content), 500)
-
- if (this.lint) this.provideLinting(content)
- })
-
- if (this.lang === "json" && this.provideOutline) {
- editor.session.selection.on("changeCursor", () => {
- const index = editor.session.doc.positionToIndex(
- editor.selection.getCursor(),
- 0
- )
- const path = this.outline.genPath(index)
- if (path.success) {
- this.currentPath = path.res
- }
- })
- }
-
- // Disable linting, if lint prop is false
- if (this.lint) this.provideLinting(this.value)
- },
-
- destroyed() {
- this.editor.destroy()
- },
-
- methods: {
- defineTheme() {
- if (this.theme) {
- return this.theme
- }
- const strip = (str) =>
- str.replace(/#/g, "").replace(/ /g, "").replace(/"/g, "")
- return strip(
- window
- .getComputedStyle(document.documentElement)
- .getPropertyValue("--editor-theme")
- )
- },
-
- provideLinting: debounce(function (code) {
- if (this.lang === "json") {
- try {
- jsonParse(code)
- this.editor.session.setAnnotations([])
- } catch (e) {
- 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",
- },
- ])
- }
- }
- }, 2000),
-
- onBlockClick(index) {
- if (this.siblingDropDownIndex === index) {
- this.clearSiblingList()
- } else {
- this.currentSibling = this.outline.getSiblings(index)
- if (this.currentSibling.length) this.siblingDropDownIndex = index
- }
- },
- clearSiblingList() {
- this.currentSibling = []
- this.siblingDropDownIndex = null
- },
- goToSibling(obj) {
- this.clearSiblingList()
- if (obj.start) {
- const pos = this.editor.session.doc.indexToPosition(obj.start, 0)
- if (pos) {
- this.editor.session.selection.moveCursorTo(pos.row, pos.column, true)
- this.editor.session.selection.clearSelection()
- this.editor.scrollToLine(pos.row, false, true, null)
- }
- }
- },
- initOutline: debounce(function (content) {
- if (this.lang === "json") {
- try {
- this.outline.init(content)
-
- if (content[0] === "[") this.currentPath.push("[]")
- else this.currentPath.push("{}")
- } catch (e) {
- console.log("Outline error: ", e)
- }
- }
- }),
- },
-})
-</script>
-
-<style scoped lang="scss">
-.show-if-initialized {
- &.initialized {
- @apply opacity-100;
- }
-
- & > * {
- @apply transition-none;
- }
-}
-</style>
diff --git a/components/smart/AutoComplete.vue b/components/smart/AutoComplete.vue
index 402128a2ff5ee95a54be1fd4790e3f1dd97adb44..c495247ce13dd616b45d9bd12c42dec26315064b 100644
--- a/components/smart/AutoComplete.vue
+++ b/components/smart/AutoComplete.vue
@@ -151,6 +151,17 @@
handleKeystroke(event) {
switch (event.code) {
<template>
+ */
+ event.preventDefault()
+ if (this.currentSuggestionIndex > -1)
+ this.forceSuggestion(
+ this.suggestions.find(
+ (_item, index) => index === this.currentSuggestionIndex
+ )
+ )
+ break
+
+<template>
@input="updateSuggestions"
event.preventDefault()
this.currentSuggestionIndex =
diff --git a/components/smart/EnvInput.vue b/components/smart/EnvInput.vue
index 75c8aa94e2a646624d7c384312947f3875115759..0040ca44ee7ded9883c14ba9c1087619adc4ca9e 100644
--- a/components/smart/EnvInput.vue
+++ b/components/smart/EnvInput.vue
@@ -484,7 +484,7 @@ line-height: 1.9;
&::before {
https://github.com/SyedWasiHaider/vue-highlightable-input
- "cursor-help transition rounded px-1 focus:outline-none mx-0.5",
+ highlightEnabled() {
@apply opacity-25;
@apply pointer-events-none;
@@ -502,7 +502,6 @@ @apply overflow-x-auto;
@apply overflow-y-hidden;
@apply resize-none;
@apply focus:outline-none;
- @apply transition;
}
.env-input::-webkit-scrollbar {
diff --git a/components/smart/JsEditor.vue b/components/smart/JsEditor.vue
deleted file mode 100644
index f78102525d544020150cf9a51c580c8121ac1441..0000000000000000000000000000000000000000
--- a/components/smart/JsEditor.vue
+++ /dev/null
@@ -1,292 +0,0 @@
-<template>
- <div class="show-if-initialized" :class="{ initialized }">
- <pre ref="editor" :class="styles"></pre>
- </div>
-</template>
-
-<script>
-import ace from "ace-builds"
-import "ace-builds/webpack-resolver"
-import "ace-builds/src-noconflict/ext-language_tools"
-import "ace-builds/src-noconflict/mode-graphqlschema"
-import * as esprima from "esprima"
-import { defineComponent } from "@nuxtjs/composition-api"
-import debounce from "~/helpers/utils/debounce"
-import {
- getPreRequestScriptCompletions,
- getTestScriptCompletions,
- performPreRequestLinting,
- performTestLinting,
-} from "~/helpers/tern"
-
-export default defineComponent({
- props: {
- value: {
- type: String,
- default: "",
- },
- theme: {
- type: String,
- required: false,
- default: null,
- },
- options: {
- type: Object,
- default: () => {},
- },
- styles: {
- type: String,
- default: "",
- },
- completeMode: {
- type: String,
- required: true,
- default: "none",
- },
- },
-
- data() {
- return {
- initialized: false,
- editor: null,
- cacheValue: "",
- }
- },
-
- computed: {
- appFontSize() {
- return getComputedStyle(document.documentElement).getPropertyValue(
- "--body-font-size"
- )
- },
- },
-
- watch: {
- value(value) {
- if (value !== this.cacheValue) {
- this.editor.session.setValue(value, 1)
- this.cacheValue = value
- if (this.lint) this.provideLinting(value)
- }
- },
- theme() {
- this.initialized = false
- this.editor.setTheme(`ace/theme/${this.defineTheme()}`, () => {
- this.$nextTick()
- .then(() => {
- this.initialized = true
- })
- .catch(() => {
- // nextTick shouldn't really ever throw but still
- this.initialized = true
- })
- })
- },
- options(value) {
- this.editor.setOptions(value)
- },
- },
-
- mounted() {
- // const langTools = ace.require("ace/ext/language_tools")
-
- const editor = ace.edit(this.$refs.editor, {
- mode: `ace/mode/javascript`,
- enableBasicAutocompletion: true,
- enableLiveAutocompletion: true,
- ...this.options,
- })
-
- // 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
- })
- .catch(() => {
- // nextTIck shouldn't really ever throw but still
- this.initialized = true
- })
- })
-
- editor.setFontSize(this.appFontSize)
-
- const completer = {
- getCompletions: (
- editor,
- _session,
- { row, column },
- _prefix,
- callback
- ) => {
- if (this.completeMode === "pre") {
- getPreRequestScriptCompletions(editor.getValue(), row, column)
- .then((res) => {
- callback(
- null,
- res.completions.map((r, index, arr) => ({
- name: r.name,
- value: r.name,
- score: (arr.length - index) / arr.length,
- meta: r.type,
- }))
- )
- })
- .catch(() => callback(null, []))
- } else if (this.completeMode === "test") {
- getTestScriptCompletions(editor.getValue(), row, column)
- .then((res) => {
- callback(
- null,
- res.completions.map((r, index, arr) => ({
- name: r.name,
- value: r.name,
- score: (arr.length - index) / arr.length,
- meta: r.type,
- }))
- )
- })
- .catch(() => callback(null, []))
- }
- },
- }
-
- editor.completers = [completer]
-
- 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)
- })
-
- this.provideLinting(this.value)
- },
-
- destroyed() {
- this.editor.destroy()
- },
-
- methods: {
- defineTheme() {
- if (this.theme) {
- return this.theme
- }
- const strip = (str) =>
- str.replace(/#/g, "").replace(/ /g, "").replace(/"/g, "")
- return strip(
- window
- .getComputedStyle(document.documentElement)
- .getPropertyValue("--editor-theme")
- )
- },
-
- provideLinting: debounce(function (code) {
- let results = []
-
- const lintFunc =
- this.completeMode === "pre"
- ? performPreRequestLinting
- : performTestLinting
-
- lintFunc(code)
- .then((semanticLints) => {
- results = results.concat(
- semanticLints.map((lint) => ({
- row: lint.from.line,
- column: lint.from.ch,
- text: `[semantic] ${lint.message}`,
- type: "error",
- }))
- )
-
- try {
- const res = esprima.parseScript(code, { tolerant: true })
- if (res.errors && res.errors.length > 0) {
- results = results.concat(
- res.errors.map((err) => {
- const pos = this.editor.session
- .getDocument()
- .indexToPosition(err.index, 0)
-
- return {
- row: pos.row,
- column: pos.column,
- text: `[syntax] ${err.description}`,
- type: "error",
- }
- })
- )
- }
- } catch (e) {
- const pos = this.editor.session
- .getDocument()
- .indexToPosition(e.index, 0)
- results = results.concat([
- {
- row: pos.row,
- column: pos.column,
- text: `[syntax] ${e.description}`,
- type: "error",
- },
- ])
- }
-
- this.editor.session.setAnnotations(results)
- })
- .catch(() => {
- try {
- const res = esprima.parseScript(code, { tolerant: true })
- if (res.errors && res.errors.length > 0) {
- results = results.concat(
- res.errors.map((err) => {
- const pos = this.editor.session
- .getDocument()
- .indexToPosition(err.index, 0)
-
- return {
- row: pos.row,
- column: pos.column,
- text: `[syntax] ${err.description}`,
- type: "error",
- }
- })
- )
- }
- } catch (e) {
- const pos = this.editor.session
- .getDocument()
- .indexToPosition(e.index, 0)
- results = results.concat([
- {
- row: pos.row,
- column: pos.column,
- text: `[syntax] ${e.description}`,
- type: "error",
- },
- ])
- }
-
- this.editor.session.setAnnotations(results)
- })
- }, 2000),
- },
-})
-</script>
-
-<style scoped lang="scss">
-.show-if-initialized {
- &.initialized {
- @apply opacity-100;
- }
-
- & > * {
- @apply transition-none;
- }
-}
-</style>
diff --git a/helpers/codegen/codegen.ts b/helpers/codegen/codegen.ts
index 45baf2d6f42f3c5bace6f3f8b4a621441a61ab6f..88c057e70baeba4e8923bee8debdea7373d75145 100644
--- a/helpers/codegen/codegen.ts
+++ b/helpers/codegen/codegen.ts
@@ -151,9 +151,6 @@ .map((x) => ({ ...x, active: true }))
: request.effectiveFinalHeaders.map((x) => ({ ...x, active: true }))
import {
-/* Register code generators here.
-
-import {
* A code generator is defined as an object with the following structure.
name: request.name,
uri: request.effectiveFinalURL,
diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7319c74138de82d5fe7cc84f0b1701e30f1205cf
--- /dev/null
+++ b/helpers/editor/codemirror.ts
@@ -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,
+ }
+}
diff --git a/helpers/editor/completion/gqlQuery.ts b/helpers/editor/completion/gqlQuery.ts
new file mode 100644
index 0000000000000000000000000000000000000000..672ef134ab7faa07532586c4f390f952a7418669
--- /dev/null
+++ b/helpers/editor/completion/gqlQuery.ts
@@ -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
diff --git a/helpers/editor/completion/index.ts b/helpers/editor/completion/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc78e6193fe34faab0d838652f868683f277343e
--- /dev/null
+++ b/helpers/editor/completion/index.ts
@@ -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>
diff --git a/helpers/editor/completion/preRequest.ts b/helpers/editor/completion/preRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc723155d7553344afe80aef1ffaf320f3bdab4b
--- /dev/null
+++ b/helpers/editor/completion/preRequest.ts
@@ -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
diff --git a/helpers/editor/completion/testScript.ts b/helpers/editor/completion/testScript.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88286ac469b44296875da1e9a9350a9999feef44
--- /dev/null
+++ b/helpers/editor/completion/testScript.ts
@@ -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
diff --git a/helpers/editor/linting/gqlQuery.ts b/helpers/editor/linting/gqlQuery.ts
new file mode 100644
index 0000000000000000000000000000000000000000..648cfa732db1b03be104597a199e3aea2f3fe330
--- /dev/null
+++ b/helpers/editor/linting/gqlQuery.ts
@@ -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",
+ },
+ ])
+ }
+}
diff --git a/helpers/editor/linting/json.ts b/helpers/editor/linting/json.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46a690197f3d02849ce1ff3cb07a5494865c01f2
--- /dev/null
+++ b/helpers/editor/linting/json.ts
@@ -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
diff --git a/helpers/editor/linting/linter.ts b/helpers/editor/linting/linter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..704270cbe4e6edbc92e31b5acdd0a30de050fe96
--- /dev/null
+++ b/helpers/editor/linting/linter.ts
@@ -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[]>
diff --git a/helpers/editor/linting/preRequest.ts b/helpers/editor/linting/preRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db42d986474415910e8199fa48e39acb0d7ebff3
--- /dev/null
+++ b/helpers/editor/linting/preRequest.ts
@@ -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
diff --git a/helpers/editor/linting/testScript.ts b/helpers/editor/linting/testScript.ts
new file mode 100644
index 0000000000000000000000000000000000000000..902d1778c9a5d4f2774da71d59325e21dea5d6d8
--- /dev/null
+++ b/helpers/editor/linting/testScript.ts
@@ -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
diff --git a/helpers/editor/modes/graphql.ts b/helpers/editor/modes/graphql.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c4949882b46a59c41032e2e9ace211210d02c71
--- /dev/null
+++ b/helpers/editor/modes/graphql.ts
@@ -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)
+}
diff --git a/helpers/editor/utils.ts b/helpers/editor/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7dcb1c12fe5288db07c8deba4bc239ac532b933
--- /dev/null
+++ b/helpers/editor/utils.ts
@@ -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
+}
diff --git a/helpers/editorutils.js b/helpers/editorutils.js
index 0955bbf37ac363198070bfef8c4cc1f18505427c..9dd46a4bf34220739343272cd22b6aba881d9795 100644
--- a/helpers/editorutils.js
+++ b/helpers/editorutils.js
@@ -1,13 +1,19 @@
const mimeToMode = {
+const mimeToMode = {
"text/plain": "plain_text",
+const mimeToMode = {
"text/html": "html",
+const mimeToMode = {
"application/xml": "xml",
+const mimeToMode = {
"application/hal+json": "json",
+const mimeToMode = {
"application/vnd.api+json": "json",
+const mimeToMode = {
"application/json": "json",
}
export function getEditorLangForMimeType(mimeType) {
const mimeToMode = {
-const mimeToMode = {
+}
}
diff --git a/helpers/jsonParse.js b/helpers/jsonParse.js
deleted file mode 100644
index d196e3cc1769f2aa1d291e97c6c94fa25302f0da..0000000000000000000000000000000000000000
--- a/helpers/jsonParse.js
+++ /dev/null
@@ -1,318 +0,0 @@
-/**
- * Copyright (c) 2019 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.
- */
-
-/**
- * This JSON parser simply walks the input, generating an AST. Use this in lieu
- * of JSON.parse if you need character offset parse errors and an AST parse tree
- * with location information.
- *
- * If an error is encountered, a SyntaxError will be thrown, with properties:
- *
- * - message: string
- * - start: int - the start inclusive offset of the syntax error
- * - end: int - the end exclusive offset of the syntax error
- *
- */
-export default function jsonParse(str) {
- string = str
- strLen = str.length
- start = end = lastEnd = -1
- ch()
- lex()
- try {
- const ast = parseObj()
- expect("EOF")
- return ast
- } catch (e) {
- // Try parsing expecting a root array
- const ast = parseArr()
- expect("EOF")
- return ast
- }
-}
-
-let string
-let strLen
-let start
-let end
-let lastEnd
-let code
-let kind
-
-function parseObj() {
- const nodeStart = start
- const members = []
- expect("{")
- if (!skip("}")) {
- do {
- members.push(parseMember())
- } while (skip(","))
- expect("}")
- }
- return {
- kind: "Object",
- start: nodeStart,
- end: lastEnd,
- members,
- }
-}
-
-function parseMember() {
- const nodeStart = start
- const key = kind === "String" ? curToken() : null
- expect("String")
- expect(":")
- const value = parseVal()
- return {
- kind: "Member",
- start: nodeStart,
- end: lastEnd,
- key,
- value,
- }
-}
-
-function parseArr() {
- const nodeStart = start
- const values = []
- expect("[")
- if (!skip("]")) {
- do {
- values.push(parseVal())
- } while (skip(","))
- expect("]")
- }
- return {
- 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":
- // eslint-disable-next-line no-case-declarations
- const token = curToken()
- lex()
- return token
- }
- return expect("Value")
-}
-
-function curToken() {
- return { kind, start, end, value: JSON.parse(string.slice(start, end)) }
-}
-
-function expect(str) {
- if (kind === str) {
- lex()
- return
- }
-
- let found
- if (kind === "EOF") {
- found = "[end of file]"
- } else if (end - start > 1) {
- found = `\`${string.slice(start, end)}\``
- } else {
- const match = string.slice(start).match(/^.+?\b/)
- found = `\`${match ? match[0] : string[start]}\``
- }
-
- throw syntaxError(`Expected ${str} but found ${found}.`)
-}
-
-function syntaxError(message) {
- return { message, start, end }
-}
-
-function skip(k) {
- if (kind === k) {
- lex()
- return true
- }
-}
-
-function ch() {
- if (end < strLen) {
- end++
- code = end === strLen ? 0 : string.charCodeAt(end)
- }
-}
-
-function lex() {
- lastEnd = end
-
- while (code === 9 || code === 10 || code === 13 || code === 32) {
- ch()
- }
-
- if (code === 0) {
- kind = "EOF"
- return
- }
-
- start = end
-
- switch (code) {
- // "
- case 34:
- kind = "String"
- return readString()
- // -, 0-9
- case 45:
- case 48:
- case 49:
- case 50:
- case 51:
- case 52:
- case 53:
- case 54:
- case 55:
- case 56:
- case 57:
- kind = "Number"
- return readNumber()
- // f
- case 102:
- if (string.slice(start, start + 5) !== "false") {
- break
- }
- end += 4
- ch()
-
- kind = "Boolean"
- return
- // n
- case 110:
- if (string.slice(start, start + 4) !== "null") {
- break
- }
- end += 3
- ch()
-
- kind = "Null"
- return
- // t
- case 116:
- if (string.slice(start, start + 4) !== "true") {
- break
- }
- end += 3
- ch()
-
- kind = "Boolean"
- return
- }
-
- kind = string[start]
- ch()
-}
-
-function readString() {
- ch()
- while (code !== 34 && code > 31) {
- if (code === 92) {
- // \
- ch()
- switch (code) {
- case 34: // "
- case 47: // /
- case 92: // \
- case 98: // b
- case 102: // f
- case 110: // n
- case 114: // r
- case 116: // t
- ch()
- break
- case 117: // u
- ch()
- readHex()
- readHex()
- readHex()
- readHex()
- break
- default:
- throw syntaxError("Bad character escape sequence.")
- }
- } else if (end === strLen) {
- throw syntaxError("Unterminated string.")
- } else {
- ch()
- }
- }
-
- if (code === 34) {
- ch()
- return
- }
-
- throw syntaxError("Unterminated string.")
-}
-
-function readHex() {
- if (
- (code >= 48 && code <= 57) || // 0-9
- (code >= 65 && code <= 70) || // A-F
- (code >= 97 && code <= 102) // a-f
- ) {
- return ch()
- }
- throw syntaxError("Expected hexadecimal digit.")
-}
-
-function readNumber() {
- if (code === 45) {
- // -
- ch()
- }
-
- if (code === 48) {
- // 0
- ch()
- } else {
- readDigits()
- }
-
- if (code === 46) {
- // .
- ch()
- readDigits()
- }
-
- if (code === 69 || code === 101) {
- // E e
- ch()
- if (code === 43 || code === 45) {
- // + -
- ch()
- }
- readDigits()
- }
-}
-
-function readDigits() {
- if (code < 48 || code > 57) {
- // 0 - 9
- throw syntaxError("Expected decimal digit.")
- }
- do {
- ch()
- } while (code >= 48 && code <= 57) // 0 - 9
-}
diff --git a/helpers/jsonParse.ts b/helpers/jsonParse.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6262ba27896b9137839cdd8ce5196a72903086ae
--- /dev/null
+++ b/helpers/jsonParse.ts
@@ -0,0 +1,397 @@
+/**
+ * Copyright (c) 2019 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.
+ */
+
+/**
+ * This JSON parser simply walks the input, generating an AST. Use this in lieu
+ * of JSON.parse if you need character offset parse errors and an AST parse tree
+ * with location information.
+ *
+ * If an error is encountered, a SyntaxError will be thrown, with properties:
+ *
+ * - message: string
+ * - start: int - the start inclusive offset of the syntax error
+ * - end: int - the end exclusive offset of the syntax error
+ *
+ */
+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
+ ch()
+ lex()
+ try {
+ const ast = parseObj()
+ expect("EOF")
+ return ast
+ } catch (e) {
+ // Try parsing expecting a root array
+ const ast = parseArr()
+ expect("EOF")
+ return ast
+ }
+}
+
+let string: string
+let strLen: number
+let start: number
+let end: number
+let lastEnd: number
+let code: number
+let kind: string
+
+function parseObj(): JSONObjectValue {
+ const nodeStart = start
+ const members = []
+ expect("{")
+ if (!skip("}")) {
+ do {
+ members.push(parseMember())
+ } while (skip(","))
+ expect("}")
+ }
+ return {
+ kind: "Object",
+ start: nodeStart,
+ end: lastEnd,
+ members,
+ }
+}
+
+function parseMember(): JSONObjectMember {
+ const nodeStart = start
+ const key = kind === "String" ? (curToken() as JSONStringValue) : null
+ expect("String")
+ expect(":")
+ const value = parseVal()
+ return {
+ kind: "Member",
+ start: nodeStart,
+ end: lastEnd,
+ key: key!,
+ value,
+ }
+}
+
+function parseArr(): JSONArrayValue {
+ const nodeStart = start
+ const values: JSONValue[] = []
+ expect("[")
+ if (!skip("]")) {
+ do {
+ values.push(parseVal())
+ } while (skip(","))
+ expect("]")
+ }
+ return {
+ kind: "Array",
+ start: nodeStart,
+ end: lastEnd,
+ values,
+ }
+}
+
+function parseVal(): JSONValue {
+ switch (kind) {
+ case "[":
+ return parseArr()
+ case "{":
+ return parseObj()
+ case "String":
+ case "Number":
+ case "Boolean":
+ case "Null":
+ // eslint-disable-next-line no-case-declarations
+ const token = curToken()
+ lex()
+ return token
+ }
+ return expect("Value") as never
+}
+
+function curToken(): JSONPrimitiveValue {
+ return {
+ kind: kind as any,
+ start,
+ end,
+ value: JSON.parse(string.slice(start, end)),
+ }
+}
+
+function expect(str: string) {
+ if (kind === str) {
+ lex()
+ return
+ }
+
+ let found
+ if (kind === "EOF") {
+ found = "[end of file]"
+ } else if (end - start > 1) {
+ found = `\`${string.slice(start, end)}\``
+ } else {
+ const match = string.slice(start).match(/^.+?\b/)
+ found = `\`${match ? match[0] : string[start]}\``
+ }
+
+ throw syntaxError(`Expected ${str} but found ${found}.`)
+}
+
+type SyntaxError = {
+ message: string
+ start: number
+ end: number
+}
+
+function syntaxError(message: string): SyntaxError {
+ return { message, start, end }
+}
+
+function skip(k: string) {
+ if (kind === k) {
+ lex()
+ return true
+ }
+}
+
+function ch() {
+ if (end < strLen) {
+ end++
+ code = end === strLen ? 0 : string.charCodeAt(end)
+ }
+}
+
+function lex() {
+ lastEnd = end
+
+ while (code === 9 || code === 10 || code === 13 || code === 32) {
+ ch()
+ }
+
+ if (code === 0) {
+ kind = "EOF"
+ return
+ }
+
+ start = end
+
+ switch (code) {
+ // "
+ case 34:
+ kind = "String"
+ return readString()
+ // -, 0-9
+ case 45:
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ kind = "Number"
+ return readNumber()
+ // f
+ case 102:
+ if (string.slice(start, start + 5) !== "false") {
+ break
+ }
+ end += 4
+ ch()
+
+ kind = "Boolean"
+ return
+ // n
+ case 110:
+ if (string.slice(start, start + 4) !== "null") {
+ break
+ }
+ end += 3
+ ch()
+
+ kind = "Null"
+ return
+ // t
+ case 116:
+ if (string.slice(start, start + 4) !== "true") {
+ break
+ }
+ end += 3
+ ch()
+
+ kind = "Boolean"
+ return
+ }
+
+ kind = string[start]
+ ch()
+}
+
+function readString() {
+ ch()
+ while (code !== 34 && code > 31) {
+ if (code === (92 as any)) {
+ // \
+ ch()
+ switch (code) {
+ case 34: // "
+ case 47: // /
+ case 92: // \
+ case 98: // b
+ case 102: // f
+ case 110: // n
+ case 114: // r
+ case 116: // t
+ ch()
+ break
+ case 117: // u
+ ch()
+ readHex()
+ readHex()
+ readHex()
+ readHex()
+ break
+ default:
+ throw syntaxError("Bad character escape sequence.")
+ }
+ } else if (end === strLen) {
+ throw syntaxError("Unterminated string.")
+ } else {
+ ch()
+ }
+ }
+
+ if (code === 34) {
+ ch()
+ return
+ }
+
+ throw syntaxError("Unterminated string.")
+}
+
+function readHex() {
+ if (
+ (code >= 48 && code <= 57) || // 0-9
+ (code >= 65 && code <= 70) || // A-F
+ (code >= 97 && code <= 102) // a-f
+ ) {
+ return ch()
+ }
+ throw syntaxError("Expected hexadecimal digit.")
+}
+
+function readNumber() {
+ if (code === 45) {
+ // -
+ ch()
+ }
+
+ if (code === 48) {
+ // 0
+ ch()
+ } else {
+ readDigits()
+ }
+
+ if (code === 46) {
+ // .
+ ch()
+ readDigits()
+ }
+
+ if (code === 69 || code === 101) {
+ // E e
+ ch()
+ if (code === (43 as any) || code === (45 as any)) {
+ // + -
+ ch()
+ }
+ readDigits()
+ }
+}
+
+function readDigits() {
+ if (code < 48 || code > 57) {
+ // 0 - 9
+ throw syntaxError("Expected decimal digit.")
+ }
+ do {
+ ch()
+ } while (code >= 48 && code <= 57) // 0 - 9
+}
diff --git a/helpers/newOutline.ts b/helpers/newOutline.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b40efaa7bbe66580064e595c7083d9f627e02e5b
--- /dev/null
+++ b/helpers/newOutline.ts
@@ -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
+ }
+}
diff --git a/layouts/default.vue b/layouts/default.vue
index 5bc5bfdcd1d0933b410b260b6346c7a924d961b9..544568052af9cd646cb99a579202ed7ab97f6316 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -24,7 +24,7 @@ horizontal
>
<Pane class="flex flex-1 hide-scrollbar !overflow-auto">
<main class="flex flex-1 w-full">
- <nuxt class="flex flex-1" />
+ <nuxt class="flex overflow-y-auto flex-1" />
</main>
</Pane>
</Splitpanes>
diff --git a/locales/en.json b/locales/en.json
index a8f11b80c8f083883d9e06deba8ccce1de763cef..8af61b8b7305fb794444665f8ad7f0491f4a21bf 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -421,6 +421,7 @@ "enabled": "Enabled",
"file_imported": "File imported",
"finished_in": "Finished in {duration}ms",
"history_deleted": "History deleted",
+ "linewrap": "Wrap lines",
"loading": "Loading...",
"none": "None",
"nothing_found": "Nothing found for",
diff --git a/modules/emit-volar-types.ts b/modules/emit-volar-types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cad34e0110f1efa5e45772df582889c0c875f571
--- /dev/null
+++ b/modules/emit-volar-types.ts
@@ -0,0 +1,134 @@
+import { resolve } from "path"
+import { Module } from "@nuxt/types"
+import ts from "typescript"
+import chokidar from "chokidar"
+
+const { readdir, writeFile } = require("fs").promises
+
+function titleCase(str: string): string {
+ return str[0].toUpperCase() + str.substring(1)
+}
+
+async function* getFilesInDir(dir: string): AsyncIterable<string> {
+ const dirents = await readdir(dir, { withFileTypes: true })
+ for (const dirent of dirents) {
+ const res = resolve(dir, dirent.name)
+ if (dirent.isDirectory()) {
+ yield* getFilesInDir(res)
+ } else {
+ yield res
+ }
+ }
+}
+
+async function getAllVueComponentPaths(): Promise<string[]> {
+ const vueFilePaths: string[] = []
+
+ for await (const f of getFilesInDir("./components")) {
+ if (f.endsWith(".vue")) {
+ const componentsIndex = f.split("/").indexOf("components")
+
+ vueFilePaths.push(`./${f.split("/").slice(componentsIndex).join("/")}`)
+ }
+ }
+
+ return vueFilePaths
+}
+
+function resolveComponentName(filename: string): string {
+ const index = filename.split("/").indexOf("components")
+
+ return filename
+ .split("/")
+ .slice(index + 1)
+ .filter((x) => x !== "index.vue") // Remove index.vue
+ .map((x) => x.split(".vue")[0]) // Remove extension
+ .filter((x) => x.toUpperCase() !== x.toLowerCase()) // Remove non-word stuff
+ .map((x) => titleCase(x)) // titlecase it
+ .join("")
+}
+
+function createTSImports(components: [string, string][]) {
+ return components.map(([componentName, componentPath]) => {
+ return ts.factory.createImportDeclaration(
+ undefined,
+ undefined,
+ ts.factory.createImportClause(
+ false,
+ ts.factory.createIdentifier(componentName),
+ undefined
+ ),
+ ts.factory.createStringLiteral(componentPath)
+ )
+ })
+}
+
+function createTSProps(components: [string, string][]) {
+ return components.map(([componentName]) => {
+ return ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createIdentifier(componentName),
+ undefined,
+ ts.factory.createTypeQueryNode(ts.factory.createIdentifier(componentName))
+ )
+ })
+}
+
+function generateTypeScriptDef(components: [string, string][]) {
+ const statements = [
+ ...createTSImports(components),
+ ts.factory.createModuleDeclaration(
+ undefined,
+ [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
+ ts.factory.createIdentifier("global"),
+ ts.factory.createModuleBlock([
+ ts.factory.createInterfaceDeclaration(
+ undefined,
+ undefined,
+ ts.factory.createIdentifier("__VLS_GlobalComponents"),
+ undefined,
+ undefined,
+ [...createTSProps(components)]
+ ),
+ ]),
+ ts.NodeFlags.ExportContext |
+ ts.NodeFlags.GlobalAugmentation |
+ ts.NodeFlags.ContextFlags
+ ),
+ ]
+
+ const source = ts.factory.createSourceFile(
+ statements,
+ ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
+ ts.NodeFlags.None
+ )
+
+ const printer = ts.createPrinter({
+ newLine: ts.NewLineKind.LineFeed,
+ })
+
+ return printer.printFile(source)
+}
+
+async function generateShim() {
+ const results = await getAllVueComponentPaths()
+ const fileComponentNameCombo: [string, string][] = results.map((x) => [
+ resolveComponentName(x),
+ x,
+ ])
+ const typescriptString = generateTypeScriptDef(fileComponentNameCombo)
+
+ await writeFile(resolve("shims-volar.d.ts"), typescriptString)
+}
+
+const module: Module<{}> = async function () {
+ if (!this.nuxt.options.dev) return
+
+ await generateShim()
+
+ chokidar.watch(resolve("../components/")).on("all", async () => {
+ await generateShim()
+ })
+}
+
+export default module
diff --git a/nuxt.config.js b/nuxt.config.js
index b77f39f75ca41bb6e0d51a0c642bf712b4249b07..39da3043461f78d00fd1217b2c34f9f392c8474a 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -133,6 +133,7 @@ // https://github.com/nuxt-community/composition-api
"@nuxtjs/composition-api/module",
// https://github.com/antfu/unplugin-vue2-script-setup
"unplugin-vue2-script-setup/nuxt",
+ "~/modules/emit-volar-types.ts",
],
// Modules (https://go.nuxtjs.dev/config-modules)
@@ -281,7 +282,7 @@ config.module.rules.push({
test: /\.js$/,
include: /(node_modules)/,
-// Common options
+ content: "IE=edge, chrome=1",
loader: "babel-loader",
options: {
plugins: [
diff --git a/package-lock.json b/package-lock.json
index 9b1c7c63cb6dae45542878dddc40ac9e66ec91d9..49ed46fdf26c6c86ee1ff1b03e77e49a65374857 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,16 +16,21 @@ "@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/toast": "^3.3.1",
{
- "version": "2.0.0",
+ "dependencies": {
+ "acorn-walk": "^8.2.0",
{
+ "@types/lodash": "^4.14.172",
"dependencies": {
+ "lockfileVersion": 2,
- "acorn-walk": "^8.2.0",
+ "codemirror-theme-github": "^1.0.0",
"core-js": "^3.17.3",
"esprima": "^4.0.1",
"firebase": "^9.0.2",
"fuse.js": "^6.4.6",
"graphql": "^15.5.0",
"name": "hoppscotch",
+ "packages": {
+ "node_modules/@jest/core/node_modules/supports-color": {
"packages": {
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
@@ -63,7 +68,9 @@ "@nuxtjs/pwa": "^3.3.5",
"@nuxtjs/stylelint-module": "^4.0.0",
"@nuxtjs/svg": "^0.2.0",
"@testing-library/jest-dom": "^5.14.1",
+ "@types/codemirror": "^5.60.2",
"@types/cookie": "^0.4.1",
+ "@types/esprima": "^4.0.3",
"@types/lodash": "^4.14.172",
"@types/splitpanes": "^2.2.1",
"@vue/runtime-dom": "^3.2.11",
@@ -7918,6 +7925,15 @@ "engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/@types/codemirror": {
+ "version": "5.60.2",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz",
+ "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==",
+ "dev": true,
+ "dependencies": {
+ "@types/tern": "*"
+ }
+ },
"node_modules/@types/component-emitter": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
@@ -7966,6 +7982,21 @@ "node_modules/@types/cors": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz",
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
+ },
+ "node_modules/@types/esprima": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz",
+ "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "0.0.50",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
+ "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
+ "dev": true
},
"node_modules/@types/etag": {
"version": "1.8.0",
@@ -8350,6 +8381,15 @@ "node_modules/@types/tapable": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz",
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ=="
+ },
+ "node_modules/@types/tern": {
+ "version": "0.23.4",
+ "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
+ "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
},
"node_modules/@types/terser-webpack-plugin": {
"version": "4.2.1",
@@ -9408,11 +9448,6 @@ },
"engines": {
"node": ">= 0.6"
}
- },
- "node_modules/ace-builds": {
- "version": "1.4.12",
- "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz",
- "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg=="
},
"node_modules/acorn": {
"version": "8.5.0",
@@ -13146,6 +13181,16 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"engines": {
"node": ">=0.10.0"
}
+ },
+ "node_modules/codemirror": {
+ "version": "5.62.3",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
+ "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
+ },
+ "node_modules/codemirror-theme-github": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/codemirror-theme-github/-/codemirror-theme-github-1.0.0.tgz",
+ "integrity": "sha512-suheFec2wlI4klyqn61MOFXjjrKPZiNY7d2py0OvTd5Z+7AsNxoGKDaS/HI59y7EAG1SkkXW/JQ1Rt2gDMxHfA=="
},
"node_modules/collect-v8-coverage": {
"version": "1.0.1",
@@ -42496,6 +42541,16 @@ }
}
},
{
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "version": "5.60.2",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz",
+ "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==",
+ "dev": true,
+ "requires": {
+ "@types/tern": "*"
+ }
+ },
+{
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
@@ -42544,6 +42599,21 @@ "@types/cors": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz",
"integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="
+ },
+ "@types/esprima": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz",
+ "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.50",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
+ "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
+ "dev": true
},
"@types/etag": {
"version": "1.8.0",
@@ -42928,6 +42998,15 @@ "@types/tapable": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz",
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ=="
+ },
+ "@types/tern": {
+ "version": "0.23.4",
+ "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
+ "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
},
"@types/terser-webpack-plugin": {
"version": "4.2.1",
@@ -43813,11 +43892,6 @@ "requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
- },
- "ace-builds": {
- "version": "1.4.12",
- "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz",
- "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg=="
},
"acorn": {
"version": "8.5.0",
@@ -46774,6 +46848,16 @@ "code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ },
+ "codemirror": {
+ "version": "5.62.3",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
+ "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
+ },
+ "codemirror-theme-github": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/codemirror-theme-github/-/codemirror-theme-github-1.0.0.tgz",
+ "integrity": "sha512-suheFec2wlI4klyqn61MOFXjjrKPZiNY7d2py0OvTd5Z+7AsNxoGKDaS/HI59y7EAG1SkkXW/JQ1Rt2gDMxHfA=="
},
"collect-v8-coverage": {
"version": "1.0.1",
diff --git a/package.json b/package.json
index b8ebb3c9b386ff01ae06ce8a8958af57086ed110..5a438568eda9b7a9cec600c520285095da891dd9 100644
--- a/package.json
+++ b/package.json
@@ -32,16 +32,18 @@ "@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/toast": "^3.3.1",
"version": "2.0.0",
- "version": "2.0.0",
- "version": "2.0.0",
"description": "Open source API development ecosystem",
"acorn-walk": "^8.2.0",
+ "codemirror": "^5.62.3",
+ "codemirror-theme-github": "^1.0.0",
"core-js": "^3.17.3",
"esprima": "^4.0.1",
"firebase": "^9.0.2",
"fuse.js": "^6.4.6",
"graphql": "^15.5.0",
"description": "Open source API development ecosystem",
+{
+ "generate": "nuxt generate --modern",
{
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
@@ -79,7 +81,9 @@ "@nuxtjs/pwa": "^3.3.5",
"@nuxtjs/stylelint-module": "^4.0.0",
"@nuxtjs/svg": "^0.2.0",
"@testing-library/jest-dom": "^5.14.1",
+ "@types/codemirror": "^5.60.2",
"@types/cookie": "^0.4.1",
+ "@types/esprima": "^4.0.3",
"@types/lodash": "^4.14.172",
"@types/splitpanes": "^2.2.1",
"@vue/runtime-dom": "^3.2.11",
diff --git a/pages/documentation.vue b/pages/documentation.vue
index 266a6efa3d2fb46995687d97dd41ca757b710a6d..2feb1421089aa10e8d34432187b732548bd87c53 100644
--- a/pages/documentation.vue
+++ b/pages/documentation.vue
@@ -61,24 +61,22 @@ svg="trash-2"
@click.native="collectionJSON = '[]'"
/>
</div>
- <SmartAceEditor
- >
<Splitpanes
- :lang="'json'"
- >
+ <Pane class="hide-scrollbar !overflow-auto">
:dbl-click-splitter="false"
- >
+ </div>
:horizontal="!(windowInnerWidth.x.value >= 768)"
>
- >
+ <Splitpanes
- >
+ <Splitpanes
<Pane class="hide-scrollbar !overflow-auto">
>
- <Splitpanes class="smart-splitter" :dbl-click-splitter="false" horizontal>
- showPrintMargin: false,
+ <Splitpanes
<Pane class="hide-scrollbar !overflow-auto">
+ <Pane class="hide-scrollbar !overflow-auto">
+ <Splitpanes
<Pane class="hide-scrollbar !overflow-auto">
-<template>
+ <Splitpanes class="smart-splitter" :dbl-click-splitter="false" horizontal>
/>
<div
class="
diff --git a/pages/graphql.vue b/pages/graphql.vue
index 449bc91a2929a12a54abc9e742520a97d762afb3..5a7a4ce20c4d3e571385dddf1ccba1d1f6456b8a 100644
--- a/pages/graphql.vue
+++ b/pages/graphql.vue
@@ -1,52 +1,49 @@
<template>
- <div>
- <Splitpanes
- class="smart-splitter"
+ :horizontal="!(windowInnerWidth.x.value >= 768)"
:dbl-click-splitter="false"
:horizontal="!(windowInnerWidth.x.value >= 768)"
+ :horizontal="!(windowInnerWidth.x.value >= 768)"
+ :horizontal="!(windowInnerWidth.x.value >= 768)"
>
+ :horizontal="!(windowInnerWidth.x.value >= 768)"
<Pane class="hide-scrollbar !overflow-auto">
+ :horizontal="!(windowInnerWidth.x.value >= 768)"
<Splitpanes
-<template>
+ >
-<template>
+ >
<template>
-<template>
+ >
<div>
-<template>
+ >
<Splitpanes
-<template>
+ >
class="smart-splitter"
-<template>
+ >
:dbl-click-splitter="false"
-<template>
+ <Pane class="hide-scrollbar !overflow-auto">
+ >
:horizontal="!(windowInnerWidth.x.value >= 768)"
-<template>
>
+ :dbl-click-splitter="false"
- <Pane class="hide-scrollbar !overflow-auto">
+ </Splitpanes>
-<template>
+ >
<Pane class="hide-scrollbar !overflow-auto">
-<template>
>
-<template>
<Splitpanes
- <div>
+ <Pane class="hide-scrollbar !overflow-auto">
- <div>
+ <Pane class="hide-scrollbar !overflow-auto">
<template>
- <div>
+ <Pane class="hide-scrollbar !overflow-auto">
<div>
- <div>
+ <Pane class="hide-scrollbar !overflow-auto">
<Splitpanes
- <div>
+ <Pane class="hide-scrollbar !overflow-auto">
class="smart-splitter"
- min-size="20"
- class="hide-scrollbar !overflow-auto"
- <div>
>
- <div>
<Pane class="hide-scrollbar !overflow-auto">
+ :dbl-click-splitter="false"
- </Pane>
- </Splitpanes>
+ </Pane>
- </div>
+ </Splitpanes>
</template>
<script lang="ts">
diff --git a/pages/settings.vue b/pages/settings.vue
index d728c42c98f2485107e057333544b355bb2f5df4..58f19c9953a92a3029c09cb24bea9de70e2b3f95 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -298,8 +298,8 @@
</div>
</div>
<div class="flex space-x-2 py-4 items-center">
+ <div class="divide-y divide-dividerLight space-y-8">
<template>
- {{ $t("settings.account") }}
<template>
<input
id="url"