From 8de91f74fe862698b5ee080a271dc83cde74300c Mon Sep 17 00:00:00 2001 From: Mildred Ki'Lya Date: Wed, 20 Nov 2024 19:43:14 +0100 Subject: [PATCH 1/2] ux: allow to view attached files directly --- main.ts | 28 +++++++++---- main/preload.ts | 4 ++ main/registerIpcMainMessageListeners.ts | 55 ++++++++++++++++++++++++- src/components/Controls/Attachment.vue | 20 +++++++++ utils/messages.ts | 1 + 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/main.ts b/main.ts index 23140d29a..1f903462c 100644 --- a/main.ts +++ b/main.ts @@ -6,6 +6,7 @@ require('source-map-support').install({ import { emitMainProcessError } from 'backend/helpers'; import { + shell, app, BrowserWindow, BrowserWindowConstructorOptions, @@ -22,6 +23,19 @@ import registerIpcMainActionListeners from './main/registerIpcMainActionListener import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners'; import registerProcessListeners from './main/registerProcessListeners'; +const EXTENSIONS = { + '.js': 'text/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.svg': 'image/svg+xml', + '.json': 'application/json', + '.pdf': 'application/pdf', +}; + +const MIME_TYPES = Object.fromEntries( + Object.entries(EXTENSIONS).map(([ext, mime]) => [mime, ext]) +); + export class Main { title = 'Frappe Books'; icon: string; @@ -119,6 +133,11 @@ export class Main { const options = this.getOptions(); this.mainWindow = new BrowserWindow(options); + this.mainWindow.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url); + return { action: 'deny' }; + }); + if (this.isDevelopment) { this.setViteServerURL(); } else { @@ -189,14 +208,7 @@ function bufferProtocolCallback( fs.readFile(filePath, (_, data) => { const extension = path.extname(filePath).toLowerCase(); - const mimeType = - { - '.js': 'text/javascript', - '.css': 'text/css', - '.html': 'text/html', - '.svg': 'image/svg+xml', - '.json': 'application/json', - }[extension] ?? ''; + const mimeType = EXTENSIONS[extension] ?? ''; callback({ mimeType, data }); }); diff --git a/main/preload.ts b/main/preload.ts index 0e4892842..30b96bb22 100644 --- a/main/preload.ts +++ b/main/preload.ts @@ -117,6 +117,10 @@ const ipc = { ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, link); }, + openDataURL(link: string, filename: string) { + ipcRenderer.send(IPC_MESSAGES.OPEN_DATA_URL, { link, filename }); + }, + async deleteFile(filePath: string) { return (await ipcRenderer.invoke( IPC_ACTIONS.DELETE_FILE, diff --git a/main/registerIpcMainMessageListeners.ts b/main/registerIpcMainMessageListeners.ts index 909bc38cf..a298088c6 100644 --- a/main/registerIpcMainMessageListeners.ts +++ b/main/registerIpcMainMessageListeners.ts @@ -1,8 +1,42 @@ -import { ipcMain, Menu, shell } from 'electron'; +import { ipcMain, Menu, shell, app } from 'electron'; +import fs from 'fs'; +import path from 'path'; import { Main } from '../main'; import { IPC_MESSAGES } from '../utils/messages'; import { emitMainProcessError } from 'backend/helpers'; +function parseDataURL(url) { + const regex = + /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i; + + const parts = url.trim().match(regex); + if (!parts) return null; + + const parsed = {}; + + parsed.mediaType = (parts[1] || 'text/plain;charset=us-ascii').toLowerCase(); + + const mediaTypeParts = parsed.mediaType + .split(';') + .map((x) => x.toLowerCase()); + parsed.contentType = mediaTypeParts[0]; + + mediaTypeParts.slice(1).forEach((attribute) => { + const p = attribute.split('='); + parsed[p[0]] = p[1]; + }); + + parsed.base64 = !!parts[parts.length - 2]; + parsed.data = parts[parts.length - 1] || ''; + parsed.encoding = parsed.base64 ? 'base64' : 'utf8'; + parsed.buffer = Buffer.from( + parsed.base64 ? parsed.data : decodeURIComponent(parsed.data), + parsed.encoding + ); + + return parsed; +} + export default function registerIpcMainMessageListeners(main: Main) { ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => { if (event.sender === null) { @@ -49,6 +83,25 @@ export default function registerIpcMainMessageListeners(main: Main) { shell.openExternal(link).catch((err) => emitMainProcessError(err)); }); + ipcMain.on( + IPC_MESSAGES.OPEN_DATA_URL, + (_, { link, filename }: { link: string; filename: string }) => { + const data = parseDataURL(link); + if (data) { + const s = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const temp = Array.apply(null, Array(16)) + .map(() => { + return s.charAt(Math.floor(Math.random() * s.length)); + }) + .join(''); + const filepath = path.join(app.getPath('temp'), temp + ' ' + filename); + fs.writeFileSync(filepath, data.buffer); + shell.openPath(filepath); + } + } + ); + ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => { return shell.showItemInFolder(filePath); }); diff --git a/src/components/Controls/Attachment.vue b/src/components/Controls/Attachment.vue index 06d355a2a..58219cecf 100644 --- a/src/components/Controls/Attachment.vue +++ b/src/components/Controls/Attachment.vue @@ -33,6 +33,14 @@ /> + + +