diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index ff1cc73..4aa14dc 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -402,6 +402,11 @@ export enum ExtensionName { * HTTP 客户端 */ HttpClient = "httpClient", + + /** + * 代码块导出图片 + */ + BlockImage = "blockImage", }; /** @@ -1170,6 +1175,11 @@ export enum KeyBindingName { * 重做选择 */ HistoryRedoSelection = "historyRedoSelection", + + /** + * 复制块为图片 + */ + CopyBlockImage = "copyBlockImage", }; export enum KeyBindingType { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9676fbe..9e3b0b7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,13 +30,13 @@ "@codemirror/lang-vue": "^0.1.3", "@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-yaml": "^6.1.2", - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.1", "@codemirror/language-data": "^6.5.2", "@codemirror/legacy-modes": "^6.5.2", "@codemirror/lint": "^6.9.2", "@codemirror/search": "^6.5.11", - "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.39.4", + "@codemirror/state": "^6.5.3", + "@codemirror/view": "^6.39.6", "@cospaia/prettier-plugin-clojure": "^0.0.2", "@lezer/highlight": "^1.2.3", "@lezer/lr": "^1.4.5", @@ -45,6 +45,7 @@ "@toml-tools/lexer": "^1.0.0", "@toml-tools/parser": "^1.0.0", "@types/katex": "^0.16.7", + "@zumer/snapdom": "^2.0.1", "codemirror": "^6.0.2", "codemirror-lang-elixir": "^4.0.0", "colors-named": "^1.0.4", @@ -60,9 +61,9 @@ "pinia": "^3.0.4", "pinia-plugin-persistedstate": "^4.7.1", "prettier": "^3.7.4", - "sass": "^1.97.0", - "vue": "^3.5.25", - "vue-i18n": "^11.2.2", + "sass": "^1.97.1", + "vue": "^3.5.26", + "vue-i18n": "^11.2.7", "vue-pick-colors": "^1.8.0", "vue-router": "^4.6.4" }, @@ -71,21 +72,21 @@ "@lezer/generator": "^1.8.0", "@types/node": "^25.0.3", "@vitejs/plugin-vue": "^6.0.3", - "@wailsio/runtime": "^3.0.0-alpha.76", + "@wailsio/runtime": "^3.0.0-alpha.77", "cross-env": "^10.1.0", "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "globals": "^16.5.0", "happy-dom": "^20.0.11", "typescript": "^5.9.3", - "typescript-eslint": "^8.50.0", + "typescript-eslint": "^8.50.1", "unplugin-vue-components": "^30.0.0", "vite": "npm:rolldown-vite@latest", "vite-plugin-node-polyfills": "^0.24.0", "vitepress": "^2.0.0-alpha.12", "vitest": "^4.0.16", "vue-eslint-parser": "^10.2.0", - "vue-tsc": "^3.1.8" + "vue-tsc": "^3.2.1" } }, "node_modules/@antfu/install-pkg": { @@ -524,14 +525,14 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.3", - "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.3.tgz", - "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "version": "6.12.1", + "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", + "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" @@ -600,18 +601,18 @@ } }, "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.3.tgz", + "integrity": "sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "node_modules/@codemirror/view": { - "version": "6.39.4", - "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.4.tgz", - "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", + "version": "6.39.6", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.39.6.tgz", + "integrity": "sha512-/N+SoP5NndJjkGInp3BwlUa3KQKD6bDo0TV6ep37ueAdQ7BVu/PqlZNywmgjCq0MQoZadZd8T+MZucSr7fktyQ==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.5.0", @@ -1340,13 +1341,13 @@ } }, "node_modules/@intlify/core-base": { - "version": "11.2.2", - "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.2.tgz", - "integrity": "sha512-0mCTBOLKIqFUP3BzwuFW23hYEl9g/wby6uY//AC5hTgQfTsM2srCYF2/hYGp+a5DZ/HIFIgKkLJMzXTt30r0JQ==", + "version": "11.2.7", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.7.tgz", + "integrity": "sha512-+Ra9I/LAzXDnmv/IrTO03WMCiLya7pHRmGJvNl9fKwx/W4REJ0xaMk2PxCRqnxcBsX443amEMdebQ3R1geiuIw==", "license": "MIT", "dependencies": { - "@intlify/message-compiler": "11.2.2", - "@intlify/shared": "11.2.2" + "@intlify/message-compiler": "11.2.7", + "@intlify/shared": "11.2.7" }, "engines": { "node": ">= 16" @@ -1356,12 +1357,12 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "11.2.2", - "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.2.tgz", - "integrity": "sha512-XS2p8Ff5JxWsKhgfld4/MRQzZRQ85drMMPhb7Co6Be4ZOgqJX1DzcZt0IFgGTycgqL8rkYNwgnD443Q+TapOoA==", + "version": "11.2.7", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.7.tgz", + "integrity": "sha512-TFamC+GzJAotAFwUNvbtRVBgvuSn2nCwKNresmPUHv3IIVMmXJt7QQJj/DORI1h8hs46ZF6L0Fs2xBohSOE4iQ==", "license": "MIT", "dependencies": { - "@intlify/shared": "11.2.2", + "@intlify/shared": "11.2.7", "source-map-js": "^1.0.2" }, "engines": { @@ -1372,9 +1373,9 @@ } }, "node_modules/@intlify/shared": { - "version": "11.2.2", - "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.2.tgz", - "integrity": "sha512-OtCmyFpSXxNu/oET/aN6HtPCbZ01btXVd0f3w00YsHOb13Kverk1jzA2k47pAekM55qbUw421fvPF1yxZ+gicw==", + "version": "11.2.7", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.7.tgz", + "integrity": "sha512-uvlkvc/0uQ4FDlHQZccpUnmcOwNcaI3i+69ck2YJ+GqM35AoVbuS63b+YfirV4G0SZh64Ij2UMcFRMmB4nr95w==", "license": "MIT", "engines": { "node": ">= 16" @@ -1433,9 +1434,9 @@ } }, "node_modules/@lezer/common": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.3.0.tgz", - "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.5.0.tgz", + "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", "license": "MIT" }, "node_modules/@lezer/cpp": { @@ -2867,17 +2868,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", - "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", + "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/type-utils": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/type-utils": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" @@ -2890,7 +2891,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.50.0", + "@typescript-eslint/parser": "^8.50.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2906,16 +2907,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.50.0.tgz", - "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.50.1.tgz", + "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4" }, "engines": { @@ -2931,14 +2932,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", - "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.0", - "@typescript-eslint/types": "^8.50.0", + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", "debug": "^4.3.4" }, "engines": { @@ -2953,14 +2954,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", - "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0" + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2971,9 +2972,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", - "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", "dev": true, "license": "MIT", "engines": { @@ -2988,15 +2989,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", - "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", + "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3013,9 +3014,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.50.0.tgz", - "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", "dev": true, "license": "MIT", "engines": { @@ -3027,16 +3028,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", - "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.0", - "@typescript-eslint/tsconfig-utils": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -3081,16 +3082,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.50.0.tgz", - "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0" + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3105,13 +3106,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", - "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/types": "8.50.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3268,68 +3269,68 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.26", - "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.26.tgz", - "integrity": "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==", + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.26" + "@volar/source-map": "2.4.27" } }, "node_modules/@volar/source-map": { - "version": "2.4.26", - "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.26.tgz", - "integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==", + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.26", - "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.26.tgz", - "integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==", + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -3337,13 +3338,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/devtools-api": { @@ -3380,27 +3381,19 @@ } }, "node_modules/@vue/language-core": { - "version": "3.1.8", - "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.1.8.tgz", - "integrity": "sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.2.1.tgz", + "integrity": "sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, "node_modules/@vue/language-core/node_modules/picomatch": { @@ -3417,53 +3410,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz", - "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.25" + "@vue/shared": "3.5.26" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz", - "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", - "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/runtime-core": "3.5.25", - "@vue/shared": "3.5.25", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz", - "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" }, "peerDependencies": { - "vue": "3.5.25" + "vue": "3.5.26" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", "license": "MIT" }, "node_modules/@vueuse/core": { @@ -3599,6 +3592,12 @@ "regexp-to-ast": "0.5.0" } }, + "node_modules/@zumer/snapdom": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@zumer/snapdom/-/snapdom-2.0.1.tgz", + "integrity": "sha512-78/qbYl2FTv4H6qaXcNfAujfIOSzdvs83NW63VbyC9QA3sqNPfPvhn4xYMO6Gy11hXwJUEhd0z65yKiNzDwy9w==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", @@ -5168,9 +5167,9 @@ "license": "MIT" }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -7656,9 +7655,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.97.0", - "resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.0.tgz", - "integrity": "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==", + "version": "1.97.1", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -8232,16 +8231,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.50.0.tgz", - "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", + "version": "8.50.1", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.50.1.tgz", + "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0" + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8902,16 +8901,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.25", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz", - "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "version": "3.5.26", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-sfc": "3.5.25", - "@vue/runtime-dom": "3.5.25", - "@vue/server-renderer": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" @@ -8947,13 +8946,13 @@ } }, "node_modules/vue-i18n": { - "version": "11.2.2", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.2.tgz", - "integrity": "sha512-ULIKZyRluUPRCZmihVgUvpq8hJTtOqnbGZuv4Lz+byEKZq4mU0g92og414l6f/4ju+L5mORsiUuEPYrAuX2NJg==", + "version": "11.2.7", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.7.tgz", + "integrity": "sha512-LPv8bAY5OA0UvFEXl4vBQOBqJzRrlExy92tWgRuwW7tbykHf7CH71G2Y4TM2OwGcIS4+hyqKHS2EVBqaYwPY9Q==", "license": "MIT", "dependencies": { - "@intlify/core-base": "11.2.2", - "@intlify/shared": "11.2.2", + "@intlify/core-base": "11.2.7", + "@intlify/shared": "11.2.7", "@vue/devtools-api": "^6.5.0" }, "engines": { @@ -9007,14 +9006,14 @@ "license": "MIT" }, "node_modules/vue-tsc": { - "version": "3.1.8", - "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.1.8.tgz", - "integrity": "sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==", + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.2.1.tgz", + "integrity": "sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/typescript": "2.4.26", - "@vue/language-core": "3.1.8" + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.1" }, "bin": { "vue-tsc": "bin/vue-tsc.js" diff --git a/frontend/package.json b/frontend/package.json index a035c00..5f4dde5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,13 +44,13 @@ "@codemirror/lang-vue": "^0.1.3", "@codemirror/lang-wast": "^6.0.2", "@codemirror/lang-yaml": "^6.1.2", - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.1", "@codemirror/language-data": "^6.5.2", "@codemirror/legacy-modes": "^6.5.2", "@codemirror/lint": "^6.9.2", "@codemirror/search": "^6.5.11", - "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.39.4", + "@codemirror/state": "^6.5.3", + "@codemirror/view": "^6.39.6", "@cospaia/prettier-plugin-clojure": "^0.0.2", "@lezer/highlight": "^1.2.3", "@lezer/lr": "^1.4.5", @@ -59,6 +59,7 @@ "@toml-tools/lexer": "^1.0.1", "@toml-tools/parser": "^1.0.1", "@types/katex": "^0.16.7", + "@zumer/snapdom": "^2.0.1", "codemirror": "^6.0.2", "codemirror-lang-elixir": "^4.0.0", "colors-named": "^1.0.4", @@ -74,9 +75,9 @@ "pinia": "^3.0.4", "pinia-plugin-persistedstate": "^4.7.1", "prettier": "^3.7.4", - "sass": "^1.97.0", - "vue": "^3.5.25", - "vue-i18n": "^11.2.2", + "sass": "^1.97.1", + "vue": "^3.5.26", + "vue-i18n": "^11.2.7", "vue-pick-colors": "^1.8.0", "vue-router": "^4.6.4" }, @@ -85,21 +86,21 @@ "@lezer/generator": "^1.8.0", "@types/node": "^25.0.3", "@vitejs/plugin-vue": "^6.0.3", - "@wailsio/runtime": "^3.0.0-alpha.76", + "@wailsio/runtime": "^3.0.0-alpha.77", "cross-env": "^10.1.0", "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "globals": "^16.5.0", "happy-dom": "^20.0.11", "typescript": "^5.9.3", - "typescript-eslint": "^8.50.0", + "typescript-eslint": "^8.50.1", "unplugin-vue-components": "^30.0.0", "vite": "npm:rolldown-vite@latest", "vite-plugin-node-polyfills": "^0.24.0", "vitepress": "^2.0.0-alpha.12", "vitest": "^4.0.16", "vue-eslint-parser": "^10.2.0", - "vue-tsc": "^3.1.8" + "vue-tsc": "^3.2.1" }, "overrides": { "vite": "npm:rolldown-vite@latest" diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index f128e7f..b79feac 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -83,6 +83,7 @@ export default { blockCopy: 'Copy', blockCut: 'Cut', blockPaste: 'Paste', + copyBlockImage: 'Copy block image', historyUndo: 'Undo', historyRedo: 'Redo', historyUndoSelection: 'Undo selection', @@ -328,6 +329,11 @@ export default { httpClient: { name: 'HTTP Client', description: 'Send HTTP requests directly in the editor and view responses' + }, + blockImage: { + name: 'Block Image Export', + description: 'Render the current code block to an image and copy it to the clipboard', + copyMenu: 'Copy block as image' } }, monitor: { diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 48576fd..a91e23a 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -83,6 +83,7 @@ export default { blockCopy: '复制', blockCut: '剪切', blockPaste: '粘贴', + copyBlockImage: '复制块图片', historyUndo: '撤销', historyRedo: '重做', historyUndoSelection: '撤销选择', @@ -330,6 +331,11 @@ export default { httpClient: { name: 'HTTP 客户端', description: '在编辑器中直接发送 HTTP 请求并查看响应' + }, + blockImage: { + name: '代码块导出图片', + description: '将当前代码块渲染为图片并复制到剪贴板', + copyMenu: '复制块为图片' } }, monitor: { diff --git a/frontend/src/main.ts b/frontend/src/main.ts index a13014f..f4d5924 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -6,6 +6,9 @@ import i18n from './i18n'; import router from './router'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import { registerDirectives } from './directives'; +import {EditorView} from "@codemirror/view"; + +(EditorView as any).EDIT_CONTEXT = false; const pinia = createPinia(); pinia.use(piniaPluginPersistedstate); diff --git a/frontend/src/views/editor/basic/cursorPositionExtension.ts b/frontend/src/views/editor/basic/cursorPositionExtension.ts index 4423e3b..084ad07 100644 --- a/frontend/src/views/editor/basic/cursorPositionExtension.ts +++ b/frontend/src/views/editor/basic/cursorPositionExtension.ts @@ -15,7 +15,7 @@ export function createCursorPositionExtension(documentId: number) { constructor(private view: EditorView) { const {debouncedFn, flush} = createDebounce( () => this.saveCursorPosition(), - {delay: 400} + {delay: 1000} ); this.debouncedSave = {fn: debouncedFn, flush}; diff --git a/frontend/src/views/editor/extensions/blockImage/contextMenu.ts b/frontend/src/views/editor/extensions/blockImage/contextMenu.ts new file mode 100644 index 0000000..2e662b5 --- /dev/null +++ b/frontend/src/views/editor/extensions/blockImage/contextMenu.ts @@ -0,0 +1,19 @@ +import type {MenuSchemaNode} from '../contextMenu/menuSchema'; +import {getActiveNoteBlock} from '../codeblock/state'; +import {blockImageEnabledFacet, copyBlockImageCommand} from './index'; + + +export const blockImageMenuNodes: MenuSchemaNode[] = [ + { + id: 'copy-block-image', + labelKey: 'extensions.blockImage.copyMenu', + command: copyBlockImageCommand, + visible: context => + context.view.state.facet(blockImageEnabledFacet) && + Boolean(getActiveNoteBlock(context.view.state)), + enabled: context => + context.view.state.facet(blockImageEnabledFacet) && + Boolean(getActiveNoteBlock(context.view.state)), + }, +]; + diff --git a/frontend/src/views/editor/extensions/blockImage/index.ts b/frontend/src/views/editor/extensions/blockImage/index.ts new file mode 100644 index 0000000..07817e8 --- /dev/null +++ b/frontend/src/views/editor/extensions/blockImage/index.ts @@ -0,0 +1,319 @@ +import {snapdom} from '@zumer/snapdom'; +import {syntaxTree, highlightingFor} from '@codemirror/language'; +import {Highlighter, highlightTree} from '@lezer/highlight'; +import {Facet, type Extension} from '@codemirror/state'; +import {EditorView, Command} from '@codemirror/view'; +import type {Block} from '../codeblock/types'; +import {blockState, getActiveNoteBlock} from '../codeblock/state'; + +/** + * 高亮片段信息 + */ +interface HighlightSpan { + from: number; + to: number; + cssClass: string; +} + +/** + * 从语法树获取指定范围的高亮信息 + */ +function getHighlights(view: EditorView, from: number, to: number): HighlightSpan[] { + const tree = syntaxTree(view.state); + const highlights: HighlightSpan[] = []; + + if (tree.length === 0) { + return highlights; + } + + const highlighter: Highlighter = { + style: tags => highlightingFor(view.state, tags), + }; + + highlightTree( + tree, + highlighter, + (hlFrom, hlTo, cssClass) => { + if (hlFrom < to && hlTo > from) { + highlights.push({ + from: Math.max(hlFrom, from), + to: Math.min(hlTo, to), + cssClass: cssClass || '', + }); + } + }, + from, + to, + ); + + return highlights; +} + +/** + * 构建带高亮的单行元素 + */ +function createHighlightedLine( + lineText: string, + lineFrom: number, + lineTo: number, + highlights: HighlightSpan[], +): HTMLElement { + const lineElement = document.createElement('div'); + lineElement.className = 'cm-line'; + lineElement.style.whiteSpace = 'pre'; + + if (highlights.length === 0 || lineText.length === 0) { + lineElement.textContent = lineText || ' '; + return lineElement; + } + + const spans: Array<{text: string; cssClass: string}> = []; + let pos = lineFrom; + + const lineHighlights = highlights + .filter(h => h.from < lineTo && h.to > lineFrom) + .sort((a, b) => a.from - b.from); + + for (const hl of lineHighlights) { + if (hl.from > pos) { + spans.push({ + text: lineText.slice(pos - lineFrom, hl.from - lineFrom), + cssClass: '', + }); + } + + const hlStart = Math.max(hl.from, lineFrom); + const hlEnd = Math.min(hl.to, lineTo); + spans.push({ + text: lineText.slice(hlStart - lineFrom, hlEnd - lineFrom), + cssClass: hl.cssClass, + }); + + pos = hlEnd; + } + + if (pos < lineTo) { + spans.push({ + text: lineText.slice(pos - lineFrom), + cssClass: '', + }); + } + + for (const span of spans) { + if (span.cssClass) { + const spanElement = document.createElement('span'); + spanElement.className = span.cssClass; + spanElement.textContent = span.text; + lineElement.appendChild(spanElement); + } else { + lineElement.appendChild(document.createTextNode(span.text)); + } + } + + return lineElement; +} + +/** + * 构建用于截图的块 DOM + */ +function inlineStyle(style: CSSStyleDeclaration, props: string[]): string { + return props + .map(prop => { + const val = style.getPropertyValue(prop); + return val ? `${prop}:${val};` : ''; + }) + .join(''); +} + +function getBlockDomElement(view: EditorView, block: Block): HTMLElement | null { + try { + const blocks = view.state.field(blockState, false); + if (!blocks) return null; + + const blockIndex = blocks.indexOf(block); + const isEvenBlock = blockIndex % 2 === 0; + + const blockLayerElem = view.dom.querySelector( + `.code-blocks-layer .${isEvenBlock ? 'block-even' : 'block-odd'}`, + ) as HTMLElement | null; + const backgroundColor = + blockLayerElem?.ownerDocument + ? getComputedStyle(blockLayerElem).backgroundColor + : isEvenBlock + ? '#252B37' + : '#213644'; + + const contentDom = view.dom.querySelector('.cm-content') as HTMLElement | null; + const sourceStyle = contentDom ? getComputedStyle(contentDom) : getComputedStyle(view.dom); + + const container = document.createElement('div'); + container.className = 'cm-editor cm-focused block-export-wrapper'; + container.style.cssText = ` + padding: 18px 22px; + background-color: ${backgroundColor}; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25); + display: inline-block; + min-width: 360px; + max-width: 960px; + color: ${sourceStyle.color}; + font-family: ${sourceStyle.fontFamily}; + font-size: ${sourceStyle.fontSize}; + line-height: ${sourceStyle.lineHeight}; + position: relative; + `; + + const contentWrapper = document.createElement('div'); + contentWrapper.className = 'cm-content'; + contentWrapper.style.whiteSpace = 'pre'; + contentWrapper.style.cssText += inlineStyle(sourceStyle, [ + 'color', + 'font-family', + 'font-size', + 'font-weight', + 'font-style', + 'line-height', + 'letter-spacing', + 'tab-size', + 'text-rendering', + 'background', + 'background-color', + 'text-shadow', + ]); + + const highlights = getHighlights(view, block.content.from, block.content.to); + const fromLine = view.state.doc.lineAt(block.content.from); + const toLine = view.state.doc.lineAt(block.content.to); + for (let lineNum = fromLine.number; lineNum <= toLine.number; lineNum++) { + const line = view.state.doc.line(lineNum); + const lineElement = createHighlightedLine(line.text, line.from, line.to, highlights); + contentWrapper.appendChild(lineElement); + } + + if (block.language.name && block.language.name !== 'text') { + const langLabel = document.createElement('div'); + langLabel.className = 'block-language-label'; + langLabel.textContent = block.language.name; + langLabel.style.cssText = ` + position: absolute; + top: 6px; + right: 10px; + padding: 3px 8px; + background-color: rgba(0, 0, 0, 0.35); + color: rgba(255, 255, 255, 0.85); + font-size: 11px; + font-family: system-ui, -apple-system, sans-serif; + font-weight: 600; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + pointer-events: none; + `; + container.appendChild(langLabel); + } + + container.appendChild(contentWrapper); + return container; + } catch (error) { + console.error('[blockImage] Failed to build block DOM:', error); + return null; + } +} + +/** + * 将 Canvas 转换为 PNG Blob + */ +function canvasToPngBlob(canvas: HTMLCanvasElement): Promise { + return new Promise((resolve, reject) => { + canvas.toBlob(blob => { + if (blob) { + resolve(blob); + } else { + reject(new Error('Canvas toBlob returned null')); + } + }, 'image/png'); + }); +} + +/** + * 写入剪贴板(PNG) + */ +async function writeImageToClipboard(blob: Blob): Promise { + const ClipboardItemCtor = (window as any).ClipboardItem; + if (ClipboardItemCtor && navigator.clipboard?.write) { + const item = new ClipboardItemCtor({'image/png': blob}); + await navigator.clipboard.write([item]); + return; + } +} + +/** + * 将当前活动块导出为图片并复制到剪贴板 + */ +async function copyActiveBlockAsImage(view: EditorView): Promise { + const activeBlock = getActiveNoteBlock(view.state); + if (!activeBlock) { + console.warn('[blockImage] No active block found'); + return false; + } + + const targetDom = view.scrollDOM || document.body; + const prevCursor = (targetDom as HTMLElement).style.cursor; + (targetDom as HTMLElement).style.cursor = 'progress'; + + const blockDom = getBlockDomElement(view, activeBlock); + if (!blockDom) { + console.warn('[blockImage] Cannot create block DOM'); + (targetDom as HTMLElement).style.cursor = prevCursor; + return false; + } + + // 将节点挂到文档外层,确保样式可用 + const mount = document.createElement('div'); + mount.style.cssText = 'position: fixed; left: -10000px; top: -10000px; pointer-events: none; z-index: -1;'; + mount.appendChild(blockDom); + document.body.appendChild(mount); + + try { + const canvas = await snapdom.toCanvas(blockDom, { + scale: 2, + dpr: window.devicePixelRatio || 1, + cache: 'auto', + backgroundColor: getComputedStyle(blockDom).backgroundColor, + outerShadows: false, + }); + + const blob = await canvasToPngBlob(canvas); + await writeImageToClipboard(blob); + return true; + } catch (error) { + console.error('[blockImage] Failed to copy block image:', error); + return false; + } finally { + mount.remove(); + (targetDom as HTMLElement).style.cursor = prevCursor; + } +} + +/** + * 命令:复制当前块为图片 + */ +export const copyBlockImageCommand: Command = view => { + void copyActiveBlockAsImage(view); + return true; +}; + +export const blockImageEnabledFacet = Facet.define({ + combine: values => values.some(Boolean), +}); + +/** + * BlockImage 扩展入口 + */ +export function createBlockImageExtension(): Extension { + return [ + blockImageEnabledFacet.of(true), + ]; +} + +export default createBlockImageExtension; diff --git a/frontend/src/views/editor/extensions/codeblock/decorations.ts b/frontend/src/views/editor/extensions/codeblock/decorations.ts index 174f2c1..4f66d87 100644 --- a/frontend/src/views/editor/extensions/codeblock/decorations.ts +++ b/frontend/src/views/editor/extensions/codeblock/decorations.ts @@ -7,6 +7,9 @@ import { StateField, RangeSetBuilder, EditorState, Transaction } from "@codemirr import { blockState } from "./state"; import { codeBlockEvent, USER_EVENTS } from "./annotation"; +// IME 输入状态 +let isComposing = false; + /** * 块开始装饰组件 */ @@ -222,9 +225,10 @@ const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr: any) /** * 防止选择在第一个块之前 - * 使用 transactionFilter 来确保选择不会在第一个块之前 */ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr: any) => { + if (isComposing) return tr; + if (tr.annotation(codeBlockEvent)) { return tr; } @@ -256,6 +260,24 @@ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr: a return tr; }); +// IME 状态同步 +const imeStateSynchronizer = ViewPlugin.fromClass( + class { + constructor(view: EditorView) { + isComposing = view.composing || view.compositionStarted; + } + + update(update: any) { + const view = update.view as EditorView; + isComposing = view.composing || view.compositionStarted; + } + + destroy() { + isComposing = false; + } + } +); + /** * 获取块装饰扩展 - 简化选项 */ @@ -271,6 +293,7 @@ export function getBlockDecorationExtensions(options: { atomicNoteBlock, preventFirstBlockFromBeingDeleted, preventSelectionBeforeFirstBlock, + imeStateSynchronizer, ]; if (showBackground) { diff --git a/frontend/src/views/editor/extensions/contextMenu/index.ts b/frontend/src/views/editor/extensions/contextMenu/index.ts index 8ba50e9..11aa646 100644 --- a/frontend/src/views/editor/extensions/contextMenu/index.ts +++ b/frontend/src/views/editor/extensions/contextMenu/index.ts @@ -9,6 +9,7 @@ import {useSystemStore} from '@/stores/systemStore'; import {showContextMenu} from './manager'; import type {MenuSchemaNode} from './menuSchema'; import {buildRegisteredMenu, createMenuContext, registerMenuNodes} from './menuSchema'; +import {blockImageMenuNodes} from '../blockImage/contextMenu'; function t(key: string): string { @@ -105,7 +106,7 @@ let builtinMenuRegistered = false; function ensureBuiltinMenuRegistered(): void { if (builtinMenuRegistered) return; - registerMenuNodes(builtinMenuNodes()); + registerMenuNodes([...builtinMenuNodes(), ...blockImageMenuNodes]); builtinMenuRegistered = true; } diff --git a/frontend/src/views/editor/keymap/commands.ts b/frontend/src/views/editor/keymap/commands.ts index 1dcf17e..48d0cc1 100644 --- a/frontend/src/views/editor/keymap/commands.ts +++ b/frontend/src/views/editor/keymap/commands.ts @@ -75,6 +75,7 @@ import { import {foldAll, foldCode, unfoldAll, unfoldCode} from '@codemirror/language'; import i18n from '@/i18n'; import {KeyBindingName} from '@/../bindings/voidraft/internal/models/models'; +import {copyBlockImageCommand} from '../extensions/blockImage'; const defaultBlockExtensionOptions = { defaultBlockToken: 'text', @@ -170,6 +171,10 @@ export const commands: Record handler: pasteCommand, descriptionKey: 'keybindings.commands.blockPaste' }, + [KeyBindingName.CopyBlockImage]: { + handler: copyBlockImageCommand, + descriptionKey: 'keybindings.commands.copyBlockImage' + }, [KeyBindingName.HistoryUndo]: { handler: undo, descriptionKey: 'keybindings.commands.historyUndo' diff --git a/frontend/src/views/editor/manager/extensions.ts b/frontend/src/views/editor/manager/extensions.ts index 5bc7282..94808f6 100644 --- a/frontend/src/views/editor/manager/extensions.ts +++ b/frontend/src/views/editor/manager/extensions.ts @@ -15,6 +15,7 @@ import {highlightActiveLineGutter, highlightWhitespace, highlightTrailingWhitesp import createEditorContextMenu from '../extensions/contextMenu'; import {blockLineNumbers} from '../extensions/codeblock'; import {createHttpClientExtension} from '../extensions/httpclient'; +import {createBlockImageExtension} from '../extensions/blockImage'; import {ExtensionName} from '@/../bindings/voidraft/internal/models/models'; type ExtensionEntry = { @@ -104,6 +105,11 @@ const EXTENSION_REGISTRY: Record = { definition: defineExtension(() => createHttpClientExtension()), displayNameKey: 'extensions.httpClient.name', descriptionKey: 'extensions.httpClient.description' + }, + [ExtensionName.BlockImage]: { + definition: defineExtension(() => createBlockImageExtension()), + displayNameKey: 'extensions.blockImage.name', + descriptionKey: 'extensions.blockImage.description' } }; diff --git a/go.mod b/go.mod index eb9e198..c056522 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,16 @@ go 1.25 require ( entgo.io/ent v0.14.5 - github.com/creativeprojects/go-selfupdate v1.5.1 + github.com/creativeprojects/go-selfupdate v1.5.2 github.com/go-git/go-git/v5 v5.16.4 github.com/google/uuid v1.6.0 github.com/knadh/koanf/parsers/json v1.0.0 - github.com/knadh/koanf/providers/file v1.2.0 + github.com/knadh/koanf/providers/file v1.2.1 github.com/knadh/koanf/providers/structs v1.0.0 github.com/knadh/koanf/v2 v2.3.0 github.com/mattn/go-sqlite3 v1.14.32 github.com/stretchr/testify v1.11.1 - github.com/wailsapp/wails/v3 v3.0.0-alpha.48 + github.com/wailsapp/wails/v3 v3.0.0-alpha.51 golang.org/x/net v0.48.0 golang.org/x/sys v0.39.0 golang.org/x/text v0.32.0 @@ -34,7 +34,7 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.2 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect @@ -48,10 +48,10 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/inflect v0.21.5 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/godbus/dbus/v5 v5.2.0 // indirect + github.com/godbus/dbus/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/go-github/v74 v74.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect @@ -80,10 +80,10 @@ require ( github.com/ulikunitz/xz v0.5.15 // indirect github.com/wailsapp/go-webview2 v1.0.23 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/xanzy/go-gitlab v0.115.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zclconf/go-cty v1.17.0 // indirect - github.com/zclconf/go-cty-yaml v1.1.0 // indirect + github.com/zclconf/go-cty-yaml v1.2.0 // indirect + gitlab.com/gitlab-org/api/client-go v1.10.0 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect golang.org/x/image v0.34.0 // indirect diff --git a/go.sum b/go.sum index 4cc35e6..cc3c60a 100644 --- a/go.sum +++ b/go.sum @@ -33,10 +33,10 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/creativeprojects/go-selfupdate v1.5.1 h1:fuyEGFFfqcC8SxDGolcEPYPLXGQ9Mcrc5uRyRG2Mqnk= -github.com/creativeprojects/go-selfupdate v1.5.1/go.mod h1:2uY75rP8z/D/PBuDn6mlBnzu+ysEmwOJfcgF8np0JIM= +github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= +github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/creativeprojects/go-selfupdate v1.5.2 h1:3KR3JLrq70oplb9yZzbmJ89qRP78D1AN/9u+l3k0LJ4= +github.com/creativeprojects/go-selfupdate v1.5.2/go.mod h1:BCOuwIl1dRRCmPNRPH0amULeZqayhKyY2mH/h4va7Dk= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -76,17 +76,15 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8= -github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= +github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= -github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM= +github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -113,8 +111,8 @@ github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpb github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/json v1.0.0 h1:1pVR1JhMwbqSg5ICzU+surJmeBbdT4bQm7jjgnA+f8o= github.com/knadh/koanf/parsers/json v1.0.0/go.mod h1:zb5WtibRdpxSoSJfXysqGbVxvbszdlroWDHGdDkkEYU= -github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U= -github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= +github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM= +github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4= github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= @@ -180,18 +178,18 @@ github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+ github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v3 v3.0.0-alpha.48 h1:m22PcankYJI/lKbv7NnNekxsEJYPbvIUnvHvH4WD1xQ= -github.com/wailsapp/wails/v3 v3.0.0-alpha.48/go.mod h1:yaz8baG0+YzoiN8J6osn0wKiEi0iUux0ZU5NsZFu6OQ= -github.com/xanzy/go-gitlab v0.115.0 h1:6DmtItNcVe+At/liXSgfE/DZNZrGfalQmBRmOcJjOn8= -github.com/xanzy/go-gitlab v0.115.0/go.mod h1:5XCDtM7AM6WMKmfDdOiEpyRWUqui2iS9ILfvCZ2gJ5M= +github.com/wailsapp/wails/v3 v3.0.0-alpha.51 h1:n8KT0H4lvtWld9tMIiHVX4nrR0wEMT2zy5hM/R6luMU= +github.com/wailsapp/wails/v3 v3.0.0-alpha.51/go.mod h1:yaz8baG0+YzoiN8J6osn0wKiEi0iUux0ZU5NsZFu6OQ= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= -github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCTOi1M= +github.com/zclconf/go-cty-yaml v1.2.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +gitlab.com/gitlab-org/api/client-go v1.10.0 h1:VlB9gXQdG6w643lH53VduUHVnCWQG5Ty86VbXnyi70A= +gitlab.com/gitlab-org/api/client-go v1.10.0/go.mod h1:U3QKvjbT1J1FrgLsA7w/XlhoBIendUqB4o3/Ht3UhEQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= @@ -204,14 +202,12 @@ golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= @@ -243,7 +239,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/models/extension.go b/internal/models/extension.go index 372b968..fb82116 100644 --- a/internal/models/extension.go +++ b/internal/models/extension.go @@ -27,6 +27,7 @@ const ( ContextMenu ExtensionName = "contextMenu" // 上下文菜单 Search ExtensionName = "search" // 搜索功能 HttpClient ExtensionName = "httpClient" // HTTP 客户端 + BlockImage ExtensionName = "blockImage" // 代码块导出图片 ) // NewDefaultExtensions 创建默认扩展配置 @@ -106,5 +107,10 @@ func NewDefaultExtensions() []Extension { Enabled: true, Config: ExtensionConfig{}, }, + { + Name: BlockImage, + Enabled: true, + Config: ExtensionConfig{}, + }, } } diff --git a/internal/models/key_binding.go b/internal/models/key_binding.go index 0e9b8bd..48f956c 100644 --- a/internal/models/key_binding.go +++ b/internal/models/key_binding.go @@ -103,6 +103,7 @@ const ( HistoryRedo KeyBindingName = "historyRedo" // 重做 HistoryUndoSelection KeyBindingName = "historyUndoSelection" // 撤销选择 HistoryRedoSelection KeyBindingName = "historyRedoSelection" // 重做选择 + CopyBlockImage KeyBindingName = "copyBlockImage" // 复制块为图片 ) const defaultExtension = "editor" @@ -282,6 +283,14 @@ func NewDefaultKeyBindings() []KeyBinding { Enabled: true, PreventDefault: true, }, + { + Name: CopyBlockImage, + Type: Standard, + Key: "Mod-Shift-Alt-C", + Extension: BlockImage, + Enabled: true, + PreventDefault: true, + }, // 代码折叠相关 {