122 lines
3.2 KiB
JavaScript
122 lines
3.2 KiB
JavaScript
const INVALID_DIR_CHARS = /[~#%&*{}\\<>?/`'"|+:]/g;
|
|
const IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "gif", "svg", "webp"]);
|
|
const FONT_EXTENSIONS = new Set(["otf", "ttf", "woff", "woff2", "eot"]);
|
|
|
|
export function cleanDirectoryName(name) {
|
|
let sanitized = name;
|
|
if (sanitized.includes(":")) {
|
|
if (sanitized.indexOf(":") > 15) {
|
|
sanitized = sanitized.split(":")[0];
|
|
} else {
|
|
sanitized = sanitized.replace(/:/g, ",");
|
|
}
|
|
}
|
|
sanitized = sanitized.replace(INVALID_DIR_CHARS, "_");
|
|
return sanitized.replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
export function makeFolderFriendlyName(title, bookId) {
|
|
const safeTitle = cleanDirectoryName(title);
|
|
const prefix = safeTitle.split(",").slice(0, 2).join(",");
|
|
return `${prefix || safeTitle} (${bookId})`.trim();
|
|
}
|
|
|
|
export function makeFileFriendlyName(...segments) {
|
|
const cleaned = segments
|
|
.map((segment) => segment ?? "")
|
|
.map((segment) => cleanDirectoryName(String(segment)))
|
|
.filter((segment) => segment.length > 0);
|
|
|
|
if (!cleaned.length) {
|
|
return "download";
|
|
}
|
|
|
|
return cleaned.join(" - ").replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
export function escapeXml(value) {
|
|
return value
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
export function isAbsoluteUrl(url) {
|
|
try {
|
|
const parsed = new URL(url);
|
|
return Boolean(parsed.protocol);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function isDocLink(url) {
|
|
return [".html", ".xhtml", ".pdf"].some((ext) => url.includes(ext));
|
|
}
|
|
|
|
export function isImageLink(url) {
|
|
const lowered = url.toLowerCase();
|
|
if (lowered.includes("cover") || lowered.includes("images") || lowered.includes("graphics")) {
|
|
return true;
|
|
}
|
|
const ext = lowered.split(".").pop();
|
|
return IMAGE_EXTENSIONS.has(ext);
|
|
}
|
|
|
|
export function isFontUrl(url) {
|
|
const ext = url.toLowerCase().split(".").pop().split("?")[0];
|
|
return FONT_EXTENSIONS.has(ext);
|
|
}
|
|
|
|
export function isLikelyImageAsset(url) {
|
|
const ext = url.toLowerCase().split(".").pop().split("?")[0];
|
|
return IMAGE_EXTENSIONS.has(ext);
|
|
}
|
|
|
|
export function makeValidId(value) {
|
|
const sanitized = value.replace(/\W|^(?=\d)/g, "_");
|
|
return sanitized[0]?.match(/\d/) ? `_${sanitized}` : sanitized;
|
|
}
|
|
|
|
export function resolveStylesRelativePath(relativePath) {
|
|
const baseParts = ["Styles"];
|
|
const segments = relativePath.split("/").filter(Boolean);
|
|
const resolved = [];
|
|
for (const segment of segments) {
|
|
if (segment === ".") {
|
|
continue;
|
|
}
|
|
if (segment === "..") {
|
|
if (resolved.length) {
|
|
resolved.pop();
|
|
} else if (baseParts.length) {
|
|
baseParts.pop();
|
|
}
|
|
continue;
|
|
}
|
|
resolved.push(segment);
|
|
}
|
|
return [...baseParts, ...resolved].join("/");
|
|
}
|
|
|
|
export function ensureForwardSlashes(path) {
|
|
return path.replace(/\\/g, "/");
|
|
}
|
|
|
|
export function uniqPush(collection, value) {
|
|
if (!collection.includes(value)) {
|
|
collection.push(value);
|
|
}
|
|
return collection.indexOf(value);
|
|
}
|
|
|
|
export function dataFromString(value) {
|
|
const encoder = new TextEncoder();
|
|
return encoder.encode(value);
|
|
}
|
|
|
|
export function toHex(value, width = 8) {
|
|
return value.toString(16).padStart(width, "0");
|
|
}
|