UNPKG

@contentstack/live-preview-utils

Version:

Contentstack provides the Live Preview SDK to establish a communication channel between the various Contentstack SDKs and your website, transmitting live changes to the preview pane.

423 lines (422 loc) 14.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/visualBuilder/hooks/useCommentTextArea.ts var useCommentTextArea_exports = {}; __export(useCommentTextArea_exports, { useCommentTextArea: () => useCommentTextArea }); module.exports = __toCommonJS(useCommentTextArea_exports); var import_hooks = require("preact/hooks"); var import_lodash_es = require("lodash-es"); var import_collabUtils = require("../utils/collabUtils.cjs"); var import_collab = require("../collab.style.cjs"); var import_constants = require("../utils/constants.cjs"); var import_ContextProvider = require("../components/Collab/ThreadPopup/ContextProvider/index.cjs"); var import_useDynamicTextareaRows = __toESM(require("../hooks/useDynamicTextareaRows.cjs"), 1); var initialState = { message: "", toUsers: [], images: [], createdBy: "", author: "" }; var useCommentTextArea = (userState, comment, onClose) => { const [state, setState] = (0, import_hooks.useState)(initialState); const [showSuggestions, setShowSuggestions] = (0, import_hooks.useState)(false); const [cursorPosition, setCursorPosition] = (0, import_hooks.useState)({ top: 0, left: 0, showAbove: false }); const [searchTerm, setSearchTerm] = (0, import_hooks.useState)(""); const [selectedIndex, setSelectedIndex] = (0, import_hooks.useState)(0); const [filteredUsers, setFilteredUsers] = (0, import_hooks.useState)([]); const inputRef = (0, import_hooks.useRef)(null); const listRef = (0, import_hooks.useRef)(null); const itemRefs = (0, import_hooks.useRef)([]); const { error, setError, onCreateComment, onEditComment, editComment, setThreadState, activeThread, setActiveThread, createNewThread } = (0, import_hooks.useContext)(import_ContextProvider.ThreadProvider); (0, import_useDynamicTextareaRows.default)( ".collab-thread-body--input--textarea", state.message ); (0, import_hooks.useEffect)(() => { itemRefs.current = itemRefs.current.slice( 0, userState.mentionsList.length ); }, [userState.mentionsList]); (0, import_hooks.useEffect)(() => { const filteredUsersList = userState.mentionsList.filter((user) => { if (!searchTerm) return true; return user.display.toLowerCase().includes(searchTerm.toLowerCase()); }); setFilteredUsers(filteredUsersList); }, [searchTerm, userState.mentionsList]); (0, import_hooks.useEffect)(() => { const textArea = document.getElementById( "collab-thread-body--input--textarea" ); if (!textArea) return; const baseClasses = { focus: { base: "collab-thread-body--input--textarea--focus", goober: (0, import_collab.collabStyles)()["collab-thread-body--input--textarea--focus"] }, hover: { base: "collab-thread-body--input--textarea--hover", goober: (0, import_collab.collabStyles)()["collab-thread-body--input--textarea--hover"] } }; const handleFocus = () => { textArea.classList.add( baseClasses.focus.base, baseClasses.focus.goober ); }; const handleBlur = () => { textArea.classList.remove( baseClasses.focus.base, baseClasses.focus.goober ); }; const handleMouseEnter = () => { textArea.classList.add( baseClasses.hover.base, baseClasses.hover.goober ); }; const handleMouseLeave = () => { textArea.classList.remove( baseClasses.hover.base, baseClasses.hover.goober ); }; textArea.addEventListener("focus", handleFocus); textArea.addEventListener("blur", handleBlur); textArea.addEventListener("mouseenter", handleMouseEnter); textArea.addEventListener("mouseleave", handleMouseLeave); return () => { textArea.removeEventListener("focus", handleFocus); textArea.removeEventListener("blur", handleBlur); textArea.removeEventListener("mouseenter", handleMouseEnter); textArea.removeEventListener("mouseleave", handleMouseLeave); }; }, []); (0, import_hooks.useEffect)(() => { if (!comment) return; const toUsers = []; comment?.toUsers?.forEach((userId) => { const user = userState.userMap[userId]; toUsers.push({ display: `${user.display || (0, import_collabUtils.getUserName)(user)}`, id: userId }); }); setState({ message: (0, import_collabUtils.getMessageWithDisplayName)(comment, userState, "text") ?? "", toUsers, images: comment?.images ?? [], createdBy: comment?.createdBy ?? "", author: comment?.author ?? "" }); }, [comment, userState]); const findMentionSearchPosition = (0, import_hooks.useCallback)( (text, cursorPos) => { const textBeforeCursor = text.slice(0, cursorPos); const atSymbolIndex = textBeforeCursor.lastIndexOf("@"); if (atSymbolIndex === -1) return null; const textBetweenAtAndCursor = textBeforeCursor.slice( atSymbolIndex + 1 ); if (textBetweenAtAndCursor.includes(" ")) return null; return { start: atSymbolIndex, searchTerm: textBetweenAtAndCursor }; }, [] ); const calculatePosition = (0, import_hooks.useCallback)( (textarea, cursorPosition2) => { const text = textarea?.value; const textBeforeCursor = text?.slice(0, cursorPosition2); const lines = textBeforeCursor?.split("\n"); const currentLineNumber = (lines?.length || 0) - 1; const currentLine = lines?.[currentLineNumber]; const style = window.getComputedStyle(textarea); const lineHeight = parseInt(style.lineHeight); const paddingLeft = parseInt(style.paddingLeft); const paddingTop = parseInt(style.paddingTop); const span = document.createElement("span"); span.style.font = style.font; span.style.visibility = "hidden"; span.style.position = "absolute"; span.style.whiteSpace = "pre"; span.textContent = currentLine ? currentLine : ""; document.body.appendChild(span); const left = Math.min( span.offsetWidth + paddingLeft, textarea.offsetWidth - 200 ); document.body.removeChild(span); const scrollTop = textarea.scrollTop; const currentLineY = currentLineNumber * lineHeight + paddingTop - scrollTop; const nextLineY = currentLineY + lineHeight; const viewportHeight = window.innerHeight; const suggestionsHeight = 160; const textareaRect = textarea.getBoundingClientRect(); const absoluteTop = textareaRect.top + nextLineY; const spaceBelow = viewportHeight - absoluteTop; const showAbove = spaceBelow < suggestionsHeight; const top = showAbove ? currentLineY : nextLineY; return { top, left, showAbove, absoluteTop, scrollTop, currentLineNumber }; }, [] ); const insertMention = (0, import_hooks.useCallback)( (user) => { const mention = findMentionSearchPosition( state.message, inputRef.current?.selectionStart || 0 ); if (!mention) return; const beforeMention = state.message.slice(0, mention.start); const afterMention = state.message.slice( inputRef.current?.selectionStart || 0 ); const newValue = `${beforeMention}@${user.display} ${afterMention}`; const updatedMentions = (0, import_collabUtils.filterOutInvalidMentions)(newValue, [ ...state.toUsers || [], { display: user.display, id: user.uid || "" } ]); setState((prevState) => ({ ...prevState, message: newValue, toUsers: updatedMentions.toUsers })); setShowSuggestions(false); const ele = inputRef.current; if (ele) { ele.focus(); } }, [state.message, state.toUsers, findMentionSearchPosition] ); const handleInputChange = (0, import_hooks.useCallback)( (event) => { const target = event.target; if (!target) return; const newPlainTextValue = target.value; const trimmedValue = newPlainTextValue.trim(); const newPosition = target.selectionStart; const mention = findMentionSearchPosition( newPlainTextValue, newPosition ); if (mention) { setSearchTerm(mention.searchTerm); setShowSuggestions(true); setCursorPosition( calculatePosition( inputRef.current, newPosition ) ); setSelectedIndex(0); } else { setShowSuggestions(false); } const errorMessage = (0, import_collabUtils.validateCommentAndMentions)( newPlainTextValue, state.toUsers ?? [] ); setError({ hasError: errorMessage !== "" || trimmedValue === "", message: errorMessage }); setState((prevState) => ({ ...prevState, message: newPlainTextValue })); }, [state.toUsers, findMentionSearchPosition, calculatePosition, setError] ); const handleKeyDown = (0, import_hooks.useCallback)( (e) => { if (e.key === "@") { const position = calculatePosition( inputRef.current, e.target.selectionStart ); setCursorPosition(position); setSelectedIndex(0); } if (!showSuggestions) return; switch (e.key) { case "ArrowDown": e.preventDefault(); setSelectedIndex( (prev) => prev < filteredUsers.length - 1 ? prev + 1 : prev ); break; case "ArrowUp": e.preventDefault(); setSelectedIndex((prev) => prev > 0 ? prev - 1 : prev); break; case "Enter": e.preventDefault(); if (showSuggestions) { insertMention(filteredUsers[selectedIndex]); } break; case "Escape": setShowSuggestions(false); inputRef.current?.focus(); break; } }, [ showSuggestions, filteredUsers, selectedIndex, insertMention, calculatePosition ] ); (0, import_hooks.useEffect)(() => { itemRefs.current[selectedIndex]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); }, [selectedIndex]); const handleSubmit = (0, import_hooks.useCallback)(async () => { if (error.hasError) return; try { let threadUID = activeThread?._id; if (activeThread?._id == "new") { let currentThread = await createNewThread(); threadUID = currentThread?.thread?._id; setActiveThread(currentThread?.thread); } const commentState = { ...state, createdBy: userState.currentUser.uid, author: userState.currentUser.email }; const commentPayload = { ...(0, import_collabUtils.getCommentBody)(commentState) }; const commentData = { threadUid: threadUID, commentPayload }; if (editComment) { let commentResponse = await onEditComment({ threadUid: threadUID, commentUid: editComment, payload: commentPayload }); setThreadState((prevState) => { const updatedComments = (0, import_lodash_es.cloneDeep)(prevState.comments); const commentIndex = (0, import_lodash_es.findIndex)( updatedComments, (c) => c._id === comment?._id ); updatedComments.splice( commentIndex, 1, commentResponse?.comment ); return { ...prevState, editComment: "", comments: updatedComments }; }); onClose(false); } else { let commentResponse = await onCreateComment(commentData); setThreadState((prevState) => ({ ...prevState, comments: [commentResponse.comment, ...prevState.comments], commentCount: prevState.commentCount + 1 })); setState(initialState); onClose(false); } } catch (error2) { console.error("Error submitting comment:", error2); } }, [error.hasError, state, activeThread]); (0, import_hooks.useEffect)(() => { if (state.message.length === 0) { setError({ hasError: true, message: "" }); } }, [state.message, setError]); return { state, setState, error, showSuggestions, cursorPosition, selectedIndex, filteredUsers, inputRef, listRef, itemRefs, handleInputChange, handleKeyDown, handleSubmit, insertMention, maxMessageLength: import_constants.maxMessageLength }; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { useCommentTextArea }); //# sourceMappingURL=useCommentTextArea.cjs.map