// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import marked from 'marked' declare global { interface Window { msCrypto: Crypto } } class Utils { static createGuid(): string { const crypto = window.crypto || window.msCrypto function randomDigit() { if (crypto && crypto.getRandomValues) { const rands = new Uint8Array(1) crypto.getRandomValues(rands) return (rands[0] % 16).toString(16) } return (Math.floor((Math.random() * 16))).toString(16) } return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit) } static htmlToElement(html: string): HTMLElement { const template = document.createElement('template') template.innerHTML = html.trim() return template.content.firstChild as HTMLElement } static getElementById(elementId: string): HTMLElement { const element = document.getElementById(elementId) Utils.assert(element, `getElementById "${elementId}$`) return element! } static htmlEncode(text: string): string { return String(text).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') } static htmlDecode(text: string): string { return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') } // Markdown static htmlFromMarkdown(text: string): string { // HACKHACK: Somehow, marked doesn't encode angle brackets const renderer = new marked.Renderer() renderer.link = (href, title, contents) => `${contents}` const html = marked(text.replace(/${icon}` : '' const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement link.type = 'image/x-icon' link.rel = 'shortcut icon' link.href = href document.getElementsByTagName('head')[0].appendChild(link) } // URL static replaceUrlQueryParam(paramName: string, value?: string): void { const queryString = new URLSearchParams(window.location.search) const currentValue = queryString.get(paramName) || '' if (currentValue !== value) { const newUrl = new URL(window.location.toString()) if (value) { newUrl.searchParams.set(paramName, value) } else { newUrl.searchParams.delete(paramName) } window.history.pushState({}, document.title, newUrl.toString()) } } // File names static sanitizeFilename(filename: string): string { // TODO: Use an industrial-strength sanitizer let sanitizedFilename = filename const illegalCharacters = ['\\', '/', '?', ':', '<', '>', '*', '|', '"', '.'] illegalCharacters.forEach((character) => { sanitizedFilename = sanitizedFilename.replace(character, '') }) return sanitizedFilename } // File picker static selectLocalFile(onSelect?: (file: File) => void, accept = '.jpg,.jpeg,.png'): void { const input = document.createElement('input') input.type = 'file' input.accept = accept input.onchange = async () => { const file = input.files![0] onSelect?.(file) } input.style.display = 'none' document.body.appendChild(input) input.click() // TODO: Remove or reuse input } // Arrays static arraysEqual(a: readonly any[], b: readonly any[]): boolean { if (a === b) { return true } if (a === null || b === null) { return false } if (a === undefined || b === undefined) { return false } if (a.length !== b.length) { return false } for (let i = 0; i < a.length; ++i) { if (a[i] !== b[i]) { return false } } return true } } export {Utils}