diff --git a/electron/electron.js b/electron/electron.js index 950793217..5eedd0a14 100644 --- a/electron/electron.js +++ b/electron/electron.js @@ -1,7 +1,7 @@ const fs = require('fs') const os = require("os"); const path = require('path') -const {app, BrowserWindow, ipcMain, dialog, clipboard, nativeImage, shell, Tray, Menu, globalShortcut, Notification, BrowserView, nativeTheme} = require('electron') +const {app, BrowserWindow, ipcMain, dialog, clipboard, nativeImage, shell, Tray, Menu, globalShortcut, Notification, BrowserView, nativeTheme, protocol} = require('electron') const {autoUpdater} = require("electron-updater") const log = require("electron-log"); const electronConf = require('electron-config') @@ -52,6 +52,70 @@ if (fs.existsSync(devloadCachePath)) { devloadUrl = fs.readFileSync(devloadCachePath, 'utf8') } +// 在最开始就注册协议为特权协议 +protocol.registerSchemesAsPrivileged([ + { + scheme: 'dootask-resources', + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + corsEnabled: true + } + } +]) + +/** + * 创建协议 + */ +function createProtocol() { + protocol.handle('dootask-resources', async (request) => { + const url = request.url.replace(/^dootask-resources:\/\//, '') + + if (url.includes('..')) { + return new Response('Access Denied', { status: 403 }) + } + + try { + const filePath = path.join(__dirname, devloadUrl ? '..' : '.', url) + + if (!fs.existsSync(filePath)) { + return new Response('Not Found', { status: 404 }) + } + + const data = await fs.promises.readFile(filePath) + const mimeType = getMimeType(filePath) + + return new Response(data, { + headers: { + 'Content-Type': mimeType + } + }) + } catch (error) { + console.error('Protocol handler error:', error) + return new Response('Internal Error', { status: 500 }) + } + }) +} + +/** + * MIME类型判断 + * @param filePath + * @returns {*|string} + */ +function getMimeType(filePath) { + const ext = path.extname(filePath).toLowerCase() + const mimeTypes = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.webp': 'image/webp' + } + return mimeTypes[ext] || 'application/octet-stream' +} + /** * 创建主窗口 */ @@ -596,6 +660,8 @@ if (!getTheLock) { isReady = true // SameSite utils.useCookie() + // 创建协议 + createProtocol() // 创建主窗口 createMainWindow() // 创建托盘 diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 42472db11..ef8218fb6 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -62,6 +62,7 @@ import TagInput from './components/TagInput.vue' import TableAction from './components/TableAction.vue' import QuickEdit from './components/QuickEdit.vue' import UserAvatar from './components/UserAvatar' +import Imgs from './components/Replace/Imgs' import ImgView from './components/ImgView.vue' import Scrollbar from './components/Scrollbar' @@ -72,6 +73,7 @@ Vue.component('TagInput', TagInput) Vue.component('TableAction', TableAction); Vue.component('QuickEdit', QuickEdit); Vue.component('UserAvatar', UserAvatar); +Vue.component('Imgs', Imgs); Vue.component('ImgView', ImgView); Vue.component('Scrollbar', Scrollbar); diff --git a/resources/assets/js/components/Replace/Imgs.vue b/resources/assets/js/components/Replace/Imgs.vue new file mode 100644 index 000000000..850cb9c23 --- /dev/null +++ b/resources/assets/js/components/Replace/Imgs.vue @@ -0,0 +1,26 @@ + + + + diff --git a/resources/assets/js/components/Replace/utils.js b/resources/assets/js/components/Replace/utils.js new file mode 100644 index 000000000..78e6524f5 --- /dev/null +++ b/resources/assets/js/components/Replace/utils.js @@ -0,0 +1,62 @@ +const publicImageResources = (() => { + let initialized = false + let appPreUrl = null + let serverPreUrl = null + let urlRegex = null + + // 将 escapeRegExp 移到闭包内部 + const escapeRegExp = (string) => { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + + // 初始化函数 + const initialize = () => { + if (initialized) return + + // 设置应用前缀URL + if ($A.isEEUiApp) { + appPreUrl = $A.eeuiAppRewriteUrl('../public/') + } else if ($A.Electron) { + appPreUrl = "dootask-resources://public/" + } + + // 如果没有特殊前缀,提前返回 + if (!appPreUrl) return + + // 获取服务器URL + serverPreUrl = $A.mainUrl() + const escapedPreUrl = escapeRegExp(serverPreUrl) + + // 固定的模式 + const patterns = [ + 'images/', + // 可以继续添加其他路径... + ] + + // 计算转义后的模式 + const escapedPatterns = patterns.map(pattern => escapeRegExp(pattern)) + + // 编译正则表达式 + urlRegex = new RegExp(`${escapedPreUrl}(${escapedPatterns.join('|')})`) + + initialized = true + } + + // 返回实际的处理函数 + return (url) => { + // 第一次调用时初始化 + initialize() + + // 如果没有特殊前缀,直接返回原URL + if (!appPreUrl) { + return url + } + + if (urlRegex.test(url)) { + return url.replace(serverPreUrl, appPreUrl) + } + return url + } +})() + +export {publicImageResources} diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js index e27b40b17..284ff878b 100755 --- a/resources/assets/js/functions/web.js +++ b/resources/assets/js/functions/web.js @@ -1,4 +1,5 @@ import {MarkdownPreview} from "../store/markdown"; +import {publicImageResources} from "../components/Replace/utils"; /** * 页面专用 @@ -349,7 +350,7 @@ import {MarkdownPreview} from "../store/markdown"; const value = item.original .replace(/\s+width=/, ` original-width=`) .replace(/\s+height=/, ` original-height=`) - .replace(/\s+src=(["'])([^'"]*)\1/i, ` style="width:${data.width}px;height:${data.height}px" src="${data.src}"`) + .replace(/\s+src=(["'])([^'"]*)\1/i, ` style="width:${data.width}px;height:${data.height}px" src="${publicImageResources(data.src)}"`) text = text.replace(item.original, value) } else { text = text.replace(item.original, `
${item.original}
`); diff --git a/resources/assets/js/pages/manage/components/ChatInput/emoji.vue b/resources/assets/js/pages/manage/components/ChatInput/emoji.vue index 6edb8d64b..1188f4794 100644 --- a/resources/assets/js/pages/manage/components/ChatInput/emoji.vue +++ b/resources/assets/js/pages/manage/components/ChatInput/emoji.vue @@ -17,7 +17,7 @@ @@ -32,7 +32,7 @@ 😀
  • - +