2023-04-25 14:34:48 +02:00
import React , { Fragment , useEffect , useMemo , useRef , useState } from 'react' ;
2023-04-21 10:47:39 +02:00
import { Helmet } from 'react-helmet-async' ;
2023-03-23 15:49:57 +01:00
import InfiniteScroll from 'react-infinite-scroller' ;
2023-04-21 10:47:39 +02:00
import { Link , useNavigate , useParams } from 'react-router-dom' ;
2023-03-23 15:49:57 +01:00
import { Tooltip } from 'react-tooltip' ;
2023-04-25 14:34:48 +02:00
import { useAccount , useAccountComments , useFeed , usePublishComment } from '@plebbit/plebbit-react-hooks' ;
import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils'
2023-03-25 14:54:04 +01:00
import { debounce } from 'lodash' ;
2023-04-08 13:27:17 +02:00
import useGeneralStore from '../../hooks/stores/useGeneralStore' ;
2023-04-09 08:37:29 +02:00
import { Container , NavBar , Header , Break , PostFormLink , PostFormTable , PostForm , TopBar , BoardForm } from '../styled/Board.styled' ;
2023-04-24 13:09:59 +02:00
import { Footer } from '../styled/Thread.styled' ;
2023-03-19 18:30:07 +01:00
import ImageBanner from '../ImageBanner' ;
2023-03-29 13:37:36 +02:00
import Post from '../Post' ;
2023-03-19 18:30:07 +01:00
import PostLoader from '../PostLoader' ;
2023-03-19 11:49:05 +01:00
import ReplyModal from '../ReplyModal' ;
import SettingsModal from '../SettingsModal' ;
2023-04-17 10:37:58 +02:00
import findShortParentCid from '../../utils/findShortParentCid' ;
2023-03-23 14:05:59 +01:00
import getCommentMediaInfo from '../../utils/getCommentMediaInfo' ;
import getDate from '../../utils/getDate' ;
2023-04-20 17:20:26 +02:00
import handleAddressClick from '../../utils/handleAddressClick' ;
2023-04-13 17:08:35 +02:00
import handleQuoteClick from '../../utils/handleQuoteClick' ;
2023-03-20 17:08:05 +01:00
import handleStyleChange from '../../utils/handleStyleChange' ;
import useClickForm from '../../hooks/useClickForm' ;
2023-04-04 14:42:22 +02:00
import useError from '../../hooks/useError' ;
2023-04-24 20:00:11 +00:00
import packageJson from '../../../package.json'
const { version } = packageJson
2023-02-14 21:13:15 +01:00
2023-02-05 18:44:16 +01:00
2023-03-20 17:08:05 +01:00
const Board = () => {
2023-03-13 14:29:34 +01:00
const {
2023-04-15 22:23:18 +02:00
setCaptchaResponse ,
2023-04-07 17:47:51 +02:00
setChallengesArray ,
2023-03-20 17:08:05 +01:00
defaultSubplebbits ,
2023-04-09 20:55:55 +02:00
setIsCaptchaOpen ,
2023-03-20 17:08:05 +01:00
isSettingsOpen , setIsSettingsOpen ,
2023-04-07 17:47:51 +02:00
setPendingComment ,
2023-04-24 15:48:30 +02:00
setPendingCommentIndex ,
2023-04-22 14:02:20 +02:00
setResolveCaptchaPromise ,
2023-03-20 17:08:05 +01:00
selectedAddress , setSelectedAddress ,
2023-04-12 21:56:25 +02:00
setSelectedParentCid ,
setSelectedShortCid ,
2023-03-13 14:29:34 +01:00
selectedStyle ,
2023-03-20 17:08:05 +01:00
setSelectedThread ,
selectedTitle , setSelectedTitle ,
showPostForm ,
2023-04-09 20:55:55 +02:00
showPostFormLink ,
2023-04-08 13:27:17 +02:00
} = useGeneralStore ( state => state );
2023-03-13 14:29:34 +01:00
2023-04-03 15:08:45 +02:00
const nameRef = useRef ();
const subjectRef = useRef ();
const commentRef = useRef ();
const linkRef = useRef ();
2023-03-17 14:35:03 +01:00
const [ isReplyOpen , setIsReplyOpen ] = useState ( false );
2023-02-11 22:00:13 +01:00
const navigate = useNavigate ();
2023-02-24 20:57:51 +01:00
const [ prevScrollPos , setPrevScrollPos ] = useState ( 0 );
const [ visible , setVisible ] = useState ( true );
2023-04-23 20:49:04 +02:00
const { feed , hasMore , loadMore } = useFeed ({ subplebbitAddresses : [ ` ${ selectedAddress } ` ], sortType : 'new' });
2023-03-25 14:54:04 +01:00
const [ selectedFeed , setSelectedFeed ] = useState ( feed );
2023-04-03 15:08:45 +02:00
const { subplebbitAddress } = useParams ();
2023-03-01 22:26:25 +01:00
2023-04-04 14:42:22 +02:00
const [ errorMessage , setErrorMessage ] = useState ( null );
useError ( errorMessage , [ errorMessage ]);
2023-04-25 14:34:48 +02:00
const account = useAccount ();
const flattenedRepliesByThread = useMemo (() => {
return selectedFeed . reduce (( acc , thread ) => {
const replies = flattenCommentsPages ( thread . replies );
acc [ thread . cid ] = replies ;
return acc ;
}, {});
}, [ selectedFeed ]);
const allParentCids = useMemo (() => {
const allRepliesCids = Object . values ( flattenedRepliesByThread ). flatMap ( replies => replies . map ( reply => reply . cid ));
const allThreadCids = selectedFeed . map ( thread => thread . cid );
return [... allThreadCids , ... allRepliesCids ];
}, [ flattenedRepliesByThread , selectedFeed ]);
const filter = useMemo (() => ({
parentCids : allParentCids
}), [ allParentCids ]);
const { accountComments } = useAccountComments ({ filter });
const filteredRepliesByThread = useMemo (() => {
const maxRepliesPerThread = 5 ;
const accountRepliesNotYetInCommentReplies = selectedFeed . reduce (( acc , thread ) => {
const replyCids = new Set ( flattenedRepliesByThread [ thread . cid ]. map ( reply => reply . cid ));
acc [ thread . cid ] = accountComments . filter ( accountReply => ! replyCids . has ( accountReply . cid ) && accountReply . parentCid === thread . cid );
return acc ;
}, {});
return selectedFeed . reduce (( acc , thread ) => {
const combinedReplies = [... flattenedRepliesByThread [ thread . cid ], ... accountRepliesNotYetInCommentReplies [ thread . cid ]]. sort (( a , b ) => a . timestamp - b . timestamp );
acc [ thread . cid ] = {
displayedReplies : combinedReplies . slice ( 0 , maxRepliesPerThread ),
omittedCount : Math . max ( combinedReplies . length - maxRepliesPerThread , 0 ),
};
return acc ;
}, {});
}, [ flattenedRepliesByThread , accountComments , selectedFeed ]);
const pendingReplyCounts = useMemo (() => {
return selectedFeed . reduce (( acc , thread ) => {
const replyCids = new Set ( flattenedRepliesByThread [ thread . cid ]. map ( reply => reply . cid ));
acc [ thread . cid ] = accountComments . filter ( accountReply => ! replyCids . has ( accountReply . cid ) && accountReply . parentCid === thread . cid ). length ;
return acc ;
}, {});
}, [ flattenedRepliesByThread , accountComments , selectedFeed ]);
2023-03-03 14:02:46 +01:00
// temporary title from JSON, gets subplebbitAddress from URL
2023-03-02 18:19:08 +01:00
useEffect (() => {
setSelectedAddress ( subplebbitAddress );
const selectedSubplebbit = defaultSubplebbits . find (( subplebbit ) => subplebbit . address === subplebbitAddress );
if ( selectedSubplebbit ) {
setSelectedTitle ( selectedSubplebbit . title );
}
}, [ subplebbitAddress , setSelectedAddress , setSelectedTitle , defaultSubplebbits ]);
2023-03-03 14:02:46 +01:00
// sets useFeed to address from URL
2023-03-02 11:09:03 +01:00
useEffect (() => {
setSelectedFeed ( feed );
}, [ feed ]);
2023-02-17 18:03:05 +01:00
2023-03-03 14:02:46 +01:00
// mobile navbar scroll effect
2023-03-02 13:42:26 +01:00
useEffect (() => {
2023-03-25 14:54:04 +01:00
const debouncedHandleScroll = debounce (() => {
2023-03-02 13:42:26 +01:00
const currentScrollPos = window . pageYOffset ;
setVisible ( prevScrollPos > currentScrollPos || currentScrollPos < 10 );
setPrevScrollPos ( currentScrollPos );
2023-03-25 14:54:04 +01:00
}, 50 );
window . addEventListener ( 'scroll' , debouncedHandleScroll );
return () => window . removeEventListener ( 'scroll' , debouncedHandleScroll );
2023-03-02 13:42:26 +01:00
}, [ prevScrollPos , visible ]);
2023-03-25 14:54:04 +01:00
2023-03-02 13:42:26 +01:00
2023-02-16 21:01:21 +01:00
const tryLoadMore = async () => {
2023-03-01 22:26:25 +01:00
try {
2023-03-25 14:54:04 +01:00
await loadMore ();
2023-03-01 22:26:25 +01:00
} catch ( e ) {
await new Promise ( resolve => setTimeout ( resolve , 1000 ));
}
2023-03-25 14:54:04 +01:00
};
2023-04-03 15:08:45 +02:00
2023-02-16 21:01:21 +01:00
2023-02-15 16:48:21 +01:00
const onChallengeVerification = ( challengeVerification ) => {
if ( challengeVerification . challengeSuccess === true ) {
2023-04-11 22:27:24 +02:00
navigate ( `/p/ ${ selectedAddress } /c/ ${ challengeVerification . publication ? . cid } ` );
2023-04-22 15:12:26 +02:00
console . log ( 'challenge success' );
2023-02-15 16:48:21 +01:00
}
else if ( challengeVerification . challengeSuccess === false ) {
2023-04-20 16:50:15 +02:00
setErrorMessage ( 'Challenge Failed' , { reason : challengeVerification . reason , errors : challengeVerification . errors });
2023-02-15 16:48:21 +01:00
}
2023-04-11 18:12:11 +02:00
};
2023-04-11 22:27:24 +02:00
2023-02-15 16:48:21 +01:00
const onChallenge = async ( challenges , comment ) => {
2023-04-07 17:47:51 +02:00
setPendingComment ( comment );
2023-02-15 16:48:21 +01:00
let challengeAnswers = [];
2023-04-09 20:55:55 +02:00
2023-02-15 16:48:21 +01:00
try {
challengeAnswers = await getChallengeAnswersFromUser ( challenges )
}
catch ( error ) {
2023-04-04 14:42:22 +02:00
setErrorMessage ( error );
2023-02-15 16:48:21 +01:00
}
if ( challengeAnswers ) {
await comment . publishChallengeAnswers ( challengeAnswers )
}
2023-04-03 15:08:45 +02:00
};
2023-04-23 15:14:49 +02:00
useEffect (() => {
setPublishCommentOptions (( prevPublishCommentOptions ) => ({
... prevPublishCommentOptions ,
subplebbitAddress : selectedAddress ,
}));
}, [ selectedAddress ]);
2023-04-11 17:17:32 +02:00
2023-04-12 14:02:35 +02:00
2023-04-11 17:17:32 +02:00
const [ publishCommentOptions , setPublishCommentOptions ] = useState ({
subplebbitAddress : selectedAddress ,
onChallenge ,
2023-04-14 15:59:57 +02:00
onChallengeVerification ,
2023-04-11 17:17:32 +02:00
onError : ( error ) => {
setErrorMessage ( error );
},
});
2023-04-14 15:59:57 +02:00
2023-04-11 17:17:32 +02:00
2023-04-12 14:02:35 +02:00
const { publishComment , index } = usePublishComment ( publishCommentOptions );
useEffect (() => {
if ( index !== undefined ) {
2023-04-24 15:48:30 +02:00
setPendingCommentIndex ( index );
2023-04-12 14:02:35 +02:00
navigate ( `/profile/c/ ${ index } ` );
}
}, [ index ]);
const resetFields = () => {
nameRef . current . value = '' ;
subjectRef . current . value = '' ;
commentRef . current . value = '' ;
linkRef . current . value = '' ;
};
2023-04-11 17:17:32 +02:00
const handleSubmit = async ( event ) => {
event . preventDefault ();
setPublishCommentOptions (( prevPublishCommentOptions ) => ({
... prevPublishCommentOptions ,
2023-04-16 11:21:05 +02:00
author : {
displayName : nameRef . current . value || undefined ,
},
2023-04-11 17:17:32 +02:00
title : subjectRef . current . value || undefined ,
content : commentRef . current . value || undefined ,
link : linkRef . current . value || undefined ,
}));
};
useEffect (() => {
if ( publishCommentOptions . content ) {
( async () => {
await publishComment ();
resetFields ();
})();
}
}, [ publishCommentOptions ]);
2023-04-03 15:08:45 +02:00
2023-02-15 16:48:21 +01:00
const getChallengeAnswersFromUser = async ( challenges ) => {
2023-04-07 17:47:51 +02:00
setChallengesArray ( challenges );
2023-02-15 16:48:21 +01:00
return new Promise (( resolve , reject ) => {
const imageString = challenges ? . challenges [ 0 ]. challenge ;
const imageSource = `data:image/png;base64, ${ imageString } ` ;
const challengeImg = new Image ();
challengeImg . src = imageSource ;
challengeImg . onload = () => {
2023-03-12 22:07:15 +01:00
setIsCaptchaOpen ( true );
2023-02-15 16:48:21 +01:00
2023-04-15 16:40:37 +02:00
const handleKeyDown = async ( event ) => {
2023-02-15 16:48:21 +01:00
if ( event . key === 'Enter' ) {
2023-04-15 16:40:37 +02:00
const currentCaptchaResponse = useGeneralStore . getState (). captchaResponse ;
resolve ( currentCaptchaResponse );
2023-03-12 22:07:15 +01:00
setIsCaptchaOpen ( false );
2023-02-15 16:48:21 +01:00
document . removeEventListener ( 'keydown' , handleKeyDown );
2023-04-03 15:08:45 +02:00
event . preventDefault ();
2023-02-15 16:48:21 +01:00
}
};
2023-03-12 22:07:15 +01:00
setCaptchaResponse ( '' );
2023-02-15 16:48:21 +01:00
document . addEventListener ( 'keydown' , handleKeyDown );
2023-04-22 14:02:20 +02:00
2023-04-22 14:33:59 +02:00
setResolveCaptchaPromise ( resolve );
2023-02-15 16:48:21 +01:00
};
challengeImg . onerror = () => {
2023-04-07 17:47:51 +02:00
reject ( setErrorMessage ( 'Could not load challenges' ));
2023-02-15 16:48:21 +01:00
};
});
};
2023-02-12 21:20:08 +01:00
2023-03-03 14:02:46 +01:00
// desktop navbar board select functionality
2023-02-27 15:32:16 +01:00
const handleClickTitle = ( title , address ) => {
2023-02-07 22:55:00 +01:00
setSelectedTitle ( title );
setSelectedAddress ( address );
2023-03-02 11:09:03 +01:00
setSelectedFeed ( feed . filter ( feed => feed . title === title ));
2023-04-23 12:47:59 +02:00
if ( subplebbitAddress === address ) {
window . location . reload ();
}
2023-02-07 22:55:00 +01:00
};
2023-03-03 14:02:46 +01:00
// mobile navbar board select functionality
const handleSelectChange = ( event ) => {
const selected = event . target . value ;
const selectedTitle = defaultSubplebbits . find (( subplebbit ) => subplebbit . address === selected ). title ;
setSelectedTitle ( selectedTitle );
setSelectedAddress ( selected );
2023-04-08 22:30:44 +02:00
navigate ( `/p/ ${ selected } ` );
2023-02-11 22:00:13 +01:00
};
2023-03-02 21:51:06 +01:00
2023-02-02 21:58:02 +01:00
return (
2023-04-21 10:47:39 +02:00
<>
< Helmet >
< title >{(( selectedTitle ? selectedTitle : selectedAddress ) + " - plebchan" )}</ title >
</ Helmet >
< Container >
< ReplyModal
selectedStyle = { selectedStyle }
isOpen = { isReplyOpen }
closeModal = {() => setIsReplyOpen ( false )} />
< SettingsModal
selectedStyle = { selectedStyle }
isOpen = { isSettingsOpen }
closeModal = {() => setIsSettingsOpen ( false )} />
< NavBar selectedStyle = { selectedStyle }>
<>
{ defaultSubplebbits . map ( subplebbit => (
< span className = "boardList" key = { `span- ${ subplebbit . address } ` }>
[
< Link to = { `/p/ ${ subplebbit . address } ` } key = { `a- ${ subplebbit . address } ` } onClick = {() => handleClickTitle ( subplebbit . title , subplebbit . address )}
>{ subplebbit . title ? subplebbit . title : subplebbit . address }</ Link >
] & nbsp ;
</ span >
))}
< span className = "nav" >
2023-02-07 22:55:00 +01:00
[
2023-04-08 22:30:44 +02:00
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}> Settings </ Link >
2023-04-21 10:47:39 +02:00
]
[
2023-03-10 12:42:26 +01:00
< Link to = "/" onClick = {() => handleStyleChange ({ target : { value : "Yotsuba" }}
2023-04-21 10:47:39 +02:00
)}> Home </ Link >
]
</ span >
< div id = "board-nav-mobile" style = {{ top : visible ? 0 : '-23px' }}>
< div className = "board-select" >
< strong > Board </ strong >
& nbsp ;
< select id = "board-select-mobile" value = { selectedAddress } onChange = { handleSelectChange }>
{ defaultSubplebbits . map ( subplebbit => (
< option key = { `option- ${ subplebbit . address } ` } value = { subplebbit . address }
>{ subplebbit . title ? subplebbit . title : subplebbit . address }</ option >
))}
</ select >
</ div >
< div className = "page-jump" >
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}> Settings </ Link >
& nbsp ;
< Link to = "/" onClick = {() => handleStyleChange ({ target : { value : "Yotsuba" }}
)}> Home </ Link >
</ div >
2023-02-23 16:45:29 +01:00
</ div >
2023-04-21 10:47:39 +02:00
< div id = "separator-mobile" > & nbsp ;</ div >
< div id = "separator-mobile" > & nbsp ;</ div >
</>
</ NavBar >
< Header selectedStyle = { selectedStyle }>
<>
< div className = "banner" >
< ImageBanner />
</ div >
<>
< div className = "board-title" >{ selectedTitle }</ div >
< div className = "board-address" > p / { selectedAddress }</ div >
</>
</>
</ Header >
< Break selectedStyle = { selectedStyle } />
< PostForm selectedStyle = { selectedStyle }>
< PostFormLink id = "post-form-link" showPostFormLink = { showPostFormLink } selectedStyle = { selectedStyle } >
< div id = "post-form-link-desktop" >
[
< a onClick = { useClickForm ()} onMouseOver = {( event ) => event . target . style . cursor = 'pointer' }> Start a New Thread </ a >
]
</ div >
< div id = "post-form-link-mobile" >
< span className = "btn-wrap" >
< a onClick = { useClickForm ()} onMouseOver = {( event ) => event . target . style . cursor = 'pointer' }> Start a New Thread </ a >
</ span >
</ div >
</ PostFormLink >
< PostFormTable id = "post-form" showPostForm = { showPostForm } selectedStyle = { selectedStyle } className = "post-form" >
< tbody >
< tr data - type = "Name" >
< td id = "td-name" > Name </ td >
< td >
< input name = "name" type = "text" tabIndex = { 1 } placeholder = "Anonymous" ref = { nameRef } />
</ td >
</ tr >
< tr data - type = "Subject" >
< td > Subject </ td >
< td >
< input name = "sub" type = "text" tabIndex = { 3 } ref = { subjectRef }/>
< input id = "post-button" type = "submit" value = "Post" tabIndex = { 6 }
onClick = { handleSubmit } />
</ td >
</ tr >
< tr data - type = "Comment" >
< td > Comment </ td >
< td >
< textarea name = "com" cols = "48" rows = "4" tabIndex = { 4 } wrap = "soft" ref = { commentRef } />
</ td >
</ tr >
< tr data - type = "File" >
< td > Embed File </ td >
< td >
< input name = "embed" type = "text" tabIndex = { 7 } placeholder = "Paste link" ref = { linkRef } />
< button id = "t-help" type = "button" onClick = {
() => alert ( "- Embedding media is optional, posts can be text-only. \n- A CAPTCHA challenge will appear after posting. \n- The CAPTCHA is case-sensitive." )
} data - tip = "Help" > ? </ button >
</ td >
</ tr >
</ tbody >
</ PostFormTable >
</ PostForm >
< TopBar selectedStyle = { selectedStyle }>
< hr />
< span className = "style-changer" >
Style :
< select id = "style-selector" onChange = { handleStyleChange } value = { selectedStyle }>
< option value = "Yotsuba" > Yotsuba </ option >
< option value = "Yotsuba-B" > Yotsuba B </ option >
< option value = "Futaba" > Futaba </ option >
< option value = "Burichan" > Burichan </ option >
< option value = "Tomorrow" > Tomorrow </ option >
< option value = "Photon" > Photon </ option >
</ select >
</ span >
< div id = "catalog-button-desktop" >
2023-02-24 18:13:02 +01:00
[
2023-04-21 10:47:39 +02:00
< Link to = { `/p/ ${ selectedAddress } /catalog` }> Catalog </ Link >
2023-02-24 18:13:02 +01:00
]
</ div >
2023-04-21 10:47:39 +02:00
< div id = "stats" style = {{ float : "right" , marginTop : "5px" }}>
{ feed . length > 0 ? ( null ) : (< span > Fetching IPFS ...</ span >)}
</ div >
< div id = "catalog-button-mobile" >
2023-02-24 18:13:02 +01:00
< span className = "btn-wrap" >
2023-04-21 10:47:39 +02:00
< Link to = { `/p/ ${ selectedAddress } /catalog` }> Catalog </ Link >
2023-02-24 18:13:02 +01:00
</ span >
</ div >
2023-04-21 10:47:39 +02:00
</ TopBar >
< Tooltip id = "tooltip" className = "tooltip" />
< BoardForm selectedStyle = { selectedStyle }>
< div className = "board" >
{ feed . length < 1 ? (
< PostLoader />
) : (
< InfiniteScroll
pageStart = { 0 }
loadMore = { tryLoadMore }
hasMore = { hasMore }
>
{ selectedFeed . map (( thread ) => {
2023-04-25 14:34:48 +02:00
const { replies : { pages : { topAll : {} = {} } = {} } = {} } = thread ;
const { displayedReplies , omittedCount } = filteredRepliesByThread [ thread . cid ] || {};
2023-04-21 10:47:39 +02:00
const commentMediaInfo = getCommentMediaInfo ( thread );
const fallbackImgUrl = "/assets/filedeleted-res.gif" ;
return (
< Fragment key = { `fr- ${ thread . cid } ` }>
< div key = { `t- ${ thread . cid } ` } className = "thread" >
< div key = { `c- ${ thread . cid } ` } className = "op-container" >
< div key = { `po- ${ thread . cid } ` } className = "post op op-desktop" >
< hr key = { `hr- ${ thread . cid } ` } />
< div key = { `pi- ${ thread . cid } ` } className = "post-info" >
{ commentMediaInfo ? . url ? (
< div key = { `f- ${ thread . cid } ` } className = "file" style = {{ marginBottom : "5px" }}>
< div key = { `ft- ${ thread . cid } ` } className = "file-text" >
Link :& nbsp ;
< a key = { `fa- ${ thread . cid } ` } href = { commentMediaInfo . url } target = "_blank"
rel = "noopener noreferrer" >{
commentMediaInfo ? . url . length > 30 ?
commentMediaInfo ? . url . slice ( 0 , 30 ) + "(...)" :
commentMediaInfo ? . url
}</ a > & nbsp ;({ commentMediaInfo ? . type })
</ div >
{ commentMediaInfo ? . type === "webpage" ? (
< span key = { `fta- ${ thread . cid } ` } className = "file-thumb" >
< img key = { `fti- ${ thread . cid } ` } src = { thread . thumbnailUrl } alt = "thumbnail" onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ commentMediaInfo ? . type === "image" ? (
< span key = { `fta- ${ thread . cid } ` } className = "file-thumb" >
< img key = { `fti- ${ thread . cid } ` } src = { commentMediaInfo . url } alt = { commentMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ commentMediaInfo ? . type === "video" ? (
< span key = { `fta- ${ thread . cid } ` } className = "file-thumb" >
< video controls width = "" key = { `fti- ${ thread . cid } ` } src = { commentMediaInfo . url } alt = { commentMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ commentMediaInfo ? . type === "audio" ? (
< span key = { `fta- ${ thread . cid } ` } className = "file-thumb" >
< audio controls key = { `fti- ${ thread . cid } ` } src = { commentMediaInfo . url } alt = { commentMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
2023-03-25 14:54:04 +01:00
</ div >
2023-04-21 10:47:39 +02:00
) : null }
< span key = { `nb- ${ thread . cid } ` } className = "name-block" >
{ thread . title ? (
thread . title . length > 75 ?
< Fragment key = { `fragment2- ${ thread . cid } ` }>
< span key = { `q- ${ thread . cid } ` } className = "title"
data - tooltip - id = "tooltip"
data - tooltip - content = { thread . title }
data - tooltip - place = "top" >
{ thread . title . slice ( 0 , 75 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `q- ${ thread . cid } ` } className = "title" >
{ thread . title }
</ span >)
: null } & nbsp ;
{ thread . author . displayName
? thread . author . displayName . length > 20
? < Fragment key = { `fragment3- ${ thread . cid } ` }>
< span key = { `n- ${ thread . cid } ` } className = "name"
data - tooltip - id = "tooltip"
data - tooltip - content = { thread . author . displayName }
data - tooltip - place = "top" >
{ thread . author . displayName . slice ( 0 , 20 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `n- ${ thread . cid } ` } className = "name" >
{ thread . author . displayName }</ span >
2023-03-25 21:44:28 +01:00
: < span key = { `n- ${ thread . cid } ` } className = "name" >
2023-04-21 10:47:39 +02:00
Anonymous </ span >}
2023-03-25 21:44:28 +01:00
& nbsp ;
2023-04-21 10:47:39 +02:00
( u /
< span key = { `pa- ${ thread . cid } ` } className = "poster-address address-desktop"
id = "reply-button" style = {{ cursor : "pointer" }}
onClick = {() => handleAddressClick ( thread . author . shortAddress )}
>
{ thread . author . shortAddress }
</ span >)
2023-03-25 21:44:28 +01:00
& nbsp ;
2023-04-21 10:47:39 +02:00
< span key = { `dt- ${ thread . cid } ` } className = "date-time" data - utc = "data" >{ getDate ( thread . timestamp )}</ span >
2023-03-25 21:44:28 +01:00
& nbsp ;
2023-04-21 10:47:39 +02:00
< span key = { `pn- ${ thread . cid } ` } className = "post-number post-number-desktop" >
< Link to = {() => {}} key = { `pl1- ${ thread . cid } ` } onClick = {() => {}} title = "Link to this post" > c / </ Link >
< Link to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } id = "reply-button" key = { `pl2- ${ thread . cid } ` }
2023-04-20 11:19:56 +02:00
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setIsReplyOpen ( true );
2023-04-21 10:47:39 +02:00
setSelectedShortCid ( thread . shortCid );
setSelectedParentCid ( thread . cid );
}} title = "Reply to this post" >{ thread . shortCid }</ Link >
& nbsp ;
< span key = { `rl1- ${ thread . cid } ` }> & nbsp ;
[
< Link key = { `rl2- ${ thread . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "reply-link" > Reply </ Link >
]
</ span >
2023-03-25 21:44:28 +01:00
</ span > & nbsp ;
2023-04-21 10:47:39 +02:00
< button key = { `pmb- ${ thread . cid } ` } className = "post-menu-button" title = "Post menu" style = {{ all : 'unset' , cursor : 'pointer' }} data - cmd = "post-menu" > ▶ </ button >
< div key = { `bi- ${ thread . cid } ` } id = "backlink-id" className = "backlink" >
{ thread . replies ? . pages ? . topAll . comments
2023-03-25 21:44:28 +01:00
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply ) => (
< div key = { `div- ${ reply . cid } ` } style = {{ display : 'inline-block' }}>
2023-04-21 10:47:39 +02:00
< Link key = { `ql- ${ reply . cid } ` }
to = {() => {}} className = "quote-link"
onClick = {( event ) => handleQuoteClick ( reply , null , event )}>
2023-03-30 22:17:42 +02:00
c / { reply . shortCid }</ Link >
2023-03-25 21:44:28 +01:00
& nbsp ;
</ div >
))
}
</ div >
2023-04-21 10:47:39 +02:00
</ span >
{ thread . content ? (
2023-04-22 18:02:47 +02:00
thread . content ? . length > 1000 ?
2023-04-21 10:47:39 +02:00
< Fragment key = { `fragment5- ${ thread . cid } ` }>
< blockquote key = { `bq- ${ thread . cid } ` }>
2023-04-22 18:02:47 +02:00
< Post content = { thread . content ? . slice ( 0 , 1000 )} key = { `post- ${ thread . cid } ` } />
2023-04-21 10:47:39 +02:00
< span key = { `ttl-s- ${ thread . cid } ` } className = "ttl" > (...)
< br key = { `ttl-s-br1- ${ thread . cid } ` } />< br key = { `ttl-s-br2 ${ thread . cid } ` } />
Post too long . & nbsp ;
< Link key = { `ttl-l- ${ thread . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "ttl-link" > Click here </ Link >
& nbsp ; to view . </ span >
2023-04-17 16:26:37 +02:00
</ blockquote >
</ Fragment >
2023-04-21 10:47:39 +02:00
: < blockquote key = { `bq- ${ thread . cid } ` }>
< Post content = { thread . content } key = { `post- ${ thread . cid } ` } />
2023-04-17 16:26:37 +02:00
</ blockquote >)
: null }
2023-03-25 21:44:28 +01:00
</ div >
</ div >
</ div >
2023-04-21 10:47:39 +02:00
< span key = { `summary- ${ thread . cid } ` } className = "summary" >
{ omittedCount > 0 ? (
< span key = { `oc- ${ thread . cid } ` } className = "ttl" >
< span key = { `oc1- ${ thread . cid } ` }>
{ omittedCount } post { omittedCount > 1 ? "s" : "" } omitted . Click & nbsp ;
< Link key = { `oc2- ${ thread . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "ttl-link" > here </ Link >
& nbsp ; to view .
</ span >
</ span >) : null }
</ span >
2023-04-25 14:34:48 +02:00
{ displayedReplies ? . map (( reply ) => {
2023-04-21 10:47:39 +02:00
const replyMediaInfo = getCommentMediaInfo ( reply );
const fallbackImgUrl = "/assets/filedeleted-res.gif" ;
const shortParentCid = findShortParentCid ( reply . parentCid , selectedFeed );
return (
< div key = { `rc- ${ reply . cid } ` } className = "reply-container" >
< div key = { `sa- ${ reply . cid } ` } className = "side-arrows" >{ '>>' }</ div >
< div key = { `pr- ${ reply . cid } ` } className = "post-reply post-reply-desktop" >
< div key = { `pi- ${ reply . cid } ` } className = "post-info" >
< span key = { `nb- ${ reply . cid } ` } className = "nameblock" >
{ reply . author . displayName
? reply . author . displayName . length > 12
? < Fragment key = { `fragment6- ${ reply . cid } ` }>
< span key = { `mob-n- ${ reply . cid } ` } className = "name"
data - tooltip - id = "tooltip"
data - tooltip - content = { reply . author . displayName }
data - tooltip - place = "top" >
{ reply . author . displayName . slice ( 0 , 12 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `mob-n- ${ reply . cid } ` } className = "name" >
{ reply . author . displayName }</ span >
: < span key = { `mob-n- ${ reply . cid } ` } className = "name" >
Anonymous </ span >}
& nbsp ;
< span key = { `pa- ${ reply . cid } ` } className = "poster-address address-desktop"
id = "reply-button" style = {{ cursor : "pointer" }}
onClick = {() => handleAddressClick ( reply . author . shortAddress )}
>
( u /
2023-04-25 14:34:48 +02:00
{ reply . author ? . shortAddress ?
(
< span key = { `mob-ha- ${ reply . cid } ` }>
{ reply . author ? . shortAddress }
</ span >
) : (
< span key = { `mob-ha- ${ reply . cid } ` }
data - tooltip - id = "tooltip"
data - tooltip - content = { account ? . author ? . address }
data - tooltip - place = "top"
>
{ account ? . author ? . address . slice ( 0 , 10 ) + "(...)" }
</ span >
)
}
2023-04-21 10:47:39 +02:00
)
2023-03-25 21:44:28 +01:00
</ span >
2023-04-21 10:47:39 +02:00
</ span >
& nbsp ;
< span key = { `dt- ${ reply . cid } ` } className = "date-time" data - utc = "data" >{ getDate ( reply . timestamp )}</ span >
& nbsp ;
< span key = { `pn- ${ reply . cid } ` } className = "post-number post-number-desktop" >
< Link to = {() => {}} key = { `pl1- ${ reply . cid } ` } onClick = {() => {}} title = "Link to this post" > c / </ Link >
2023-04-25 14:34:48 +02:00
{ reply . shortCid ? (
< Link to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } id = "reply-button" key = { `pl2- ${ reply . cid } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setIsReplyOpen ( true );
setSelectedShortCid ( reply . shortCid );
setSelectedParentCid ( reply . cid );
}} title = "Reply to this post" >{ reply . shortCid }</ Link >
) : (
< span key = "pending" style = {{ color : 'red' , fontWeight : '700' }}> Pending </ span >
)}
2023-04-21 10:47:39 +02:00
</ span > & nbsp ;
< button key = { `pmb- ${ reply . cid } ` } className = "post-menu-button" onClick = {() => {}} title = "Post menu" style = {{ all : 'unset' , cursor : 'pointer' }} data - cmd = "post-menu" > ▶ </ button >
< div id = "backlink-id" className = "backlink" >
{ reply . replies ? . pages ? . topAll . comments
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply ) => (
< div key = { `div- ${ reply . cid } ` } style = {{ display : 'inline-block' }}>
< Link to = {() => {}} key = { `ql- ${ reply . cid } ` }
className = "quote-link"
onClick = {( event ) => handleQuoteClick ( reply , reply . shortCid , event )}>
c / { reply . shortCid }</ Link >
& nbsp ;
</ div >
))
}
</ div >
</ div >
{ replyMediaInfo ? . url ? (
< div key = { `f- ${ reply . cid } ` } className = "file"
style = {{ marginBottom : "5px" }}>
< div key = { `ft- ${ reply . cid } ` } className = "reply-file-text" >
Link :& nbsp ;
< a key = { `fa- ${ reply . cid } ` } href = { replyMediaInfo . url } target = "_blank"
rel = "noopener noreferrer" >{
replyMediaInfo ? . url . length > 30 ?
replyMediaInfo ? . url . slice ( 0 , 30 ) + "(...)" :
replyMediaInfo ? . url
}</ a > & nbsp ;({ replyMediaInfo ? . type })
</ div >
{ replyMediaInfo ? . type === "webpage" ? (
< span key = { `fta- ${ reply . cid } ` } className = "file-thumb-reply" >
< img key = { `fti- ${ reply . cid } ` }
src = { reply . thumbnailUrl }
alt = "thumbnail"
onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ replyMediaInfo ? . type === "image" ? (
< span key = { `fta- ${ reply . cid } ` } className = "file-thumb-reply" >
< img key = { `fti- ${ reply . cid } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ replyMediaInfo ? . type === "video" ? (
< span key = { `fta- ${ reply . cid } ` } className = "file-thumb-reply" >
< video controls
key = { `fti- ${ reply . cid } ` }
src = { replyMediaInfo . url } alt = { replyMediaInfo . type }
onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
{ replyMediaInfo ? . type === "audio" ? (
< span key = { `fta- ${ reply . cid } ` } className = "file-thumb-reply" >
< audio controls
key = { `fti- ${ reply . cid } ` }
src = { replyMediaInfo . url } alt = { replyMediaInfo . type }
onError = {( e ) => e . target . src = fallbackImgUrl } />
</ span >
) : null }
</ div >
) : null }
{ reply . content ? (
2023-04-22 18:02:47 +02:00
reply . content ? . length > 500 ?
2023-04-21 10:47:39 +02:00
< Fragment key = { `fragment8- ${ reply . cid } ` }>
< blockquote key = { `pm- ${ reply . cid } ` } comment = { reply } className = "post-message" >
< Link to = {() => {}} key = { `r-pm- ${ reply . cid } ` } className = "quotelink" onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , thread . shortCid , event )}>
{ `c/ ${ shortParentCid } ` }{ shortParentCid === thread . shortCid ? " (OP)" : null }
</ Link >
2023-04-22 18:02:47 +02:00
< Post content = { reply . content ? . slice ( 0 , 500 )} key = { `post- ${ reply . cid } ` } />
2023-04-21 10:47:39 +02:00
< span key = { `ttl-s- ${ reply . cid } ` } className = "ttl" > (...)
< br key = { `ttl-s-br1- ${ reply . cid } ` } />< br key = { `ttl-s-br2 ${ reply . cid } ` } />
Comment too long . & nbsp ;
< Link key = { `ttl-l- ${ reply . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "ttl-link" > Click here </ Link >
& nbsp ; to view . </ span >
</ blockquote >
2023-03-25 21:44:28 +01:00
</ Fragment >
2023-04-21 10:47:39 +02:00
: < blockquote key = { `pm- ${ reply . cid } ` } className = "post-message" >
< Link to = {() => {}} key = { `r-pm- ${ reply . cid } ` } className = "quotelink" onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , thread . shortCid , event )}>
{ `c/ ${ shortParentCid } ` }{ shortParentCid === thread . shortCid ? " (OP)" : null }
</ Link >
< Post content = { reply . content } key = { `post- ${ reply . cid } ` } comment = { reply } />
</ blockquote >)
: null }
</ div >
</ div >
)
})}
</ div >
< div key = { `mob-t- ${ thread . cid } ` } className = "thread-mobile" >
< hr key = { `mob-hr- ${ thread . cid } ` } />
< div key = { `mob-c- ${ thread . cid } ` } className = "op-container" >
< div key = { `mob-po- ${ thread . cid } ` } className = "post op op-mobile" >
< div key = { `mob-pi- ${ thread . cid } ` } className = "post-info-mobile" >
< button key = { `mob-pb- ${ thread . cid } ` } className = "post-menu-button-mobile" onClick = {() => {}} style = {{ all : 'unset' , cursor : 'pointer' }}>...</ button >
< span key = { `mob-nbm- ${ thread . cid } ` } className = "name-block-mobile" >
{ thread . author . displayName
? thread . author . displayName . length > 15
? < Fragment key = { `fragment9- ${ thread . cid } ` }>
< span key = { `mob-n- ${ thread . cid } ` } className = "name-mobile"
data - tooltip - id = "tooltip"
data - tooltip - content = { thread . author . displayName }
data - tooltip - place = "top" >
{ thread . author . displayName . slice ( 0 , 15 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `mob-n- ${ thread . cid } ` } className = "name-mobile" >
{ thread . author . displayName }</ span >
: < span key = { `mob-n- ${ thread . cid } ` } className = "name-mobile" >
2023-03-25 21:44:28 +01:00
Anonymous </ span >}
& nbsp ;
2023-04-21 10:47:39 +02:00
< span key = { `mob-pa- ${ thread . cid } ` } className = "poster-address-mobile address-mobile"
2023-04-20 17:20:26 +02:00
id = "reply-button" style = {{ cursor : "pointer" }}
2023-04-21 10:47:39 +02:00
onClick = {() => handleAddressClick ( thread . author . shortAddress )}
2023-04-20 17:20:26 +02:00
>
2023-03-25 21:44:28 +01:00
( u /
2023-04-21 10:47:39 +02:00
< span key = { `mob-ha- ${ thread . cid } ` } className = "highlight-address-mobile" >
{ thread . author . shortAddress }
2023-03-29 14:36:41 +02:00
</ span >
2023-03-25 21:44:28 +01:00
) & nbsp ;
</ span >
2023-04-21 10:47:39 +02:00
< br key = { `mob-br1- ${ thread . cid } ` } />
{ thread . title ? (
thread . title . length > 30 ?
< Fragment key = { `fragment11- ${ thread . cid } ` }>
< span key = { `mob-t- ${ thread . cid } ` } className = "subject-mobile"
data - tooltip - id = "tooltip"
data - tooltip - content = { thread . title }
data - tooltip - place = "top" >
{ thread . title . slice ( 0 , 30 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `mob-t- ${ thread . cid } ` } className = "subject-mobile" >
{ thread . title }
</ span >)
: null }
2023-03-25 21:44:28 +01:00
</ span >
2023-04-21 10:47:39 +02:00
< span key = { `mob-dt- ${ thread . cid } ` } className = "date-time-mobile post-number-mobile" >
{ getDate ( thread . timestamp )}
& nbsp ;
< Link to = {() => {}} key = { `mob-no- ${ thread . cid } ` } onClick = {() => {}} title = "Link to this post" > c / </ Link >
< Link to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } id = "reply-button" key = { `mob-no2- ${ thread . cid } ` }
2023-04-20 11:19:56 +02:00
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setIsReplyOpen ( true );
2023-04-21 10:47:39 +02:00
setSelectedShortCid ( thread . shortCid );
setSelectedParentCid ( thread . cid );
}} title = "Reply to this post" >{ thread . shortCid }
2023-04-20 11:19:56 +02:00
</ Link >
2023-03-25 21:44:28 +01:00
</ span >
</ div >
2023-04-21 10:47:39 +02:00
{ thread . link ? (
< div key = { `mob-f- ${ thread . cid } ` } className = "file-mobile" >
< a key = { `link-a- ${ thread . cid } ` } href = { commentMediaInfo ? . url } target = "_blank"
rel = "noopener noreferrer" >
{ commentMediaInfo ? . url ? (
commentMediaInfo . type === "webpage" ? (
< span key = { `mob-ft ${ thread . cid } ` } className = "file-thumb-mobile" >
< img key = { `mob-img- ${ thread . cid } ` } src = { thread . thumbnailUrl } alt = "thumbnail" onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ thread . cid } ` } className = "file-info-mobile" >{ commentMediaInfo ? . type }</ div >
2023-03-29 22:02:44 +02:00
</ span >
2023-04-21 10:47:39 +02:00
) : commentMediaInfo . type === "image" ? (
< span key = { `mob-ft ${ thread . cid } ` } className = "file-thumb-mobile" >
< img key = { `mob-img- ${ thread . cid } ` } src = { commentMediaInfo . url } alt = { commentMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ thread . cid } ` } className = "file-info-mobile" >{ commentMediaInfo ? . type }</ div >
2023-03-29 22:02:44 +02:00
</ span >
2023-04-21 10:47:39 +02:00
) : commentMediaInfo . type === "video" ? (
< span key = { `mob-ft ${ thread . cid } ` } className = "file-thumb-mobile" >
< video key = { `fti- ${ thread . cid } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
style = {{ pointerEvents : "none" }}
onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ thread . cid } ` } className = "file-info-mobile" >{ commentMediaInfo ? . type }</ div >
2023-03-29 22:02:44 +02:00
</ span >
2023-04-21 10:47:39 +02:00
) : commentMediaInfo . type === "audio" ? (
< span key = { `mob-ft ${ thread . cid } ` } className = "file-thumb-mobile" >
< audio key = { `mob-img- ${ thread . cid } ` } src = { commentMediaInfo . url } alt = { commentMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ thread . cid } ` } className = "file-info-mobile" >{ commentMediaInfo ? . type }</ div >
2023-03-29 22:02:44 +02:00
</ span >
) : null
) : null }
</ a >
</ div >
2023-03-29 18:55:29 +02:00
) : null }
2023-04-21 10:47:39 +02:00
{ thread . content ? (
2023-04-22 18:02:47 +02:00
thread . content ? . length > 500 ?
2023-04-21 10:47:39 +02:00
< Fragment key = { `fragment12- ${ thread . cid } ` }>
< blockquote key = { `mob-bq- ${ thread . cid } ` } className = "post-message-mobile" >
2023-04-22 18:02:47 +02:00
< Post content = { thread . content ? . slice ( 0 , 500 )} key = { `post-mobile- ${ thread . cid } ` } />
2023-04-21 10:47:39 +02:00
< span key = { `mob-ttl-s- ${ thread . cid } ` } className = "ttl" > (...)
< br key = { `mob-ttl-s-br1- ${ thread . cid } ` } />< br key = { `mob-ttl-s-br2 ${ thread . cid } ` } />
Post too long . & nbsp ;
< Link key = { `mob-ttl-l- ${ thread . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "ttl-link" > Click here </ Link >
& nbsp ; to view . </ span >
</ blockquote >
</ Fragment >
: < blockquote key = { `mob-bq- ${ thread . cid } ` } className = "post-message-mobile" >
< Post content = { thread . content } key = { `post-mobile- ${ thread . cid } ` } />
</ blockquote >)
: null }
</ div >
< div key = { `mob-pl- ${ thread . cid } ` } className = "post-link-mobile" >
< span key = { `mob-info- ${ thread . cid } ` } className = "info-mobile" >{
2023-04-25 14:34:48 +02:00
( thread . replyCount + pendingReplyCounts [ thread . cid ]) === 0 ?
2023-04-21 10:47:39 +02:00
( "No replies" )
2023-04-25 14:34:48 +02:00
: ( thread . replyCount + pendingReplyCounts [ thread . cid ]) === 1 ?
2023-04-21 10:47:39 +02:00
( "1 reply" )
2023-04-25 14:34:48 +02:00
: ( thread . replyCount + pendingReplyCounts [ thread . cid ]) > 1 ?
(( thread . replyCount + pendingReplyCounts [ thread . cid ]) + " replies" )
2023-04-21 10:47:39 +02:00
: null
}</ span >
< Link key = { `rl2- ${ thread . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "button-mobile" > View Thread </ Link >
</ div >
</ div >
2023-04-25 14:34:48 +02:00
{ displayedReplies ? . map (( reply ) => {
2023-04-21 10:47:39 +02:00
const replyMediaInfo = getCommentMediaInfo ( reply );
const shortParentCid = findShortParentCid ( reply . parentCid , selectedFeed );
return (
< div key = { `mob-rc- ${ reply . cid } ` } className = "reply-container" >
< div key = { `mob-pr- ${ reply . cid } ` } className = "post-reply post-reply-mobile" >
< div key = { `mob-pi- ${ reply . cid } ` } className = "post-info-mobile" >
< button key = { `pmbm- ${ reply . cid } ` } className = "post-menu-button-mobile" title = "Post menu" style = {{ all : 'unset' , cursor : 'pointer' }}>...</ button >
< span key = { `mob-nb- ${ reply . cid } ` } className = "name-block-mobile" >
{ reply . author . displayName
? reply . author . displayName . length > 12
? < Fragment key = { `fragment13- ${ reply . cid } ` }>
< span key = { `mob-n- ${ reply . cid } ` } className = "name-mobile"
data - tooltip - id = "tooltip"
data - tooltip - content = { reply . author . displayName }
data - tooltip - place = "top" >
{ reply . author . displayName . slice ( 0 , 12 ) + " (...)" }
</ span >
</ Fragment >
: < span key = { `mob-n- ${ reply . cid } ` } className = "name-mobile" >
{ reply . author . displayName }</ span >
: < span key = { `mob-n- ${ reply . cid } ` } className = "name-mobile" >
Anonymous </ span >}
& nbsp ;
< span key = { `mob-pa- ${ reply . cid } ` } className = "poster-address-mobile address-mobile"
id = "reply-button" style = {{ cursor : "pointer" }}
onClick = {() => handleAddressClick ( reply . author . shortAddress )}
>
( u /
2023-04-25 14:34:48 +02:00
{ reply . author ? . shortAddress ?
(
< span key = { `mob-ha- ${ reply . cid } ` } className = "highlight-address-mobile" >
{ reply . author ? . shortAddress }
</ span >
) : (
< span key = { `mob-ha- ${ reply . cid } ` }
data - tooltip - id = "tooltip"
data - tooltip - content = { account ? . author ? . address }
data - tooltip - place = "top"
>
{ account ? . author ? . address . slice ( 0 , 8 ) + "(...)" }
</ span >
)
}
2023-04-21 10:47:39 +02:00
) & nbsp ;
</ span >
< br key = { `mob-br- ${ reply . cid } ` } />
</ span >
< span key = { `mob-dt- ${ reply . cid } ` } className = "date-time-mobile post-number-mobile" >
{ getDate ( reply . timestamp )} & nbsp ;
< Link to = {() => {}} key = { `mob-pl1- ${ reply . cid } ` } onClick = {() => {}} title = "Link to this post" > c / </ Link >
2023-04-25 14:34:48 +02:00
{ reply . shortCid ? (
< Link to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } id = "reply-button" key = { `mob-pl2- ${ reply . cid } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setIsReplyOpen ( true );
setSelectedShortCid ( reply . shortCid );
setSelectedParentCid ( reply . cid );
}} title = "Reply to this post" >{ reply . shortCid }
</ Link >
) : (
< span key = "pending" style = {{ color : 'red' , fontWeight : '700' }}> Pending </ span >
)}
2023-04-21 10:47:39 +02:00
</ span >
</ div >
{ reply . link ? (
< div key = { `mob-f- ${ reply . cid } ` } className = "file-mobile" >
< a key = { `link-a- ${ reply . cid } ` } href = { replyMediaInfo ? . url } target = "_blank" rel = "noopener noreferrer" >
{ replyMediaInfo ? . url ? (
replyMediaInfo . type === "webpage" ? (
< span key = { `mob-ft ${ reply . cid } ` } className = "file-thumb-mobile" >
< img key = { `mob-img- ${ reply . cid } ` } src = { reply . thumbnailUrl } alt = "thumbnail" onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ reply . cid } ` } className = "file-info-mobile" >{ replyMediaInfo . type }</ div >
</ span >
) : replyMediaInfo . type === "image" ? (
< span key = { `mob-ft ${ reply . cid } ` } className = "file-thumb-mobile" >
< img key = { `mob-img- ${ reply . cid } ` } src = { replyMediaInfo . url } alt = { replyMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ reply . cid } ` } className = "file-info-mobile" >{ replyMediaInfo . type }</ div >
</ span >
) : replyMediaInfo . type === "video" ? (
< span key = { `mob-ft ${ reply . cid } ` } className = "file-thumb-mobile" >
< video key = { `fti- ${ reply . cid } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
style = {{ pointerEvents : "none" }}
onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ reply . cid } ` } className = "file-info-mobile" >{ replyMediaInfo . type }</ div >
</ span >
) : replyMediaInfo . type === "audio" ? (
< span key = { `mob-ft ${ reply . cid } ` } className = "file-thumb-mobile" >
< audio key = { `mob-img- ${ reply . cid } ` } src = { replyMediaInfo . url } alt = { replyMediaInfo . type } onError = {( e ) => e . target . src = fallbackImgUrl } />
< div key = { `mob-fi- ${ reply . cid } ` } className = "file-info-mobile" >{ replyMediaInfo . type }</ div >
</ span >
) : null
) : null }
</ a >
</ div >
) : null }
{ reply . content ? (
2023-04-22 18:02:47 +02:00
reply . content ? . length > 500 ?
2023-04-21 10:47:39 +02:00
< Fragment key = { `fragment15- ${ reply . cid } ` }>
< blockquote key = { `mob-pm- ${ reply . cid } ` } className = "post-message" >
< Link to = {() => {}} key = { `mob-r-pm- ${ reply . cid } ` } className = "quotelink" onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , thread . shortCid , event )}>
{ `c/ ${ shortParentCid } ` }{ shortParentCid === thread . shortCid ? " (OP)" : null }
</ Link >
2023-04-22 18:02:47 +02:00
< Post content = { reply . content ? . slice ( 0 , 500 )} key = { `post-mobile- ${ reply . cid } ` } comment = { reply } />
2023-04-21 10:47:39 +02:00
< span key = { `mob-ttl-s- ${ reply . cid } ` } className = "ttl" > (...)
< br key = { `mob-ttl-s-br1- ${ reply . cid } ` } />< br key = { `mob-ttl-s-br2 ${ reply . cid } ` } />
Comment too long . & nbsp ;
< Link key = { `mob-ttl-l- ${ reply . cid } ` } to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } onClick = {() => setSelectedThread ( thread . cid )} className = "ttl-link" > Click here </ Link >
& nbsp ; to view . </ span >
</ blockquote >
</ Fragment >
: < blockquote key = { `mob-pm- ${ reply . cid } ` } className = "post-message" >
2023-04-18 15:23:39 +02:00
< Link to = {() => {}} key = { `mob-r-pm- ${ reply . cid } ` } className = "quotelink" onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , thread . shortCid , event )}>
2023-04-17 21:07:38 +02:00
{ `c/ ${ shortParentCid } ` }{ shortParentCid === thread . shortCid ? " (OP)" : null }
2023-04-17 16:26:37 +02:00
</ Link >
2023-04-21 10:47:39 +02:00
< Post content = { reply . content } key = { `post-mobile- ${ reply . cid } ` } comment = { reply } />
</ blockquote >)
: null }
{ reply . replyCount > 0 ? (
< div key = { `back-mob- ${ reply . cid } ` } className = 'backlink backlink-mobile' >
{ reply . replies ? . pages ? . topAll . comments
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply ) => (
< div key = { `div-back ${ reply . cid } ` } style = {{ display : 'inline-block' }}>
< Link key = { `ql- ${ reply . cid } ` } to = {() => {}}
onClick = {( event ) => handleQuoteClick ( reply , reply . shortCid , event )} className = "quote-link" >
c / { reply . shortCid }</ Link >
& nbsp ;
</ div >
))}
2023-04-16 12:54:51 +02:00
</ div >
2023-04-21 10:47:39 +02:00
) : null }
</ div >
2023-03-25 21:44:28 +01:00
</ div >
2023-04-21 10:47:39 +02:00
)})}
</ div >
</ Fragment >
)})}
</ InfiniteScroll >
)}
</ div >
</ BoardForm >
2023-04-24 13:09:59 +02:00
< Footer selectedStyle = { selectedStyle }>
< Break id = "break" selectedStyle = { selectedStyle } style = {{
marginTop : "-36px" ,
width : "100%" ,
}} />
< Break selectedStyle = { selectedStyle } style = {{
width : "100%" ,
}} />
< span className = "style-changer" style = {{
float : "right" ,
marginTop : "2px" ,
}}>
Style :
< select id = "style-selector" onChange = { handleStyleChange } value = { selectedStyle }>
< option value = "Yotsuba" > Yotsuba </ option >
< option value = "Yotsuba-B" > Yotsuba B </ option >
< option value = "Futaba" > Futaba </ option >
< option value = "Burichan" > Burichan </ option >
< option value = "Tomorrow" > Tomorrow </ option >
< option value = "Photon" > Photon </ option >
</ select >
</ span >
< NavBar selectedStyle = { selectedStyle } style = {{
marginTop : "42px" ,
}}>
<>
{ defaultSubplebbits . map ( subplebbit => (
< span className = "boardList" key = { `span- ${ subplebbit . address } ` }>
[
< Link key = { `a- ${ subplebbit . address } ` }
to = { `/p/ ${ subplebbit . address } ` }
onClick = {() => {
setSelectedTitle ( subplebbit . title );
setSelectedAddress ( subplebbit . address );
}}
>{ subplebbit . title ? subplebbit . title : subplebbit . address }</ Link >
] & nbsp ;
</ span >
))}
< span className = "nav" >
[
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}> Settings </ Link >
]
[
< Link to = "/" onClick = {() => handleStyleChange ({ target : { value : "Yotsuba" }}
)}> Home </ Link >
]
</ span >
</>
</ NavBar >
< div id = "version" >
2023-04-24 20:00:11 +00:00
plebchan v { version }. GPL - 2.0
2023-04-24 13:09:59 +02:00
</ div >
< div className = "footer-links"
2023-04-24 17:53:09 +00:00
style = {{
textAlign : "center" ,
fontSize : "x-small" ,
fontFamily : "arial" ,
marginTop : "5px" ,
marginBottom : "15px" ,
}}>
< a style = {{ textDecoration : 'underline' }} href = "https://plebbit.com" target = "_blank" rel = "noopener noreferrer" > About </ a >
2023-04-24 13:09:59 +02:00
& nbsp ; • & nbsp ;
2023-04-24 20:06:35 +00:00
< a style = {{ textDecoration : 'underline' }} href = "https://github.com/plebbit/plebchan/releases/latest" target = "_blank" rel = "noopener noreferrer" > App </ a >
2023-04-24 13:09:59 +02:00
& nbsp ; • & nbsp ;
2023-04-24 20:06:35 +00:00
< a style = {{ textDecoration : 'underline' }} href = "https://twitter.com/plebchan_eth" target = "_blank" rel = "noopener noreferrer" > Twitter </ a >
2023-04-24 13:09:59 +02:00
& nbsp ; • & nbsp ;
2023-04-24 17:53:09 +00:00
< a style = {{ textDecoration : 'underline' }} href = "https://t.me/plebbit" target = "_blank" rel = "noopener noreferrer" > Telegram </ a >
2023-04-24 13:09:59 +02:00
</ div >
</ Footer >
2023-04-21 10:47:39 +02:00
</ Container >
</>
2023-02-07 22:55:00 +01:00
);
2023-01-31 23:08:57 +01:00
}
export default Board ;