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..1747b6776 100644
--- a/src/components/Controls/Attachment.vue
+++ b/src/components/Controls/Attachment.vue
@@ -33,6 +33,14 @@
/>
+
+
+