import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { Comment, setAccount, useAccount, useAccountComment, useAccountSubplebbits, useEditedComment } from '@plebbit/plebbit-react-hooks'; import Plebbit from '@plebbit/plebbit-js'; import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits'; import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages'; import { getHasThumbnail, getLinkMediaInfo } from '../../lib/utils/media-utils'; import { formatMarkdown } from '../../lib/utils/post-utils'; import { isValidURL } from '../../lib/utils/url-utils'; import { isAllView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; import usePublishPost from '../../hooks/use-publish-post'; import usePublishReply from '../../hooks/use-publish-reply'; import FileUploader from '../../plugins/file-uploader'; import styles from './post-form.module.css'; import { Capacitor } from '@capacitor/core'; import _ from 'lodash'; const isAndroid = Capacitor.getPlatform() === 'android'; export const LinkTypePreviewer = ({ link }: { link: string }) => { const { t } = useTranslation(); const mediaInfo = getLinkMediaInfo(link); let type = mediaInfo?.type; const gifFrameUrl = useFetchGifFirstFrame(mediaInfo?.url); if (type === 'gif' && gifFrameUrl !== null) { type = t('animated_gif'); } else if (type === 'gif' && gifFrameUrl === null) { type = t('gif'); } return isValidURL(link) ? type : t('invalid_url'); }; const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid: string }) => { const { t } = useTranslation(); const params = useParams(); const account = useAccount(); const [url, setUrl] = useState(''); const author = account?.author || {}; const { displayName } = author || {}; const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const resolvedAddress = useResolvedSubplebbitAddress(); const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress; const { setPublishPostOptions, postIndex, publishPost, publishPostOptions, resetPublishPostOptions } = usePublishPost({ subplebbitAddress }); const textRef = useRef(null); const urlRef = useRef(null); const subjectRef = useRef(null); const location = useLocation(); const isInAllView = isAllView(location.pathname); const isInModView = isModView(location.pathname); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const subscriptions = account?.subscriptions || []; const defaultSubplebbits = useDefaultSubplebbits(); const { accountSubplebbits } = useAccountSubplebbits(); const accountSubplebbitAddresses = Object.keys(accountSubplebbits); const [lengthError, setLengthError] = useState(null); const checkContentLength = useRef( _.debounce((content: string, t: Function) => { const length = content.trim().length; if (length > 2000) { setLengthError(`${t('error')}: ${t('comment_field_too_long', { length })}`); } else { setLengthError(null); } }, 1000), ).current; const resetFields = () => { if (textRef.current) { textRef.current.value = ''; } if (urlRef.current) { urlRef.current.value = ''; } if (subjectRef.current) { subjectRef.current.value = ''; } }; const onPublishPost = () => { const currentTitle = subjectRef.current?.value.trim() || ''; const currentContent = textRef.current?.value.trim() || ''; const currentUrl = urlRef.current?.value.trim() || ''; checkContentLength.cancel(); setLengthError(null); if (!currentTitle && !currentContent && !currentUrl) { alert(t('empty_comment_alert')); return; } if (currentUrl && !isValidURL(currentUrl)) { alert(t('invalid_url_alert')); return; } if (currentContent.length > 2000) { alert(t('error') + ': ' + t('field_too_long')); return; } if ((isInAllView || isInSubscriptionsView || isInModView) && !publishPostOptions.subplebbitAddress) { alert(t('no_board_selected_warning')); return; } if (!isInPostView) { const linkMediaInfo = getLinkMediaInfo(currentUrl); const hasThumbnail = getHasThumbnail(linkMediaInfo, currentUrl); if (!hasThumbnail) { const confirmMessage = t('missing_link_confirm'); if (!window.confirm(confirmMessage)) { return; } } } publishPost(); }; // redirect to pending page when pending comment is created const navigate = useNavigate(); useEffect(() => { if (typeof postIndex === 'number') { resetPublishPostOptions(); resetFields(); navigate(`/pending/${postIndex}`); } }, [postIndex, resetPublishPostOptions, navigate]); // in post page, publish a reply to the post const isInPostView = isPostPageView(location.pathname, params); const cid = params?.commentCid as string; const { setPublishReplyOptions, resetPublishReplyOptions, replyIndex, publishReply } = usePublishReply({ cid, subplebbitAddress }); const handleContentChange = (e: React.ChangeEvent) => { const formattedContent = formatMarkdown(e.target.value); isInPostView ? setPublishReplyOptions({ content: formattedContent }) : setPublishPostOptions({ content: formattedContent }); checkContentLength(formattedContent, t); }; const onPublishReply = () => { const currentContent = textRef.current?.value.trim() || ''; const currentUrl = urlRef.current?.value.trim() || ''; checkContentLength.cancel(); setLengthError(null); if (!currentContent && !currentUrl) { alert(t('empty_comment_alert')); return; } if (currentUrl && !isValidURL(currentUrl)) { alert(t('invalid_url_alert')); return; } if (currentContent.length > 2000) { alert(t('error') + ': ' + t('field_too_long')); return; } publishReply(); }; useEffect(() => { if (typeof replyIndex === 'number') { resetPublishReplyOptions(); resetFields(); closeForm(); } }, [replyIndex, resetPublishReplyOptions, closeForm]); // on android, auto upload file to image hosting sites with open api const [isUploading, setIsUploading] = useState(false); const [uploadedFileName, setUploadedFileName] = useState(null); const handleUpload = async () => { try { setIsUploading(true); const result = await FileUploader.pickAndUploadMedia(); console.log('Upload result:', result); if (result.url) { setUrl(result.url); if (urlRef.current) { urlRef.current.value = result.url; } isInPostView ? setPublishReplyOptions({ link: result.url || undefined }) : setPublishPostOptions({ link: result.url || undefined }); if (result.fileName) { setUploadedFileName(result.fileName); } } } catch (error) { console.error('Upload failed:', error); if (error instanceof Error && error.message !== 'File selection cancelled') { alert(`${t('upload_failed')}: ${error.message}`); } else if (typeof error === 'string' && error !== 'File selection cancelled') { alert(`${t('upload_failed')}: ${error}`); } } finally { setIsUploading(false); } }; const hasInitializedDisplayName = useRef(false); useEffect(() => { if (displayName && !hasInitializedDisplayName.current) { hasInitializedDisplayName.current = true; if (isInPostView) { setPublishReplyOptions({ displayName }); } else { setPublishPostOptions({ displayName }); } } }, [displayName, isInPostView, setPublishReplyOptions, setPublishPostOptions]); return ( {!isInPostView && ( )}
{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 }); } }} /> {isInPostView && ( )}
{t('subject')} { setPublishPostOptions({ title: e.target.value }); }} />
{t('comment')}