Files
Trilium/apps/client/src/services/open.ts

203 lines
7.1 KiB
TypeScript
Raw Normal View History

import utils from "./utils.js";
import server from "./server.js";
2025-01-09 18:07:02 +02:00
type ExecFunction = (command: string, cb: (err: string, stdout: string, stderror: string) => void) => void;
2024-08-04 13:27:23 +03:00
interface TmpResponse {
tmpFilePath: string;
}
function checkType(type: string) {
2025-01-09 18:07:02 +02:00
if (type !== "notes" && type !== "attachments") {
2023-05-03 10:23:20 +02:00
throw new Error(`Unrecognized type '${type}', should be 'notes' or 'attachments'`);
}
}
2024-08-04 13:27:23 +03:00
function getFileUrl(type: string, noteId?: string) {
2023-05-03 10:23:20 +02:00
checkType(type);
return getUrlForDownload(`api/${type}/${noteId}/download`);
}
2023-05-03 10:23:20 +02:00
2024-08-04 13:27:23 +03:00
function getOpenFileUrl(type: string, noteId: string) {
2023-05-03 10:23:20 +02:00
checkType(type);
return getUrlForDownload(`api/${type}/${noteId}/open`);
}
2024-08-04 13:27:23 +03:00
function download(url: string) {
if (utils.isElectron()) {
2025-01-09 18:07:02 +02:00
const remote = utils.dynamicRequire("@electron/remote");
remote.getCurrentWebContents().downloadURL(url);
} else {
window.location.href = url;
}
}
2024-08-04 13:27:23 +03:00
function downloadFileNote(noteId: string) {
2025-01-09 18:07:02 +02:00
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
download(url);
}
2024-08-04 13:27:23 +03:00
function downloadAttachment(attachmentId: string) {
2025-01-09 18:07:02 +02:00
const url = `${getFileUrl("attachments", attachmentId)}?${Date.now()}`; // don't use cache
2023-05-03 10:23:20 +02:00
download(url);
}
2024-08-04 13:27:23 +03:00
async function openCustom(type: string, entityId: string, mime: string) {
2023-09-07 06:33:51 +00:00
checkType(type);
2023-05-17 23:57:32 +02:00
if (!utils.isElectron() || utils.isMac()) {
return;
}
2024-08-04 13:27:23 +03:00
const resp = await server.post<TmpResponse>(`${type}/${entityId}/save-to-tmp-dir`);
2023-05-17 23:57:32 +02:00
let filePath = resp.tmpFilePath;
2025-01-09 18:07:02 +02:00
const exec = utils.dynamicRequire("child_process").exec as ExecFunction;
2023-05-17 23:57:32 +02:00
const platform = process.platform;
2025-01-09 18:07:02 +02:00
if (platform === "linux") {
2023-05-17 23:57:32 +02:00
// we don't know which terminal is available, try in succession
2025-01-09 18:07:02 +02:00
const terminals = ["x-terminal-emulator", "gnome-terminal", "konsole", "xterm", "xfce4-terminal", "mate-terminal", "rxvt", "terminator", "terminology"];
2024-08-04 13:27:23 +03:00
const openFileWithTerminal = (terminal: string) => {
2023-05-17 23:57:32 +02:00
const command = `${terminal} -e 'mimeopen -d "${filePath}"'`;
console.log(`Open Note custom: ${command} `);
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Open Note custom: Failed to open file with ${terminal}: ${error}`);
searchTerminal(terminals.indexOf(terminal) + 1);
} else {
console.log(`Open Note custom: File opened with ${terminal}: ${stdout}`);
}
});
2023-05-16 11:57:28 +00:00
};
2023-05-17 23:57:32 +02:00
2024-08-04 13:27:23 +03:00
const searchTerminal = (index: number) => {
2023-05-17 23:57:32 +02:00
const terminal = terminals[index];
if (!terminal) {
2025-01-09 18:07:02 +02:00
console.error("Open Note custom: No terminal found!");
// TODO: Remove {url: true} if not needed.
2025-01-09 18:07:02 +02:00
(open as any)(getFileUrl(type, entityId), { url: true });
2023-05-17 23:57:32 +02:00
return;
2023-05-16 11:57:28 +00:00
}
2023-05-17 23:57:32 +02:00
exec(`which ${terminal}`, (error, stdout, stderr) => {
if (stdout.trim()) {
openFileWithTerminal(terminal);
} else {
searchTerminal(index + 1);
}
});
2023-05-16 11:57:28 +00:00
};
searchTerminal(0);
2025-01-09 18:07:02 +02:00
} else if (platform === "win32") {
2023-05-16 11:57:28 +00:00
if (filePath.indexOf("/") !== -1) {
2023-05-17 23:57:32 +02:00
// Note that the path separator must be \ instead of /
filePath = filePath.replace(/\//g, "\\");
2023-05-16 11:57:28 +00:00
}
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ` + filePath;
exec(command, (err, stdout, stderr) => {
2023-05-17 23:57:32 +02:00
if (err) {
console.error("Open Note custom: ", err);
2024-08-04 13:27:23 +03:00
// TODO: This appears to be broken, since getFileUrl expects two arguments, with the first one being the type.
// Also don't know why {url: true} is passed.
2025-01-09 18:07:02 +02:00
(open as any)(getFileUrl(entityId), { url: true });
2023-05-17 23:57:32 +02:00
return;
}
2023-05-16 11:57:28 +00:00
});
2023-05-17 23:57:32 +02:00
} else {
2023-05-16 11:57:28 +00:00
console.log('Currently "Open Note custom" only supports linux and windows systems');
2024-08-04 13:27:23 +03:00
// TODO: This appears to be broken, since getFileUrl expects two arguments, with the first one being the type.
// Also don't know why {url: true} is passed.
2025-01-09 18:07:02 +02:00
(open as any)(getFileUrl(entityId), { url: true });
2023-05-16 11:57:28 +00:00
}
2023-05-17 23:57:32 +02:00
}
2023-05-16 11:57:28 +00:00
2025-01-09 18:07:02 +02:00
const openNoteCustom = async (noteId: string, mime: string) => await openCustom("notes", noteId, mime);
const openAttachmentCustom = async (attachmentId: string, mime: string) => await openCustom("attachments", attachmentId, mime);
2023-09-07 06:33:51 +00:00
2024-08-04 13:27:23 +03:00
function downloadRevision(noteId: string, revisionId: string) {
const url = getUrlForDownload(`api/revisions/${revisionId}/download`);
download(url);
}
/**
* @param url - should be without initial slash!!!
*/
2024-08-04 13:27:23 +03:00
function getUrlForDownload(url: string) {
if (utils.isElectron()) {
// electron needs absolute URL, so we extract current host, port, protocol
return `${getHost()}/${url}`;
2025-01-09 18:07:02 +02:00
} else {
2023-06-29 23:32:19 +02:00
// web server can be deployed on subdomain, so we need to use a relative path
return url;
}
}
2024-08-04 13:27:23 +03:00
function canOpenInBrowser(mime: string) {
2025-01-09 18:07:02 +02:00
return mime === "application/pdf" || mime.startsWith("image") || mime.startsWith("audio") || mime.startsWith("video");
2023-05-03 10:23:20 +02:00
}
2024-08-04 13:27:23 +03:00
async function openExternally(type: string, entityId: string, mime: string) {
2023-05-03 10:23:20 +02:00
checkType(type);
if (utils.isElectron()) {
2024-08-04 13:27:23 +03:00
const resp = await server.post<TmpResponse>(`${type}/${entityId}/save-to-tmp-dir`);
2023-05-03 10:23:20 +02:00
2025-01-09 18:07:02 +02:00
const electron = utils.dynamicRequire("electron");
2023-05-03 10:23:20 +02:00
const res = await electron.shell.openPath(resp.tmpFilePath);
if (res) {
// fallback in case there's no default application for this file
window.open(getFileUrl(type, entityId));
2023-05-03 10:23:20 +02:00
}
2025-01-09 18:07:02 +02:00
} else {
2023-05-03 10:23:20 +02:00
// allow browser to handle opening common file
if (canOpenInBrowser(mime)) {
window.open(getOpenFileUrl(type, entityId));
} else {
window.location.href = getFileUrl(type, entityId);
}
}
}
2025-01-09 18:07:02 +02:00
const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime);
2023-05-03 10:23:20 +02:00
function getHost() {
const url = new URL(window.location.href);
return `${url.protocol}//${url.hostname}:${url.port}`;
}
async function openDirectory(directory: string) {
2024-09-04 11:08:04 +00:00
try {
if (utils.isElectron()) {
2025-01-09 18:07:02 +02:00
const electron = utils.dynamicRequire("electron");
2024-09-04 11:08:04 +00:00
const res = await electron.shell.openPath(directory);
if (res) {
2025-01-09 18:07:02 +02:00
console.error("Failed to open directory:", res);
2024-09-04 11:08:04 +00:00
}
} else {
2025-01-09 18:07:02 +02:00
console.error("Not running in an Electron environment.");
2024-09-04 11:08:04 +00:00
}
} catch (err: any) {
2024-09-04 11:08:04 +00:00
// Handle file system errors (e.g. path does not exist or is inaccessible)
2025-01-09 18:07:02 +02:00
console.error("Error:", err.message);
2024-09-04 11:08:04 +00:00
}
}
export default {
2020-11-12 22:13:59 +01:00
download,
downloadFileNote,
downloadRevision,
2023-05-03 10:23:20 +02:00
downloadAttachment,
getUrlForDownload,
openNoteExternally,
openAttachmentExternally,
2023-05-16 11:57:28 +00:00
openNoteCustom,
2023-09-07 06:33:51 +00:00
openAttachmentCustom,
2024-09-04 12:10:13 +00:00
openDirectory
2025-01-09 18:07:02 +02:00
};