diff --git a/.gitignore b/.gitignore
index 9d566a677..211118bff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,3 +104,6 @@ tests/*/screenshots
# Tests videos
tests/*/videos
+
+# Andrew's crazy Volar shim generator
+shims-volar.d.ts
diff --git a/assets/icons/corner-down-left.svg b/assets/icons/corner-down-left.svg
new file mode 100644
index 000000000..7a8b7473a
--- /dev/null
+++ b/assets/icons/corner-down-left.svg
@@ -0,0 +1 @@
+
diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss
index f54c9535e..3b179a194 100644
--- a/assets/scss/styles.scss
+++ b/assets/scss/styles.scss
@@ -17,7 +17,7 @@
::-webkit-scrollbar-thumb {
@apply bg-divider bg-clip-content;
@apply rounded-full;
- @apply border-solid border-4 border-transparent;
+ @apply border-solid border-transparent border-4;
@apply hover:(bg-dividerDark bg-clip-content);
}
@@ -36,8 +36,9 @@
}
input::placeholder,
-textarea::placeholder {
- @apply text-secondaryDark;
+textarea::placeholder,
+.CodeMirror-empty {
+ @apply text-secondary;
@apply opacity-25;
}
@@ -116,8 +117,8 @@ a {
&.link {
@apply items-center;
- @apply px-1 py-0.5;
- @apply -mx-1 -my-0.5;
+ @apply py-0.5 px-1;
+ @apply -my-0.5 -mx-1;
@apply text-accent;
@apply rounded;
@apply hover:text-accentDark;
@@ -198,7 +199,7 @@ hr {
.textarea {
@apply flex;
@apply w-full;
- @apply px-4 py-2;
+ @apply py-2 px-4;
@apply bg-transparent;
@apply rounded;
@apply text-secondaryDark;
@@ -293,7 +294,7 @@ input[type="checkbox"] {
@apply cursor-pointer;
&::before {
- @apply border-2 border-divider;
+ @apply border-divider border-2;
@apply rounded;
@apply inline-flex;
@apply items-center;
@@ -347,6 +348,7 @@ input[type="checkbox"] {
@apply justify-start;
@apply shadow;
@apply font-medium;
+ @apply transition;
font-size: var(--body-font-size);
line-height: var(--body-line-height);
@@ -358,7 +360,6 @@ input[type="checkbox"] {
@apply ml-auto;
@apply last:ml-4;
@apply sm:ml-8;
- @apply transition;
@apply rounded;
@apply text-current;
@apply normal-case;
@@ -461,6 +462,32 @@ input[type="checkbox"] {
@apply w-full;
}
+.CodeMirror {
+ @apply !h-auto;
+
+ font-size: var(--body-font-size);
+
+ &:not(.CodeMirror-focused) .CodeMirror-activeline-background {
+ background: transparent !important;
+ }
+
+ .CodeMirror-dialog-top {
+ @apply bg-primaryLight;
+ @apply border-dividerLight;
+ @apply px-4;
+ @apply py-2;
+ @apply z-5;
+ }
+
+ .CodeMirror-scroll {
+ @apply min-h-64;
+ }
+
+ * {
+ font-family: "Roboto Mono", monospace;
+ }
+}
+
@media (max-width: 767px) {
main {
margin-bottom: env(safe-area-inset-bottom);
diff --git a/components/firebase/Login.vue b/components/firebase/Login.vue
index 78fefbf73..ca6ddfaf7 100644
--- a/components/firebase/Login.vue
+++ b/components/firebase/Login.vue
@@ -26,7 +26,7 @@
/>
-
+
-
diff --git a/components/graphql/RequestOptions.vue b/components/graphql/RequestOptions.vue
index 7113b4e4a..cd17475be 100644
--- a/components/graphql/RequestOptions.vue
+++ b/components/graphql/RequestOptions.vue
@@ -42,9 +42,7 @@
/>
@@ -57,20 +55,7 @@
/>
-
+
@@ -108,19 +93,7 @@
/>
-
+
@@ -173,27 +146,7 @@
/>
-
-
-
+
-
diff --git a/components/graphql/Response.vue b/components/graphql/Response.vue
index feca15fcd..f0027d92a 100644
--- a/components/graphql/Response.vue
+++ b/components/graphql/Response.vue
@@ -18,6 +18,13 @@
{{ $t("response.title") }}
+
-
+
-
+
- {{ $t("shortcut.request.send_request") }}
-
-
- {{ $t("shortcut.general.show_all") }}
-
-
+
- {{ getSpecialKey() }}
- G
-
-
- {{ getSpecialKey() }}
- K
-
-
+
-
diff --git a/components/lenses/renderers/ImageLensRenderer.vue b/components/lenses/renderers/ImageLensRenderer.vue
index a2ee88044..230406d0b 100644
--- a/components/lenses/renderers/ImageLensRenderer.vue
+++ b/components/lenses/renderers/ImageLensRenderer.vue
@@ -27,12 +27,10 @@
/>
-
-
![]()
-
+
diff --git a/components/lenses/renderers/JSONLensRenderer.vue b/components/lenses/renderers/JSONLensRenderer.vue
index fc6bc2f12..79652d54f 100644
--- a/components/lenses/renderers/JSONLensRenderer.vue
+++ b/components/lenses/renderers/JSONLensRenderer.vue
@@ -13,10 +13,18 @@
justify-between
"
>
-
+
+
-
-
+
+
diff --git a/components/lenses/renderers/RawLensRenderer.vue b/components/lenses/renderers/RawLensRenderer.vue
index 563f9de1f..9203bddc2 100644
--- a/components/lenses/renderers/RawLensRenderer.vue
+++ b/components/lenses/renderers/RawLensRenderer.vue
@@ -17,6 +17,14 @@
{{ $t("response.body") }}
+
-
-
-
+
-
diff --git a/components/lenses/renderers/XMLLensRenderer.vue b/components/lenses/renderers/XMLLensRenderer.vue
index eb5340f41..25d173c91 100644
--- a/components/lenses/renderers/XMLLensRenderer.vue
+++ b/components/lenses/renderers/XMLLensRenderer.vue
@@ -17,6 +17,14 @@
{{ $t("response.body") }}
+
-
-
-
+
-
diff --git a/components/smart/AceEditor.vue b/components/smart/AceEditor.vue
deleted file mode 100644
index 299c0f7f6..000000000
--- a/components/smart/AceEditor.vue
+++ /dev/null
@@ -1,282 +0,0 @@
-
-
-
-
-
-
-
diff --git a/components/smart/AutoComplete.vue b/components/smart/AutoComplete.vue
index 402128a2f..c495247ce 100644
--- a/components/smart/AutoComplete.vue
+++ b/components/smart/AutoComplete.vue
@@ -150,6 +150,16 @@ export default defineComponent({
handleKeystroke(event) {
switch (event.code) {
+ case "Enter":
+ event.preventDefault()
+ if (this.currentSuggestionIndex > -1)
+ this.forceSuggestion(
+ this.suggestions.find(
+ (_item, index) => index === this.currentSuggestionIndex
+ )
+ )
+ break
+
case "ArrowUp":
event.preventDefault()
this.currentSuggestionIndex =
diff --git a/components/smart/EnvInput.vue b/components/smart/EnvInput.vue
index 75c8aa94e..0040ca44e 100644
--- a/components/smart/EnvInput.vue
+++ b/components/smart/EnvInput.vue
@@ -483,7 +483,7 @@ export default defineComponent({
line-height: 1.9;
&::before {
- @apply text-secondaryDark;
+ @apply text-secondary;
@apply opacity-25;
@apply pointer-events-none;
@@ -501,7 +501,6 @@ export default defineComponent({
@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 f78102525..000000000
--- a/components/smart/JsEditor.vue
+++ /dev/null
@@ -1,292 +0,0 @@
-
-
-
-
-
-
-
diff --git a/helpers/codegen/codegen.ts b/helpers/codegen/codegen.ts
index 45baf2d6f..88c057e70 100644
--- a/helpers/codegen/codegen.ts
+++ b/helpers/codegen/codegen.ts
@@ -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,
diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts
new file mode 100644
index 000000000..7319c7413
--- /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
+ 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,
+ value: Ref,
+ options: CodeMirrorOptions
+): { cm: Ref; cursor: Ref } {
+ const { $colorMode } = useContext() as any
+
+ const cm = ref(null)
+ const cursor = ref({ 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 {
+ 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 000000000..672ef134a
--- /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) => Completer =
+ (schemaRef: Ref) => (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({
+ completions: completions.map(
+ (x, i) =>
+ {
+ 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 000000000..bc78e6193
--- /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
diff --git a/helpers/editor/completion/preRequest.ts b/helpers/editor/completion/preRequest.ts
new file mode 100644
index 000000000..cc723155d
--- /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 {
+ 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 000000000..88286ac46
--- /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 {
+ 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 000000000..648cfa732
--- /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
+) => LinterDefinition = (schema: Ref) => (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 }) =>
+ {
+ 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([
+ {
+ 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 000000000..46a690197
--- /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([
+ {
+ 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 000000000..704270cbe
--- /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
diff --git a/helpers/editor/linting/preRequest.ts b/helpers/editor/linting/preRequest.ts
new file mode 100644
index 000000000..db42d9864
--- /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 {
+ 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([
+ {
+ 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 000000000..902d1778c
--- /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 {
+ 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([
+ {
+ 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 000000000..2c4949882
--- /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["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 & {
+ 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 000000000..c7dcb1c12
--- /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 0955bbf37..9dd46a4bf 100644
--- a/helpers/editorutils.js
+++ b/helpers/editorutils.js
@@ -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"
}
diff --git a/helpers/jsonParse.js b/helpers/jsonParse.ts
similarity index 71%
rename from helpers/jsonParse.js
rename to helpers/jsonParse.ts
index d196e3cc1..6262ba278 100644
--- a/helpers/jsonParse.js
+++ b/helpers/jsonParse.ts
@@ -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()
}
diff --git a/helpers/newOutline.ts b/helpers/newOutline.ts
new file mode 100644
index 000000000..b40efaa7b
--- /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 5bc5bfdcd..544568052 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -24,7 +24,7 @@
>
-
+
diff --git a/locales/en.json b/locales/en.json
index a8f11b80c..8af61b8b7 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -421,6 +421,7 @@
"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 000000000..cad34e011
--- /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 {
+ 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 {
+ 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 b77f39f75..39da30434 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -133,6 +133,7 @@ export default {
"@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)
@@ -280,7 +281,7 @@ export default {
config.module.rules.push({
test: /\.js$/,
include: /(node_modules)/,
- exclude: /(node_modules)\/(ace-builds)|(@firebase)/,
+ exclude: /(node_modules)\/(@firebase)/,
loader: "babel-loader",
options: {
plugins: [
diff --git a/package-lock.json b/package-lock.json
index 9b1c7c63c..49ed46fdf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,15 +15,17 @@
"@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/toast": "^3.3.1",
- "ace-builds": "^1.4.12",
"acorn": "^8.5.0",
"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",
"graphql-language-service-interface": "^2.8.4",
+ "graphql-language-service-parser": "^1.9.2",
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
@@ -60,7 +62,9 @@
"@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",
@@ -7915,6 +7919,15 @@
"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",
@@ -7964,6 +7977,21 @@
"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",
"resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz",
@@ -8348,6 +8376,15 @@
"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",
"resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz",
@@ -9406,11 +9443,6 @@
"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",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
@@ -13144,6 +13176,16 @@
"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",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
@@ -42492,6 +42534,15 @@
}
}
},
+ "@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,
+ "requires": {
+ "@types/tern": "*"
+ }
+ },
"@types/component-emitter": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
@@ -42541,6 +42592,21 @@
"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",
"resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz",
@@ -42925,6 +42991,15 @@
"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",
"resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz",
@@ -43810,11 +43885,6 @@
"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",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
@@ -46771,6 +46841,16 @@
"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",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
diff --git a/package.json b/package.json
index b8ebb3c9b..5a438568e 100644
--- a/package.json
+++ b/package.json
@@ -31,15 +31,17 @@
"@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@nuxtjs/toast": "^3.3.1",
- "ace-builds": "^1.4.12",
"acorn": "^8.5.0",
"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",
"graphql-language-service-interface": "^2.8.4",
+ "graphql-language-service-parser": "^1.9.2",
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
@@ -76,7 +78,9 @@
"@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 266a6efa3..2feb14210 100644
--- a/pages/documentation.vue
+++ b/pages/documentation.vue
@@ -61,17 +61,12 @@
@click.native="collectionJSON = '[]'"
/>
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+