2026-03-22 20:17:48 +02:00
|
|
|
import { getCrypto,utils as coreUtils } from "@triliumnext/core";
|
2025-02-22 01:37:02 +02:00
|
|
|
import chardet from "chardet";
|
2024-07-18 21:37:45 +03:00
|
|
|
import crypto from "crypto";
|
2026-01-06 13:06:14 +02:00
|
|
|
import { t } from "i18next";
|
|
|
|
|
import { release as osRelease } from "os";
|
2024-07-18 21:37:45 +03:00
|
|
|
import path from "path";
|
2026-01-06 13:06:14 +02:00
|
|
|
import stripBom from "strip-bom";
|
|
|
|
|
|
2025-02-20 18:06:19 +02:00
|
|
|
import log from "./log.js";
|
2026-01-06 13:06:14 +02:00
|
|
|
import type NoteMeta from "./meta/note_meta.js";
|
2025-09-06 17:47:13 +03:00
|
|
|
|
|
|
|
|
const osVersion = osRelease().split('.').map(Number);
|
2017-10-28 19:55:55 -04:00
|
|
|
|
2025-01-22 18:57:06 +01:00
|
|
|
export const isMac = process.platform === "darwin";
|
|
|
|
|
|
|
|
|
|
export const isWindows = process.platform === "win32";
|
|
|
|
|
|
2025-09-06 17:47:13 +03:00
|
|
|
export const isWindows11 = isWindows && osVersion[0] === 10 && osVersion[2] >= 22000;
|
|
|
|
|
|
2025-01-22 18:57:06 +01:00
|
|
|
export const isElectron = !!process.versions["electron"];
|
|
|
|
|
|
2025-01-22 19:08:38 +01:00
|
|
|
export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev");
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function newEntityId() {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.newEntityId();
|
2018-02-11 00:18:59 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function randomString(length: number): string {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.randomString(length);
|
2017-10-14 23:31:44 -04:00
|
|
|
}
|
|
|
|
|
|
2025-01-02 12:24:57 +01:00
|
|
|
export function md5(content: crypto.BinaryLike) {
|
2025-01-09 18:07:02 +02:00
|
|
|
return crypto.createHash("md5").update(content).digest("hex");
|
2018-11-05 12:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function hashedBlobId(content: string | Buffer) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.hashedBlobId(content);
|
2023-03-15 22:44:08 +01:00
|
|
|
}
|
|
|
|
|
|
2025-01-02 12:24:57 +01:00
|
|
|
export function toBase64(plainText: string | Buffer) {
|
2025-03-01 10:16:24 +02:00
|
|
|
const buffer = (Buffer.isBuffer(plainText) ? plainText : Buffer.from(plainText));
|
|
|
|
|
return buffer.toString("base64");
|
2017-10-14 23:31:44 -04:00
|
|
|
}
|
|
|
|
|
|
2025-01-02 12:24:57 +01:00
|
|
|
export function fromBase64(encodedText: string) {
|
2025-01-09 18:07:02 +02:00
|
|
|
return Buffer.from(encodedText, "base64");
|
2017-10-14 23:31:44 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-22 20:17:48 +02:00
|
|
|
export function hmac(secret: string | Uint8Array, value: string | Uint8Array) {
|
|
|
|
|
return getCrypto().hmac(secret, value);
|
2017-10-29 14:55:48 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-21 08:58:38 -08:00
|
|
|
/**
|
|
|
|
|
* Constant-time string comparison to prevent timing attacks.
|
|
|
|
|
* Uses crypto.timingSafeEqual to ensure comparison time is independent
|
|
|
|
|
* of how many characters match.
|
|
|
|
|
*
|
|
|
|
|
* @param a First string to compare
|
|
|
|
|
* @param b Second string to compare
|
|
|
|
|
* @returns true if strings are equal, false otherwise
|
2025-12-21 09:22:53 -08:00
|
|
|
* @note Returns false for null/undefined/non-string inputs. Empty strings are considered equal.
|
2025-12-21 08:58:38 -08:00
|
|
|
*/
|
2025-12-21 09:22:53 -08:00
|
|
|
export function constantTimeCompare(a: string | null | undefined, b: string | null | undefined): boolean {
|
|
|
|
|
// Handle null/undefined/non-string cases safely
|
|
|
|
|
if (typeof a !== "string" || typeof b !== "string") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 08:58:38 -08:00
|
|
|
const bufA = Buffer.from(a, "utf-8");
|
|
|
|
|
const bufB = Buffer.from(b, "utf-8");
|
|
|
|
|
|
|
|
|
|
// If lengths differ, we still do a constant-time comparison
|
|
|
|
|
// to avoid leaking length information through timing
|
|
|
|
|
if (bufA.length !== bufB.length) {
|
|
|
|
|
// Compare bufA against itself to maintain constant time behavior
|
|
|
|
|
crypto.timingSafeEqual(bufA, bufA);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return crypto.timingSafeEqual(bufA, bufB);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-02 12:24:57 +01:00
|
|
|
export function toObject<T, K extends string | number | symbol, V>(array: T[], fn: (item: T) => [K, V]): Record<K, V> {
|
2024-02-18 20:29:23 +02:00
|
|
|
const obj: Record<K, V> = {} as Record<K, V>; // TODO: unsafe?
|
2018-03-04 12:06:35 -05:00
|
|
|
|
|
|
|
|
for (const item of array) {
|
|
|
|
|
const ret = fn(item);
|
|
|
|
|
|
|
|
|
|
obj[ret[0]] = ret[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-02 12:24:57 +01:00
|
|
|
export function stripTags(text: string) {
|
2025-01-09 18:07:02 +02:00
|
|
|
return text.replace(/<(?:.|\n)*?>/gm, "");
|
2018-05-27 12:26:34 -04:00
|
|
|
}
|
|
|
|
|
|
2025-02-20 18:06:19 +02:00
|
|
|
export async function crash(message: string) {
|
2025-01-22 18:57:06 +01:00
|
|
|
if (isElectron) {
|
2025-02-20 18:06:19 +02:00
|
|
|
const electron = await import("electron");
|
|
|
|
|
electron.dialog.showErrorBox(t("modals.error_title"), message);
|
|
|
|
|
electron.app.exit(1);
|
2024-07-18 23:26:21 +03:00
|
|
|
} else {
|
2025-02-20 18:06:19 +02:00
|
|
|
log.error(message);
|
2018-11-18 09:05:50 +01:00
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:41:34 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function getContentDisposition(filename: string) {
|
2026-01-06 15:41:34 +02:00
|
|
|
return coreUtils.getContentDisposition(filename);
|
2019-01-13 10:22:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:41:34 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function isStringNote(type: string | undefined, mime: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.isStringNote(type, mime);
|
2019-10-31 21:58:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function quoteRegex(url: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.quoteRegex(url);
|
2020-05-12 10:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function replaceAll(string: string, replaceWhat: string, replaceWith: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.replaceAll(string, replaceWhat, replaceWith);
|
2020-05-12 10:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:41:34 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function formatDownloadTitle(fileName: string, type: string | null, mime: string) {
|
2026-01-06 15:41:34 +02:00
|
|
|
return coreUtils.formatDownloadTitle(fileName, type, mime);
|
2020-04-02 22:55:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-03 14:29:18 +02:00
|
|
|
export function removeFileExtension(filePath: string, mime?: string) {
|
2020-05-30 16:15:00 -05:00
|
|
|
const extension = path.extname(filePath).toLowerCase();
|
|
|
|
|
|
2026-03-03 16:19:44 +02:00
|
|
|
if (mime?.startsWith("video/") || mime?.startsWith("audio/")) {
|
2026-03-03 14:29:18 +02:00
|
|
|
return filePath.substring(0, filePath.length - extension.length);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 00:17:51 +03:00
|
|
|
switch (extension) {
|
|
|
|
|
case ".md":
|
2025-02-20 20:29:00 +02:00
|
|
|
case ".mdx":
|
2024-10-20 00:17:51 +03:00
|
|
|
case ".markdown":
|
|
|
|
|
case ".html":
|
|
|
|
|
case ".htm":
|
2025-03-14 22:13:31 +02:00
|
|
|
case ".excalidraw":
|
2025-03-22 15:41:56 +02:00
|
|
|
case ".mermaid":
|
2025-03-22 15:45:36 +02:00
|
|
|
case ".mmd":
|
2026-01-16 16:25:15 +02:00
|
|
|
case ".pdf":
|
2025-01-02 12:24:57 +01:00
|
|
|
return filePath.substring(0, filePath.length - extension.length);
|
2024-10-20 00:17:51 +03:00
|
|
|
default:
|
|
|
|
|
return filePath;
|
2020-05-30 16:15:00 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 08:46:56 +01:00
|
|
|
export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) {
|
2025-01-31 21:52:26 +01:00
|
|
|
const trimmedNoteMeta = noteMeta?.title?.trim();
|
|
|
|
|
if (trimmedNoteMeta) return trimmedNoteMeta;
|
|
|
|
|
|
2026-03-03 14:29:18 +02:00
|
|
|
const basename = path.basename(removeFileExtension(filePath, noteMeta?.mime));
|
2025-01-31 21:38:23 +01:00
|
|
|
return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename;
|
2020-05-30 16:15:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function removeDiacritic(str: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.removeDiacritic(str);
|
2021-09-27 22:09:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 13:06:14 +02:00
|
|
|
/** @deprecated */
|
2025-01-02 12:24:57 +01:00
|
|
|
export function normalize(str: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return coreUtils.normalize(str);
|
2021-09-27 22:09:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:43:36 +02:00
|
|
|
/** @deprecated */
|
2025-02-01 14:11:17 +01:00
|
|
|
export function toMap<T extends Record<string, any>>(list: T[], key: keyof T) {
|
2026-01-06 15:43:36 +02:00
|
|
|
return coreUtils.toMap(list, key);
|
2023-09-08 21:53:57 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-23 20:18:05 +01:00
|
|
|
// try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined
|
|
|
|
|
export function envToBoolean(val: string | undefined) {
|
|
|
|
|
if (val === undefined || typeof val !== "string") return undefined;
|
|
|
|
|
|
|
|
|
|
const valLc = val.toLowerCase().trim();
|
|
|
|
|
|
|
|
|
|
if (valLc === "true") return true;
|
|
|
|
|
if (valLc === "false") return false;
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 15:10:19 +03:00
|
|
|
/**
|
|
|
|
|
* Parses a string value to an integer. If the resulting number is NaN or undefined, the result is also undefined.
|
|
|
|
|
*
|
|
|
|
|
* @param val the value to parse.
|
|
|
|
|
* @returns the parsed value.
|
|
|
|
|
*/
|
|
|
|
|
export function stringToInt(val: string | undefined) {
|
|
|
|
|
if (!val) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parsed = parseInt(val, 10);
|
|
|
|
|
if (Number.isNaN(parsed)) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 11:49:33 +02:00
|
|
|
/**
|
|
|
|
|
* Returns the directory for resources. On Electron builds this corresponds to the `resources` subdirectory inside the distributable package.
|
2025-04-24 11:38:50 +03:00
|
|
|
* On development builds, this simply refers to the src directory of the application.
|
2024-12-22 15:42:15 +02:00
|
|
|
*
|
2024-11-02 11:49:33 +02:00
|
|
|
* @returns the resource dir.
|
|
|
|
|
*/
|
|
|
|
|
export function getResourceDir() {
|
2025-04-24 16:08:02 +03:00
|
|
|
if (process.env.TRILIUM_RESOURCE_DIR) {
|
|
|
|
|
return process.env.TRILIUM_RESOURCE_DIR;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-26 03:23:53 +03:00
|
|
|
if (isElectron && !isDev) return __dirname;
|
2025-04-24 14:55:11 +03:00
|
|
|
if (!isDev) {
|
|
|
|
|
return path.dirname(process.argv[1]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-23 17:56:44 +03:00
|
|
|
return path.join(__dirname, "..");
|
2024-11-02 11:49:33 +02:00
|
|
|
}
|
|
|
|
|
|
2025-02-04 21:15:47 +02:00
|
|
|
// TODO: Deduplicate with src/public/app/services/utils.ts
|
|
|
|
|
/**
|
|
|
|
|
* Compares two semantic version strings.
|
|
|
|
|
* Returns:
|
|
|
|
|
* 1 if v1 is greater than v2
|
|
|
|
|
* 0 if v1 is equal to v2
|
|
|
|
|
* -1 if v1 is less than v2
|
|
|
|
|
*
|
|
|
|
|
* @param v1 First version string
|
|
|
|
|
* @param v2 Second version string
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
function compareVersions(v1: string, v2: string): number {
|
|
|
|
|
// Remove 'v' prefix and everything after dash if present
|
|
|
|
|
v1 = v1.replace(/^v/, "").split("-")[0];
|
|
|
|
|
v2 = v2.replace(/^v/, "").split("-")[0];
|
|
|
|
|
|
|
|
|
|
const v1parts = v1.split(".").map(Number);
|
|
|
|
|
const v2parts = v2.split(".").map(Number);
|
|
|
|
|
|
|
|
|
|
// Pad shorter version with zeros
|
|
|
|
|
while (v1parts.length < 3) v1parts.push(0);
|
|
|
|
|
while (v2parts.length < 3) v2parts.push(0);
|
|
|
|
|
|
|
|
|
|
// Compare major version
|
|
|
|
|
if (v1parts[0] !== v2parts[0]) {
|
|
|
|
|
return v1parts[0] > v2parts[0] ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare minor version
|
|
|
|
|
if (v1parts[1] !== v2parts[1]) {
|
|
|
|
|
return v1parts[1] > v2parts[1] ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare patch version
|
|
|
|
|
if (v1parts[2] !== v2parts[2]) {
|
|
|
|
|
return v1parts[2] > v2parts[2] ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 01:37:02 +02:00
|
|
|
/**
|
|
|
|
|
* For buffers, they are scanned for a supported encoding and decoded (UTF-8, UTF-16). In some cases, the BOM is also stripped.
|
|
|
|
|
*
|
|
|
|
|
* For strings, they are returned immediately without any transformation.
|
|
|
|
|
*
|
|
|
|
|
* For nullish values, an empty string is returned.
|
|
|
|
|
*
|
|
|
|
|
* @param data the string or buffer to process.
|
|
|
|
|
* @returns the string representation of the buffer, or the same string is it's a string.
|
|
|
|
|
*/
|
|
|
|
|
export function processStringOrBuffer(data: string | Buffer | null) {
|
|
|
|
|
if (!data) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Buffer.isBuffer(data)) {
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const detectedEncoding = chardet.detect(data);
|
|
|
|
|
switch (detectedEncoding) {
|
|
|
|
|
case "UTF-16LE":
|
|
|
|
|
return stripBom(data.toString("utf-16le"));
|
|
|
|
|
case "UTF-8":
|
|
|
|
|
default:
|
|
|
|
|
return data.toString("utf-8");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 19:37:40 +00:00
|
|
|
/**
|
|
|
|
|
* Normalizes a path pattern for custom request handlers.
|
|
|
|
|
* Ensures both trailing slash and non-trailing slash versions are handled.
|
2025-08-13 15:10:19 +03:00
|
|
|
*
|
2025-06-17 19:37:40 +00:00
|
|
|
* @param pattern The original pattern from customRequestHandler attribute
|
|
|
|
|
* @returns An array of patterns to match both with and without trailing slash
|
|
|
|
|
*/
|
2025-06-18 21:07:12 +00:00
|
|
|
export function normalizeCustomHandlerPattern(pattern: string | null | undefined): (string | null | undefined)[] {
|
2025-06-17 19:37:40 +00:00
|
|
|
if (!pattern || typeof pattern !== 'string') {
|
|
|
|
|
return [pattern];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pattern = pattern.trim();
|
2025-08-13 15:10:19 +03:00
|
|
|
|
2025-06-17 19:37:40 +00:00
|
|
|
if (!pattern) {
|
|
|
|
|
return [pattern];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If pattern already ends with optional trailing slash, return as-is
|
|
|
|
|
if (pattern.endsWith('/?$') || pattern.endsWith('/?)')) {
|
|
|
|
|
return [pattern];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If pattern ends with $, handle it specially
|
|
|
|
|
if (pattern.endsWith('$')) {
|
|
|
|
|
const basePattern = pattern.slice(0, -1);
|
2025-08-13 15:10:19 +03:00
|
|
|
|
2025-06-17 19:37:40 +00:00
|
|
|
// If already ends with slash, create both versions
|
|
|
|
|
if (basePattern.endsWith('/')) {
|
2026-01-06 13:06:14 +02:00
|
|
|
const withoutSlash = `${basePattern.slice(0, -1) }$`;
|
2025-06-17 19:37:40 +00:00
|
|
|
const withSlash = pattern;
|
|
|
|
|
return [withoutSlash, withSlash];
|
|
|
|
|
}
|
2026-01-06 13:06:14 +02:00
|
|
|
// Add optional trailing slash
|
|
|
|
|
const withSlash = `${basePattern }/?$`;
|
|
|
|
|
return [withSlash];
|
|
|
|
|
|
2025-06-17 19:37:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For patterns without $, add both versions
|
|
|
|
|
if (pattern.endsWith('/')) {
|
|
|
|
|
const withoutSlash = pattern.slice(0, -1);
|
|
|
|
|
return [withoutSlash, pattern];
|
|
|
|
|
}
|
2026-01-06 13:06:14 +02:00
|
|
|
const withSlash = `${pattern }/`;
|
|
|
|
|
return [pattern, withSlash];
|
|
|
|
|
|
2025-06-17 19:37:40 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-12 12:17:22 +03:00
|
|
|
export function formatUtcTime(time: string) {
|
2026-01-06 13:06:14 +02:00
|
|
|
return time.replace("T", " ").substring(0, 19);
|
2025-09-12 12:17:22 +03:00
|
|
|
}
|
2025-03-08 17:07:25 +01:00
|
|
|
|
2025-09-12 12:27:41 +03:00
|
|
|
// TODO: Deduplicate with client utils
|
|
|
|
|
export function formatSize(size: number | null | undefined) {
|
|
|
|
|
if (size === null || size === undefined) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size = Math.max(Math.round(size / 1024), 1);
|
|
|
|
|
|
|
|
|
|
if (size < 1024) {
|
|
|
|
|
return `${size} KiB`;
|
|
|
|
|
}
|
2026-01-06 13:06:14 +02:00
|
|
|
return `${Math.round(size / 102.4) / 10} MiB`;
|
|
|
|
|
|
2025-09-12 12:27:41 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-23 14:19:54 +03:00
|
|
|
function slugify(text: string) {
|
2025-10-23 14:06:13 +03:00
|
|
|
return text
|
2025-10-23 14:29:47 +03:00
|
|
|
.normalize("NFC") // keep composed form, preserves accents
|
2025-10-23 14:06:13 +03:00
|
|
|
.toLowerCase()
|
2025-10-23 14:29:47 +03:00
|
|
|
.replace(/[^\p{Letter}\p{Number}]+/gu, "-") // replace non-letter/number with "-"
|
|
|
|
|
.replace(/(^-|-$)+/g, ""); // trim dashes
|
2025-10-23 14:06:13 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-07 11:45:40 +02:00
|
|
|
/** @deprecated */
|
2026-01-06 15:43:36 +02:00
|
|
|
export const escapeHtml = coreUtils.escapeHtml;
|
2026-01-07 11:45:40 +02:00
|
|
|
/** @deprecated */
|
2026-03-22 14:03:48 +02:00
|
|
|
export const escapeRegExp = coreUtils.escapeRegExp;
|
|
|
|
|
/** @deprecated */
|
2026-01-06 15:43:36 +02:00
|
|
|
export const unescapeHtml = coreUtils.unescapeHtml;
|
2026-01-07 11:45:40 +02:00
|
|
|
/** @deprecated */
|
2026-01-06 16:29:30 +02:00
|
|
|
export const randomSecureToken = coreUtils.randomSecureToken;
|
2026-01-07 11:45:40 +02:00
|
|
|
/** @deprecated */
|
|
|
|
|
export const safeExtractMessageAndStackFromError = coreUtils.safeExtractMessageAndStackFromError;
|
2026-01-12 19:25:45 +02:00
|
|
|
/** @deprecated */
|
|
|
|
|
export const isEmptyOrWhitespace = coreUtils.isEmptyOrWhitespace;
|
2026-03-22 20:02:08 +02:00
|
|
|
/** @deprecated */
|
|
|
|
|
export const normalizeUrl = coreUtils.normalizeUrl;
|
|
|
|
|
export const timeLimit = coreUtils.timeLimit;
|
|
|
|
|
export const sanitizeSqlIdentifier = coreUtils.sanitizeSqlIdentifier;
|
2026-01-06 15:43:36 +02:00
|
|
|
|
2026-01-31 01:00:57 -08:00
|
|
|
export function waitForStreamToFinish(stream: any): Promise<void> {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
stream.on("finish", () => resolve());
|
|
|
|
|
stream.on("error", (err) => reject(err));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-18 21:47:30 +03:00
|
|
|
export default {
|
2025-02-04 21:28:27 +02:00
|
|
|
compareVersions,
|
2025-12-21 08:58:38 -08:00
|
|
|
constantTimeCompare,
|
2025-01-31 07:59:12 +01:00
|
|
|
crash,
|
|
|
|
|
envToBoolean,
|
|
|
|
|
escapeHtml,
|
|
|
|
|
escapeRegExp,
|
|
|
|
|
formatDownloadTitle,
|
2017-10-29 14:55:48 -04:00
|
|
|
fromBase64,
|
2025-01-31 07:59:12 +01:00
|
|
|
getContentDisposition,
|
|
|
|
|
getNoteTitle,
|
|
|
|
|
getResourceDir,
|
|
|
|
|
hashedBlobId,
|
2017-11-05 10:41:54 -05:00
|
|
|
hmac,
|
2025-01-31 07:59:12 +01:00
|
|
|
isDev,
|
2017-11-05 21:56:42 -05:00
|
|
|
isElectron,
|
2017-12-16 00:05:37 -05:00
|
|
|
isEmptyOrWhitespace,
|
2025-01-31 07:59:12 +01:00
|
|
|
isMac,
|
2020-04-02 22:55:11 +02:00
|
|
|
isStringNote,
|
2025-01-31 07:59:12 +01:00
|
|
|
isWindows,
|
|
|
|
|
md5,
|
|
|
|
|
newEntityId,
|
|
|
|
|
normalize,
|
2025-06-17 19:37:40 +00:00
|
|
|
normalizeCustomHandlerPattern,
|
2020-05-12 10:28:31 +02:00
|
|
|
quoteRegex,
|
2025-01-31 07:59:12 +01:00
|
|
|
randomSecureToken,
|
|
|
|
|
randomString,
|
|
|
|
|
removeDiacritic,
|
2026-01-16 16:25:15 +02:00
|
|
|
removeFileExtension,
|
2025-01-31 07:59:12 +01:00
|
|
|
replaceAll,
|
2025-03-08 17:07:25 +01:00
|
|
|
safeExtractMessageAndStackFromError,
|
2025-01-31 07:59:12 +01:00
|
|
|
stripTags,
|
2025-10-23 14:19:54 +03:00
|
|
|
slugify,
|
2025-01-31 07:59:12 +01:00
|
|
|
toBase64,
|
2023-09-08 21:53:57 +02:00
|
|
|
toMap,
|
2025-01-31 07:59:12 +01:00
|
|
|
toObject,
|
2026-01-31 01:00:57 -08:00
|
|
|
unescapeHtml,
|
|
|
|
|
waitForStreamToFinish
|
2020-05-12 10:28:31 +02:00
|
|
|
};
|