UNPKG

@paperbits/common

Version:
410 lines (332 loc) 12.6 kB
import * as h2p from "html2plaintext"; import { Quadrant } from "./ui"; import { Breakpoints } from "."; import { Query } from "./persistence"; export function guid(): string { function s4(): string { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4(); } export function identifier(length: number = 5): string { let result = ""; const possible = "abcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < length; i++) { result += possible.charAt(Math.floor(Math.random() * possible.length)); } return result; } /** * Returns randomly generated CSS class name. */ export function randomClassName(): string { let result = ""; const possible = "abcdefghijklmnopqrstuvwxyz"; for (let i = 0; i < 10; i++) { result += possible.charAt(Math.floor(Math.random() * possible.length)); } return result; } export function downloadFile(url: string): Promise<Uint8Array> { return new Promise<Uint8Array>((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.onload = () => resolve(new Uint8Array(xhr.response)); xhr.open("GET", url); xhr.send(); }); } export function arrayBufferToBase64(buffer: ArrayBuffer): string { if (Buffer) { return Buffer.from(buffer).toString("base64"); } else { let binary = ""; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } } export function base64ToArrayBuffer(base64: string): Uint8Array { const buffer = Buffer.from(base64, "base64"); const arrayBuffer = new ArrayBuffer(buffer.length); const uint8Array = new Uint8Array(arrayBuffer); for (let i = 0; i < buffer.length; ++i) { uint8Array[i] = buffer[i]; } return uint8Array; } export function readFileAsByteArray(file: Blob): Promise<Uint8Array> { return new Promise<Uint8Array>((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve((<any>event.target).result); reader.readAsArrayBuffer(file); }); } export function readBlobAsDataUrl(blob: Blob): Promise<string> { return readDataUrlFromReader(reader => reader.readAsDataURL(blob)); } export function readDataUrlFromReader(read: (reader: FileReader) => void): Promise<string> { return new Promise<string>((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve((<any>event.target).result); read(reader); }); } export function progressEventToProgress(progress: (precent: number) => void): (event: ProgressEvent) => void { return (event: ProgressEvent) => { if (event.lengthComputable) { const percentLoaded = Math.round((event.loaded / event.total) * 100); progress(percentLoaded); } }; } export function isDirectUrl(url: string): boolean { return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("data:") || url.startsWith("blob:"); } export function getCookie(name: string): string { const value = "; " + document.cookie; const parts = value.split("; " + name + "="); if (parts.length === 2) { return parts.pop().split(";").shift(); } } export function stringToUnit8Array(content: string): Uint8Array { const escstr = encodeURIComponent(content); const binstr = escstr.replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode(<any>("0x" + p1)); }); const bytes = new Uint8Array(binstr.length); Array.prototype.forEach.call(binstr, (ch, i) => { bytes[i] = ch.charCodeAt(0); }); return bytes; } export function uint8ArrayToString(bytes: Uint8Array): string { return new TextDecoder().decode(bytes); } export function intersectDeepMany(target, nonObjectHandler: (target: any, source: any, key: string) => any, ...sources: any[]) { let result = target; sources.forEach(source => { result = this.intersectDeep(result, nonObjectHandler, source); }); return result; } export function replace(path: string, target: object, value: any, delimiter: string = "/"): object { target = JSON.parse(JSON.stringify(target)); const segments = path.split(delimiter); let segmentObject = target; let segment: string; let parent: any = target; segments.forEach(s => { if (!segmentObject[s]) { segmentObject[s] = {}; } parent = segmentObject; segmentObject = segmentObject[s]; segment = s; }); if (segment) { parent[segment] = value; } return target; } export function findNodesRecursively(predicate: (x: object) => boolean, source: object): object[] { const result = []; if (predicate(source)) { result.push(source); } const keys = Object.keys(source); // This includes array keys keys.forEach(key => { const child = source[key]; if (child instanceof Object) { const childResult = findNodesRecursively(predicate, child); result.push.apply(result, childResult); } }); return result; } export function elementsFromPoint(ownerDocument: Document, x: number, y: number): HTMLElement[] { if (!x || !y) { return []; } if (ownerDocument.elementsFromPoint) { return Array.prototype.slice.call(ownerDocument.elementsFromPoint(Math.floor(x), Math.floor(y))); } else if (ownerDocument["msElementsFromPoint"]) { return Array.prototype.slice.call(ownerDocument["msElementsFromPoint"](Math.floor(x), Math.floor(y))); } else { throw new Error(`Method "elementsFromPoint" not supported by browser.`); } } export function slugify(text: string): string { return text.toString().toLowerCase().trim() .replace(/[^\w\s-]/g, "") // remove non-word [a-z0-9_], non-whitespace, non-hyphen characters .replace(/[\s_-]+/g, "-") // swap any length of whitespace, underscore, hyphen characters with a single _ .replace(/^-+|-+$/g, ""); // remove leading, trailing - } export function pointerToClientQuadrant(pointerX: number, pointerY: number, element: HTMLElement): Quadrant { const rect = element.getBoundingClientRect(); const clientX = pointerX - rect.left; const clientY = pointerY - rect.top; let vertical; let horizontal; if (clientX > rect.width / 2) { horizontal = "right"; } else { horizontal = "left"; } if (clientY > rect.height / 2) { vertical = "bottom"; } else { vertical = "top"; } return { vertical: vertical, horizontal: horizontal }; } export function optimizeBreakpoints(breakpoints: Breakpoints<any>): Breakpoints<any> { const result: Breakpoints<any> = {}; let lastAssigned = null; const breakpointKeys = ["xs", "sm", "md", "lg", "xl"]; breakpointKeys.forEach(breakpoint => { const value = breakpoints[breakpoint]; if (value && value !== lastAssigned) { result[breakpoint] = value; lastAssigned = value; } }); const resultKeys = Object.keys(result); if (resultKeys.length === 1) { const singleKey = resultKeys[0]; return result[singleKey]; } return result; } export function getClosestBreakpoint(source: Breakpoints<any>, current: string): string { const breakpoints = ["xs", "sm", "md", "lg", "xl"]; let index = breakpoints.indexOf(current); let breakpoint = null; do { breakpoint = breakpoints[index]; index--; } while (!source[breakpoint] && index >= 0); return breakpoint; } export function camelCaseToKebabCase(str: string): string { return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/\s/g, "-"); } export function getUrlHashPart(urlPath: string): string { if (urlPath.indexOf("#") !== -1) { return urlPath.split("#")[1]; } return undefined; } export function matchUrl(urlPath: string, urlTemplate: string): { index: number, name: string, value?: string }[] { if (urlPath.charAt(0) === "/") { urlPath = urlPath.slice(1); } if (urlTemplate.charAt(0) === "/") { urlTemplate = urlTemplate.slice(1); } if (urlPath.charAt(urlPath.length - 1) === "/") { urlPath = urlPath.slice(0, -1); } if (urlTemplate.charAt(urlTemplate.length - 1) === "/") { urlTemplate = urlTemplate.slice(0, -1); } const pathSegments = urlPath.split("/"); const templateSegments = urlTemplate.split("/"); if (pathSegments.length !== templateSegments.length && urlTemplate.indexOf("*") === -1) { return undefined; } const tokens: { index: number, name: string, value?: string }[] = []; templateSegments.filter((t, index) => { if (t.charAt(0) === "{") { tokens.push({ index: index, name: t.replace(/{|}/g, "") }); } }); for (let i = 0; i < templateSegments.length; i++) { const segment = pathSegments[i]; const token = tokens.find(t => t.index === i); if (!token && (segment === templateSegments[i] || templateSegments[i] === "*")) { if (templateSegments[i] === "*") { return tokens; } continue; } else { if (token) { const hashIndex = segment.indexOf("#"); if (hashIndex === 0) { token.value = segment.substring(1); } else { if (hashIndex > 0) { return undefined; } else { token.value = segment; } } } else { if (templateSegments.length - 1 - i <= 1 && segment.indexOf("#") > 0) { tokens.push({ index: -1, name: "#", value: segment.split("#")[1] }); return tokens; } return undefined; } } } return tokens; } export function localizeQuery<T>(query: Query<T>, locale: string): Query<T> { const localizedQuery = query.copy(); localizedQuery.filters.forEach(x => x.left = `locales/${locale}/${x.left}`); if (localizedQuery.orderingBy) { localizedQuery.orderingBy = `locales/${locale}/${localizedQuery.orderingBy}`; } return localizedQuery; } export function delay(ms: number): Promise<void> { return new Promise<void>(resolve => setTimeout(resolve, ms)); } export function ensureLeadingSlash(url: string = ""): string { return url.startsWith("/") ? url : `/${url}`; } export function ensureTrailingSlash(url: string = ""): string { return url.endsWith("/") ? url : `${url}/`; } export function isNumber(candidate: unknown): boolean { return !isNaN(<number>candidate); } export function stripHtml(html: string): string { html = html.replaceAll("</h1>", "</h1>."); return h2p(html); } /** * Trucncates specified string value adding ellipsis at the end. * @param value - String value. * @param length - Cut-off length. */ export function truncate(value: string, length: number = 100): string { return value.length > length ? `${value.substring(0, length)}...` : value; } export function debounce(func: () => Promise<void>): () => Promise<void> { let promise: Promise<void>; return () => promise ? promise : promise = func(); } export function appendSuffixToFileName(fileName: string, suffix: string): string { if (!suffix) { return fileName; } const extensionPosition = fileName.lastIndexOf("."); const suffixedFileName = [fileName.slice(0, extensionPosition), `.${suffix}`, fileName.slice(extensionPosition)].join(""); return suffixedFileName }