Files

123 lines
4.0 KiB
TypeScript

import { editor, space, system } from "@silverbulletmd/silverbullet/syscalls";
import {
defaultLinkStyle,
maximumDocumentSize,
} from "@silverbulletmd/silverbullet/constants";
import { resolveMarkdownLink } from "@silverbulletmd/silverbullet/lib/resolve";
import {
encodePageURI,
isValidPath,
} from "@silverbulletmd/silverbullet/lib/ref";
import type { UploadFile } from "@silverbulletmd/silverbullet/type/client";
function ensureValidFilenameWithExtension(filename: string): string {
if (isValidPath(filename)) {
return filename;
}
const match = filename.match(/\.([^.]+)$/);
return `file.${match ? match[1] : "txt"}`;
}
export async function saveFile(file: UploadFile) {
const invalidPathMessage =
"Unable to upload file, invalid target filename or path";
const maxSize = await system.getConfig<number>(
"maximumDocumentSize",
maximumDocumentSize,
);
if (typeof maxSize !== "number") {
await editor.flashNotification(
"The setting 'maximumDocumentSize' must be a number",
"error",
);
}
if (file.content.length > maxSize * 1024 * 1024) {
void editor.flashNotification(
`Document is too large, maximum is ${maxSize}MiB`,
"error",
);
return;
}
let desiredFilePath = await editor.prompt(
"File name for uploaded document",
resolveMarkdownLink(
await editor.getCurrentPath(),
ensureValidFilenameWithExtension(file.name),
),
);
if (desiredFilePath === undefined) {
// User hit cancel, so they know why we stopped and dont need an notification.
return;
}
desiredFilePath = desiredFilePath.trim();
if (!isValidPath(desiredFilePath)) {
void editor.flashNotification(invalidPathMessage, "error");
return;
}
// Check the given desired file path wont clobber an existing file. If it
// would, ask the user to confirm or provide another filename. Repeat this
// check for every new filename they give.
// Note: duplicate any modifications here to client/code_mirror/editor_paste.ts
let finalFilePath = null;
while (finalFilePath == null) {
if (await space.fileExists(desiredFilePath)) {
let confirmedFilePath = await editor.prompt(
"A file with that name already exists, keep the same name to replace it, or rename your file",
resolveMarkdownLink(
await editor.getCurrentPath(),
ensureValidFilenameWithExtension(desiredFilePath),
),
);
if (confirmedFilePath === undefined) {
// Unlike the initial filename prompt, we're inside a workflow here
// and should be explicit that the user action cancelled the whole
// operation.
void editor.flashNotification("Upload cancelled by user", "info");
return;
}
confirmedFilePath = confirmedFilePath.trim();
if (!isValidPath(confirmedFilePath)) {
void editor.flashNotification(invalidPathMessage, "error");
return;
}
if (desiredFilePath === confirmedFilePath) {
// if we got back the same path, we're replacing and should accept the given name
finalFilePath = desiredFilePath;
} else {
// we got a new path, so we must repeat the check
desiredFilePath = confirmedFilePath;
confirmedFilePath = undefined;
}
} else {
finalFilePath = desiredFilePath;
}
}
await space.writeDocument(finalFilePath, file.content);
if ((await editor.getCurrentEditor()) === "page") {
const linkStyle = await system.getConfig(
"defaultLinkStyle",
defaultLinkStyle,
);
let documentMarkdown = "";
if (linkStyle === "wikilink") {
documentMarkdown = `[[${finalFilePath}]]`;
} else {
documentMarkdown = `[${finalFilePath}](${encodePageURI(finalFilePath)})`;
}
if (file.contentType.startsWith("image/")) {
documentMarkdown = `!${documentMarkdown}`;
}
void editor.insertAtCursor(documentMarkdown);
}
}
export async function uploadFile(_ctx: any, accept?: string, capture?: string) {
const uploadFile = await editor.uploadFile(accept, capture);
await saveFile(uploadFile);
}