mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
127 lines
3.5 KiB
TypeScript
127 lines
3.5 KiB
TypeScript
|
import {
|
||
|
ChangeEvent,
|
||
|
KeyboardEvent,
|
||
|
useCallback,
|
||
|
useEffect,
|
||
|
useState,
|
||
|
} from 'react'
|
||
|
import { getJSON } from '../../../../infrastructure/fetch-json'
|
||
|
import useAbortController from '../../../../shared/hooks/use-abort-controller'
|
||
|
|
||
|
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
|
||
|
|
||
|
function matchLocalAndDomain(emailHint: string) {
|
||
|
const match = emailHint.match(LOCAL_AND_DOMAIN_REGEX)
|
||
|
if (match) {
|
||
|
return { local: match[1], domain: match[2] }
|
||
|
} else {
|
||
|
return { local: null, domain: null }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type InstitutionInfo = { hostname: string; university: { id: number } }
|
||
|
|
||
|
let domainCache = new Map<string, InstitutionInfo>()
|
||
|
|
||
|
export function clearDomainCache() {
|
||
|
domainCache = new Map<string, InstitutionInfo>()
|
||
|
}
|
||
|
|
||
|
type AddEmailInputProps = {
|
||
|
onChange: (value: string, institution?: InstitutionInfo) => void
|
||
|
}
|
||
|
|
||
|
export function AddEmailInput({ onChange }: AddEmailInputProps) {
|
||
|
const { signal } = useAbortController()
|
||
|
|
||
|
const [suggestion, setSuggestion] = useState<string>(null)
|
||
|
const [inputValue, setInputValue] = useState<string>(null)
|
||
|
const [matchedInstitution, setMatchedInstitution] =
|
||
|
useState<InstitutionInfo>(null)
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (inputValue == null) {
|
||
|
return
|
||
|
}
|
||
|
if (matchedInstitution && suggestion === inputValue) {
|
||
|
onChange(inputValue, matchedInstitution)
|
||
|
} else {
|
||
|
onChange(inputValue)
|
||
|
}
|
||
|
}, [onChange, inputValue, suggestion, matchedInstitution])
|
||
|
|
||
|
const handleEmailChange = useCallback(
|
||
|
(event: ChangeEvent<HTMLInputElement>) => {
|
||
|
const hint = event.target.value
|
||
|
setInputValue(hint)
|
||
|
const match = matchLocalAndDomain(hint)
|
||
|
if (!matchedInstitution?.hostname.startsWith(match.domain)) {
|
||
|
setSuggestion(null)
|
||
|
}
|
||
|
if (!match.domain) {
|
||
|
return
|
||
|
}
|
||
|
if (domainCache.has(match.domain)) {
|
||
|
const cachedDomain = domainCache.get(match.domain)
|
||
|
setSuggestion(`${match.local}@${cachedDomain.hostname}`)
|
||
|
setMatchedInstitution(cachedDomain)
|
||
|
return
|
||
|
}
|
||
|
const query = `?hostname=${match.domain}&limit=1`
|
||
|
getJSON(`/institutions/domains${query}`, { signal })
|
||
|
.then(data => {
|
||
|
if (!(data && data[0])) {
|
||
|
return
|
||
|
}
|
||
|
const hostname = data[0]?.hostname
|
||
|
if (hostname) {
|
||
|
domainCache.set(match.domain, data[0])
|
||
|
setSuggestion(`${match.local}@${hostname}`)
|
||
|
setMatchedInstitution(data[0])
|
||
|
} else {
|
||
|
setSuggestion(null)
|
||
|
setMatchedInstitution(null)
|
||
|
}
|
||
|
})
|
||
|
.catch(error => {
|
||
|
setSuggestion(null)
|
||
|
setMatchedInstitution(null)
|
||
|
console.error(error)
|
||
|
})
|
||
|
},
|
||
|
[signal, matchedInstitution]
|
||
|
)
|
||
|
|
||
|
const handleKeyDownEvent = useCallback(
|
||
|
(event: KeyboardEvent) => {
|
||
|
if (event.key === 'Tab' || event.key === 'Enter') {
|
||
|
event.preventDefault()
|
||
|
if (suggestion) {
|
||
|
setInputValue(suggestion)
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
[suggestion]
|
||
|
)
|
||
|
|
||
|
return (
|
||
|
<div className="input-suggestions">
|
||
|
<div className="form-control input-suggestions-shadow">
|
||
|
<div className="input-suggestions-shadow-suggested">
|
||
|
{suggestion || ''}
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<input
|
||
|
id="affiliations-email"
|
||
|
className="form-control input-suggestions-main"
|
||
|
type="email"
|
||
|
onChange={handleEmailChange}
|
||
|
onKeyDown={handleKeyDownEvent}
|
||
|
value={inputValue || ''}
|
||
|
placeholder="e.g. johndoe@mit.edu"
|
||
|
/>
|
||
|
</div>
|
||
|
)
|
||
|
}
|