UNPKG

@replyke/core

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

131 lines 4.91 kB
import { useState, useEffect, useCallback, useRef } from "react"; import useFetchManySpaces from "./useFetchManySpaces"; import { handleError } from "../../utils/handleError"; function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } const useSpaceMentions = ({ content, setContent, focus, cursorPosition, isSelectionActive, trigger = "#", minChars = 3, debounceDelay = 1000, validPattern = "[\\w.\\-]+", }) => { const fetchManySpaces = useFetchManySpaces(); const [loadingState, setLoadingState] = useState(false); const [mentions, setMentions] = useState([]); const [isSpaceMentionActive, setIsSpaceMentionActive] = useState(false); const [mentionTrigger, setMentionTrigger] = useState(""); const [spaceMentionSuggestions, setSpaceMentionSuggestions] = useState([]); const debounceTimerRef = useRef(null); const resetSpaceMentions = () => { setMentions([]); setIsSpaceMentionActive(false); setMentionTrigger(""); setSpaceMentionSuggestions([]); setLoadingState(false); }; const addSpaceMention = (space) => { if (!space.slug) throw new Error("Space has no slug set"); setMentions((prevMentions) => { if (prevMentions.some((mention) => mention.id === space.id)) { return prevMentions; } return [ ...prevMentions, { id: space.id, slug: space.slug, type: "space", }, ]; }); }; const handleSpaceMentionClick = (space) => { const mentionRegex = new RegExp(`${escapeRegex(trigger)}${escapeRegex(mentionTrigger)}(\\s|$)`); setContent(content.replace(mentionRegex, `${trigger}${space.slug} `)); addSpaceMention(space); setIsSpaceMentionActive(false); setMentionTrigger(""); setSpaceMentionSuggestions([]); setLoadingState(false); focus(); }; const handleFetchSpaceSuggestions = useCallback(async (query) => { try { const result = await fetchManySpaces({ searchAny: query, limit: 5, }); if (result.data && result.data.length > 0) { setSpaceMentionSuggestions(result.data); } else { setSpaceMentionSuggestions([]); setIsSpaceMentionActive(false); } } catch (err) { handleError(err, "Error fetching space suggestions"); } finally { setLoadingState(false); } }, [fetchManySpaces]); useEffect(() => { let start = cursorPosition - 1; // Move backward from cursor to find the word directly before the cursor while (start >= 0 && content[start] !== " ") { start--; } // Extract potential trigger word (start + 1 because `start` is on the space) const potentialTrigger = content.slice(start + 1, cursorPosition); const validMentionPattern = new RegExp("^" + escapeRegex(trigger) + validPattern + "$"); if (!isSelectionActive && validMentionPattern.test(potentialTrigger) && potentialTrigger.length >= trigger.length + minChars) { const triggerText = potentialTrigger.slice(trigger.length); setMentionTrigger(triggerText); setIsSpaceMentionActive(true); setLoadingState(true); // Clear the previous debounce timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // Set a new debounce timer debounceTimerRef.current = setTimeout(() => { handleFetchSpaceSuggestions(triggerText); }, debounceDelay); } else { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } setMentionTrigger(""); setIsSpaceMentionActive(false); setSpaceMentionSuggestions([]); setLoadingState(false); } // Cleanup on component unmount to clear any remaining timer return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, [ cursorPosition, isSelectionActive, handleFetchSpaceSuggestions, content, trigger, minChars, debounceDelay, validPattern, ]); return { isSpaceMentionActive, loading: loadingState, spaceMentionSuggestions, handleSpaceMentionClick, mentions, addSpaceMention, resetSpaceMentions, }; }; export default useSpaceMentions; //# sourceMappingURL=useSpaceMentions.js.map