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