first commit
This commit is contained in:
52
services/web/frontend/js/shared/utils/colors.ts
Normal file
52
services/web/frontend/js/shared/utils/colors.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { generateMD5Hash } from './md5'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const ANONYMOUS_HUE = 100
|
||||
const OWN_HUE = 200 // We will always appear as this color to ourselves
|
||||
const OWN_HUE_BLOCKED_SIZE = 20 // no other user should have a HUE in this range
|
||||
const TOTAL_HUES = 360 // actually 361, but 360 for legacy reasons
|
||||
|
||||
export function getHueForUserId(userId?: string): number {
|
||||
if (userId == null || userId === 'anonymous-user') {
|
||||
return ANONYMOUS_HUE
|
||||
}
|
||||
|
||||
if (getMeta('ol-user_id') === userId) {
|
||||
return OWN_HUE
|
||||
}
|
||||
|
||||
let hue = getHueForId(userId)
|
||||
|
||||
// if `hue` is within `OWN_HUE_BLOCKED_SIZE` degrees of the personal hue
|
||||
// (`OWN_HUE`), shift `hue` to the end of available hues by adding
|
||||
if (
|
||||
hue > OWN_HUE - OWN_HUE_BLOCKED_SIZE &&
|
||||
hue < OWN_HUE + OWN_HUE_BLOCKED_SIZE
|
||||
) {
|
||||
hue = hue - OWN_HUE // `hue` now at 0 +/- `OWN_HUE_BLOCKED_SIZE`
|
||||
hue = hue + TOTAL_HUES - OWN_HUE_BLOCKED_SIZE
|
||||
}
|
||||
|
||||
return hue
|
||||
}
|
||||
|
||||
export function getBackgroundColorForUserId(userId?: string) {
|
||||
return `hsl(${getHueForUserId(userId)}, 70%, 50%)`
|
||||
}
|
||||
|
||||
const cachedHues = new Map()
|
||||
|
||||
export function getHueForId(id: string) {
|
||||
if (cachedHues.has(id)) {
|
||||
return cachedHues.get(id)
|
||||
}
|
||||
|
||||
const hash = generateMD5Hash(id)
|
||||
|
||||
const hue =
|
||||
parseInt(hash.slice(0, 8), 16) % (TOTAL_HUES - OWN_HUE_BLOCKED_SIZE * 2)
|
||||
|
||||
cachedHues.set(id, hue)
|
||||
|
||||
return hue
|
||||
}
|
41
services/web/frontend/js/shared/utils/crypto.js
Normal file
41
services/web/frontend/js/shared/utils/crypto.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* From: https://github.com/pvorb/node-crypt/blob/master/crypt.js
|
||||
* Copyright © 2011, Paul Vorbach. All rights reserved.
|
||||
* Copyright © 2009, Jeff Mott. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
* 3. Neither the name Crypto-JS nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Convert a byte array to big-endian 32-bit words
|
||||
export function bytesToWords(bytes) {
|
||||
const words = []
|
||||
for (let i = 0, b = 0; i < bytes.length; i++, b += 8)
|
||||
words[b >>> 5] |= bytes[i] << (24 - (b % 32))
|
||||
return words
|
||||
}
|
||||
|
||||
// Convert big-endian 32-bit words to a byte array
|
||||
export function wordsToBytes(words) {
|
||||
const bytes = []
|
||||
for (let b = 0; b < words.length * 32; b += 8)
|
||||
bytes.push((words[b >>> 5] >>> (24 - (b % 32))) & 0xff)
|
||||
return bytes
|
||||
}
|
28
services/web/frontend/js/shared/utils/currency.ts
Normal file
28
services/web/frontend/js/shared/utils/currency.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const DEFAULT_LOCALE = getMeta('ol-i18n')?.currentLangCode ?? 'en'
|
||||
|
||||
export function formatCurrency(
|
||||
amount: number,
|
||||
currency: string,
|
||||
locale: string = DEFAULT_LOCALE,
|
||||
stripIfInteger = false
|
||||
): string {
|
||||
const options: Intl.NumberFormatOptions = { style: 'currency', currency }
|
||||
if (stripIfInteger && Number.isInteger(amount)) {
|
||||
options.minimumFractionDigits = 0
|
||||
}
|
||||
|
||||
try {
|
||||
return amount.toLocaleString(locale, {
|
||||
...options,
|
||||
currencyDisplay: 'narrowSymbol',
|
||||
})
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
return amount.toLocaleString(locale, options)
|
||||
} catch {}
|
||||
|
||||
return `${currency} ${amount}`
|
||||
}
|
12
services/web/frontend/js/shared/utils/email.tsx
Normal file
12
services/web/frontend/js/shared/utils/email.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copied from backend code: https://github.com/overleaf/internal/blob/6af8ae850bd8075e6bf0ebcafd2731177cdf49ad/services/web/app/src/Features/Helpers/EmailHelper.js#L5
|
||||
const EMAIL_REGEXP =
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
/^([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
export function isValidEmail(email: string | undefined | null) {
|
||||
if (!email) {
|
||||
return false
|
||||
} else {
|
||||
return EMAIL_REGEXP.test(email)
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type ExternalScriptLoaderProps = {
|
||||
children: JSX.Element
|
||||
src: string
|
||||
}
|
||||
|
||||
function ExternalScriptLoader({ children, src }: ExternalScriptLoaderProps) {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const body = document.querySelector('body')
|
||||
const script = document.createElement('script')
|
||||
|
||||
script.async = true
|
||||
script.src = src
|
||||
script.onload = () => {
|
||||
setLoaded(true)
|
||||
}
|
||||
|
||||
body?.appendChild(script)
|
||||
|
||||
return () => {
|
||||
body?.removeChild(script)
|
||||
}
|
||||
}, [src])
|
||||
|
||||
return loaded ? children : null
|
||||
}
|
||||
|
||||
export default ExternalScriptLoader
|
9
services/web/frontend/js/shared/utils/formatDate.ts
Normal file
9
services/web/frontend/js/shared/utils/formatDate.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import moment from 'moment'
|
||||
|
||||
export function formatUtcDate(date: moment.MomentInput) {
|
||||
if (date) {
|
||||
return moment(date).utc().format('D MMM YYYY, HH:mm:ss') + ' UTC'
|
||||
} else {
|
||||
return 'N/A'
|
||||
}
|
||||
}
|
3
services/web/frontend/js/shared/utils/grammarly.ts
Normal file
3
services/web/frontend/js/shared/utils/grammarly.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function grammarlyExtensionPresent() {
|
||||
return !!document.querySelector('grammarly-desktop-integration')
|
||||
}
|
179
services/web/frontend/js/shared/utils/md5.ts
Normal file
179
services/web/frontend/js/shared/utils/md5.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Inspired by https://stackoverflow.com/a/60467595/237917
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
|
||||
export function generateMD5Hash(inputString: string): string {
|
||||
const hc = '0123456789abcdef'
|
||||
function rh(n: number) {
|
||||
let j
|
||||
let s = ''
|
||||
for (j = 0; j <= 3; j++)
|
||||
s +=
|
||||
hc.charAt((n >> (j * 8 + 4)) & 0x0f) + hc.charAt((n >> (j * 8)) & 0x0f)
|
||||
return s
|
||||
}
|
||||
function ad(x: number, y: number) {
|
||||
const l = (x & 0xffff) + (y & 0xffff)
|
||||
const m = (x >> 16) + (y >> 16) + (l >> 16)
|
||||
return (m << 16) | (l & 0xffff)
|
||||
}
|
||||
function rl(n: number, c: number) {
|
||||
return (n << c) | (n >>> (32 - c))
|
||||
}
|
||||
function cm(
|
||||
q: number,
|
||||
a: number,
|
||||
b: number,
|
||||
x: number,
|
||||
s: number,
|
||||
t: number
|
||||
) {
|
||||
return ad(rl(ad(ad(a, q), ad(x, t)), s), b)
|
||||
}
|
||||
function ff(
|
||||
a: number,
|
||||
b: number,
|
||||
c: number,
|
||||
d: number,
|
||||
x: number,
|
||||
s: number,
|
||||
t: number
|
||||
) {
|
||||
return cm((b & c) | (~b & d), a, b, x, s, t)
|
||||
}
|
||||
function gg(
|
||||
a: number,
|
||||
b: number,
|
||||
c: number,
|
||||
d: number,
|
||||
x: number,
|
||||
s: number,
|
||||
t: number
|
||||
) {
|
||||
return cm((b & d) | (c & ~d), a, b, x, s, t)
|
||||
}
|
||||
function hh(
|
||||
a: number,
|
||||
b: number,
|
||||
c: number,
|
||||
d: number,
|
||||
x: number,
|
||||
s: number,
|
||||
t: number
|
||||
) {
|
||||
return cm(b ^ c ^ d, a, b, x, s, t)
|
||||
}
|
||||
function ii(
|
||||
a: number,
|
||||
b: number,
|
||||
c: number,
|
||||
d: number,
|
||||
x: number,
|
||||
s: number,
|
||||
t: number
|
||||
) {
|
||||
return cm(c ^ (b | ~d), a, b, x, s, t)
|
||||
}
|
||||
function sb(x: string) {
|
||||
let i
|
||||
const nblk = ((x.length + 8) >> 6) + 1
|
||||
const blks = new Array(nblk * 16)
|
||||
for (i = 0; i < nblk * 16; i++) blks[i] = 0
|
||||
for (i = 0; i < x.length; i++)
|
||||
blks[i >> 2] |= x.charCodeAt(i) << ((i % 4) * 8)
|
||||
blks[i >> 2] |= 0x80 << ((i % 4) * 8)
|
||||
blks[nblk * 16 - 2] = x.length * 8
|
||||
return blks
|
||||
}
|
||||
let i
|
||||
const x = sb('' + inputString)
|
||||
let a = 1732584193
|
||||
let b = -271733879
|
||||
let c = -1732584194
|
||||
let d = 271733878
|
||||
let olda
|
||||
let oldb
|
||||
let oldc
|
||||
let oldd
|
||||
for (i = 0; i < x.length; i += 16) {
|
||||
olda = a
|
||||
oldb = b
|
||||
oldc = c
|
||||
oldd = d
|
||||
a = ff(a, b, c, d, x[i + 0], 7, -680876936)
|
||||
d = ff(d, a, b, c, x[i + 1], 12, -389564586)
|
||||
c = ff(c, d, a, b, x[i + 2], 17, 606105819)
|
||||
b = ff(b, c, d, a, x[i + 3], 22, -1044525330)
|
||||
a = ff(a, b, c, d, x[i + 4], 7, -176418897)
|
||||
d = ff(d, a, b, c, x[i + 5], 12, 1200080426)
|
||||
c = ff(c, d, a, b, x[i + 6], 17, -1473231341)
|
||||
b = ff(b, c, d, a, x[i + 7], 22, -45705983)
|
||||
a = ff(a, b, c, d, x[i + 8], 7, 1770035416)
|
||||
d = ff(d, a, b, c, x[i + 9], 12, -1958414417)
|
||||
c = ff(c, d, a, b, x[i + 10], 17, -42063)
|
||||
b = ff(b, c, d, a, x[i + 11], 22, -1990404162)
|
||||
a = ff(a, b, c, d, x[i + 12], 7, 1804603682)
|
||||
d = ff(d, a, b, c, x[i + 13], 12, -40341101)
|
||||
c = ff(c, d, a, b, x[i + 14], 17, -1502002290)
|
||||
b = ff(b, c, d, a, x[i + 15], 22, 1236535329)
|
||||
a = gg(a, b, c, d, x[i + 1], 5, -165796510)
|
||||
d = gg(d, a, b, c, x[i + 6], 9, -1069501632)
|
||||
c = gg(c, d, a, b, x[i + 11], 14, 643717713)
|
||||
b = gg(b, c, d, a, x[i + 0], 20, -373897302)
|
||||
a = gg(a, b, c, d, x[i + 5], 5, -701558691)
|
||||
d = gg(d, a, b, c, x[i + 10], 9, 38016083)
|
||||
c = gg(c, d, a, b, x[i + 15], 14, -660478335)
|
||||
b = gg(b, c, d, a, x[i + 4], 20, -405537848)
|
||||
a = gg(a, b, c, d, x[i + 9], 5, 568446438)
|
||||
d = gg(d, a, b, c, x[i + 14], 9, -1019803690)
|
||||
c = gg(c, d, a, b, x[i + 3], 14, -187363961)
|
||||
b = gg(b, c, d, a, x[i + 8], 20, 1163531501)
|
||||
a = gg(a, b, c, d, x[i + 13], 5, -1444681467)
|
||||
d = gg(d, a, b, c, x[i + 2], 9, -51403784)
|
||||
c = gg(c, d, a, b, x[i + 7], 14, 1735328473)
|
||||
b = gg(b, c, d, a, x[i + 12], 20, -1926607734)
|
||||
a = hh(a, b, c, d, x[i + 5], 4, -378558)
|
||||
d = hh(d, a, b, c, x[i + 8], 11, -2022574463)
|
||||
c = hh(c, d, a, b, x[i + 11], 16, 1839030562)
|
||||
b = hh(b, c, d, a, x[i + 14], 23, -35309556)
|
||||
a = hh(a, b, c, d, x[i + 1], 4, -1530992060)
|
||||
d = hh(d, a, b, c, x[i + 4], 11, 1272893353)
|
||||
c = hh(c, d, a, b, x[i + 7], 16, -155497632)
|
||||
b = hh(b, c, d, a, x[i + 10], 23, -1094730640)
|
||||
a = hh(a, b, c, d, x[i + 13], 4, 681279174)
|
||||
d = hh(d, a, b, c, x[i + 0], 11, -358537222)
|
||||
c = hh(c, d, a, b, x[i + 3], 16, -722521979)
|
||||
b = hh(b, c, d, a, x[i + 6], 23, 76029189)
|
||||
a = hh(a, b, c, d, x[i + 9], 4, -640364487)
|
||||
d = hh(d, a, b, c, x[i + 12], 11, -421815835)
|
||||
c = hh(c, d, a, b, x[i + 15], 16, 530742520)
|
||||
b = hh(b, c, d, a, x[i + 2], 23, -995338651)
|
||||
a = ii(a, b, c, d, x[i + 0], 6, -198630844)
|
||||
d = ii(d, a, b, c, x[i + 7], 10, 1126891415)
|
||||
c = ii(c, d, a, b, x[i + 14], 15, -1416354905)
|
||||
b = ii(b, c, d, a, x[i + 5], 21, -57434055)
|
||||
a = ii(a, b, c, d, x[i + 12], 6, 1700485571)
|
||||
d = ii(d, a, b, c, x[i + 3], 10, -1894986606)
|
||||
c = ii(c, d, a, b, x[i + 10], 15, -1051523)
|
||||
b = ii(b, c, d, a, x[i + 1], 21, -2054922799)
|
||||
a = ii(a, b, c, d, x[i + 8], 6, 1873313359)
|
||||
d = ii(d, a, b, c, x[i + 15], 10, -30611744)
|
||||
c = ii(c, d, a, b, x[i + 6], 15, -1560198380)
|
||||
b = ii(b, c, d, a, x[i + 13], 21, 1309151649)
|
||||
a = ii(a, b, c, d, x[i + 4], 6, -145523070)
|
||||
d = ii(d, a, b, c, x[i + 11], 10, -1120210379)
|
||||
c = ii(c, d, a, b, x[i + 2], 15, 718787259)
|
||||
b = ii(b, c, d, a, x[i + 9], 21, -343485551)
|
||||
a = ad(a, olda)
|
||||
b = ad(b, oldb)
|
||||
c = ad(c, oldc)
|
||||
d = ad(d, oldd)
|
||||
}
|
||||
return rh(a) + rh(b) + rh(c) + rh(d)
|
||||
}
|
1
services/web/frontend/js/shared/utils/os.ts
Normal file
1
services/web/frontend/js/shared/utils/os.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const isMac = /Mac/.test(window.navigator?.platform)
|
99
services/web/frontend/js/shared/utils/sha1.js
Normal file
99
services/web/frontend/js/shared/utils/sha1.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* From https://github.com/pvorb/node-sha1/blob/master/sha1.js
|
||||
* Copyright © 2009, Jeff Mott. All rights reserved.
|
||||
* Copyright © 2011, Paul Vorbach. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
* 3. Neither the name Crypto-JS nor the names of its contributors may be used to
|
||||
* endorse or promote products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import { wordsToBytes, bytesToWords } from './crypto'
|
||||
|
||||
export function generateSHA1Hash(inputString) {
|
||||
const encoder = new TextEncoder()
|
||||
const uint8Array = encoder.encode(inputString)
|
||||
|
||||
const m = bytesToWords(uint8Array)
|
||||
const l = uint8Array.length * 8
|
||||
const w = []
|
||||
let H0 = 1732584193
|
||||
let H1 = -271733879
|
||||
let H2 = -1732584194
|
||||
let H3 = 271733878
|
||||
let H4 = -1009589776
|
||||
|
||||
// Padding
|
||||
m[l >> 5] |= 0x80 << (24 - (l % 32))
|
||||
m[(((l + 64) >>> 9) << 4) + 15] = l
|
||||
|
||||
for (let i = 0; i < m.length; i += 16) {
|
||||
const a = H0
|
||||
const b = H1
|
||||
const c = H2
|
||||
const d = H3
|
||||
const e = H4
|
||||
|
||||
for (let j = 0; j < 80; j++) {
|
||||
if (j < 16) w[j] = m[i + j]
|
||||
else {
|
||||
const n = w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16]
|
||||
w[j] = (n << 1) | (n >>> 31)
|
||||
}
|
||||
|
||||
const t =
|
||||
((H0 << 5) | (H0 >>> 27)) +
|
||||
H4 +
|
||||
(w[j] >>> 0) +
|
||||
(j < 20
|
||||
? ((H1 & H2) | (~H1 & H3)) + 1518500249
|
||||
: j < 40
|
||||
? (H1 ^ H2 ^ H3) + 1859775393
|
||||
: j < 60
|
||||
? ((H1 & H2) | (H1 & H3) | (H2 & H3)) - 1894007588
|
||||
: (H1 ^ H2 ^ H3) - 899497514)
|
||||
|
||||
H4 = H3
|
||||
H3 = H2
|
||||
H2 = (H1 << 30) | (H1 >>> 2)
|
||||
H1 = H0
|
||||
H0 = t
|
||||
}
|
||||
|
||||
H0 += a
|
||||
H1 += b
|
||||
H2 += c
|
||||
H3 += d
|
||||
H4 += e
|
||||
}
|
||||
|
||||
const result = wordsToBytes([H0, H1, H2, H3, H4])
|
||||
|
||||
// Convert array of bytes to a hex string
|
||||
// padStart is used to ensure numbers that are
|
||||
// less than 16 will still be converted into the two-character format
|
||||
// For example:
|
||||
// "5" => "05"
|
||||
// "a" => "0a"
|
||||
// "ff" => "ff"
|
||||
return result.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
}
|
29
services/web/frontend/js/shared/utils/styles.ts
Normal file
29
services/web/frontend/js/shared/utils/styles.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type OverallTheme = '' | 'light-'
|
||||
|
||||
export const fontFamilies = {
|
||||
monaco: ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'monospace'],
|
||||
lucida: ['Lucida Console', 'Source Code Pro', 'monospace'],
|
||||
opendyslexicmono: ['OpenDyslexic Mono', 'monospace'],
|
||||
}
|
||||
|
||||
export type FontFamily = keyof typeof fontFamilies
|
||||
|
||||
export const lineHeights = {
|
||||
compact: 1.33,
|
||||
normal: 1.6,
|
||||
wide: 2,
|
||||
}
|
||||
|
||||
export type LineHeight = keyof typeof lineHeights
|
||||
|
||||
type Options = {
|
||||
fontFamily: FontFamily
|
||||
fontSize: number
|
||||
lineHeight: LineHeight
|
||||
}
|
||||
|
||||
export const userStyles = ({ fontFamily, fontSize, lineHeight }: Options) => ({
|
||||
fontFamily: fontFamilies[fontFamily]?.join(','),
|
||||
fontSize: `${fontSize}px`,
|
||||
lineHeight: lineHeights[lineHeight],
|
||||
})
|
14
services/web/frontend/js/shared/utils/url-helper.ts
Normal file
14
services/web/frontend/js/shared/utils/url-helper.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function buildUrlWithDetachRole(mode: string | null) {
|
||||
return cleanURL(new URL(window.location.href), mode)
|
||||
}
|
||||
|
||||
export function cleanURL(url: URL, mode: string | null) {
|
||||
let cleanPathname = url.pathname
|
||||
.replace(/\/(detached|detacher)\/?$/, '')
|
||||
.replace(/\/$/, '')
|
||||
if (mode) {
|
||||
cleanPathname += `/${mode}`
|
||||
}
|
||||
url.pathname = cleanPathname
|
||||
return url
|
||||
}
|
Reference in New Issue
Block a user