From d81a9ce25cc12cd06bca1bbc04f91b1ad2e2e4d9 Mon Sep 17 00:00:00 2001 From: Eli Kinsey Date: Thu, 19 Sep 2024 19:30:22 -0700 Subject: [PATCH 01/14] mentions --- src/components/Squeak/components/RichText.tsx | 423 +++++++++++------- 1 file changed, 271 insertions(+), 152 deletions(-) diff --git a/src/components/Squeak/components/RichText.tsx b/src/components/Squeak/components/RichText.tsx index a50093bc6679..ed0868011e43 100644 --- a/src/components/Squeak/components/RichText.tsx +++ b/src/components/Squeak/components/RichText.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, useEffect, useRef, useState, useCallback } from 'react' +import React, { ChangeEvent, useEffect, useRef, useState, useCallback, useContext } from 'react' import MarkdownLogo from './MarkdownLogo' import { useDropzone } from 'react-dropzone' import Spinner from 'components/Spinner' @@ -7,6 +7,9 @@ import slugify from 'slugify' import { Edit } from 'components/Icons' import Tooltip from 'components/Tooltip' import { isURL } from 'lib/utils' +import { CurrentQuestionContext } from './Question' +import Avatar from './Avatar' +import { AnimatePresence, motion } from 'framer-motion' const buttons = [ { @@ -78,6 +81,77 @@ const buttons = [ }, ] +const MentionProfiles = ({ onSelect }) => { + const currentQuestion = useContext(CurrentQuestionContext) ?? {} + const replies = currentQuestion?.question?.replies + const mentionProfiles = [ + { attributes: { profile: { data: currentQuestion?.question?.profile?.data } } }, + ...replies?.data, + ] + .map((reply) => reply?.attributes?.profile?.data) + .filter((profile, index, self) => profile && self.findIndex((p) => p?.id === profile.id) === index) + const listRef = useRef(null) + const [focused, setFocused] = useState(0) + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'ArrowDown') { + e.preventDefault() + setFocused((prev) => (prev + 1) % mentionProfiles.length) + } + if (e.key === 'ArrowUp') { + e.preventDefault() + setFocused((prev) => (prev - 1 + mentionProfiles.length) % mentionProfiles.length) + } + if (e.key === 'Tab' || e.key === 'Enter') { + e.preventDefault() + onSelect?.(mentionProfiles[focused]) + } + } + + window.addEventListener('keydown', handleKeyDown) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, [focused]) + + return ( + +
    + {mentionProfiles.map((profile, index) => { + const { firstName, lastName, avatar } = profile.attributes + const name = [firstName, lastName].filter(Boolean).join(' ') + return ( +
  • + +
  • + ) + })} +
+
+ ) +} + export default function RichText({ initialValue = '', setFieldValue, @@ -93,6 +167,8 @@ export default function RichText({ const [cursor, setCursor] = useState(null) const [imageLoading, setImageLoading] = useState(false) const [showPreview, setShowPreview] = useState(false) + const [showMentionProfiles, setShowMentionProfiles] = useState(false) + const mentionProfilesRef = useRef(null) const onDrop = useCallback( async (acceptedFiles) => { @@ -188,167 +264,210 @@ export default function RichText({ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && onSubmit) { onSubmit() } + if (e.key === '@' && e.shiftKey) { + setShowMentionProfiles(true) + } + } + + const handleContainerClick = (e) => { + if (!e.target.contains(mentionProfilesRef.current)) { + setShowMentionProfiles(false) + } + } + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' || e.key === 'Backspace') { + setShowMentionProfiles(false) + } + } + + window.addEventListener('keydown', handleKeyDown) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, []) + + const handleProfileSelect = (profile) => { + setValue((prevValue) => prevValue + `${profile.id} `) + setShowMentionProfiles(false) } return (
- - {showPreview ? ( -
- { - const objectURL = values.images.find( - (image) => image.fakeImagePath === fakeImagePath - )?.objectURL - return objectURL || fakeImagePath - }} - > - {value} - -
- ) : ( -
-