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/Request.vue b/components/graphql/Request.vue index a84a16459..304ca6482 100644 --- a/components/graphql/Request.vue +++ b/components/graphql/Request.vue @@ -33,45 +33,32 @@
- 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 " > - +
+
-
- +
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ chevron_right +
- + + 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 = '[]'" /> -
+ + + + + + + + + + + + - - - - - - - - - - - - - - - -
+ + +