mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import { useRef, useEffect, type FC, useCallback, useState } from 'react'
|
|
import Linkify from 'react-linkify'
|
|
import useIsMounted from '../../../shared/hooks/use-is-mounted'
|
|
import { loadMathJax } from '../../mathjax/load-mathjax'
|
|
import { debugConsole } from '@/utils/debugging'
|
|
import { Message, useChatContext } from '@/features/chat/context/chat-context'
|
|
import OLButton from '@/shared/components/ol/ol-button'
|
|
import { useTranslation } from 'react-i18next'
|
|
import AutoExpandingTextArea from '@/shared/components/auto-expanding-text-area'
|
|
|
|
const MessageContent: FC<{
|
|
content: Message['content']
|
|
messageId: Message['id']
|
|
edited: Message['edited']
|
|
}> = ({ content, messageId, edited }) => {
|
|
const { t } = useTranslation()
|
|
const root = useRef<HTMLDivElement | null>(null)
|
|
const mounted = useIsMounted()
|
|
const { idOfMessageBeingEdited, cancelMessageEdit, editMessage } =
|
|
useChatContext()
|
|
const [editedContent, setEditedContent] = useState(content)
|
|
|
|
const editing = idOfMessageBeingEdited === messageId
|
|
|
|
useEffect(() => {
|
|
if (root.current) {
|
|
// adds attributes to all the links generated by <Linkify/>, required due to https://github.com/tasti/react-linkify/issues/99
|
|
for (const a of root.current.getElementsByTagName('a')) {
|
|
a.setAttribute('target', '_blank')
|
|
a.setAttribute('rel', 'noreferrer noopener')
|
|
}
|
|
|
|
// MathJax v3 typesetting
|
|
loadMathJax()
|
|
.then(async MathJax => {
|
|
if (mounted.current) {
|
|
const element = root.current
|
|
try {
|
|
await MathJax.typesetPromise([element])
|
|
MathJax.typesetClear([element])
|
|
} catch (error) {
|
|
debugConsole.error(error)
|
|
}
|
|
}
|
|
})
|
|
.catch(debugConsole.error)
|
|
}
|
|
}, [content, mounted])
|
|
|
|
const completeEdit = useCallback(() => {
|
|
editMessage(messageId, editedContent)
|
|
}, [editMessage, editedContent, messageId])
|
|
|
|
const handleKeyDown = useCallback(
|
|
(e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault()
|
|
completeEdit()
|
|
} else if (e.key === 'Escape') {
|
|
e.preventDefault()
|
|
cancelMessageEdit()
|
|
setEditedContent(content)
|
|
}
|
|
},
|
|
[cancelMessageEdit, completeEdit, content]
|
|
)
|
|
|
|
const handleChange = useCallback(
|
|
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
setEditedContent(e.target.value)
|
|
},
|
|
[]
|
|
)
|
|
|
|
const handleAutoFocus = useCallback(
|
|
(textarea: HTMLTextAreaElement) => textarea.select(),
|
|
[]
|
|
)
|
|
|
|
return editing ? (
|
|
<>
|
|
<AutoExpandingTextArea
|
|
value={editedContent}
|
|
style={{ width: '100%' }}
|
|
onKeyDown={handleKeyDown}
|
|
onChange={handleChange}
|
|
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
|
|
onAutoFocus={handleAutoFocus}
|
|
/>
|
|
<br />
|
|
<OLButton
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={() => {
|
|
cancelMessageEdit()
|
|
setEditedContent(content)
|
|
}}
|
|
>
|
|
{t('cancel')}
|
|
</OLButton>
|
|
<OLButton size="sm" variant="secondary" onClick={() => completeEdit()}>
|
|
{t('save')}
|
|
</OLButton>
|
|
</>
|
|
) : (
|
|
<p ref={root} translate="no">
|
|
<Linkify>{content}</Linkify>
|
|
{edited ? (
|
|
<>
|
|
{' '}
|
|
<span className="message-edited">({t('edited')})</span>
|
|
</>
|
|
) : null}
|
|
</p>
|
|
)
|
|
}
|
|
|
|
export default MessageContent
|