From b86fc187d712c1e95005a55f51660c04baaaf2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 31 Dec 2025 02:49:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E5=85=A8=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=BC=8F=E5=8A=A0=E8=BD=BDmonaco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 4 +- web/pnpm-lock.yaml | 111 +++++++++++++++----- web/src/components/common/CommonEditor.vue | 111 ++++++++++++++++++-- web/src/components/common/FileEditor.vue | 97 +++++++++++++++-- web/src/utils/file/index.ts | 3 + web/src/utils/monaco/index.ts | 115 +++++++++++++++++++++ web/types/monaco.d.ts | 36 +++++++ 7 files changed, 426 insertions(+), 51 deletions(-) create mode 100644 web/src/utils/monaco/index.ts create mode 100644 web/types/monaco.d.ts diff --git a/web/package.json b/web/package.json index d7ce6752..6cb4c741 100644 --- a/web/package.json +++ b/web/package.json @@ -37,7 +37,6 @@ "@xterm/addon-web-links": "^0.12.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", - "ace-builds": "^1.43.5", "alova": "^3.3.4", "echarts": "^6.0.0", "install": "^0.13.0", @@ -45,6 +44,8 @@ "luxon": "^3.7.2", "marked": "^17.0.0", "mitt": "^3.0.1", + "monaco-editor": "^0.55.1", + "monaco-editor-nginx": "^2.0.2", "node-forge": "^1.3.1", "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.5.0", @@ -52,7 +53,6 @@ "vue": "^3.5.22", "vue-echarts": "^8.0.1", "vue-router": "^4.6.3", - "vue3-ace-editor": "^2.2.4", "vue3-gettext": "4.0.0-beta.1" }, "devDependencies": { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e8c155ad..0ba246c0 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -44,9 +44,6 @@ importers: '@xterm/xterm': specifier: ^6.0.0 version: 6.0.0 - ace-builds: - specifier: ^1.43.5 - version: 1.43.5 alova: specifier: ^3.3.4 version: 3.4.1 @@ -68,6 +65,12 @@ importers: mitt: specifier: ^3.0.1 version: 3.0.1 + monaco-editor: + specifier: ^0.55.1 + version: 0.55.1 + monaco-editor-nginx: + specifier: ^2.0.2 + version: 2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) node-forge: specifier: ^1.3.1 version: 1.3.3 @@ -89,9 +92,6 @@ importers: vue-router: specifier: ^4.6.3 version: 4.6.4(vue@3.5.26(typescript@5.9.3)) - vue3-ace-editor: - specifier: ^2.2.4 - version: 2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3)) vue3-gettext: specifier: 4.0.0-beta.1 version: 4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)) @@ -345,6 +345,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -779,6 +783,9 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@nginx/reference-lib@1.1.25': + resolution: {integrity: sha512-y8g/3Z17VKCvnGB2KRtAvlcEWvRUaO0Ln5MIAdBsqX6qsfMIDznFt0lnFQMVZM51AQgQeY4TP+nWTOiKe6+wsQ==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1039,6 +1046,9 @@ packages: '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -1350,9 +1360,6 @@ packages: '@xterm/xterm@6.0.0': resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} - ace-builds@1.43.5: - resolution: {integrity: sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1716,6 +1723,9 @@ packages: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -2414,6 +2424,11 @@ packages: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + marked@17.0.1: resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} engines: {node: '>= 20'} @@ -2481,6 +2496,18 @@ packages: resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==} hasBin: true + monaco-editor-nginx@2.0.2: + resolution: {integrity: sha512-F/qejb0w0hxIyxbu2JMCe+MhnOV7bxGbUynnyMXk7Cy25Na5fD99u5QqRr7WfRhIn0xcdhBEkJ/jikQsxVnEOA==} + peerDependencies: + '@babel/runtime': '>=7.10.0' + '@nginx/reference-lib': '>=1.1.0' + monaco-editor: '>=0.22.3' + react: '>=16.9.0' + react-dom: '>=16.9.0' + + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -2749,6 +2776,15 @@ packages: rate-limiter-flexible@5.0.5: resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + read-package-json-fast@4.0.0: resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -2780,9 +2816,6 @@ packages: remove@0.1.5: resolution: {integrity: sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==} - resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2834,6 +2867,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -3306,12 +3342,6 @@ packages: peerDependencies: typescript: '>=5.0.0' - vue3-ace-editor@2.2.4: - resolution: {integrity: sha512-FZkEyfpbH068BwjhMyNROxfEI8135Sc+x8ouxkMdCNkuj/Tuw83VP/gStFQqZHqljyX9/VfMTCdTqtOnJZGN8g==} - peerDependencies: - ace-builds: '*' - vue: ^3 - vue3-gettext@4.0.0-beta.1: resolution: {integrity: sha512-1A46SmubgTMyy7i5hj8ay50NFl6/vzwoIVZPuGCin/X3a/NVCAs99G0EbcnfJiR7NZNTJgUjvBzppufC7Kq+4A==} engines: {node: '>= 20.19.0'} @@ -3586,6 +3616,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -4169,6 +4201,8 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@nginx/reference-lib@1.1.25': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4358,6 +4392,9 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/trusted-types@2.0.7': + optional: true + '@types/web-bluetooth@0.0.21': {} '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': @@ -4836,8 +4873,6 @@ snapshots: '@xterm/xterm@6.0.0': {} - ace-builds@1.43.5: {} - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -5199,6 +5234,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -5952,6 +5991,8 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 + marked@14.0.0: {} + marked@17.0.1: {} math-intrinsics@1.1.0: {} @@ -6029,6 +6070,19 @@ snapshots: dependencies: commander: 14.0.2 + monaco-editor-nginx@2.0.2(@babel/runtime@7.28.4)(@nginx/reference-lib@1.1.25)(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/runtime': 7.28.4 + '@nginx/reference-lib': 1.1.25 + monaco-editor: 0.55.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + mrmime@2.0.1: {} ms@2.0.0: {} @@ -6279,6 +6333,13 @@ snapshots: rate-limiter-flexible@5.0.5: {} + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react@19.2.3: {} + read-package-json-fast@4.0.0: dependencies: json-parse-even-better-errors: 4.0.0 @@ -6318,8 +6379,6 @@ snapshots: dependencies: seq: 0.3.5 - resize-observer-polyfill@1.5.1: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -6397,6 +6456,8 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.1 + scheduler@0.27.0: {} + scule@1.3.0: {} seemly@0.3.10: {} @@ -6953,12 +7014,6 @@ snapshots: '@vue/language-core': 3.2.1 typescript: 5.9.3 - vue3-ace-editor@2.2.4(ace-builds@1.43.5)(vue@3.5.26(typescript@5.9.3)): - dependencies: - ace-builds: 1.43.5 - resize-observer-polyfill: 1.5.1 - vue: 3.5.26(typescript@5.9.3) - vue3-gettext@4.0.0-beta.1(@vue/compiler-sfc@3.5.26)(vue@3.5.26(typescript@5.9.3)): dependencies: '@vue/compiler-sfc': 3.5.26 diff --git a/web/src/components/common/CommonEditor.vue b/web/src/components/common/CommonEditor.vue index e862a0ab..f779100e 100644 --- a/web/src/components/common/CommonEditor.vue +++ b/web/src/components/common/CommonEditor.vue @@ -1,6 +1,7 @@ - + diff --git a/web/src/components/common/FileEditor.vue b/web/src/components/common/FileEditor.vue index 56969485..37b69be0 100644 --- a/web/src/components/common/FileEditor.vue +++ b/web/src/components/common/FileEditor.vue @@ -1,9 +1,11 @@ - + diff --git a/web/src/utils/file/index.ts b/web/src/utils/file/index.ts index 49c812e2..c3d8115e 100644 --- a/web/src/utils/file/index.ts +++ b/web/src/utils/file/index.ts @@ -160,6 +160,9 @@ const languageByPath = (path: string) => { return 'html' case 'ini': case 'conf': + if (path.toLowerCase().includes('nginx')) { + return 'nginx' + } return 'ini' case 'java': return 'java' diff --git a/web/src/utils/monaco/index.ts b/web/src/utils/monaco/index.ts new file mode 100644 index 00000000..886cd19a --- /dev/null +++ b/web/src/utils/monaco/index.ts @@ -0,0 +1,115 @@ +import type * as Monaco from 'monaco-editor' + +let monacoInstance: typeof Monaco | null = null +let isInitialized = false +let initPromise: Promise | null = null + +async function loadMonacoLocale(locale: string) { + switch (locale) { + case 'cs': + await import('monaco-editor/esm/nls.messages.cs.js') + break + case 'de': + await import('monaco-editor/esm/nls.messages.de.js') + break + case 'es': + await import('monaco-editor/esm/nls.messages.es.js') + break + case 'fr': + await import('monaco-editor/esm/nls.messages.fr.js') + break + case 'it': + await import('monaco-editor/esm/nls.messages.it.js') + break + case 'ja': + await import('monaco-editor/esm/nls.messages.ja.js') + break + case 'ko': + await import('monaco-editor/esm/nls.messages.ko.js') + break + case 'pl': + await import('monaco-editor/esm/nls.messages.pl.js') + break + case 'pt_BR': + await import('monaco-editor/esm/nls.messages.pt-br.js') + break + case 'ru': + await import('monaco-editor/esm/nls.messages.ru.js') + break + case 'tr': + await import('monaco-editor/esm/nls.messages.tr.js') + break + case 'zh_CN': + await import('monaco-editor/esm/nls.messages.zh-cn.js') + break + case 'zh_TW': + await import('monaco-editor/esm/nls.messages.zh-tw.js') + break + default: + break + } +} + +async function setupMonacoWorkers() { + if (self.MonacoEnvironment) return + + const [editorWorker, jsonWorker, cssWorker, htmlWorker, tsWorker] = await Promise.all([ + import('monaco-editor/esm/vs/editor/editor.worker?worker'), + import('monaco-editor/esm/vs/language/json/json.worker?worker'), + import('monaco-editor/esm/vs/language/css/css.worker?worker'), + import('monaco-editor/esm/vs/language/html/html.worker?worker'), + import('monaco-editor/esm/vs/language/typescript/ts.worker?worker') + ]) + + self.MonacoEnvironment = { + getWorker(_: any, label: string) { + if (label === 'json') { + return new jsonWorker.default() + } + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker.default() + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker.default() + } + if (label === 'typescript' || label === 'javascript') { + return new tsWorker.default() + } + return new editorWorker.default() + } + } +} + +/** + * 获取 Monaco 实例 + * @param locale 可选的语言设置 + * @returns Monaco 实例 + */ +export async function getMonaco(locale?: string): Promise { + if (isInitialized && monacoInstance) { + return monacoInstance + } + + if (initPromise) { + return initPromise + } + + initPromise = (async () => { + if (locale) { + await loadMonacoLocale(locale) + } + + await setupMonacoWorkers() + + const monaco = await import('monaco-editor') + await import('monaco-editor-nginx') + monacoInstance = monaco + isInitialized = true + + return monaco + })() + + return initPromise +} + +export type { Monaco } diff --git a/web/types/monaco.d.ts b/web/types/monaco.d.ts new file mode 100644 index 00000000..656fa9a6 --- /dev/null +++ b/web/types/monaco.d.ts @@ -0,0 +1,36 @@ +// Monaco Editor 本地化模块声明 +declare module 'monaco-editor/esm/nls.messages.cs.js' +declare module 'monaco-editor/esm/nls.messages.de.js' +declare module 'monaco-editor/esm/nls.messages.es.js' +declare module 'monaco-editor/esm/nls.messages.fr.js' +declare module 'monaco-editor/esm/nls.messages.it.js' +declare module 'monaco-editor/esm/nls.messages.ja.js' +declare module 'monaco-editor/esm/nls.messages.ko.js' +declare module 'monaco-editor/esm/nls.messages.pl.js' +declare module 'monaco-editor/esm/nls.messages.pt-br.js' +declare module 'monaco-editor/esm/nls.messages.ru.js' +declare module 'monaco-editor/esm/nls.messages.tr.js' +declare module 'monaco-editor/esm/nls.messages.zh-cn.js' +declare module 'monaco-editor/esm/nls.messages.zh-tw.js' + +// Monaco Editor Worker 模块声明 +declare module 'monaco-editor/esm/vs/editor/editor.worker?worker' { + const EditorWorker: new () => Worker + export default EditorWorker +} +declare module 'monaco-editor/esm/vs/language/json/json.worker?worker' { + const JsonWorker: new () => Worker + export default JsonWorker +} +declare module 'monaco-editor/esm/vs/language/css/css.worker?worker' { + const CssWorker: new () => Worker + export default CssWorker +} +declare module 'monaco-editor/esm/vs/language/html/html.worker?worker' { + const HtmlWorker: new () => Worker + export default HtmlWorker +} +declare module 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' { + const TsWorker: new () => Worker + export default TsWorker +}