first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View 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
}

View 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
}

View 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}`
}

View 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)
}
}

View File

@@ -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

View 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'
}
}

View File

@@ -0,0 +1,3 @@
export default function grammarlyExtensionPresent() {
return !!document.querySelector('grammarly-desktop-integration')
}

View 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)
}

View File

@@ -0,0 +1 @@
export const isMac = /Mac/.test(window.navigator?.platform)

View 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('')
}

View 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],
})

View 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
}