diff --git a/electron/electron-menu.js b/electron/electron-menu.js index 1cc656588..4a64a9307 100644 --- a/electron/electron-menu.js +++ b/electron/electron-menu.js @@ -9,6 +9,7 @@ const { const fs = require('fs') const url = require('url') const request = require("request"); +const utils = require('./utils') const MAILTO_PREFIX = "mailto:"; @@ -52,9 +53,30 @@ const electronMenu = { }, async saveImageAs(url, params) { - const targetFileName = params.suggestedFilename || params.altText || "image.png"; + let extension = ''; + if (utils.isProtocolResource(url)) { + extension = utils.protocolResourcePath(url).split('.').pop().toLowerCase(); + } else { + const urlExtension = url.split('.').pop().split(/[#?]/)[0].toLowerCase(); + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(urlExtension)) { + extension = urlExtension; + } + } + + if (!extension) { + extension = 'png'; + } + + let targetFileName = params.suggestedFilename || params.altText || "image"; + if (!targetFileName.toLowerCase().endsWith('.' + extension)) { + targetFileName = targetFileName.replace(/\.[^/.]+$/, '') + '.' + extension; + } + const {filePath} = await dialog.showSaveDialog({ defaultPath: targetFileName, + filters: [ + { name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] } + ] }); if (!filePath) return; // user cancelled dialog @@ -62,6 +84,8 @@ const electronMenu = { try { if (electronMenu.isBlobOrDataUrl(url)) { await electronMenu.writeNativeImage(filePath, nativeImage.createFromDataURL(url)); + } else if (utils.isProtocolResource(url)) { + await fs.promises.copyFile(utils.protocolResourcePath(url), filePath); } else { const writeStream = fs.createWriteStream(filePath) const readStream = request(url) @@ -98,7 +122,7 @@ const electronMenu = { const url = params.linkURL || params.srcURL; const popupMenu = new Menu(); - if (!electronMenu.isBlobOrDataUrl(url)) { + if (!electronMenu.isBlobOrDataUrl(url) && !utils.isProtocolResource(url)) { popupMenu.append( new MenuItem({ label: electronMenu.language.openInBrowser, @@ -144,7 +168,7 @@ const electronMenu = { }, }), ); - } else { + } else if (!utils.isProtocolResource(url)) { popupMenu.append( new MenuItem({ label: params.hasImageContents ? electronMenu.language.copyImageAddress : electronMenu.language.copyLinkAddress, diff --git a/electron/electron.js b/electron/electron.js index 61eeb8676..0391530f4 100644 --- a/electron/electron.js +++ b/electron/electron.js @@ -76,21 +76,19 @@ protocol.registerSchemesAsPrivileged([ */ function createProtocol() { protocol.handle('dootask-resources', async (request) => { - const url = request.url.replace(/^dootask-resources:\/\//, '') + const url = utils.protocolResourcePath(request.url) if (url.includes('..')) { return new Response('Access Denied', { status: 403 }) } try { - const filePath = path.join(__dirname, devloadUrl ? '..' : '.', url) - - if (!fs.existsSync(filePath)) { + if (!fs.existsSync(url)) { return new Response('Not Found', { status: 404 }) } - const data = await fs.promises.readFile(filePath) - const mimeType = utils.getMimeType(filePath) + const data = await fs.promises.readFile(url) + const mimeType = utils.getMimeType(url) return new Response(data, { headers: { diff --git a/electron/utils.js b/electron/utils.js index 79595b157..082eaf89c 100644 --- a/electron/utils.js +++ b/electron/utils.js @@ -606,6 +606,39 @@ const utils = { }); }); }, + + /** + * 是否协议资源 + * @param {string} url + * @returns {boolean} + */ + isProtocolResource(url) { + return url.startsWith('dootask-resources://') + }, + + /** + * 协议资源路径 + * @param {string} url + * @returns {string} + */ + protocolResourcePath(url) { + if (!utils.isProtocolResource(url)) { + return url + } + + let p0 = url.replace(/^dootask-resources:\/\//, '') + + const p1 = path.join(__dirname, '.', p0) + if (fs.existsSync(p1)) { + return p1 + } + + const p2 = path.join(__dirname, '..', p0) + if (fs.existsSync(p2)) { + return p2 + } + return url + } } module.exports = utils; diff --git a/resources/assets/js/components/Replace/utils.js b/resources/assets/js/components/Replace/utils.js index 37d4a2f76..a3e84a9fa 100644 --- a/resources/assets/js/components/Replace/utils.js +++ b/resources/assets/js/components/Replace/utils.js @@ -62,4 +62,11 @@ const publicImageResources = (() => { } })() -export {publicImageResources} +const isPublicResources = (url) => { + return url && ( + url.startsWith('file://') || + url.startsWith('dootask-resources://') + ) +} + +export {publicImageResources, isPublicResources} diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index d16e5b31b..eff26a6e6 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -673,6 +673,7 @@ import DialogGroupVote from "./DialogGroupVote"; import DialogComplaint from "./DialogComplaint"; import touchclick from "../../../directives/touchclick"; import {languageList} from "../../../language"; +import {isPublicResources} from "../../../components/Replace/utils"; export default { name: "DialogWrapper", @@ -2879,7 +2880,7 @@ export default { }) } if (msgData.type === 'text') { - if (event.target.nodeName === 'IMG') { + if (event.target.nodeName === 'IMG' && !isPublicResources(event.target.currentSrc)) { this.operateCopys.push({ type: 'imagedown', icon: '', diff --git a/resources/mobile b/resources/mobile index c5f85805d..9804c916c 160000 --- a/resources/mobile +++ b/resources/mobile @@ -1 +1 @@ -Subproject commit c5f85805ddd9f9cc36924b018a1fd779d70025fa +Subproject commit 9804c916c25dd6a413b489096675286548e36f40