import seedrandom = require("seedrandom"); import pathHelper = require("path-browserify"); export function slice(str: string, len: number) { return str.length > len ? `${str.slice(0, len - 3)}...` : str; } export function fill( str: string, len: number, options: { char: string; position: "start" | "end" } = { char: " ", position: "end", }, ) { if (str.length >= len) { return str; } return str[options.position === "start" ? "padStart" : "padEnd"]( len - str.length, options.char, ); } export function formatPath(path: string, suffix: string = "") { path = Zotero.File.normalizeToUnix(path); if (Zotero.isWin) { path = path.replace(/\//g, "\\"); } if (suffix && !path.endsWith(suffix)) { path += suffix; } return path; } export async function getFileContent(path: string) { const contentOrXHR = await Zotero.File.getContentsAsync(path); const content = typeof contentOrXHR === "string" ? contentOrXHR : (contentOrXHR as any as XMLHttpRequest).response; return content; } export function randomString(len: number, seed?: string, chars?: string) { if (!chars) { chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; } if (!len) { len = 8; } let str = ""; const random = seedrandom(seed); for (let i = 0; i < len; i++) { const rnum = Math.floor(random() * chars.length); str += chars.substring(rnum, rnum + 1); } return str; } function arrayBufferToBase64(buffer: ArrayBufferLike) { let binary = ""; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return ztoolkit.getGlobal("btoa")(binary); } export async function getItemDataURL(item: Zotero.Item) { if (addon.data.imageCache[item.id]) { return addon.data.imageCache[item.id]; } const path = (await item.getFilePathAsync()) as string; const buf = (await IOUtils.read(path)).buffer; const dataURL = "data:" + item.attachmentContentType + ";base64," + arrayBufferToBase64(buf); const keys = Object.keys(addon.data.imageCache); // Limit cache size while (keys.length > 100) { delete addon.data.imageCache[keys.shift() as any]; } addon.data.imageCache[item.id] = dataURL; return dataURL; } export async function fileExists(path: string): Promise { if (!path) { return false; } try { // IOUtils.exists() will throw error if path is not valid return await IOUtils.exists(formatPath(path)); } catch (e) { ztoolkit.log("[fileExists]", e); return false; } } export function jointPath(...paths: string[]) { try { return formatPath( pathHelper.join(...paths.map((p) => p.replaceAll("\\", "/"))), ); } catch (e) { ztoolkit.log("[jointPath]", e); return ""; } } export function tryDecodeParse(s: string) { try { return JSON.parse(decodeURIComponent(s)); } catch (e) { return null; } } export function htmlEscape(doc: Document, str: string) { const div = doc.createElement("div"); const text = doc.createTextNode(str); div.appendChild(text); return div.innerHTML.replace(/"/g, """).replace(/'/g, "'"); } export function htmlUnescape( str: string, options: { excludeLineBreak?: boolean; } = {}, ) { const map: Record = { " ": " ", """: '"', "'": "'", }; if (!options.excludeLineBreak) { map["\n"] = ""; } const re = new RegExp(Object.keys(map).join("|"), "g"); return str.replace(re, function (match) { return map[match as keyof typeof map]; }); }