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 @@