messenger.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { View, Row, ScrollView } from 'app/design/view'
  2. import { useEffect, useMemo } from 'react'
  3. import { getFormFieldByData } from 'app/lib/form/form-helpers'
  4. import { Keyboard, Platform } from 'react-native'
  5. import { useFormContext } from 'react-hook-form'
  6. import { editorHtmlHasContent, editorHtmlIsSubmittable, hasFiles, removeEmptyTags } from 'app/lib/util'
  7. import { useCurrentUser } from 'app/context/user'
  8. import Profile from 'app/ui/molecules/profile/profile'
  9. import { FileButton } from 'app/lib/form/form-helpers'
  10. import { useWindowHeight, useIsDesktop } from 'app/context/measure'
  11. import emitter, { EVENTS } from 'app/context/emitter'
  12. import { useTranslation } from 'react-i18next'
  13. import { MessageInput } from 'app/components/elements/chat/parts/message-input'
  14. /** Send time in seconds. Module level on purpose: it is called from the submit handler, never while rendering. */
  15. const nowInSeconds = () => Math.floor(Date.now() / 1000)
  16. export default function FormMessenger(props) {
  17. const { t } = useTranslation()
  18. const isWeb = Platform.OS == 'web'
  19. const screenHeight = useWindowHeight()
  20. const isDesktop = useIsDesktop()
  21. const minHeightEditor = 20
  22. const maxHeightEditor = isWeb ? 160 : (screenHeight - 300) / 2
  23. const formContext = useFormContext()
  24. const { currentUser } = useCurrentUser()
  25. const rawEditorText = formContext.watch('message')
  26. const imagesValue = formContext.watch('files')
  27. const hasText = useMemo(
  28. () => editorHtmlHasContent(rawEditorText),
  29. [rawEditorText]
  30. )
  31. const hasSubmittableText = useMemo(
  32. () => editorHtmlIsSubmittable(rawEditorText),
  33. [rawEditorText]
  34. )
  35. useEffect(() => {
  36. // The success cue plays when UNA returns the sent message (messenger/parts/common.js).
  37. if (formContext.formState.isSubmitted) {
  38. formContext.setValue('message', '')
  39. emitter.emit(EVENTS.editor, { action: 'set_content', value: '' })
  40. emitter.emit(EVENTS.fieldFiles('files'), { action: 'clear' })
  41. if (!isWeb) {
  42. emitter.emit(EVENTS.editor, { action: 'blur' })
  43. Keyboard.dismiss()
  44. }
  45. }
  46. }, [formContext.formState.isSubmitted, formContext, isWeb])
  47. const handleSubmitWithSanitization = () => {
  48. let sanitizedHtml = removeEmptyTags(formContext.getValues('message') || '')
  49. const images = formContext.getValues('files')
  50. if (!editorHtmlIsSubmittable(sanitizedHtml) && !hasFiles(images)) return
  51. formContext.setValue('message', sanitizedHtml, { shouldValidate: true, shouldDirty: true })
  52. formContext.setValue('payload', nowInSeconds())
  53. props.handleSubmit()
  54. }
  55. const handleEditorEnterSubmit = () => {
  56. handleSubmitWithSanitization()
  57. }
  58. // Copies with the composer's look; `props` itself is never written to.
  59. const submitInput = {
  60. ...props.data.inputs['submit'],
  61. hide_errors: true,
  62. icon: 'ArrowUp',
  63. style: 'bordered',
  64. rounded: 'true',
  65. icon_only: true,
  66. }
  67. const filesInput = {
  68. ...props.data.inputs['files'],
  69. rounded: 'true',
  70. variant: 'default',
  71. }
  72. const hasContent = !!hasText || !!imagesValue
  73. return (
  74. <MessageInput
  75. expanded={hasContent}
  76. header={<>
  77. {getFormFieldByData(props.data.inputs['action'], props.handleSubmit, 'custom')}
  78. {getFormFieldByData(props.data.inputs['cf'], props.handleSubmit, 'custom')}
  79. {getFormFieldByData(props.data.inputs['payload'], props.handleSubmit, 'custom')}
  80. {getFormFieldByData(props.data.inputs['id'], props.handleSubmit, 'custom')}
  81. {getFormFieldByData(props.data.inputs['message_id'], props.handleSubmit, 'custom')}
  82. {getFormFieldByData(props.data.inputs['send'], props.handleSubmit, 'custom')}
  83. </>}
  84. avatar={currentUser ? (
  85. <Profile
  86. {...currentUser}
  87. url_avatar={currentUser.avatar}
  88. displayType="unit_wo_info"
  89. displaySize="sm"
  90. />
  91. ) : null}
  92. actions={<>
  93. <FileButton style="borderless" field_name='files' size="sm" icon="Image" source='library' />
  94. {!isWeb ? (
  95. <FileButton field_name='files' style="borderless" size='sm' icon="Camera" source='camera' />
  96. ) : null}
  97. {hasContent ? (
  98. <View className="">
  99. {getFormFieldByData(
  100. submitInput,
  101. handleSubmitWithSanitization,
  102. 'custom',
  103. {
  104. form_name: props.name,
  105. disabled: !hasSubmittableText && !hasFiles(imagesValue),
  106. className: 'w-full h-full',
  107. notFullWidth: true,
  108. noPadding: true,
  109. icon_only: true,
  110. icon: 'ArrowUp',
  111. title: t('Send'),
  112. size: 'sm',
  113. style: 'glassProminent',
  114. rounded: true,
  115. alt: t('Send'),
  116. tooltip: t('Send'),
  117. }
  118. )}
  119. </View>
  120. ) : null}
  121. </>}
  122. footer={<>
  123. <ScrollView horizontal={true}>
  124. <Row className="flex-wrap mx-1">
  125. {getFormFieldByData(
  126. filesInput,
  127. props.handleSubmit,
  128. 'notitle',
  129. { hide_button: true, list_only: true, asDefaultStorage: true, form_name: props.name }
  130. )}
  131. </Row>
  132. </ScrollView>
  133. {getFormFieldByData(props.data.inputs['cmt_mood'], props.handleSubmit, 'custom')}
  134. </>}
  135. >
  136. {getFormFieldByData(
  137. props.data.inputs['message'],
  138. props.handleSubmit,
  139. 'custom',
  140. {
  141. form_name: props.name,
  142. container_class: 'comments',
  143. classes: 'text-card-foreground tiptap-comments',
  144. autofocus: false,
  145. bg: 'transparent',
  146. placeholder: t('Message...'),
  147. noPadding: true,
  148. initialHeight: minHeightEditor,
  149. maxHeight: maxHeightEditor,
  150. onEnterSubmit: handleEditorEnterSubmit,
  151. enableSubmitOnEnter: isDesktop,
  152. focus: true,
  153. }
  154. )}
  155. </MessageInput>
  156. )
  157. }