2024-04-27 12:41:54 +02:00
import { useEffect , useRef , useState } from 'react' ;
2024-03-20 13:08:08 +01:00
import { useTranslation } from 'react-i18next' ;
2024-04-26 19:13:23 +02:00
import { useLocation , useNavigate , useParams } from 'react-router-dom' ;
2024-06-08 21:22:17 +02:00
import { PublishCommentOptions , setAccount , useAccount , useAccountComment , useComment , usePublishComment , useSubplebbit } from '@plebbit/plebbit-react-hooks' ;
2024-04-26 19:13:23 +02:00
import { create } from 'zustand' ;
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils' ;
2024-04-24 08:48:31 +02:00
import { getLinkMediaInfo } from '../../lib/utils/media-utils' ;
import { isValidURL } from '../../lib/utils/url-utils' ;
2024-05-17 14:56:09 +02:00
import { isAllView , isDescriptionView , isPostPageView , isRulesView , isSubscriptionsView } from '../../lib/utils/view-utils' ;
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits' ;
2024-04-27 12:41:54 +02:00
import useReply from '../../hooks/use-reply' ;
2024-05-31 10:28:57 +02:00
import useChallengesStore from '../../stores/use-challenges-store' ;
2024-05-17 14:56:09 +02:00
import styles from './post-form.module.css' ;
2024-04-28 17:12:19 +02:00
import _ from 'lodash' ;
2024-06-08 21:22:17 +02:00
import { getFormattedTimeAgo } from '../../lib/utils/time-utils' ;
2024-04-26 19:13:23 +02:00
type SubmitState = {
subplebbitAddress : string | undefined ;
title : string | undefined ;
content : string | undefined ;
link : string | undefined ;
2024-05-31 16:10:38 +02:00
spoiler : boolean | undefined ;
2024-04-26 19:13:23 +02:00
publishCommentOptions : PublishCommentOptions ;
setSubmitStore : ( data : Partial < SubmitState >) => void ;
resetSubmitStore : () => void ;
};
2024-05-31 10:28:57 +02:00
const { addChallenge } = useChallengesStore . getState ();
2024-04-26 19:13:23 +02:00
const useSubmitStore = create < SubmitState >(( set ) => ({
subplebbitAddress : undefined ,
title : undefined ,
content : undefined ,
link : undefined ,
2024-05-31 16:10:38 +02:00
spoiler : undefined ,
2024-04-26 19:13:23 +02:00
publishCommentOptions : {},
2024-05-31 16:10:38 +02:00
setSubmitStore : ({ subplebbitAddress , title , content , link , spoiler }) =>
2024-04-26 19:13:23 +02:00
set (( state ) => {
const nextState = { ... state };
if ( subplebbitAddress !== undefined ) nextState . subplebbitAddress = subplebbitAddress ;
if ( title !== undefined ) nextState . title = title || undefined ;
if ( content !== undefined ) nextState . content = content || undefined ;
if ( link !== undefined ) nextState . link = link || undefined ;
2024-05-31 16:10:38 +02:00
if ( spoiler !== undefined ) nextState . spoiler = spoiler || undefined ;
2024-04-26 19:13:23 +02:00
nextState . publishCommentOptions = {
... nextState ,
onChallenge : (... args : any ) => addChallenge ( args ),
onChallengeVerification : alertChallengeVerificationFailed ,
onError : ( error : Error ) => {
console . error ( error );
let errorMessage = error . message ;
alert ( errorMessage );
},
};
return nextState ;
}),
2024-05-31 16:10:38 +02:00
resetSubmitStore : () => set ({ subplebbitAddress : undefined , title : undefined , content : undefined , link : undefined , spoiler : undefined , publishCommentOptions : {} }),
2024-04-26 19:13:23 +02:00
}));
2024-03-20 13:00:11 +01:00
2024-05-31 15:45:48 +02:00
export const LinkTypePreviewer = ({ link } : { link : string }) => {
2024-05-31 16:51:22 +02:00
const { t } = useTranslation ();
2024-04-24 08:48:31 +02:00
const mediaInfo = getLinkMediaInfo ( link );
2024-05-31 16:51:22 +02:00
return isValidURL ( link ) ? mediaInfo?.type : t ( 'invalid_url' );
2024-04-24 08:48:31 +02:00
};
2024-05-06 22:49:23 +02:00
const PostFormTable = ({ closeForm } : { closeForm : () => void }) => {
2024-04-28 17:12:19 +02:00
const { t } = useTranslation ();
2024-04-24 08:48:31 +02:00
const account = useAccount ();
2024-04-26 19:13:23 +02:00
const { displayName } = account ? . author || {};
const [ url , setUrl ] = useState ( '' );
const { title , content , link , publishCommentOptions , setSubmitStore , resetSubmitStore } = useSubmitStore ();
const { index , publishComment } = usePublishComment ( publishCommentOptions );
2024-05-06 22:49:23 +02:00
const textRef = useRef < HTMLTextAreaElement >( null );
const urlRef = useRef < HTMLInputElement >( null );
const subjectRef = useRef < HTMLInputElement >( null );
2024-05-17 14:56:09 +02:00
const location = useLocation ();
const isInAllView = isAllView ( location . pathname );
const isInSubscriptionsView = isSubscriptionsView ( location . pathname );
const subscriptions = account ? . subscriptions || [];
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses ();
2024-05-06 22:49:23 +02:00
const resetFields = () => {
if ( textRef . current ) {
textRef . current . value = '' ;
}
if ( urlRef . current ) {
urlRef . current . value = '' ;
}
if ( subjectRef . current ) {
subjectRef . current . value = '' ;
}
};
2024-04-27 12:41:54 +02:00
const onPublishPost = () => {
2024-04-26 19:13:23 +02:00
if ( ! title && ! content && ! link ) {
alert ( `Cannot post empty comment` );
return ;
}
if ( link && ! isValidURL ( link )) {
alert ( `Invalid link` );
return ;
}
publishComment ();
};
const params = useParams ();
const accountComment = useAccountComment ({ commentIndex : params?.accountCommentIndex as any });
const subplebbitAddress = params ? . subplebbitAddress || accountComment ? . subplebbitAddress ;
useEffect (() => {
if ( subplebbitAddress ) {
setSubmitStore ({ subplebbitAddress });
}
}, [ subplebbitAddress , setSubmitStore ]);
// redirect to pending page when pending comment is created
const navigate = useNavigate ();
useEffect (() => {
if ( typeof index === 'number' ) {
resetSubmitStore ();
2024-05-06 22:49:23 +02:00
resetFields ();
2024-04-26 19:13:23 +02:00
navigate ( `/profile/ ${ index } ` );
}
}, [ index , resetSubmitStore , navigate ]);
2024-04-24 08:48:31 +02:00
2024-04-27 12:41:54 +02:00
// in post page, publish a reply to the post
2024-05-17 15:29:03 +02:00
const isInPostView = isPostPageView ( location . pathname , params );
2024-04-27 12:41:54 +02:00
const cid = params ? . commentCid as string ;
const { setContent , resetContent , replyIndex , publishReply } = useReply ({ cid , subplebbitAddress });
const onPublishReply = () => {
const currentContent = textRef . current ? . value || '' ;
const currentUrl = urlRef . current ? . value || '' ;
if ( ! currentContent . trim () && ! currentUrl ) {
alert ( `Cannot post empty comment` );
return ;
}
if ( currentUrl && ! isValidURL ( currentUrl )) {
alert ( 'The provided link is not a valid URL.' );
return ;
}
publishReply ();
};
useEffect (() => {
if ( typeof replyIndex === 'number' ) {
resetContent ();
2024-05-06 22:49:23 +02:00
resetFields ();
closeForm ();
2024-04-27 12:41:54 +02:00
}
2024-05-17 16:12:50 +02:00
}, [ replyIndex , resetContent , closeForm ]);
2024-04-27 12:41:54 +02:00
2024-04-24 08:48:31 +02:00
return (
2024-04-24 15:20:55 +02:00
< table className = { styles . postFormTable }>
2024-04-24 08:48:31 +02:00
< tbody >
< tr >
2024-04-28 17:12:19 +02:00
< td >{ t ( 'name' )}</ td >
2024-04-24 08:48:31 +02:00
< td >
2024-04-26 19:13:23 +02:00
< input
type = 'text'
2024-04-28 17:12:19 +02:00
placeholder = { ! displayName ? _ . capitalize ( t ( 'anonymous' )) : undefined }
2024-04-26 19:13:23 +02:00
defaultValue = { displayName || undefined }
onChange = {( e ) => setAccount ({ ... account , author : { ... account ? . author , displayName : e.target.value } })}
/>
2024-05-17 15:29:03 +02:00
{ isInPostView && < button onClick = { onPublishReply }>{ t ( 'post' )}</ button >}
2024-04-24 08:48:31 +02:00
</ td >
</ tr >
2024-05-17 15:29:03 +02:00
{ ! isInPostView && (
2024-04-27 12:41:54 +02:00
< tr >
2024-04-28 17:12:19 +02:00
< td >{ t ( 'subject' )}</ td >
2024-04-27 12:41:54 +02:00
< td >
< input
type = 'text'
2024-05-06 22:49:23 +02:00
ref = { subjectRef }
2024-04-27 12:41:54 +02:00
onChange = {( e ) => {
setSubmitStore ({ title : e.target.value });
}}
/>
2024-04-28 17:12:19 +02:00
< button onClick = { onPublishPost }>{ t ( 'post' )}</ button >
2024-04-27 12:41:54 +02:00
</ td >
</ tr >
)}
2024-04-24 08:48:31 +02:00
< tr >
2024-04-28 17:12:19 +02:00
< td >{ t ( 'comment' )}</ td >
2024-04-24 16:47:51 +02:00
< td >
2024-04-27 12:41:54 +02:00
< textarea
cols = { 48 }
rows = { 4 }
wrap = 'soft'
ref = { textRef }
onChange = {( e ) => {
2024-05-07 22:23:49 +02:00
const content = e . target . value . replace ( /\n/g , '\n\n' );
2024-05-17 15:29:03 +02:00
isInPostView ? setContent . content ( content ) : setSubmitStore ({ content });
2024-04-27 12:41:54 +02:00
}}
/>
2024-04-24 16:47:51 +02:00
</ td >
2024-04-24 08:48:31 +02:00
</ tr >
< tr >
2024-04-28 17:12:19 +02:00
< td >{ t ( 'link' )}</ td >
2024-04-24 08:48:31 +02:00
< td >
2024-04-26 19:13:23 +02:00
< input
type = 'text'
autoCorrect = 'off'
autoComplete = 'off'
spellCheck = 'false'
2024-04-27 12:41:54 +02:00
ref = { urlRef }
2024-04-26 19:13:23 +02:00
onChange = {( e ) => {
setUrl ( e . target . value );
2024-05-17 15:29:03 +02:00
isInPostView ? setContent . link ( e . target . value ) : setSubmitStore ({ link : e.target.value });
2024-04-26 19:13:23 +02:00
}}
/>
2024-05-31 16:10:38 +02:00
</ td >
</ tr >
< tr className = { styles . linkType }>
2024-05-31 16:51:22 +02:00
< td >{ t ( 'file_type' )}</ td >
< td >{ url ? < LinkTypePreviewer link = { url } /> : t ( 'no_link_provided' )}</ td >
2024-05-31 16:10:38 +02:00
</ tr >
< tr className = { styles . spoilerButton }>
< td >{ t ( 'options' )}</ td >
< td >
[
< label >
< input type = 'checkbox' onChange = {( e ) => ( isInPostView ? setContent . spoiler ( e . target . checked ) : setSubmitStore ({ spoiler : e.target.checked }))} />
2024-05-31 17:09:20 +02:00
{ _ . capitalize ( t ( 'spoiler' ))} ?
2024-05-31 16:10:38 +02:00
</ label >
]
2024-04-24 08:48:31 +02:00
</ td >
</ tr >
2024-05-17 14:56:09 +02:00
{( isInAllView || isInSubscriptionsView ) && (
< tr >
< td >{ t ( 'board' )}</ td >
< td >
< select onChange = {( e ) => setSubmitStore ({ subplebbitAddress : e.target.value })} value = { subplebbitAddress }>
2024-05-31 17:09:20 +02:00
< option value = '' > -- { t ( 'no_board_selected' )} -- </ option >
2024-05-17 14:56:09 +02:00
{ isInAllView &&
defaultSubplebbitAddresses . map (( address : string ) => (
< option key = { address } value = { address }>
{ address }
</ option >
))}
{ isInSubscriptionsView &&
subscriptions . map (( sub : string ) => (
< option key = { sub } value = { sub }>
{ sub }
</ option >
))}
</ select >
</ td >
</ tr >
)}
2024-04-24 08:48:31 +02:00
</ tbody >
</ table >
);
};
2024-04-08 17:13:02 +02:00
const PostForm = () => {
2024-03-20 13:08:08 +01:00
const { t } = useTranslation ();
2024-04-08 17:32:12 +02:00
const location = useLocation ();
const params = useParams ();
const isInDescriptionView = isDescriptionView ( location . pathname , params );
2024-05-17 15:29:03 +02:00
const isInPostView = isPostPageView ( location . pathname , params );
2024-04-08 17:32:12 +02:00
const isInRulesView = isRulesView ( location . pathname , params );
2024-04-26 19:13:23 +02:00
const comment = useComment ({ commentCid : useParams (). commentCid });
2024-04-08 17:13:02 +02:00
const { deleted , locked , removed } = comment || {};
2024-04-08 17:32:12 +02:00
const isThreadClosed = deleted || locked || removed || isInDescriptionView || isInRulesView ;
2024-04-08 17:13:02 +02:00
2024-03-22 12:22:20 +01:00
const [ showForm , setShowForm ] = useState ( false );
2024-03-20 13:08:08 +01:00
2024-06-08 21:22:17 +02:00
const subplebbit = useSubplebbit ({ subplebbitAddress : params?.subplebbitAddress });
const { updatedAt } = subplebbit || {};
const isBoardOffline = subplebbit ? . updatedAt && subplebbit . updatedAt < Date . now () / 1000 - 60 * 60 ;
const offlineAlert =
showForm &&
2024-06-08 21:33:56 +02:00
( updatedAt ? (
isBoardOffline && (
< div className = { styles . offlineBoard }>{ `Posts last synced ${ getFormattedTimeAgo ( updatedAt ) } , the subplebbit might be offline and publishing might fail.` }</ div >
)
) : (
< div className = { styles . offlineBoard }>{ `The subplebbit might be offline and publishing might fail.` }</ div >
));
2024-06-08 21:22:17 +02:00
2024-03-20 13:00:11 +01:00
return (
2024-03-21 15:21:33 +01:00
<>
2024-04-24 15:20:55 +02:00
< div className = { styles . postFormDesktop }>
2024-06-08 21:22:17 +02:00
{ offlineAlert }
2024-04-08 17:13:02 +02:00
{ isThreadClosed ? (
< div className = { styles . closed }>
2024-04-08 18:36:51 +02:00
{ t ( 'thread_closed' )}
2024-04-08 17:13:02 +02:00
< br />
2024-04-08 18:36:51 +02:00
{ t ( 'may_not_reply' )}
2024-04-08 17:13:02 +02:00
</ div >
2024-04-24 08:48:31 +02:00
) : ! showForm ? (
2024-04-08 17:13:02 +02:00
< div >
[
< button className = 'button' onClick = {() => setShowForm ( true )}>
2024-05-17 15:29:03 +02:00
{ isInPostView ? t ( 'post_a_reply' ) : t ( 'start_new_thread' )}
2024-04-08 17:13:02 +02:00
</ button >
]
</ div >
2024-04-24 08:48:31 +02:00
) : (
2024-05-06 22:49:23 +02:00
< PostFormTable closeForm = {() => setShowForm ( false )} />
2024-04-08 17:13:02 +02:00
)}
2024-03-21 15:21:33 +01:00
</ div >
2024-04-24 15:20:55 +02:00
< div className = { styles . postFormMobile }>
2024-06-08 21:22:17 +02:00
{ offlineAlert }
2024-04-08 17:46:36 +02:00
{ isThreadClosed ? (
< div className = { styles . closed }>
2024-04-14 14:42:05 +02:00
{ t ( 'thread_closed' )}
2024-04-08 17:46:36 +02:00
< br />
2024-04-14 14:42:05 +02:00
{ t ( 'may_not_reply' )}
2024-04-08 17:46:36 +02:00
</ div >
) : (
2024-04-24 15:20:55 +02:00
<>
< button className = { ` ${ styles . showFormButton } button` } onClick = {() => setShowForm ( showForm ? false : true )}>
2024-05-17 15:29:03 +02:00
{ showForm ? t ( 'close_post_form' ) : isInPostView ? t ( 'post_a_reply' ) : t ( 'start_new_thread' )}
2024-04-24 15:20:55 +02:00
</ button >
2024-05-06 22:49:23 +02:00
{ showForm && < PostFormTable closeForm = {() => setShowForm ( false )} />}
2024-05-31 15:16:21 +02:00
< hr />
2024-04-24 15:20:55 +02:00
</>
2024-04-08 17:46:36 +02:00
)}
2024-03-21 15:21:33 +01:00
</ div >
</>
2024-03-20 13:00:11 +01:00
);
};
export default PostForm ;