From e0e6d182d5f730746c3d4056e636b302544f7c74 Mon Sep 17 00:00:00 2001 From: John Simons Date: Fri, 14 Mar 2025 08:45:27 +1000 Subject: [PATCH 1/3] Adding CodeMirror editor for code highlighting and editing Removed previous code highlighter --- src/Frontend/package-lock.json | 438 +++++++++++------- src/Frontend/package.json | 10 +- src/Frontend/src/components/CodeEditor.vue | 75 +++ .../src/components/CopyToClipboard.vue | 24 + .../src/components/codeEditorTypes.ts | 1 + .../configuration/EndpointConnection.vue | 8 +- .../failedmessages/EditRetryDialog.vue | 41 +- ...ssageRedirectForBackwardsCompatibility.vue | 3 + .../src/components/messages/BodyView.vue | 9 +- .../src/components/messages/MessageView.vue | 120 +---- .../components/messages/StacktraceView.vue | 4 +- .../src/composables/contentTypeParser.ts | 63 +++ src/Frontend/src/mount.ts | 10 +- src/Frontend/src/resources/FailedMessage.ts | 1 + .../setup/ConfigurationCode.vue | 11 +- src/Frontend/src/vue-highlight-code.d.ts | 1 - .../failedmessages/edit-and-retry.spec.ts | 26 ++ .../questions/getEditAndRetryEditor.ts | 2 +- 18 files changed, 517 insertions(+), 330 deletions(-) create mode 100644 src/Frontend/src/components/CodeEditor.vue create mode 100644 src/Frontend/src/components/CopyToClipboard.vue create mode 100644 src/Frontend/src/components/codeEditorTypes.ts create mode 100644 src/Frontend/src/composables/contentTypeParser.ts delete mode 100644 src/Frontend/src/vue-highlight-code.d.ts diff --git a/src/Frontend/package-lock.json b/src/Frontend/package-lock.json index e09057178..8b7731cac 100644 --- a/src/Frontend/package-lock.json +++ b/src/Frontend/package-lock.json @@ -8,22 +8,26 @@ "name": "service-pulse", "version": "1.0.0", "dependencies": { + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/legacy-modes": "^6.5.0", "@tinyhttp/content-disposition": "^2.2.2", "@vue-flow/core": "^1.41.5", - "@wdns/vue-code-block": "^2.3.3", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", - "highlight.js": "^11.10.0", + "codemirror": "^6.0.1", "lossless-json": "^4.0.2", "memoize-one": "^6.0.0", "moment": "^2.30.1", "pinia": "^2.2.8", "vue": "^3.5.13", + "vue-codemirror6": "^1.3.12", "vue-router": "^4.4.5", "vue-tippy": "^6.5.0", "vue-toastification": "^2.0.0-rc.5", "vue3-cookies": "^1.0.6", - "vue3-simple-typeahead": "^1.0.11" + "vue3-simple-typeahead": "^1.0.11", + "xml-formatter": "^3.6.4" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -267,6 +271,119 @@ "tough-cookie": "^4.1.4" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz", + "integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "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/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.8", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz", + "integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.0.tgz", + "integrity": "sha512-dNw5pwTqtR1giYjaJyEajunLqxGavZqV0XRtVZyMJnNOD2HmK9DMUmuCAr6RMFGRJ4l8OeQDjpI/us+R09mQsw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", + "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.10", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz", + "integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.36.4", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz", + "integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -1087,6 +1204,58 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@mswjs/interceptors": { "version": "0.37.1", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.1.tgz", @@ -1187,32 +1356,6 @@ "pinia": ">=2.2.6" } }, - "node_modules/@pinia/testing/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2266,31 +2409,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/@vueuse/metadata": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz", @@ -2310,52 +2428,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@wdns/vue-code-block": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@wdns/vue-code-block/-/vue-code-block-2.3.3.tgz", - "integrity": "sha512-eOsCTatfi/8/zcgk7yzjuu+t4Ms4Te9SwYUE5PA/+JYcgp+JXAnYBgvqwPFVoTVKq3IpQCiGZg2zMblssvUCUQ==", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/webdevnerdstuff" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/WebDevNerdStuff" - } - ], - "dependencies": { - "highlight.js": "^11.8.0", - "prismjs": "^1.29.0", - "ua-parser-js": "^1.0.38", - "vue": "^3.4.31" - } - }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -2802,6 +2874,21 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2867,6 +2954,12 @@ "node": ">= 0.6" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -4095,14 +4188,6 @@ "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", "dev": true }, - "node_modules/highlight.js": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", - "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -5336,31 +5421,6 @@ } } }, - "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -5473,15 +5533,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -5964,6 +6015,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6230,28 +6287,6 @@ } } }, - "node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "engines": { - "node": "*" - } - }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -6600,12 +6635,60 @@ } } }, + "node_modules/vue-codemirror6": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.12.tgz", + "integrity": "sha512-rLy87f7ufU4X2DDxEFGvikCe4wyl02uPYPcLtviVT+7IwY+vD/090euP7ZuoAswr1d3k/j8oEQnTr9UxoK1+KA==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.8.0", + "@codemirror/language": "^6.10.8", + "@codemirror/lint": "^6.8.4", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.36.3", + "codemirror": "^6.0.1", + "style-mod": "^4.1.2", + "vue-demi": "latest" + }, + "engines": { + "pnpm": ">=10.3.0" + }, + "peerDependencies": { + "vue": "^2.7.14 || ^3.4" + } + }, "node_modules/vue-component-type-helpers": { "version": "2.0.26", "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.26.tgz", "integrity": "sha512-sO9qQ8oC520SW6kqlls0iqDak53gsTVSrYylajgjmkt1c0vcgjsGSy1KzlDrbEx8pm02IEYhlUkU5hCYf8rwtg==", "dev": true }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", @@ -6700,6 +6783,12 @@ "vue": "^3.0.5" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -6972,6 +7061,18 @@ } } }, + "node_modules/xml-formatter": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.6.4.tgz", + "integrity": "sha512-vkvTNw4u9mp72lMmJHw771NE9EJLX0kfwIcP+ZEo9eJ6HmotX23vmykyROyIQ9Y3a+ckdUdhxIE2ZO66rYuPrg==", + "license": "MIT", + "dependencies": { + "xml-parser-xo": "^4.1.2" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -6981,6 +7082,15 @@ "node": ">=12" } }, + "node_modules/xml-parser-xo": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-4.1.3.tgz", + "integrity": "sha512-U6eN5Pyrlek9ottHVpT9e8YUax75oVYXbnYxU+utzDC7i+OyWj9ynsNMiZNQZvpuazbG0O7iLAs9FkcFmzlgSA==", + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/src/Frontend/package.json b/src/Frontend/package.json index 3d05afc21..265bbaeae 100644 --- a/src/Frontend/package.json +++ b/src/Frontend/package.json @@ -17,22 +17,26 @@ "test:application": "npm run test:application:vitest" }, "dependencies": { + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/legacy-modes": "^6.5.0", "@tinyhttp/content-disposition": "^2.2.2", "@vue-flow/core": "^1.41.5", - "@wdns/vue-code-block": "^2.3.3", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", - "highlight.js": "^11.10.0", + "codemirror": "^6.0.1", "lossless-json": "^4.0.2", "memoize-one": "^6.0.0", "moment": "^2.30.1", "pinia": "^2.2.8", "vue": "^3.5.13", + "vue-codemirror6": "^1.3.12", "vue-router": "^4.4.5", "vue-tippy": "^6.5.0", "vue-toastification": "^2.0.0-rc.5", "vue3-cookies": "^1.0.6", - "vue3-simple-typeahead": "^1.0.11" + "vue3-simple-typeahead": "^1.0.11", + "xml-formatter": "^3.6.4" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/src/Frontend/src/components/CodeEditor.vue b/src/Frontend/src/components/CodeEditor.vue new file mode 100644 index 000000000..3398739c1 --- /dev/null +++ b/src/Frontend/src/components/CodeEditor.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/Frontend/src/components/CopyToClipboard.vue b/src/Frontend/src/components/CopyToClipboard.vue new file mode 100644 index 000000000..e247b44fb --- /dev/null +++ b/src/Frontend/src/components/CopyToClipboard.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/Frontend/src/components/codeEditorTypes.ts b/src/Frontend/src/components/codeEditorTypes.ts new file mode 100644 index 000000000..c1f7bcd46 --- /dev/null +++ b/src/Frontend/src/components/codeEditorTypes.ts @@ -0,0 +1 @@ +export type CodeLanguage = "json" | "xml" | "shell" | "powershell" | "csharp"; diff --git a/src/Frontend/src/components/configuration/EndpointConnection.vue b/src/Frontend/src/components/configuration/EndpointConnection.vue index 7ae3b8a6f..5faa08b39 100644 --- a/src/Frontend/src/components/configuration/EndpointConnection.vue +++ b/src/Frontend/src/components/configuration/EndpointConnection.vue @@ -5,7 +5,7 @@ import ServiceControlNotAvailable from "../ServiceControlNotAvailable.vue"; import { licenseStatus } from "@/composables/serviceLicense"; import { connectionState, useServiceControlConnections } from "@/composables/serviceServiceControl"; import BusyIndicator from "../BusyIndicator.vue"; -import VCodeBlock from "@wdns/vue-code-block"; +import CodeEditor from "@/components/CodeEditor.vue"; const isExpired = licenseStatus.isExpired; @@ -109,7 +109,7 @@ function switchJsonTab() {
- +
@@ -119,11 +119,11 @@ function switchJsonTab() {

Note that when using JSON for configuration, you also need to change the endpoint configuration as shown below.

Endpoint configuration:

- +

JSON configuration file:

- +
diff --git a/src/Frontend/src/components/failedmessages/EditRetryDialog.vue b/src/Frontend/src/components/failedmessages/EditRetryDialog.vue index e1552e452..d8ee8d622 100644 --- a/src/Frontend/src/components/failedmessages/EditRetryDialog.vue +++ b/src/Frontend/src/components/failedmessages/EditRetryDialog.vue @@ -5,6 +5,9 @@ import MessageHeader from "./EditMessageHeader.vue"; import { EditAndRetryConfig } from "@/resources/Configuration"; import type Header from "@/resources/Header"; import { ExtendedFailedMessage } from "@/resources/FailedMessage"; +import parseContentType from "@/composables/contentTypeParser"; +import { CodeLanguage } from "@/components/codeEditorTypes"; +import CodeEditor from "@/components/CodeEditor.vue"; interface HeaderWithEditing extends Header { isLocked: boolean; @@ -28,6 +31,7 @@ interface LocalMessageState { isBodyChanged: boolean; isBodyEmpty: boolean; isContentTypeSupported: boolean; + language?: CodeLanguage; bodyContentType: string | undefined; bodyUnavailable: boolean; isEvent: boolean; @@ -104,30 +108,6 @@ function getContentType() { return header?.value; } -function isContentTypeSupported(contentType: string | undefined) { - if (contentType === undefined) return false; - - if (contentType.startsWith("text/")) return true; - - const charsetUtf8 = "; charset=utf-8"; - - if (contentType.endsWith(charsetUtf8)) { - contentType = contentType.substring(0, contentType.length - charsetUtf8.length); - } - - if (contentType === "application/json") return true; - - if (contentType.startsWith("application/")) { - // Some examples: - // application/atom+xml - // application/ld+json - // application/vnd.masstransit+json - if (contentType.endsWith("+json") || contentType.endsWith("+xml")) return true; - } - - return false; -} - function getMessageIntent() { const intent = findHeadersByKey("NServiceBus.MessageIntent"); return intent?.value; @@ -167,7 +147,9 @@ function initializeMessageBodyAndHeaders() { const contentType = getContentType(); localMessage.value.bodyContentType = contentType; - localMessage.value.isContentTypeSupported = isContentTypeSupported(contentType); + const parsedContentType = parseContentType(contentType); + localMessage.value.isContentTypeSupported = parsedContentType.isSupported; + localMessage.value.language = parsedContentType.language; const messageIntent = getMessageIntent(); localMessage.value.isEvent = messageIntent === "Publish"; @@ -248,7 +230,9 @@ onMounted(() => {
- +
+ +
Message body cannot be empty Reset changes
{{ localMessage.bodyUnavailable }}
@@ -369,9 +353,4 @@ onMounted(() => { overflow-y: auto; padding-right: 15px; } - -.modal-msg-editor :deep(textarea) { - height: 100%; - margin-top: 20px; -} diff --git a/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue b/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue index 00203b686..43fcad227 100644 --- a/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue +++ b/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue @@ -11,3 +11,6 @@ onMounted(async () => { await router.push({ path: routeLinks.messages.message.link(id.toString()), query: { back: routeLinks.failedMessage.failedMessages.link } }); }); + diff --git a/src/Frontend/src/components/messages/BodyView.vue b/src/Frontend/src/components/messages/BodyView.vue index ef667251b..d4b90a8ca 100644 --- a/src/Frontend/src/components/messages/BodyView.vue +++ b/src/Frontend/src/components/messages/BodyView.vue @@ -1,15 +1,20 @@ diff --git a/src/Frontend/src/components/messages/MessageView.vue b/src/Frontend/src/components/messages/MessageView.vue index 60a3f6e24..c7635e2ee 100644 --- a/src/Frontend/src/components/messages/MessageView.vue +++ b/src/Frontend/src/components/messages/MessageView.vue @@ -19,10 +19,11 @@ import Message from "@/resources/Message"; import { NServiceBusHeaders } from "@/resources/Header"; import { useConfiguration } from "@/composables/configuration"; import { useIsMassTransitConnected } from "@/composables/useIsMassTransitConnected"; -import { parse, stringify } from "lossless-json"; import BodyView from "@/components/messages/BodyView.vue"; import HeadersView from "@/components/messages/HeadersView.vue"; import StackTraceView from "@/components/messages/StacktraceView.vue"; +import { stringify, parse } from "lossless-json"; +import xmlFormat from "xml-formatter"; let refreshInterval: number | undefined; let pollingFaster = false; @@ -150,116 +151,19 @@ async function downloadBody(message: ExtendedFailedMessage) { } try { - switch (response.headers.get("content-type")) { - case "application/json": { - const jsonBodyRaw = await response.text(); - const jsonBody = parse(jsonBodyRaw.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => (g ? "" : m))); - message.messageBody = formatJson(jsonBody); - return; - } - case "text/xml": { - const xmlBody = await response.text(); - message.messageBody = formatXml(xmlBody); - return; - } - default: { - message.messageBody = await response.text(); - } - } - } catch { - message.bodyUnavailable = true; - } -} - -// taken from https://github.com/krtnio/angular-pretty-xml/blob/master/src/angular-pretty-xml.js -function formatXml(xml: string) { - function createShiftArr(step: string) { - let space: string; - if (isNaN(parseInt(step))) { - // argument is string - space = step; - } else { - // argument is integer - space = " ".repeat(parseInt(step)); - } - - const shift = ["\n"]; // array of shifts + const contentType = response.headers.get("content-type"); + message.contentType = contentType ?? "text/plain"; + message.messageBody = await response.text(); - for (let ix = 0; ix < 100; ix++) { - shift.push(shift[ix] + space); + if (contentType === "application/json") { + message.messageBody = stringify(parse(message.messageBody), null, 2) ?? message.messageBody; } - - return shift; - } - - const indent = "\t"; - - const arr = xml - .replace(/>\s*<") - .replace(/ or // - if (arr[i].indexOf("-->") !== -1 || arr[i].indexOf("]>") !== -1 || arr[i].indexOf("!DOCTYPE") !== -1) { - inComment = false; - } - } else if (arr[i].indexOf("-->") !== -1 || arr[i].indexOf("]>") !== -1) { - // end comment or // - string += arr[i]; - inComment = false; - } else if ( - /^<\w/.test(arr[i - 1]) && - /^<\/\w/.test(arr[i]) && // // - m1 && - m2 && - m1[0] === m2[0].replace("/", "") - ) { - string += arr[i]; - if (!inComment) depth--; - } else if (arr[i].search(/<\w/) !== -1 && arr[i].indexOf("") === -1) { - // // - string += !inComment ? shift[depth++] + arr[i] : arr[i]; - } else if (arr[i].search(/<\w/) !== -1 && arr[i].indexOf("... // - string += !inComment ? shift[depth] + arr[i] : arr[i]; - } else if (arr[i].search(/<\//) > -1) { - // // - string += !inComment ? shift[--depth] + arr[i] : arr[i]; - } else if (arr[i].indexOf("/>") !== -1) { - // // - string += !inComment ? shift[depth] + arr[i] : arr[i]; - } else if (arr[i].indexOf(" // - string += shift[depth] + arr[i]; - } else if (arr[i].indexOf("xmlns:") !== -1 || arr[i].indexOf("xmlns=") !== -1) { - // xmlns // - string += shift[depth] + arr[i]; - } else { - string += arr[i]; + if (contentType === "text/xml") { + message.messageBody = xmlFormat(message.messageBody, { indentation: " ", collapseContent: true }); } + } catch { + message.bodyUnavailable = true; } - - return string.trim(); -} - -function formatJson(json: unknown) { - return stringify(json, null, 2) as string; } function togglePanel(panelNum: number) { @@ -446,7 +350,7 @@ onUnmounted(() => {
- + diff --git a/src/Frontend/src/components/messages/StacktraceView.vue b/src/Frontend/src/components/messages/StacktraceView.vue index 55512e13e..6638ea9ea 100644 --- a/src/Frontend/src/components/messages/StacktraceView.vue +++ b/src/Frontend/src/components/messages/StacktraceView.vue @@ -1,6 +1,6 @@ diff --git a/src/Frontend/src/composables/contentTypeParser.ts b/src/Frontend/src/composables/contentTypeParser.ts new file mode 100644 index 000000000..2805fdcdf --- /dev/null +++ b/src/Frontend/src/composables/contentTypeParser.ts @@ -0,0 +1,63 @@ +import { CodeLanguage } from "@/components/codeEditorTypes"; + +function parseContentType(contentType: string | undefined): { isSupported: boolean; language?: CodeLanguage } { + if (contentType === undefined) { + return { + isSupported: false, + }; + } + + // remove content type parameter, e.g. charset=utf-8 + contentType = contentType.split(";")[0].trim(); + + if (contentType === "application/json") { + return { + isSupported: true, + language: "json", + }; + } + + if (contentType === "text/xml") { + return { + isSupported: true, + language: "xml", + }; + } + + if (contentType.startsWith("text/")) { + return { + isSupported: true, + }; + } + + if (contentType === "application/xml") { + return { + isSupported: true, + language: "xml", + }; + } + + if (contentType.startsWith("application/")) { + // Some examples: + // application/atom+xml + // application/ld+json + // application/vnd.masstransit+json + if (contentType.endsWith("+json")) { + return { + isSupported: true, + language: "json", + }; + } else if (contentType.endsWith("+xml")) { + return { + isSupported: true, + language: "xml", + }; + } + } + + return { + isSupported: false, + }; +} + +export default parseContentType; diff --git a/src/Frontend/src/mount.ts b/src/Frontend/src/mount.ts index 571a1ebf2..d8eab9349 100644 --- a/src/Frontend/src/mount.ts +++ b/src/Frontend/src/mount.ts @@ -5,8 +5,6 @@ import Toast, { type PluginOptions, POSITION } from "vue-toastification"; import VueTippy from "vue-tippy"; import { createPinia } from "pinia"; import SimpleTypeahead from "vue3-simple-typeahead"; -import { createVCodeBlock } from "@wdns/vue-code-block"; -import "highlight.js/styles/github-dark.css"; const toastOptions: PluginOptions = { position: POSITION.BOTTOM_RIGHT, @@ -24,14 +22,8 @@ export function mount({ router }: { router: Router }) { next(); }); - const VCodeBlock = createVCodeBlock({ - theme: "github-dark", - cssPath: "highlight.js/styles/github-dark.css", - highlightjs: true, - }); - const app = createApp(App); - app.use(router).use(Toast, toastOptions).use(SimpleTypeahead).use(VCodeBlock).use(createPinia()).use(VueTippy); + app.use(router).use(Toast, toastOptions).use(SimpleTypeahead).use(createPinia()).use(VueTippy); app.mount(`#app`); app.config.errorHandler = (err, instance) => { diff --git a/src/Frontend/src/resources/FailedMessage.ts b/src/Frontend/src/resources/FailedMessage.ts index e8a38ceb3..74f1b208d 100644 --- a/src/Frontend/src/resources/FailedMessage.ts +++ b/src/Frontend/src/resources/FailedMessage.ts @@ -38,6 +38,7 @@ export interface ExtendedFailedMessage extends FailedMessage { headers: Header[]; conversationId: string; messageBody: string; + contentType: string; isEditAndRetryEnabled: boolean; redirect: boolean; submittedForRetrial: boolean; diff --git a/src/Frontend/src/views/throughputreport/setup/ConfigurationCode.vue b/src/Frontend/src/views/throughputreport/setup/ConfigurationCode.vue index 8d6ba2c1b..9b031a12f 100644 --- a/src/Frontend/src/views/throughputreport/setup/ConfigurationCode.vue +++ b/src/Frontend/src/views/throughputreport/setup/ConfigurationCode.vue @@ -1,8 +1,9 @@