focalboard/webapp/src/utils.ts

192 lines
5.6 KiB
TypeScript
Raw Normal View History

2020-10-20 21:50:53 +02:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
2020-10-20 21:52:56 +02:00
import marked from 'marked'
2020-10-08 18:21:27 +02:00
declare global {
2020-10-20 21:50:53 +02:00
interface Window {
msCrypto: Crypto
}
2020-10-08 18:21:27 +02:00
}
class Utils {
2020-10-20 22:26:06 +02:00
static createGuid(): string {
2020-10-20 21:50:53 +02:00
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')
2020-10-20 22:26:06 +02:00
template.innerHTML = html.trim()
2020-10-20 21:50:53 +02:00
return template.content.firstChild as HTMLElement
}
static getElementById(elementId: string): HTMLElement {
const element = document.getElementById(elementId)
Utils.assert(element, `getElementById "${elementId}$`)
return element!
}
2020-10-20 22:26:06 +02:00
static htmlEncode(text: string): string {
2020-10-20 21:50:53 +02:00
return String(text).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
2020-10-20 22:26:06 +02:00
static htmlDecode(text: string): string {
2020-10-20 21:50:53 +02:00
return String(text).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"')
}
// Markdown
static htmlFromMarkdown(text: string): string {
// HACKHACK: Somehow, marked doesn't encode angle brackets
2020-10-22 01:06:11 +02:00
const renderer = new marked.Renderer()
renderer.link = ( href, title, text ) => `<a target="_blank" href="${ href }" title="${ title || '' }">${ text }</a>`
const html = marked(text.replace(/</g, '&lt;'), { renderer })
2020-10-20 21:50:53 +02:00
return html
}
// Date and Time
static displayDate(date: Date): string {
const dateTimeFormat = new Intl.DateTimeFormat('en', {year: 'numeric', month: 'short', day: '2-digit'})
const text = dateTimeFormat.format(date)
return text
}
static displayDateTime(date: Date): string {
const dateTimeFormat = new Intl.DateTimeFormat(
'en',
{
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
})
const text = dateTimeFormat.format(date)
return text
}
// Errors
2020-10-20 22:26:06 +02:00
static assertValue(valueObject: any): void {
2020-10-20 21:50:53 +02:00
const name = Object.keys(valueObject)[0]
const value = valueObject[name]
if (!value) {
Utils.logError(`ASSERT VALUE [${name}]`)
}
}
2020-10-20 22:26:06 +02:00
static assert(condition: any, tag = ''): void {
2020-10-20 21:50:53 +02:00
/// #!if ENV !== "production"
if (!condition) {
Utils.logError(`ASSERT [${tag ?? new Error().stack}]`)
}
/// #!endif
}
2020-10-20 22:26:06 +02:00
static assertFailure(tag = ''): void {
2020-10-20 21:50:53 +02:00
/// #!if ENV !== "production"
Utils.assert(false, tag)
/// #!endif
}
2020-10-20 22:26:06 +02:00
static log(message: string): void {
2020-10-20 21:50:53 +02:00
/// #!if ENV !== "production"
const timestamp = (Date.now() / 1000).toFixed(2)
2020-10-20 22:26:06 +02:00
// eslint-disable-next-line no-console
2020-10-20 21:50:53 +02:00
console.log(`[${timestamp}] ${message}`)
/// #!endif
}
2020-10-20 22:26:06 +02:00
static logError(message: string): void {
2020-10-20 21:50:53 +02:00
/// #!if ENV !== "production"
const timestamp = (Date.now() / 1000).toFixed(2)
2020-10-20 22:26:06 +02:00
// eslint-disable-next-line no-console
2020-10-20 21:50:53 +02:00
console.error(`[${timestamp}] ${message}`)
/// #!endif
}
// favicon
2020-10-20 22:26:06 +02:00
static setFavicon(icon?: string): void {
2020-10-20 21:50:53 +02:00
const href = icon ?
`data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">${icon}</text></svg>` :
2020-10-20 21:52:56 +02:00
''
2020-10-20 21:50:53 +02:00
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement
2020-10-20 21:52:56 +02:00
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
2020-10-20 21:50:53 +02:00
link.href = href
document.getElementsByTagName('head')[0].appendChild(link)
}
// File names
2020-10-20 22:26:06 +02:00
static sanitizeFilename(filename: string): string {
2020-10-20 21:50:53 +02:00
// TODO: Use an industrial-strength sanitizer
let sanitizedFilename = filename
const illegalCharacters = ['\\', '/', '?', ':', '<', '>', '*', '|', '"', '.']
illegalCharacters.forEach((character) => {
sanitizedFilename = sanitizedFilename.replace(character, '')
2020-10-20 21:52:56 +02:00
})
2020-10-20 21:50:53 +02:00
return sanitizedFilename
}
// File picker
static selectLocalFile(onSelect?: (file: File) => void, accept = '.jpg,.jpeg,.png'): void {
const input = document.createElement('input')
2020-10-20 21:52:56 +02:00
input.type = 'file'
2020-10-20 21:50:53 +02:00
input.accept = accept
input.onchange = async () => {
const file = input.files![0]
onSelect?.(file)
2020-10-20 21:52:56 +02:00
}
2020-10-20 21:50:53 +02:00
2020-10-20 21:52:56 +02:00
input.style.display = 'none'
2020-10-20 21:50:53 +02:00
document.body.appendChild(input)
input.click()
// TODO: Remove or reuse input
}
// Arrays
2020-10-20 22:26:06 +02:00
static arraysEqual(a: any[], b: any[]): boolean {
2020-10-20 21:50:53 +02:00
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
}
2020-10-08 18:21:27 +02:00
}
2020-10-20 21:50:53 +02:00
export {Utils}