mirror of
https://github.com/zadam/trilium.git
synced 2026-02-17 03:04:24 +01:00
138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
import { getCrypto } from "../encryption/crypto";
|
|
import { sanitizeFileName } from "../sanitizer";
|
|
import { encodeBase64 } from "./binary";
|
|
import mimeTypes from "mime-types";
|
|
import escape from "escape-html";
|
|
import unescape from "unescape";
|
|
|
|
// TODO: Implement platform detection.
|
|
export const isElectron = false;
|
|
export const isMac = false;
|
|
export const isWindows = false;
|
|
|
|
// render and book are string note in the sense that they are expected to contain empty string
|
|
const STRING_NOTE_TYPES = new Set(["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas", "webView"]);
|
|
const STRING_MIME_TYPES = new Set(["application/javascript", "application/x-javascript", "application/json", "application/x-sql", "image/svg+xml"]);
|
|
|
|
export function hash(text: string) {
|
|
return encodeBase64(getCrypto().createHash("sha1", text.normalize()));
|
|
}
|
|
|
|
export function isStringNote(type: string | undefined, mime: string) {
|
|
return (type && STRING_NOTE_TYPES.has(type)) || mime.startsWith("text/") || STRING_MIME_TYPES.has(mime);
|
|
}
|
|
|
|
// TODO: Refactor to use getCrypto() directly.
|
|
export function randomString(length: number) {
|
|
return getCrypto().randomString(length);
|
|
}
|
|
|
|
export function newEntityId() {
|
|
return randomString(12);
|
|
}
|
|
|
|
export function hashedBlobId(content: string | Uint8Array) {
|
|
if (content === null || content === undefined) {
|
|
content = "";
|
|
}
|
|
|
|
// sha512 is faster than sha256
|
|
const base64Hash = encodeBase64(getCrypto().createHash("sha512", content));
|
|
|
|
// we don't want such + and / in the IDs
|
|
const kindaBase62Hash = base64Hash.replaceAll("+", "X").replaceAll("/", "Y");
|
|
|
|
// 20 characters of base62 gives us ~120 bit of entropy which is plenty enough
|
|
return kindaBase62Hash.substr(0, 20);
|
|
}
|
|
|
|
export function quoteRegex(url: string) {
|
|
return url.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
export function replaceAll(string: string, replaceWhat: string, replaceWith: string) {
|
|
const quotedReplaceWhat = quoteRegex(replaceWhat);
|
|
|
|
return string.replace(new RegExp(quotedReplaceWhat, "g"), replaceWith);
|
|
}
|
|
|
|
export function removeDiacritic(str: string) {
|
|
if (!str) {
|
|
return "";
|
|
}
|
|
str = str.toString();
|
|
return str.normalize("NFD").replace(/\p{Diacritic}/gu, "");
|
|
}
|
|
|
|
export function normalize(str: string) {
|
|
return removeDiacritic(str).toLowerCase();
|
|
}
|
|
|
|
export function sanitizeAttributeName(origName: string) {
|
|
const fixedName = origName === "" ? "unnamed" : origName.replace(/[^\p{L}\p{N}_:]/gu, "_");
|
|
// any not allowed character should be replaced with underscore
|
|
|
|
return fixedName;
|
|
}
|
|
|
|
export function getContentDisposition(filename: string) {
|
|
const sanitizedFilename = sanitizeFileName(filename).trim() || "file";
|
|
const uriEncodedFilename = encodeURIComponent(sanitizedFilename);
|
|
return `file; filename="${uriEncodedFilename}"; filename*=UTF-8''${uriEncodedFilename}`;
|
|
}
|
|
|
|
export function formatDownloadTitle(fileName: string, type: string | null, mime: string) {
|
|
const fileNameBase = !fileName ? "untitled" : sanitizeFileName(fileName);
|
|
|
|
const getExtension = () => {
|
|
if (type === "text") return ".html";
|
|
if (type === "relationMap" || type === "canvas" || type === "search") return ".json";
|
|
if (!mime) return "";
|
|
|
|
const mimeLc = mime.toLowerCase();
|
|
|
|
// better to just return the current name without a fake extension
|
|
// it's possible that the title still preserves the correct extension anyways
|
|
if (mimeLc === "application/octet-stream") return "";
|
|
|
|
// if fileName has an extension matching the mime already - reuse it
|
|
const mimeTypeFromFileName = mimeTypes.lookup(fileName);
|
|
if (mimeTypeFromFileName === mimeLc) return "";
|
|
|
|
// as last resort try to get extension from mimeType
|
|
const extensions = mimeTypes.extension(mime);
|
|
return extensions ? `.${extensions}` : "";
|
|
};
|
|
|
|
return `${fileNameBase}${getExtension()}`;
|
|
}
|
|
|
|
export function toMap<T extends Record<string, any>>(list: T[], key: keyof T) {
|
|
const map = new Map<string, T>();
|
|
for (const el of list) {
|
|
const keyForMap = el[key];
|
|
if (!keyForMap) continue;
|
|
// TriliumNextTODO: do we need to handle the case when the same key is used?
|
|
// currently this will overwrite the existing entry in the map
|
|
map.set(keyForMap, el);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
export const escapeHtml = escape;
|
|
|
|
export const unescapeHtml = unescape;
|
|
|
|
export function randomSecureToken(bytes = 32) {
|
|
return encodeBase64(getCrypto().randomBytes(32));
|
|
}
|
|
|
|
export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: string, errStack: string | undefined] {
|
|
return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
|
|
}
|
|
|
|
export function isEmptyOrWhitespace(str: string | null | undefined) {
|
|
if (!str) return true;
|
|
return str.match(/^ *$/) !== null;
|
|
}
|