import { View, Row, ScrollView } from 'app/design/view' import { useEffect, useMemo } from 'react' import { getFormFieldByData } from 'app/lib/form/form-helpers' import { Keyboard, Platform } from 'react-native' import { useFormContext } from 'react-hook-form' import { editorHtmlHasContent, editorHtmlIsSubmittable, hasFiles, removeEmptyTags, getStoragePickerKind } from 'app/lib/util' import { useCurrentUser } from 'app/context/user' import Profile from 'app/ui/molecules/profile/profile' import { FileButton } from 'app/lib/form/form-helpers' import { useWindowHeight, useIsDesktop } from 'app/context/measure'; import emitter, { EVENTS } from 'app/context/emitter'; import { useTranslation } from 'react-i18next' import { StarsAction } from 'app/ui/atoms/stars' export default function FormComments(props) { const { t } = useTranslation() const isWeb = Platform.OS == 'web' const screenHeight = useWindowHeight(); const isDesktop = useIsDesktop(); const exProps = props?.exProps ?? {}; const isModalForm = !!exProps.isModal; const shouldGrowFromBottom = isModalForm || !isDesktop; const minHeightEditor = props.data.inputs['cmt_id']?.value ? 160 : 20; const maxHeightEditor = isWeb ? 160 : (screenHeight - 300) / 2 // 300 is approximate keyboard height const formContext = useFormContext() const { register, setValue } = formContext const { currentUser } = useCurrentUser() let isAutoFocus = false; if (props.data.inputs['cmt_text']?.value){ isAutoFocus = true; } if (props.data.inputs['cmt_text'].autofocus){ isAutoFocus = props.data.inputs['cmt_text'].autofocus; } // Jobs reviews send `cmt_mood` (star rating). const hasMood = !!props.data.inputs['cmt_mood'] const rawEditorText = formContext.watch('cmt_text') const imagesValue = formContext.watch('cmt_image') const moodValue = formContext.watch('cmt_mood') const hasText = useMemo( () => editorHtmlHasContent(rawEditorText), [rawEditorText] ); const hasSubmittableText = useMemo( () => editorHtmlIsSubmittable(rawEditorText), [rawEditorText] ); function setIsFocus() { // setIsExImage(false) } useEffect(() => { // The success cue plays when UNA returns the new comment (comments-browse). if (formContext.formState.isSubmitted) { formContext.setValue('cmt_text', ''); if (hasMood) { formContext.setValue('cmt_mood', 0); } emitter.emit(EVENTS.comment, { action: 'send'}) emitter.emit(EVENTS.editor, { action: 'set_content', value: '' }) emitter.emit(EVENTS.fieldFiles('cmt_image'), { action: 'clear' }) if (!isWeb) { emitter.emit(EVENTS.editor, { action: 'blur' }) Keyboard.dismiss() } } }, [formContext.formState.isSubmitted, formContext, isWeb, hasMood]); const handleSubmitWithSanitization = () => { let sanitizedHtml = removeEmptyTags(formContext.getValues('cmt_text') || ''); const images = formContext.getValues('cmt_image'); if (!editorHtmlIsSubmittable(sanitizedHtml) && !hasFiles(images)) return; formContext.setValue('cmt_text', sanitizedHtml, { shouldValidate: true, shouldDirty: true }); props.handleSubmit(); }; const handleEditorEnterSubmit = () => { handleSubmitWithSanitization(); }; // Copies with the composer's look; `props` itself is never written to. const submitInput = { ...props.data.inputs['cmt_submit'], hide_errors: true, icon: 'ArrowUp', style: 'bordered', rounded: 'true', icon_only: true, } const imageInput = { ...props.data.inputs['cmt_image'], rounded: 'true', variant: 'default', } // Comment storage decides: images-only modules keep the photo icon, others get a generic attachment. const attachIcon = getStoragePickerKind(imageInput.ext_allow) === 'any' ? 'Paperclip' : 'Image' const hasContent = !!hasText || !!imagesValue || props.data.inputs['cmt_text'].html == 2 || props.data.inputs['cmt_text'].html == 1 // The mood stars sit on the composer toolbar so the empty pill stays a single // line — same row as the placeholder and attach button — then drop to the // bottom with attach/send once the composer expands. const moodInline = hasMood && !hasContent useEffect(() => { if (!hasMood) return register('cmt_mood') }, [register, hasMood]) return ( // Resting composer is a single-line pill; once there's content (or the // rich toolbar expands it) the taller box switches to a card radius. {getFormFieldByData( props.data.inputs['action'], props.handleSubmit, 'custom' )} {getFormFieldByData( props.data.inputs['cmt_cf'], props.handleSubmit, 'custom' )} {getFormFieldByData( props.data.inputs['cmt_parent_id'], props.handleSubmit, 'custom' )} {getFormFieldByData( props.data.inputs['id'], props.handleSubmit, 'custom' )} {getFormFieldByData( props.data.inputs['sys'], props.handleSubmit, 'custom' )} {getFormFieldByData( props.data.inputs['cmt_text'], props.handleSubmit, 'custom', { form_name: props.name, container_class: 'comments', classes: 'text-card-foreground tiptap-comments', autofocus: isAutoFocus, bg: 'transparent', placeholder: t('Leave a comment...'), noPadding: true, initialHeight: minHeightEditor, maxHeight: maxHeightEditor, onFocus: setIsFocus, // Cmd/Ctrl+Enter submits; plain Enter is a newline // unless `comments.submit_comment_on_enter` is on. onEnterSubmit: handleEditorEnterSubmit, } )} {hasMood ? ( setValue('cmt_mood', next, { shouldDirty: true })} starSize={isDesktop ? 22 : 20} hitSize={isDesktop ? 40 : 36} /> ) : null} {!isWeb ? ( ) : null} {hasContent ? ( {getFormFieldByData( submitInput, handleSubmitWithSanitization, 'custom', { form_name: props.name, disabled: !hasSubmittableText && !hasFiles(imagesValue), className: 'w-full h-full', notFullWidth: true, noPadding: true, icon_only: true, icon: 'ArrowUp', title: t('Send'), size: 'sm', style: 'glassProminent', rounded: true, alt: t('Post'), tooltip: t('Post'), } )} ) : null} {currentUser ? ( ) : null} {getFormFieldByData( imageInput, props.handleSubmit, 'notitle', { hide_button: true, list_only: true, asDefaultStorage: true, form_name: props.name} )} ) }