diff --git a/packages/ui/package-lock.json b/packages/ui/package-lock.json index 9805ba99..67899943 100644 --- a/packages/ui/package-lock.json +++ b/packages/ui/package-lock.json @@ -8,14 +8,14 @@ "name": "restfox-ui", "version": "0.0.0", "dependencies": { - "@codemirror/autocomplete": "^6.16.0", - "@codemirror/commands": "^6.5.0", + "@codemirror/autocomplete": "^6.18.0", + "@codemirror/commands": "^6.6.1", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-json": "^6.0.1", - "@codemirror/language": "^6.10.1", + "@codemirror/language": "^6.10.2", "@codemirror/search": "^6.5.6", "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.26.3", + "@codemirror/view": "^6.33.0", "@flawiddsouza/quickjs-emscripten-sync": "^1.5.2", "@jitl/quickjs-singlefile-browser-release-asyncify": "^0.29.1", "chai": "^4.3.10", @@ -28,6 +28,7 @@ "graphql-request": "^7.1.0", "httpsnippet-browser": "github:flawiddsouza/httpsnippet-browser#ed12aba", "insomnia-importers-browser": "github:flawiddsouza/insomnia-importers-browser", + "js-base64": "^3.7.7", "js-yaml": "^4.1.0", "jsonpath-plus": "9.0.0", "jszip": "^3.7.1", @@ -1751,9 +1752,10 @@ } }, "node_modules/@codemirror/autocomplete": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz", - "integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz", + "integrity": "sha512-5DbOvBbY4qW5l57cjDsmmpDh3/TeK1vXfTHa+BUMrRzdWdcxKZ4U4V7vQaTtOpApNU4kLS4FQ6cINtLg245LXA==", + "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -1768,13 +1770,14 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz", - "integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.1.tgz", + "integrity": "sha512-iBfKbyIoXS1FGdsKcZmnrxmbc8VcbMrSgD7AVrsnX+WyAYjmUDWvE93dt5D874qS4CCVu4O1JpbagHdXbbLiOw==", + "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, @@ -1782,6 +1785,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", + "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", @@ -1796,15 +1800,17 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "node_modules/@codemirror/language": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz", - "integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", + "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", + "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -1828,6 +1834,7 @@ "version": "6.5.6", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz", "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==", + "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -1837,12 +1844,14 @@ "node_modules/@codemirror/state": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", - "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" }, "node_modules/@codemirror/view": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", - "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", + "version": "6.33.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", + "integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", + "license": "MIT", "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", @@ -6323,6 +6332,12 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "license": "BSD-3-Clause" + }, "node_modules/js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -10918,9 +10933,9 @@ } }, "@codemirror/autocomplete": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz", - "integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz", + "integrity": "sha512-5DbOvBbY4qW5l57cjDsmmpDh3/TeK1vXfTHa+BUMrRzdWdcxKZ4U4V7vQaTtOpApNU4kLS4FQ6cINtLg245LXA==", "requires": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -10929,13 +10944,13 @@ } }, "@codemirror/commands": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz", - "integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.1.tgz", + "integrity": "sha512-iBfKbyIoXS1FGdsKcZmnrxmbc8VcbMrSgD7AVrsnX+WyAYjmUDWvE93dt5D874qS4CCVu4O1JpbagHdXbbLiOw==", "requires": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, @@ -10963,9 +10978,9 @@ } }, "@codemirror/language": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz", - "integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", + "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", "requires": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -11001,9 +11016,9 @@ "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" }, "@codemirror/view": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz", - "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==", + "version": "6.33.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", + "integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", "requires": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", @@ -14109,6 +14124,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", diff --git a/packages/ui/package.json b/packages/ui/package.json index 431eda38..d5457d8e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -18,14 +18,14 @@ "ci-test": "start-server-and-test dev http://127.0.0.1:5173/ e2e-test" }, "dependencies": { - "@codemirror/autocomplete": "^6.16.0", - "@codemirror/commands": "^6.5.0", + "@codemirror/autocomplete": "^6.18.0", + "@codemirror/commands": "^6.6.1", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-json": "^6.0.1", - "@codemirror/language": "^6.10.1", + "@codemirror/language": "^6.10.2", "@codemirror/search": "^6.5.6", "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.26.3", + "@codemirror/view": "^6.33.0", "@flawiddsouza/quickjs-emscripten-sync": "^1.5.2", "@jitl/quickjs-singlefile-browser-release-asyncify": "^0.29.1", "chai": "^4.3.10", @@ -38,6 +38,7 @@ "graphql-request": "^7.1.0", "httpsnippet-browser": "github:flawiddsouza/httpsnippet-browser#ed12aba", "insomnia-importers-browser": "github:flawiddsouza/insomnia-importers-browser", + "js-base64": "^3.7.7", "js-yaml": "^4.1.0", "jsonpath-plus": "9.0.0", "jszip": "^3.7.1", diff --git a/packages/ui/src/codemirror-extensions/tags.ts b/packages/ui/src/codemirror-extensions/tags.ts new file mode 100644 index 00000000..53dd8cea --- /dev/null +++ b/packages/ui/src/codemirror-extensions/tags.ts @@ -0,0 +1,79 @@ +import { ViewPlugin, Decoration, EditorView, ViewUpdate, MatchDecorator, WidgetType, DecorationSet } from '@codemirror/view' +import { parseFunction } from '@/parsers/tag' +import type { ParsedResult } from '@/parsers/tag' + +type OnClickType = (parsedFunc: ParsedResult, updateFunc: (updatedTag: string) => void) => void + +class PlaceholderWidget extends WidgetType { + readonly #onClick: () => void + #parsed: ParsedResult + readonly #view: EditorView + readonly #pos: number + + constructor(readonly placeholder: string, readonly onClick: OnClickType, readonly view: EditorView, readonly pos: number) { + super() + this.#parsed = parseFunction(placeholder) + this.#onClick = () => { + onClick(this.#parsed, this.updateTag) + } + this.#view = view + this.#pos = pos + } + + eq(other: PlaceholderWidget) { + return this.placeholder === other.placeholder + } + + toDOM() { + const span = document.createElement('span') + span.className = 'tag' + span.textContent = `${this.#parsed.functionName}(...)` + span.title = this.placeholder + span.addEventListener('click', this.#onClick) + return span + } + + ignoreEvent(): boolean { + return false + } + + destroy(dom: HTMLElement): void { + dom.removeEventListener('click', this.#onClick) + } + + updateTag = (updatedTag: string) => { + this.#parsed = parseFunction(updatedTag) + this.#view.dispatch({ + changes: { from: this.#pos + 3, to: this.#pos + this.placeholder.length + 3, insert: updatedTag } + }) + } +} + +export function tags(onClick: OnClickType) { + const placeholderMatcher = new MatchDecorator({ + regexp: /{% (.+?) %}/g, + decoration: (match, view, pos) => Decoration.replace({ + widget: new PlaceholderWidget(match[1], onClick, view, pos), + }) + }) + + const tags = ViewPlugin.fromClass( + class { + placeholders: DecorationSet + constructor(view: EditorView) { + this.placeholders = placeholderMatcher.createDeco(view) + } + update(update: ViewUpdate) { + this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders) + } + }, + { + decorations: instance => instance.placeholders, + provide: plugin => EditorView.atomicRanges.of(view => { + return view.plugin(plugin)?.placeholders || Decoration.none + }) + } + ) + + return tags +} diff --git a/packages/ui/src/components/CodeMirrorEditor.vue b/packages/ui/src/components/CodeMirrorEditor.vue index 21217cf8..c5cd0809 100644 --- a/packages/ui/src/components/CodeMirrorEditor.vue +++ b/packages/ui/src/components/CodeMirrorEditor.vue @@ -5,16 +5,18 @@