import { useCallback, useEffect, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Comment, setAccount, useAccount, useEditedComment } from '@bitsocial/bitsocial-react-hooks'; import getShortAddress from '../../lib/get-short-address'; import useCommunitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages'; import { getDisplayMediaInfoType, getLinkMediaInfo } from '../../lib/utils/media-utils'; import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation-utils'; import { type DiceRoll, type FortuneEntry, type PostOptionsValidationError, POST_OPTIONS_VALIDATION_DELAY_MS, getContentWithPostOptionState as getContentWithOptions, getNonokoPendingRouteState, getPostOptionsDirectoryCode, getPostOptionsValidationError, hasNonokoOption, isPostOptionsValidationError, } from '../../lib/utils/post-options-utils'; import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils'; import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/utils/url-utils'; import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { getBoardPath } from '../../lib/utils/route-utils'; import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory, type CommentFlagSelectOption } from '../../lib/comment-flag-selection'; import { FLASH_TAG_OPTIONS, getFlashTagPublishOptionsForDirectoryCode, isFlashDirectoryCode, type FlashTagOption } from '../../lib/flash-tags'; import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses'; import { useDirectories } from '../../hooks/use-directories'; import { useDirectoryEntry } from '../../hooks/use-directory-entry'; import { useCommunityField } from '../../hooks/use-stable-community'; import useIsMobile from '../../hooks/use-is-mobile'; import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import usePublishPost from '../../hooks/use-publish-post'; import usePublishReply from '../../hooks/use-publish-reply'; import { useFileUpload } from '../../hooks/use-file-upload'; import { getShowUploadControls, isWebRuntime } from '../../lib/media-hosting/show-upload-controls'; import { OEKAKI_WEB_WARNING_TEXT } from '../../lib/oekaki/oekaki-copy'; import { isCommentArchived } from '../../lib/utils/comment-moderation-utils'; import useMediaHostingStore from '../../stores/use-media-hosting-store'; import BoardOfflineAlert from '../board-offline-alert/board-offline-alert'; import BbcodeEditorToolbar, { BbcodePreview } from '../bbcode-editor-toolbar/bbcode-editor-toolbar'; import LoadingEllipsis from '../loading-ellipsis'; import OekakiDrawingControls from '../oekaki-drawing-controls'; import PostOptionsErrorMessage from '../post-options-error-message/post-options-error-message'; import styles from './post-form.module.css'; import capitalize from 'lodash/capitalize'; import debounce from 'lodash/debounce'; const FILE_LINK_PLACEHOLDER = 'https://website.com/image.jpg'; const POST_FORM_FILE_DISPLAY_MAX_LENGTH = 28; const mergeFlairs = (...flairGroups: Array): Comment['flairs'] | undefined => { const flairs = flairGroups.flatMap((group) => (Array.isArray(group) ? group : [])); return flairs.length > 0 ? flairs : undefined; }; const getPostFormFileDisplayLabel = (url: string, uploadedFileName: string | null | undefined, noFileLabel: string): string => { const raw = getPublishURLFilename(url) || uploadedFileName; if (!raw) return noFileLabel; return truncateWithEllipsisInMiddle(raw, POST_FORM_FILE_DISPLAY_MAX_LENGTH); }; export const LinkTypePreviewer = ({ link }: { link: string }) => { const { t } = useTranslation(); const mediaInfo = getLinkMediaInfo(link); let type = mediaInfo?.type; const { status: gifFrameStatus } = useFetchGifFirstFrame(type === 'gif' ? mediaInfo?.url : undefined); if (type === 'gif' && gifFrameStatus === 'ready') { type = t('animated_gif'); } else if (type === 'gif') { type = t('gif'); } else if (type) { type = getDisplayMediaInfoType(type, t); } return isValidURL(link) ? `type: ${type}` : t('invalid_url'); }; const PostFormActions = ({ disableReplyPublish = false, variant, t, isInPostView, onPublishReply, onPublishPost, handleUpload, isUploading, showUploadControls, }: { disableReplyPublish?: boolean; variant: 'reply' | 'post' | 'upload'; t: TFunction; isInPostView: boolean; onPublishReply: () => void; onPublishPost: () => void; handleUpload: () => void; isUploading: boolean; showUploadControls: boolean; }) => { if (variant === 'reply' && isInPostView) { return ( ); } if (variant === 'post' && !isInPostView) { return ( ); } if (variant === 'upload' && showUploadControls) { return ( ); } return null; }; interface PostFormFieldsProps { t: TFunction; account: ReturnType; displayName: string | undefined; bbcodePreviewContent: string; isInPostView: boolean; isBbcodePreviewing: boolean; postCid: string; subjectRef: React.Ref; optionsRef: React.RefObject; flagRef: React.RefObject; flashTagRef: React.RefObject; textRef: React.RefObject; urlRef: React.Ref; url: string; lengthError: string | null; handleContentChange: (e: React.ChangeEvent) => void; handleContentValueChange: (content: string, options?: string) => void; handleOptionsChange: (e: React.ChangeEvent) => void; setPublishPostOptions: (opts: Record) => void; setPublishReplyOptions: (opts: Record) => void; setUrl: (url: string) => void; isUploading: boolean; uploadedFileName: string | null | undefined; showUploadControls: boolean; showOekakiControls: boolean; showSpoilerForPost: boolean; showSpoilerForReply: boolean; isInAllView: boolean; isInSubscriptionsView: boolean; isInModView: boolean; directories: ReturnType; accountCommunityAddresses: string[]; subscriptions: string[]; communityAddress: string | undefined; rulesPath: string; requirePostLinkIsMedia: boolean; flagOptions: CommentFlagSelectOption[]; flashTagOptions: FlashTagOption[]; showFlashTagSelector: boolean; showFlashUploadPrompt: boolean; showBbcodeToolbar: boolean; onBbcodePreviewToggle: () => void; onPublishReply: () => void; onPublishPost: () => void; handleUpload: () => void; uploadFile: ReturnType['uploadFile']; onOekakiClearUploadedUrl: (url: string) => void; disableReplyPublish: boolean; } const PostFormFields = ({ t, account, displayName, bbcodePreviewContent, isInPostView, isBbcodePreviewing, postCid, subjectRef, optionsRef, flagRef, flashTagRef, textRef, urlRef, url, lengthError, handleContentChange, handleContentValueChange, handleOptionsChange, setPublishPostOptions, setPublishReplyOptions, setUrl, isUploading, uploadedFileName, showUploadControls, showOekakiControls, showSpoilerForPost, showSpoilerForReply, isInAllView, isInSubscriptionsView, isInModView, directories, accountCommunityAddresses, subscriptions, communityAddress, rulesPath, requirePostLinkIsMedia, flagOptions, flashTagOptions, showFlashTagSelector, showFlashUploadPrompt, showBbcodeToolbar, onBbcodePreviewToggle, onPublishReply, onPublishPost, handleUpload, uploadFile, onOekakiClearUploadedUrl, disableReplyPublish, }: PostFormFieldsProps) => ( <> {t('name')} { const newDisplayName = e.target.value.trim() || undefined; setAccount({ ...account, author: { ...account?.author, displayName: newDisplayName } }); if (isInPostView) { setPublishReplyOptions({ displayName: newDisplayName }); } else { setPublishPostOptions({ displayName: newDisplayName }); } }} /> {t('options')} {!isInPostView && ( {t('subject')} { setPublishPostOptions({ title: e.target.value }); }} /> )} {showBbcodeToolbar ? ( format handleContentValueChange(content)} isPreviewing={isBbcodePreviewing} onPreviewToggle={onBbcodePreviewToggle} /> ) : null} {t('comment')} {showBbcodeToolbar && isBbcodePreviewing && ( )}