support locally downloading attachments
This commit is contained in:
Vendored
+3
@@ -44,6 +44,9 @@ declare global {
|
|||||||
fetchMetadata: (url: string) => Promise<LinkMetadata | null>;
|
fetchMetadata: (url: string) => Promise<LinkMetadata | null>;
|
||||||
openExternal: (url: string) => Promise<void>;
|
openExternal: (url: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
electronAttachment: {
|
||||||
|
download: (url: string, filename?: string) => void;
|
||||||
|
};
|
||||||
electronApp: {
|
electronApp: {
|
||||||
setDockBadge: (count: number) => void;
|
setDockBadge: (count: number) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,15 +49,6 @@ function formatSize(bytes?: number): string | null {
|
|||||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFromUrl(url: string, filename: string) {
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AttachmentLightbox({
|
export function AttachmentLightbox({
|
||||||
items,
|
items,
|
||||||
openIndex,
|
openIndex,
|
||||||
@@ -104,7 +95,7 @@ export function AttachmentLightbox({
|
|||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
if (!current || !url || current.source.kind !== "remote") return;
|
if (!current || !url || current.source.kind !== "remote") return;
|
||||||
downloadFromUrl(url, current.filename);
|
window.electronAttachment.download(url, current.filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
|
|||||||
@@ -65,12 +65,7 @@ function ImageAttachment({
|
|||||||
|
|
||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const a = document.createElement("a");
|
window.electronAttachment.download(url, particle.properties.filename);
|
||||||
a.href = url;
|
|
||||||
a.download = particle.properties.filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,12 +113,7 @@ function FileAttachment({
|
|||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
const a = document.createElement("a");
|
window.electronAttachment.download(url, particle.properties.filename);
|
||||||
a.href = url;
|
|
||||||
a.download = particle.properties.filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -454,6 +454,28 @@ ipcMain.handle('link:open-external', async (_event, url: string) => {
|
|||||||
await shell.openExternal(url);
|
await shell.openExternal(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Attachment download ---
|
||||||
|
// Triggers a native download with save-as dialog. Cross-origin safe — unlike
|
||||||
|
// the web `<a download>` hack, which is ignored for cross-origin URLs.
|
||||||
|
ipcMain.on(
|
||||||
|
'attachment:download',
|
||||||
|
(event, payload: { url: string; filename?: string }) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!win) return;
|
||||||
|
const { url, filename } = payload ?? {};
|
||||||
|
if (typeof url !== 'string') return;
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://')) return;
|
||||||
|
|
||||||
|
const dlSession = win.webContents.session;
|
||||||
|
const onWillDownload = (_e: Electron.Event, item: Electron.DownloadItem) => {
|
||||||
|
if (filename) item.setSaveDialogOptions({ defaultPath: filename });
|
||||||
|
dlSession.removeListener('will-download', onWillDownload);
|
||||||
|
};
|
||||||
|
dlSession.on('will-download', onWillDownload);
|
||||||
|
win.webContents.downloadURL(url);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ contextBridge.exposeInMainWorld('electronLink', {
|
|||||||
openExternal: (url: string) => ipcRenderer.invoke('link:open-external', url),
|
openExternal: (url: string) => ipcRenderer.invoke('link:open-external', url),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAttachment', {
|
||||||
|
download: (url: string, filename?: string) =>
|
||||||
|
ipcRenderer.send('attachment:download', { url, filename }),
|
||||||
|
});
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronApp', {
|
contextBridge.exposeInMainWorld('electronApp', {
|
||||||
setDockBadge: (count: number) => ipcRenderer.send('app:set-dock-badge', count),
|
setDockBadge: (count: number) => ipcRenderer.send('app:set-dock-badge', count),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user