2023-07-01 13:23:03 +02:00
import React , { Fragment , useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
2023-05-25 15:40:16 +02:00
import { createPortal } from 'react-dom' ;
2023-04-21 10:47:39 +02:00
import { Helmet } from 'react-helmet-async' ;
2023-10-05 16:42:47 +02:00
import { Link , useLocation , useNavigate , useParams } from 'react-router-dom' ;
2023-05-23 16:08:32 +02:00
import { confirmAlert } from 'react-confirm-alert' ;
2023-03-23 15:49:57 +01:00
import { Tooltip } from 'react-tooltip' ;
2023-04-28 18:04:16 +02:00
import { Virtuoso } from 'react-virtuoso' ;
2023-05-23 16:08:32 +02:00
import { useAccount , useAccountComments , useFeed , usePublishComment , usePublishCommentEdit , useSubplebbit , useSubscribe } from '@plebbit/plebbit-react-hooks' ;
2023-09-21 14:38:12 +02:00
import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils' ;
2023-03-25 14:54:04 +01:00
import { debounce } from 'lodash' ;
2023-09-14 13:41:24 +02:00
import { Container , NavBar , Header , Break , PostFormLink , PostFormTable , PostForm , TopBar , BoardForm , PostMenu , PostMenuMobile } from '../styled/views/Board.styled' ;
2023-08-13 21:53:04 +02:00
import { Footer } from '../styled/views/Thread.styled' ;
import { AlertModal } from '../styled/modals/AlertModal.styled' ;
2023-05-25 15:40:16 +02:00
import { PostMenuCatalog } from '../styled/views/Catalog.styled' ;
2023-05-24 12:15:43 +02:00
import EditModal from '../modals/EditModal' ;
2023-06-28 15:25:28 +02:00
import AdminListModal from '../modals/AdminListModal' ;
2023-05-24 12:15:43 +02:00
import ModerationModal from '../modals/ModerationModal' ;
2023-08-19 15:05:16 +02:00
import BoardSettings from '../BoardSettings' ;
2023-08-22 16:18:55 +02:00
import BoardStats from '../BoardStats' ;
import EditLabel from '../EditLabel' ;
2023-09-09 17:09:09 +02:00
import Embed from '../Embed' ;
2023-08-22 16:18:55 +02:00
import ImageBanner from '../ImageBanner' ;
2023-05-06 15:40:30 +02:00
import OfflineIndicator from '../OfflineIndicator' ;
2023-06-15 14:17:33 +02:00
import PendingLabel from '../PendingLabel' ;
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-06-03 16:33:59 +02:00
import PostOnHover from '../PostOnHover' ;
2023-06-01 10:43:17 +02:00
import StateLabel from '../StateLabel' ;
2023-07-08 14:37:08 +02:00
import VerifiedAuthor from '../VerifiedAuthor' ;
2023-07-19 18:16:01 +02:00
import CreateBoardModal from '../modals/CreateBoardModal' ;
2023-05-24 12:15:43 +02:00
import ReplyModal from '../modals/ReplyModal' ;
import SettingsModal from '../modals/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-08-17 13:15:37 +02:00
import getFormattedTime from '../../utils/getFormattedTime' ;
2023-04-20 17:20:26 +02:00
import handleAddressClick from '../../utils/handleAddressClick' ;
2023-04-29 17:44:27 +02:00
import handleImageClick from '../../utils/handleImageClick' ;
2023-04-13 17:08:35 +02:00
import handleQuoteClick from '../../utils/handleQuoteClick' ;
2023-06-01 21:41:48 +02:00
import handleQuoteHover from '../../utils/handleQuoteHover' ;
2023-08-22 14:17:38 +02:00
import handleShareClick from '../../utils/handleShareClick' ;
2023-03-20 17:08:05 +01:00
import handleStyleChange from '../../utils/handleStyleChange' ;
2023-06-01 21:41:48 +02:00
import removeHighlight from '../../utils/removeHighlight' ;
2023-06-17 21:55:20 +02:00
import useAnonModeRef from '../../hooks/useAnonModeRef' ;
2023-03-20 17:08:05 +01:00
import useClickForm from '../../hooks/useClickForm' ;
2023-04-04 14:42:22 +02:00
import useError from '../../hooks/useError' ;
2023-05-09 15:58:19 +02:00
import useStateString from '../../hooks/useStateString' ;
2023-05-03 11:01:41 +02:00
import useSuccess from '../../hooks/useSuccess' ;
2023-06-17 21:55:20 +02:00
import useAnonModeStore from '../../hooks/stores/useAnonModeStore' ;
2023-06-16 14:42:23 +02:00
import useGeneralStore from '../../hooks/stores/useGeneralStore' ;
2023-08-13 16:25:16 +02:00
import packageJson from '../../../package.json' ;
2023-09-21 14:38:12 +02:00
const { version } = packageJson ;
2023-07-14 14:37:12 +02:00
let lastVirtuosoStates = {};
2023-09-10 14:12:25 +02:00
const commitRef = process ? . env ? . REACT_APP_COMMIT_REF ? ` ${ process . env . REACT_APP_COMMIT_REF . slice ( 0 , 7 ) } ` : '' ;
2023-02-14 21:13:15 +01:00
2023-03-20 17:08:05 +01:00
const Board = () => {
2023-09-23 12:44:43 +02:00
const {
setCaptchaResponse ,
setChallengesArray ,
defaultSubplebbits ,
editedComment ,
editedComments ,
feedCacheStates ,
setFeedCacheState ,
setIsAuthorDelete ,
setIsAuthorEdit ,
setIsCaptchaOpen ,
isModerationOpen ,
setIsModerationOpen ,
isSettingsOpen ,
setIsSettingsOpen ,
setModeratingCommentCid ,
setPendingComment ,
setPendingCommentIndex ,
setReplyQuoteCid ,
setResolveCaptchaPromise ,
selectedAddress ,
setSelectedAddress ,
setSelectedParentCid ,
setSelectedShortCid ,
selectedStyle ,
setSelectedThread ,
setSelectedText ,
setSelectedTitle ,
showPostForm ,
showPostFormLink ,
setTriggerInsertion ,
} = useGeneralStore (( state ) => state );
const { anonymousMode } = useAnonModeStore ();
// temporary hardcode
const plebtokenRules = [
'This community is strictly SFW. Any NSFW language or content will be removed.' ,
< span >
Only post about the plebbit token ( $PLEB ). For general cryptocurrency discussion , go to { ' ' }
< Link className = 'quotelink' to = '/p/business-and-finance.eth' >
p / business - and - finance . eth
</ Link >
.
</ span >,
'FUD is allowed unless blatantly meaningless and spammy.' ,
];
const account = useAccount ();
const navigate = useNavigate ();
const { subplebbitAddress } = useParams ();
const [, setNewErrorMessage ] = useError ();
const [, setNewSuccessMessage ] = useSuccess ();
const nameRef = useRef ();
const subjectRef = useRef ();
const commentRef = useRef ();
const linkRef = useRef ();
const threadMenuRefs = useRef ({});
const threadMenuRefsMobile = useRef ({});
const replyMenuRefs = useRef ({});
const replyMenuRefsMobile = useRef ({});
const postMenuCatalogRef = useRef ( null );
const postMenuMobileRef = useRef ( null );
const backlinkRefs = useRef ({});
const quoteRefs = useRef ({});
const postRefs = useRef ({});
const backlinkRefsMobile = useRef ({});
const quoteRefsMobile = useRef ({});
const postOnHoverRef = useRef ( null );
const selectedThreadCidRef = useRef ( null );
const virtuosoRef = useRef ();
const { feed , loadMore } = useFeed ({ subplebbitAddresses : [ ` ${ selectedAddress } ` ], sortType : 'active' });
const subplebbit = useSubplebbit ({ subplebbitAddress : selectedAddress });
const { subscribed , subscribe , unsubscribe } = useSubscribe ({ subplebbitAddress : selectedAddress });
const stateString = useStateString ( subplebbit );
const [ isAdminListOpen , setIsAdminListOpen ] = useState ( false );
const [ isReplyOpen , setIsReplyOpen ] = useState ( false );
const [ isEditModalOpen , setIsEditModalOpen ] = useState ( false );
const [ originalCommentContent , setOriginalCommentContent ] = useState ( null );
const [ prevScrollPos , setPrevScrollPos ] = useState ( 0 );
const [ visible , setVisible ] = useState ( true );
const [ triggerPublishComment , setTriggerPublishComment ] = useState ( false );
const [ triggerPublishCommentEdit , setTriggerPublishCommentEdit ] = useState ( false );
const [ selectedFeed , setSelectedFeed ] = useState ( feed );
const [ deletePost , setDeletePost ] = useState ( false );
const [ isImageSearchOpen , setIsImageSearchOpen ] = useState ( false );
const [ isClientRedirectMenuOpen , setIsClientRedirectMenuOpen ] = useState ( false );
2023-09-28 21:05:03 +02:00
const [ canModerate , setCanModerate ] = useState ( false );
2023-09-23 12:44:43 +02:00
const [ commentCid , setCommentCid ] = useState ( null );
const [ menuPosition , setMenuPosition ] = useState ({ top : 0 , left : 0 });
const [ mobileMenuPosition , setMobileMenuPosition ] = useState ({ top : 0 , left : 0 });
const [ openMenuCid , setOpenMenuCid ] = useState ( null );
const [ openMobileMenuCid , setOpenMobileMenuCid ] = useState ( null );
const [ outOfViewCid , setOutOfViewCid ] = useState ( null );
const [ outOfViewPosition , setOutOfViewPosition ] = useState ({ top : 0 , left : 0 });
const [ postOnHoverHeight , setPostOnHoverHeight ] = useState ( 0 );
const [ executeAnonMode , setExecuteAnonMode ] = useState ( false );
const [ cidTracker , setCidTracker ] = useState ({});
const [ isThreadThumbnailClicked , setIsThreadThumbnailClicked ] = useState ({});
const [ isReplyThumbnailClicked , setIsReplyThumbnailClicked ] = useState ({});
const [ isMobileThreadThumbnailClicked , setIsMobileThreadThumbnailClicked ] = useState ({});
const [ isMobileReplyThumbnailClicked , setIsMobileReplyThumbnailClicked ] = useState ({});
const [ isCreateBoardOpen , setIsCreateBoardOpen ] = useState ( false );
const setSelectedThreadCid = ( cid ) => {
selectedThreadCidRef . current = cid ;
};
const isFeedCached = feedCacheStates [ selectedAddress ];
useAnonModeRef ( selectedThreadCidRef , anonymousMode && executeAnonMode );
useEffect (() => {
if ( feed ) {
setFeedCacheState ( selectedAddress , true );
}
}, [ selectedAddress , setFeedCacheState , feed ]);
useEffect (() => {
if ( subplebbit ? . roles !== undefined ) {
2023-09-29 11:44:05 +02:00
const role = subplebbit ? . roles ? .[ account ? . author . address ] ? . role ;
2023-09-23 12:44:43 +02:00
if ( role === 'moderator' || role === 'admin' || role === 'owner' ) {
2023-09-28 21:05:03 +02:00
setCanModerate ( true );
2023-09-23 12:44:43 +02:00
} else {
2023-09-28 21:05:03 +02:00
setCanModerate ( false );
2023-09-23 12:44:43 +02:00
}
}
}, [ account ? . author . address , subplebbit ? . roles ]);
const handleThumbnailClick = ( index , type ) => {
switch ( type ) {
case 'thread' :
setIsThreadThumbnailClicked (( prevState ) => ({
... prevState ,
[ index ] : ! prevState [ index ],
}));
break ;
case 'reply' :
setIsReplyThumbnailClicked (( prevState ) => ({
... prevState ,
[ index ] : ! prevState [ index ],
}));
break ;
case 'mobileThread' :
setIsMobileThreadThumbnailClicked (( prevState ) => ({
... prevState ,
[ index ] : ! prevState [ index ],
}));
break ;
case 'mobileReply' :
setIsMobileReplyThumbnailClicked (( prevState ) => ({
... prevState ,
[ index ] : ! prevState [ index ],
}));
break ;
default :
break ;
}
};
const handleOptionClick = () => {
setOpenMenuCid ( null );
};
const handleMobileOptionClick = () => {
setOpenMobileMenuCid ( null );
};
const handleOutsideClick = useCallback (
( e ) => {
if ( openMenuCid !== null && ! postMenuCatalogRef . current . contains ( e . target )) {
setOpenMenuCid ( null );
}
},
[ openMenuCid , postMenuCatalogRef ],
);
const handleMobileOutsideClick = useCallback (
( e ) => {
if ( openMobileMenuCid !== null && ! postMenuMobileRef . current . contains ( e . target )) {
setOpenMobileMenuCid ( null );
}
},
[ openMobileMenuCid , postMenuMobileRef ],
);
useEffect (() => {
if ( openMenuCid !== null ) {
document . addEventListener ( 'click' , handleOutsideClick );
} else {
document . removeEventListener ( 'click' , handleOutsideClick );
}
return () => {
document . removeEventListener ( 'click' , handleOutsideClick );
};
}, [ openMenuCid , handleOutsideClick ]);
useEffect (() => {
if ( openMobileMenuCid !== null ) {
document . addEventListener ( 'click' , handleMobileOutsideClick );
} else {
document . removeEventListener ( 'click' , handleMobileOutsideClick );
}
return () => {
document . removeEventListener ( 'click' , handleMobileOutsideClick );
};
}, [ openMobileMenuCid , handleMobileOutsideClick ]);
useEffect (() => {
if ( postOnHoverRef . current ) {
const rect = postOnHoverRef . current . getBoundingClientRect ();
setPostOnHoverHeight ( rect . height );
}
}, [ outOfViewCid ]);
const errorString = useMemo (() => {
if ( subplebbit ? . state === 'failed' ) {
let errorString = 'Failed fetching board "' + selectedAddress + '".' ;
if ( subplebbit . error ) {
errorString += `: ${ subplebbit . error . toString (). slice ( 0 , 300 ) } ` ;
}
return errorString ;
}
}, [ subplebbit ? . state , subplebbit ? . error , selectedAddress ]);
useEffect (() => {
if ( errorString ) {
setNewErrorMessage ( errorString );
}
}, [ errorString , setNewErrorMessage ]);
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 new Set ([... allThreadCids , ... allRepliesCids ]);
}, [ flattenedRepliesByThread , selectedFeed ]);
const filter = useCallback (( accountComment ) => allParentCids . has ( accountComment . parentCid ), [ allParentCids ]);
const { accountComments } = useAccountComments ({ filter });
const filteredRepliesByThread = useMemo (() => {
const maxRepliesPerThread = 5 ;
return selectedFeed . reduce (( acc , thread ) => {
2023-10-21 13:39:42 +02:00
const existingReplies = flattenedRepliesByThread [ thread . cid ] || [];
const newReplies = accountComments . filter (
( accountReply ) => accountReply . parentCid === thread . cid || existingReplies . some (( reply ) => reply . cid === accountReply . parentCid ),
);
const combinedReplies = [... existingReplies , ... newReplies ]. sort (( a , b ) => a . timestamp - b . timestamp );
2023-09-23 12:44:43 +02:00
acc [ thread . cid ] = {
displayedReplies : combinedReplies . slice ( 0 , maxRepliesPerThread ),
omittedCount : Math . max ( combinedReplies . length - maxRepliesPerThread , 0 ),
};
2023-10-21 13:39:42 +02:00
2023-09-23 12:44:43 +02:00
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 ]);
const allReplies = useMemo (() => {
return selectedFeed . flatMap (( thread ) => filteredRepliesByThread [ thread . cid ] ? . displayedReplies || []);
}, [ selectedFeed , filteredRepliesByThread ]);
// let post.jsx access full cid of user-typed short cid
useEffect (() => {
const newCidTracker = {};
allReplies . forEach (( reply ) => {
newCidTracker [ reply . shortCid ] = reply . cid ;
});
setCidTracker ( newCidTracker );
}, [ allReplies ]);
useEffect (() => {
const selectedSubplebbit = defaultSubplebbits . find (( subplebbit ) => subplebbit . address === subplebbitAddress );
if ( subplebbitAddress ) {
setSelectedAddress ( subplebbitAddress );
} else if ( subplebbit ? . address ) {
setSelectedAddress ( subplebbit . address );
}
if ( selectedSubplebbit ) {
setSelectedTitle ( selectedSubplebbit . title );
} else if ( subplebbit ? . title ) {
setSelectedTitle ( subplebbit . title );
}
}, [ subplebbitAddress , setSelectedAddress , setSelectedTitle , defaultSubplebbits , subplebbit ? . address , subplebbit ? . title ]);
// sets useFeed to address from URL
useEffect (() => {
setSelectedFeed ( feed );
}, [ feed ]);
// mobile navbar scroll effect
useEffect (() => {
const debouncedHandleScroll = debounce (() => {
const currentScrollPos = window . scrollY ;
setVisible ( prevScrollPos > currentScrollPos || currentScrollPos < 10 );
setPrevScrollPos ( currentScrollPos );
}, 50 );
window . addEventListener ( 'scroll' , debouncedHandleScroll );
return () => window . removeEventListener ( 'scroll' , debouncedHandleScroll );
}, [ prevScrollPos , visible ]);
const tryLoadMore = async () => {
try {
await loadMore ();
} catch ( e ) {
await new Promise (( resolve ) => setTimeout ( resolve , 1000 ));
}
};
const onChallengeVerification = ( challengeVerification ) => {
if ( challengeVerification . challengeSuccess === true ) {
if ( challengeVerification . publication ? . cid !== undefined ) {
navigate ( `/p/ ${ subplebbitAddress } /c/ ${ challengeVerification . publication ? . cid } ` );
console . log ( 'challenge success' );
} else {
setNewSuccessMessage ( 'Challenge Success' );
console . log ( 'challenge success' , challengeVerification );
}
} else if ( challengeVerification . challengeSuccess === false ) {
setNewErrorMessage ( `Challenge Failed, reason: ${ challengeVerification . reason } . Errors: ${ challengeVerification . errors } ` );
console . log ( 'challenge failed' , challengeVerification );
}
};
const onChallenge = async ( challenges , comment ) => {
setPendingComment ( comment );
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser ( challenges );
} catch ( error ) {
setNewErrorMessage ( error . message );
console . log ( error );
}
if ( challengeAnswers ) {
await comment . publishChallengeAnswers ( challengeAnswers );
}
};
useEffect (() => {
setPublishCommentOptions (( prevPublishCommentOptions ) => ({
... prevPublishCommentOptions ,
subplebbitAddress : selectedAddress ,
}));
}, [ selectedAddress ]);
const [ publishCommentOptions , setPublishCommentOptions ] = useState ({
subplebbitAddress : selectedAddress ,
onChallenge ,
onChallengeVerification ,
onError : ( error ) => {
setNewErrorMessage ( error . message );
console . log ( error );
},
});
const { publishComment , index } = usePublishComment ( publishCommentOptions );
useEffect (() => {
if ( index !== undefined ) {
setPendingCommentIndex ( index );
navigate ( `/profile/c/ ${ index } ` );
}
}, [ index , navigate , setPendingCommentIndex ]);
const resetFields = useCallback (() => {
if ( nameRef . current ) {
nameRef . current . value = '' ;
}
if ( subjectRef . current ) {
subjectRef . current . value = '' ;
}
if ( commentRef . current ) {
commentRef . current . value = '' ;
}
if ( linkRef . current ) {
linkRef . current . value = '' ;
}
}, []);
const handleSubmit = async ( event ) => {
event . preventDefault ();
2023-10-22 16:11:08 +02:00
if ( anonymousMode ) {
setSelectedThreadCid ( index );
}
2023-09-23 12:44:43 +02:00
setPublishCommentOptions (( prevPublishCommentOptions ) => ({
... prevPublishCommentOptions ,
author : {
displayName : nameRef . current . value || undefined ,
...( anonymousMode ? {} : { address : account ? . author . address }),
},
title : subjectRef . current . value || undefined ,
content : commentRef . current . value || undefined ,
link : linkRef . current . value || undefined ,
}));
setTriggerPublishComment ( true );
};
const updateSigner = useCallback ( async () => {
if ( anonymousMode ) {
setExecuteAnonMode ( true );
let storedSigners = JSON . parse ( localStorage . getItem ( 'storedSigners' )) || {};
let signer ;
if ( ! storedSigners [ selectedThreadCidRef ]) {
signer = await account ? . plebbit . createSigner ();
storedSigners [ selectedThreadCidRef ] = { privateKey : signer ? . privateKey , address : signer ? . address };
localStorage . setItem ( 'storedSigners' , JSON . stringify ( storedSigners ));
} else {
const signerPrivateKey = storedSigners [ selectedThreadCidRef ]. privateKey ;
try {
signer = await account ? . plebbit . createSigner ({ type : 'ed25519' , privateKey : signerPrivateKey });
} catch ( error ) {
console . log ( error );
}
}
setPublishCommentOptions (( prevPublishCommentOptions ) => {
const newPublishCommentOptions = {
... prevPublishCommentOptions ,
signer ,
author : {
... prevPublishCommentOptions . author ,
address : signer ? . address ,
},
};
if ( JSON . stringify ( prevPublishCommentOptions ) !== JSON . stringify ( newPublishCommentOptions )) {
return newPublishCommentOptions ;
}
return prevPublishCommentOptions ;
});
}
}, [ selectedThreadCidRef , anonymousMode , account ]);
useEffect (() => {
if ( anonymousMode ) {
updateSigner ();
}
}, [ updateSigner , anonymousMode ]);
useEffect (() => {
if ( publishCommentOptions && triggerPublishComment ) {
( async () => {
await publishComment ();
resetFields ();
})();
setTriggerPublishComment ( false );
setExecuteAnonMode ( false );
}
}, [ publishCommentOptions , triggerPublishComment , publishComment , resetFields ]);
const getChallengeAnswersFromUser = async ( challenges ) => {
setChallengesArray ( challenges );
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 = () => {
setIsCaptchaOpen ( true );
const handleKeyDown = async ( event ) => {
if ( event . key === 'Enter' ) {
const currentCaptchaResponse = useGeneralStore . getState (). captchaResponse ;
resolve ( currentCaptchaResponse );
setIsCaptchaOpen ( false );
document . removeEventListener ( 'keydown' , handleKeyDown );
event . preventDefault ();
}
};
setCaptchaResponse ( '' );
document . addEventListener ( 'keydown' , handleKeyDown );
setResolveCaptchaPromise ( resolve );
};
challengeImg . onerror = () => {
reject ( setNewErrorMessage ( 'Could not load challenges' ));
};
});
};
const [ publishCommentEditOptions , setPublishCommentEditOptions ] = useState ({
commentCid : commentCid ,
content : editedComment || undefined ,
subplebbitAddress ,
onChallenge ,
onChallengeVerification ,
onError : ( error ) => {
setNewErrorMessage ( error . message );
console . log ( error );
},
});
const { publishCommentEdit } = usePublishCommentEdit ( publishCommentEditOptions );
const handleAuthorDeleteClick = ( comment ) => {
handleOptionClick ( comment . cid );
confirmAlert ({
customUI : ({ onClose }) => {
return (
< AlertModal selectedStyle = { selectedStyle }>
< div className = 'author-delete-alert' >
< p > Are you sure you want to delete this post ? </ p >
< div className = 'author-delete-buttons' >
< button onClick = { onClose }> No </ button >
< button
onClick = {() => {
setIsAuthorDelete ( true );
setIsAuthorEdit ( false );
setPublishCommentEditOptions (( prevOptions ) => ({
... prevOptions ,
commentCid : comment . cid ,
subplebbitAddress : comment . subplebbitAddress ,
deleted : true ,
}));
setTriggerPublishCommentEdit ( true );
onClose ();
}}
>
Yes
</ button >
</ div >
</ div >
</ AlertModal >
);
},
});
};
const handleAuthorEditClick = ( comment ) => {
handleOptionClick ( comment . cid );
setIsAuthorEdit ( true );
setIsAuthorDelete ( false );
setCommentCid ( comment . cid );
setOriginalCommentContent ( comment . content );
setIsEditModalOpen ( true );
};
useEffect (() => {
setPublishCommentEditOptions (( prevOptions ) => ({
... prevOptions ,
commentCid : commentCid ,
content : editedComment || undefined ,
}));
}, [ commentCid , editedComment ]);
useEffect (() => {
if ( editedComment !== '' ) {
setTriggerPublishCommentEdit ( true );
}
}, [ editedComment , setIsAuthorEdit ]);
useEffect (() => {
let isActive = true ;
if ( publishCommentEditOptions && triggerPublishCommentEdit ) {
( async () => {
await publishCommentEdit ();
if ( isActive ) {
setTriggerPublishCommentEdit ( false );
}
})();
}
return () => {
isActive = false ;
};
}, [ triggerPublishCommentEdit , publishCommentEdit , publishCommentEditOptions ]);
// desktop navbar board select functionality
const handleClickTitle = ( title , address ) => {
setSelectedTitle ( title );
setSelectedAddress ( address );
setSelectedFeed ( feed . filter (( feed ) => feed . title === title ));
if ( subplebbitAddress === address ) {
window . location . reload ();
}
};
// mobile navbar board select functionality
const handleSelectChange = ( event ) => {
const selected = event . target . value ;
if ( selected === 'subscriptions' ) {
navigate ( `/p/subscriptions` );
return ;
} else if ( selected === 'all' ) {
navigate ( `/p/all` );
return ;
}
const selectedTitle = defaultSubplebbits . find (( subplebbit ) => subplebbit . address === selected ). title ;
setSelectedTitle ( selectedTitle );
setSelectedAddress ( selected );
navigate ( `/p/ ${ selected } ` );
};
const handleSubscribe = async () => {
try {
if ( subscribed === false ) {
await subscribe ( selectedAddress );
} else if ( subscribed === true ) {
await unsubscribe ( selectedAddress );
}
} catch ( error ) {
setNewErrorMessage ( error . message );
console . log ( error );
}
};
2023-10-05 16:42:47 +02:00
const location = useLocation ();
2023-09-23 12:44:43 +02:00
useEffect (() => {
2023-10-05 16:42:47 +02:00
if ( location . state ? . scrollToTop ) {
lastVirtuosoStates [ selectedAddress ] = null ;
}
2023-09-23 12:44:43 +02:00
const setLastVirtuosoState = () => {
virtuosoRef . current ? . getState (( snapshot ) => {
if ( snapshot ? . scrollTop === 0 || snapshot ? . ranges ? . length ) {
lastVirtuosoStates [ selectedAddress ] = snapshot ;
}
});
};
window . addEventListener ( 'scroll' , setLastVirtuosoState );
return () => window . removeEventListener ( 'scroll' , setLastVirtuosoState );
2023-10-05 16:42:47 +02:00
}, [ selectedAddress , location . state ? . scrollToTop ]);
2023-09-23 12:44:43 +02:00
const lastVirtuosoState = lastVirtuosoStates [ selectedAddress ];
return (
<>
< Helmet >
< title >{( subplebbit . title ? subplebbit . title : subplebbit . address ) + ' - plebchan' }</ title >
</ Helmet >
< Container >
< AdminListModal selectedStyle = { selectedStyle } isOpen = { isAdminListOpen } closeModal = {() => setIsAdminListOpen ( false )} roles = { subplebbit ? . roles } />
< CreateBoardModal selectedStyle = { selectedStyle } isOpen = { isCreateBoardOpen } closeModal = {() => setIsCreateBoardOpen ( false )} />
< EditModal selectedStyle = { selectedStyle } isOpen = { isEditModalOpen } closeModal = {() => setIsEditModalOpen ( false )} originalCommentContent = { originalCommentContent } />
< ModerationModal
selectedStyle = { selectedStyle }
isOpen = { isModerationOpen }
closeModal = {() => {
setIsModerationOpen ( false );
setDeletePost ( false );
}}
deletePost = { deletePost }
/>
< ReplyModal selectedStyle = { selectedStyle } isOpen = { isReplyOpen } closeModal = {() => setIsReplyOpen ( false )} />
< SettingsModal selectedStyle = { selectedStyle } isOpen = { isSettingsOpen } closeModal = {() => setIsSettingsOpen ( false )} />
< NavBar selectedStyle = { selectedStyle }>
<>
< span className = 'boardList' >
2023-10-05 16:42:47 +02:00
[< Link to = {{ pathname : `/p/all` , state : { scrollToTop : true } }}> All </ Link >
2023-09-23 12:44:43 +02:00
/
2023-10-05 16:42:47 +02:00
< Link to = {{ pathname : `/p/subscriptions` , state : { scrollToTop : true } }}> Subscriptions </ Link >
2023-09-23 12:44:43 +02:00
] & nbsp ;[
{ defaultSubplebbits . map (( subplebbit , index ) => (
< span className = 'boardList' key = { `span- ${ subplebbit . address } ` }>
{ index === 0 ? null : '\u00a0' }
< Link to = { `/p/ ${ subplebbit . address } ` } key = { `a- ${ subplebbit . address } ` } onClick = {() => handleClickTitle ( subplebbit . title , subplebbit . address )}>
{ subplebbit . title ? subplebbit . title : subplebbit . address }
</ Link >
{ index !== defaultSubplebbits . length - 1 ? ' /' : null }
</ span >
))}
]
</ span >
< span className = 'nav' >
[
< span
id = 'button-span'
style = {{ cursor : 'pointer' }}
onClick = {() => {
window . electron && window . electron . isElectron
? setIsCreateBoardOpen ( true )
: alert (
2023-10-01 18:28:00 +02:00
'HOW TO CREATE A BOARD (currently via CLI only):\n\n(1) have a computer you can use as server, because users will connect to your board peer-to-peer;\n\n(2) go to https://github.com/plebbit/plebbit-cli#install and follow the instructions to run your daemon, create a subplebbit (board) and edit its settings;\n\n(3) users can always connect to your board via its address, but it can be added below by submitting a pull request here: https://github.com/plebbit/temporary-default-subplebbits. This will be automated with a plebbit DAO using the plebbit token.' ,
2023-09-23 12:44:43 +02:00
);
}}
>
Create Board
</ span >
] [
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}>
Settings
</ Link >
] [< Link to = '/' > Home </ Link >]
</ span >
< div id = 'board-nav-mobile' style = {{ top : visible ? 0 : '-23px' }}>
< div className = 'nav-container' >
< div className = 'board-select' >
< strong > Board </ strong >
& nbsp ;
< select id = 'board-select-mobile' value = { selectedAddress } onChange = { handleSelectChange }>
< option value = 'all' > All </ option >
< option value = 'subscriptions' > Subscriptions </ option >
{ ! defaultSubplebbits . some (( subplebbit ) => subplebbit . address === selectedAddress ) && < option value = { selectedAddress }>{ selectedAddress }</ option >}
{ defaultSubplebbits . map (( subplebbit ) => (
< option key = { `option- ${ subplebbit . address } ` } value = { subplebbit . address }>
{ subplebbit . title ? subplebbit . title : subplebbit . address }
</ option >
))}
</ select >
< span
id = 'button-span'
style = {{ cursor : 'pointer' }}
onClick = {() => {
window . electron && window . electron . isElectron
? setIsCreateBoardOpen ( true )
: alert (
2023-10-01 18:28:00 +02:00
'HOW TO CREATE A BOARD (currently via CLI only):\n\n(1) have a computer you can use as server, because users will connect to your board peer-to-peer;\n\n(2) go to https://github.com/plebbit/plebbit-cli#install and follow the instructions to run your daemon, create a subplebbit (board) and edit its settings;\n\n(3) users can always connect to your board via its address, but it can be added below by submitting a pull request here: https://github.com/plebbit/temporary-default-subplebbits. This will be automated with a plebbit DAO using the plebbit token.' ,
2023-09-23 12:44:43 +02:00
);
}}
>
Create Board
</ span >
</ div >
< div className = 'page-jump' >
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}>
Settings
</ Link >
& nbsp ;
< Link
2023-10-05 16:42:47 +02:00
to = {{ pathname : '/' , state : { scrollToTop : true } }}
2023-09-23 12:44:43 +02:00
onClick = {() => {
handleStyleChange ({ target : { value : 'Yotsuba' } });
}}
>
Home
</ Link >
</ div >
</ div >
</ div >
< 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' >{ subplebbit . title ?? null }</ div >
< div className = 'board-address' >
p / { subplebbit . address }
< OfflineIndicator address = { subplebbit . address } className = 'offline' tooltipPlace = 'top' />
</ div >
</>
</ Header >
< Break selectedStyle = { selectedStyle } />
< PostForm selectedStyle = { selectedStyle }>
< PostFormLink id = 'post-form-link' showPostFormLink = { showPostFormLink } selectedStyle = { selectedStyle }>
< div id = 'post-form-link-desktop' >
[
< Link to = { `/p/ ${ subplebbitAddress } /post` } onClick = { useClickForm ()} style = {{ cursor : 'pointer' }}>
Start a New Thread
</ Link >
]
</ div >
< div id = 'post-form-link-mobile' >
< span className = 'btn-wrap' >
< Link to = { `/p/ ${ subplebbitAddress } /post` } onClick = { useClickForm ()} style = {{ cursor : 'pointer' }}>
Start a New Thread
</ Link >
</ 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 >
{ account ? . author . displayName ? (
< input name = 'name' type = 'text' tabIndex = { 1 } defaultValue = { account ? . author ? . displayName } ref = { nameRef } />
) : (
< input name = 'name' type = 'text' placeholder = 'Anonymous' tabIndex = { 1 } 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' >
2023-10-01 18:28:00 +02:00
< td > Link </ td >
2023-09-23 12:44:43 +02:00
< 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 >
< BoardStats subplebbitAddress = { subplebbitAddress } />
< 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-10-05 16:42:47 +02:00
[< Link to = {{ pathname : `/p/ ${ selectedAddress } /catalog` , state : { scrollToTop : true } }}> Catalog </ Link >]
{ subplebbit . roles && subplebbit ? . roles ? .[ account ? . author ? . address ] ? . role === 'admin' ? < BoardSettings subplebbit = { subplebbit } /> : null }
2023-09-23 12:44:43 +02:00
</ div >
{ subplebbit ? . state === 'succeeded' ? (
<>
< span className = 'subscribe-button-desktop' >
[
< span id = 'subscribe' style = {{ cursor : 'pointer' }}>
< span onClick = {() => handleSubscribe ()}>{ subscribed ? 'Unsubscribe' : 'Subscribe' }</ span >
</ span >
]
</ span >
< span className = 'subscribe-button-mobile' >
< span className = 'btn-wrap' onClick = {() => handleSubscribe ()}>
{ subscribed ? 'Unsubscribe' : 'Subscribe' }
</ span >
</ span >
</>
) : (
< div id = 'stats' style = {{ float : 'right' , marginTop : '5px' }}>
< span className = { stateString ? 'ellipsis' : '' }>{ stateString }</ span >
</ div >
)}
< div id = 'catalog-button-mobile' >
< span className = 'btn-wrap' >
2023-10-05 16:42:47 +02:00
< Link to = {{ pathname : `/p/ ${ selectedAddress } /catalog` , state : { scrollToTop : true } }}> Catalog </ Link >
2023-09-23 12:44:43 +02:00
</ span >
</ div >
</ TopBar >
< Tooltip id = 'tooltip' className = 'tooltip' />
< BoardForm selectedStyle = { selectedStyle }>
< div className = 'board' >
{ subplebbit ? . state === 'failed' ? null : subplebbit ? . state === 'succeeded' ? (
<>
{ subplebbit . rules ? (
<>
< div className = 'thread' >
< div className = 'op-container' >
< div className = 'post op op-desktop' >
< hr />
< div className = 'post-info' >
< span className = 'name-block' >
< span className = 'title' > Rules </ span >
& nbsp ;
< span
className = 'name capcode'
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
< span
className = 'date-time'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
< img src = 'assets/sticky.gif' alt = 'Sticky' title = 'Sticky' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
& nbsp ;
< img src = 'assets/closed.gif' alt = 'Closed' title = 'Closed' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
< span >
& nbsp ; [
< Link to = { `/p/ ${ selectedAddress } /rules` } style = {{ textDecoration : 'none' }} className = 'reply-link' >
Reply
</ Link >
]
</ span >
< PostMenu
title = 'Post menu'
ref = {( el ) => {
threadMenuRefs . current [ 'rules' ] = el ;
}}
className = 'post-menu-button'
rotated = { openMenuCid === 'rules' }
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefs . current [ 'rules' ]. getBoundingClientRect ();
setMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMenuCid (( prevCid ) => ( prevCid === 'rules' ? null : 'rules' ));
}}
>
▶
</ PostMenu >
{ createPortal (
< PostMenuCatalog
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuCatalogRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{ position : 'absolute' , top : menuPosition . top + 7 , left : menuPosition . left }}
>
< div className = { `post-menu-thread post-menu-thread- ${ 'rules' } ` } style = {{ display : openMenuCid === 'rules' ? 'block' : 'none' }}>
< ul className = 'post-menu-catalog' >
2023-09-28 21:05:03 +02:00
{ /* {canModerate ? (
2023-06-17 12:51:30 +02:00
<>
change rules
</>
) : null} */ }
2023-09-23 12:44:43 +02:00
< li
onMouseOver = {() => {
setIsClientRedirectMenuOpen ( true );
}}
onMouseLeave = {() => {
setIsClientRedirectMenuOpen ( false );
}}
>
View on »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isClientRedirectMenuOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( 'rules' )}>
< a href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebbit
</ a >
</ li >
{ /* <li onClick={() => handleOptionClick("rules")}>
2023-09-14 17:35:28 +02:00
<a
href={`https://seedit.eth.limo/#/p/${selectedAddress}`}
target="_blank" rel="noreferrer"
>Seedit</a>
</li> */ }
2023-09-23 12:44:43 +02:00
< li onClick = {() => handleOptionClick ( 'rules' )}>
< a href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebones
</ a >
</ li >
</ ul >
</ li >
</ ul >
</ div >
</ PostMenuCatalog >,
document . body ,
)}
</ span >
< blockquote >
< div className = 'custom-paragraph' >
{ subplebbit . rules . map (( rule , index ) => (
< React.Fragment key = { index }>
{ index + 1 }. { rule }
< br />
</ React.Fragment >
))}
</ div >
</ blockquote >
</ div >
</ div >
</ div >
</ div >
< div className = 'thread-mobile' >
< hr style = {{ marginTop : '10px' }} />
< div className = 'op-container' >
< div className = 'post op op-mobile' >
< div className = 'post-info-mobile' >
< button
className = 'post-menu-button-mobile'
ref = {( el ) => {
threadMenuRefsMobile . current [ 'rules' ] = el ;
}}
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefsMobile . current [ 'rules' ]. getBoundingClientRect ();
setMobileMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMobileMenuCid (( prevCid ) => ( prevCid === 'rules' ? null : 'rules' ));
}}
style = {{ all : 'unset' , cursor : 'pointer' }}
>
...
</ button >
{ createPortal (
< PostMenuMobile
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuMobileRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{
position : 'absolute' ,
display : openMobileMenuCid === 'rules' ? 'block' : 'none' ,
top : mobileMenuPosition . top + 20 ,
left : mobileMenuPosition . left ,
}}
>
< ul className = { `post-menu-mobile-thread- ${ 'rules' } ` }>
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'rules' )}> View on plebbit </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'rules' )}> View on plebones </ li >
</ a >
</ ul >
</ PostMenuMobile >,
document . body ,
)}
< span className = 'name-block-mobile' >
< span
2023-09-26 17:44:47 +02:00
className = 'name-mobile capcode'
2023-09-23 12:44:43 +02:00
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
2023-10-04 17:25:42 +02:00
< span className = 'thread-icons-mobile' >
< img
src = 'assets/sticky.gif'
alt = 'Sticky'
title = 'Sticky'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
& nbsp ;
2023-10-04 17:25:42 +02:00
< img
src = 'assets/closed.gif'
alt = 'Closed'
title = 'Closed'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
</ span >
< br />
< span className = 'subject-mobile' style = {{ marginBottom : '-15px' }}>
Rules
</ span >
& nbsp ;
</ span >
< span
className = 'date-time-mobile post-number-mobile'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
</ div >
< blockquote className = 'post-message-mobile' >
< div className = 'custom-paragraph' >
{ subplebbit . rules . map (( rule , index ) => (
< React.Fragment key = { index }>
{ index + 1 }. { rule }
< br />
</ React.Fragment >
))}
</ div >
</ blockquote >
</ div >
< div className = 'post-link-mobile' >
< Link to = { `/p/ ${ selectedAddress } /rules` } className = 'button-mobile' >
View Thread
</ Link >
</ div >
</ div >
</ div >
</>
) : subplebbit . address === 'plebtoken.eth' ? (
<>
< div className = 'thread' >
< div className = 'op-container' >
< div className = 'post op op-desktop' >
< hr />
< div className = 'post-info' >
< span className = 'name-block' >
< span className = 'title' > Rules </ span >
& nbsp ;
< span
className = 'name capcode'
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
< span
className = 'date-time'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
< img src = 'assets/sticky.gif' alt = 'Sticky' title = 'Sticky' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
& nbsp ;
< img src = 'assets/closed.gif' alt = 'Closed' title = 'Closed' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
< span >
& nbsp ; [
< Link to = { `/p/ ${ selectedAddress } /rules` } style = {{ textDecoration : 'none' }} className = 'reply-link' >
Reply
</ Link >
]
</ span >
< PostMenu
title = 'Post menu'
ref = {( el ) => {
threadMenuRefs . current [ 'rules' ] = el ;
}}
className = 'post-menu-button'
rotated = { openMenuCid === 'rules' }
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefs . current [ 'rules' ]. getBoundingClientRect ();
setMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMenuCid (( prevCid ) => ( prevCid === 'rules' ? null : 'rules' ));
}}
>
▶
</ PostMenu >
{ createPortal (
< PostMenuCatalog
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuCatalogRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{ position : 'absolute' , top : menuPosition . top + 7 , left : menuPosition . left }}
>
< div className = { `post-menu-thread post-menu-thread- ${ 'rules' } ` } style = {{ display : openMenuCid === 'rules' ? 'block' : 'none' }}>
< ul className = 'post-menu-catalog' >
2023-09-28 21:05:03 +02:00
{ /* {canModerate ? (
2023-07-01 10:01:30 +02:00
<>
change rules
</>
) : null} */ }
2023-09-23 12:44:43 +02:00
< li
onMouseOver = {() => {
setIsClientRedirectMenuOpen ( true );
}}
onMouseLeave = {() => {
setIsClientRedirectMenuOpen ( false );
}}
>
View on »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isClientRedirectMenuOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( 'rules' )}>
< a href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebbit
</ a >
</ li >
{ /* <li onClick={() => handleOptionClick("rules")}>
2023-09-14 17:35:28 +02:00
<a
href={`https://seedit.eth.limo/#/p/${selectedAddress}`}
target="_blank" rel="noreferrer"
>Seedit</a>
</li> */ }
2023-09-23 12:44:43 +02:00
< li onClick = {() => handleOptionClick ( 'rules' )}>
< a href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebones
</ a >
</ li >
</ ul >
</ li >
</ ul >
</ div >
</ PostMenuCatalog >,
document . body ,
)}
</ span >
< blockquote >
< div className = 'custom-paragraph' >
{ plebtokenRules . map (( rule , index ) => (
< React.Fragment key = { index }>
{ index + 1 }. { rule }
< br />
</ React.Fragment >
))}
</ div >
</ blockquote >
</ div >
</ div >
</ div >
</ div >
< div className = 'thread-mobile' >
< hr style = {{ marginTop : '10px' }} />
< div className = 'op-container' >
< div className = 'post op op-mobile' >
< div className = 'post-info-mobile' >
< button
className = 'post-menu-button-mobile'
ref = {( el ) => {
threadMenuRefsMobile . current [ 'rules' ] = el ;
}}
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefsMobile . current [ 'rules' ]. getBoundingClientRect ();
setMobileMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMobileMenuCid (( prevCid ) => ( prevCid === 'rules' ? null : 'rules' ));
}}
style = {{ all : 'unset' , cursor : 'pointer' }}
>
...
</ button >
{ createPortal (
< PostMenuMobile
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuMobileRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{
position : 'absolute' ,
display : openMobileMenuCid === 'rules' ? 'block' : 'none' ,
top : mobileMenuPosition . top + 20 ,
left : mobileMenuPosition . left ,
}}
>
< ul className = { `post-menu-mobile-thread- ${ 'rules' } ` }>
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'rules' )}> View on plebbit </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'rules' )}> View on plebones </ li >
</ a >
</ ul >
</ PostMenuMobile >,
document . body ,
)}
< span className = 'name-block-mobile' >
< span
2023-09-26 17:44:47 +02:00
className = 'name-mobile capcode'
2023-09-23 12:44:43 +02:00
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
2023-10-04 17:25:42 +02:00
< span className = 'thread-icons-mobile' >
< img
src = 'assets/sticky.gif'
alt = 'Sticky'
title = 'Sticky'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
& nbsp ;
2023-10-04 17:25:42 +02:00
< img
src = 'assets/closed.gif'
alt = 'Closed'
title = 'Closed'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
</ span >
< br />
< span className = 'subject-mobile' style = {{ marginBottom : '-15px' }}>
Rules
</ span >
& nbsp ;
</ span >
< span
className = 'date-time-mobile post-number-mobile'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
</ div >
< blockquote className = 'post-message-mobile' >
< div className = 'custom-paragraph' >
{ plebtokenRules . map (( rule , index ) => (
< React.Fragment key = { index }>
{ index + 1 }. { rule }
< br />
</ React.Fragment >
))}
</ div >
</ blockquote >
</ div >
< div className = 'post-link-mobile' >
< Link to = { `/p/ ${ selectedAddress } /rules` } className = 'button-mobile' >
View Thread
</ Link >
</ div >
</ div >
</ div >
</>
) : null }
{ subplebbit . description ? (
<>
< div className = 'thread' >
< div className = 'op-container' >
< div className = 'post op op-desktop' >
< hr />
< div className = 'post-info' >
{ subplebbit . suggested ? . avatarUrl ? (
< div className = 'file' style = {{ marginBottom : '5px' }}>
< div className = 'file-text' >
Link :& nbsp ;
< a href = { subplebbit . suggested ? . avatarUrl } target = '_blank' rel = 'noopener noreferrer' >
{ subplebbit . suggested ? . avatarUrl . length > 30 ? subplebbit . suggested ? . avatarUrl . slice ( 0 , 30 ) + '...' : subplebbit . suggested ? . avatarUrl }
</ a >
& nbsp ;( image )
</ div >
< div className = 'img-container' >
< span className = 'file-thumb' >
< img src = { subplebbit . suggested ? . avatarUrl } alt = 'board avatar' onClick = { handleImageClick } style = {{ cursor : 'pointer' }} />
</ span >
</ div >
</ div >
) : null }
< span className = 'name-block' >
< span className = 'title' > Welcome to { subplebbit . title || subplebbit . address } ! </ span >
& nbsp ;
< span
className = 'name capcode'
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
< span
className = 'date-time'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
< img src = 'assets/sticky.gif' alt = 'Sticky' title = 'Sticky' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
& nbsp ;
< img src = 'assets/closed.gif' alt = 'Closed' title = 'Closed' style = {{ marginBottom : '-1px' , imageRendering : 'pixelated' }} />
< span >
& nbsp ; [
< Link to = { `/p/ ${ selectedAddress } /description` } style = {{ textDecoration : 'none' }} className = 'reply-link' >
Reply
</ Link >
]
</ span >
< PostMenu
title = 'Post menu'
ref = {( el ) => {
threadMenuRefs . current [ subplebbit . pubsubTopic ] = el ;
}}
className = 'post-menu-button'
rotated = { openMenuCid === subplebbit . pubsubTopic }
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefs . current [ subplebbit . pubsubTopic ]. getBoundingClientRect ();
setMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMenuCid (( prevCid ) => ( prevCid === subplebbit . pubsubTopic ? null : subplebbit . pubsubTopic ));
}}
>
▶
</ PostMenu >
{ createPortal (
< PostMenuCatalog
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuCatalogRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{ position : 'absolute' , top : menuPosition . top + 7 , left : menuPosition . left }}
>
< div
className = { `post-menu-thread post-menu-thread- ${ subplebbit . pubsubTopic } ` }
style = {{ display : openMenuCid === subplebbit . pubsubTopic ? 'block' : 'none' }}
>
< ul className = 'post-menu-catalog' >
2023-09-28 21:05:03 +02:00
{ /* {canModerate ? (
2023-06-17 12:51:30 +02:00
<>
change description
</>
) : null} */ }
2023-09-23 12:44:43 +02:00
{ subplebbit . suggested ? . avatarUrl ? (
< li
onMouseOver = {() => {
setIsImageSearchOpen ( true );
}}
onMouseLeave = {() => {
setIsImageSearchOpen ( false );
}}
>
Image search »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isImageSearchOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( 'description' )}>
< a href = { `https://lens.google.com/uploadbyurl?url= ${ subplebbit . suggested ? . avatarUrl } ` } target = '_blank' rel = 'noreferrer' >
Google
</ a >
</ li >
< li onClick = {() => handleOptionClick ( 'description' )}>
< a href = { `https://yandex.com/images/search?url= ${ subplebbit . suggested ? . avatarUrl } ` } target = '_blank' rel = 'noreferrer' >
Yandex
</ a >
</ li >
< li onClick = {() => handleOptionClick ( 'description' )}>
< a href = { `https://saucenao.com/search.php?url= ${ subplebbit . suggested ? . avatarUrl } ` } target = '_blank' rel = 'noreferrer' >
SauceNAO
</ a >
</ li >
</ ul >
</ li >
) : null }
< li
onMouseOver = {() => {
setIsClientRedirectMenuOpen ( true );
}}
onMouseLeave = {() => {
setIsClientRedirectMenuOpen ( false );
}}
>
View on »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isClientRedirectMenuOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( 'description' )}>
< a href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebbit
</ a >
</ li >
{ /* <li onClick={() => handleOptionClick("description")}>
2023-09-14 17:35:28 +02:00
<a
href={`https://seedit.eth.limo/#/p/${selectedAddress}`}
target="_blank" rel="noreferrer"
>Seedit</a>
</li> */ }
2023-09-23 12:44:43 +02:00
< li onClick = {() => handleOptionClick ( 'description' )}>
< a href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` } target = '_blank' rel = 'noreferrer' >
Plebones
</ a >
</ li >
</ ul >
</ li >
</ ul >
</ div >
</ PostMenuCatalog >,
document . body ,
)}
</ span >
< blockquote >
< div className = 'custom-paragraph' >
< Post content = { subplebbit . description } />
</ div >
</ blockquote >
</ div >
</ div >
</ div >
</ div >
< div className = 'thread-mobile' >
{ subplebbit . rules ? < hr /> : < hr style = {{ marginTop : '10px' }} />}
< div className = 'op-container' >
< div className = 'post op op-mobile' >
< div className = 'post-info-mobile' >
< button
key = { `mob-pb-` }
className = 'post-menu-button-mobile'
ref = {( el ) => {
threadMenuRefsMobile . current [ 'description' ] = el ;
}}
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefsMobile . current [ 'description' ]. getBoundingClientRect ();
setMobileMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMobileMenuCid (( prevCid ) => ( prevCid === 'description' ? null : 'description' ));
}}
style = {{ all : 'unset' , cursor : 'pointer' }}
>
...
</ button >
{ createPortal (
< PostMenuMobile
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuMobileRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{
position : 'absolute' ,
display : openMobileMenuCid === 'description' ? 'block' : 'none' ,
top : mobileMenuPosition . top + 20 ,
left : mobileMenuPosition . left ,
}}
>
< ul className = { `post-menu-mobile-thread-description` }>
{ subplebbit . suggested ? . avatarUrl ? (
<>
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://lens.google.com/uploadbyurl?url= ${ subplebbit . suggested ? . avatarUrl } ` }
target = '_blank'
rel = 'noreferrer'
onClick = {() => handleMobileOptionClick ( 'description' )}
>
< li > Search image on Google </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://yandex.com/images/search?url= ${ subplebbit . suggested ? . avatarUrl } ` }
target = '_blank'
rel = 'noreferrer'
onClick = {() => handleMobileOptionClick ( 'description' )}
>
< li > Search image on Yandex </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://saucenao.com/search.php?url= ${ subplebbit . suggested ? . avatarUrl } ` }
target = '_blank'
rel = 'noreferrer'
onClick = {() => handleMobileOptionClick ( 'description' )}
>
< li > Search image on SauceNAO </ li >
</ a >
</>
) : null }
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'description' )}> View on plebbit </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( 'description' )}> View on plebones </ li >
</ a >
</ ul >
</ PostMenuMobile >,
document . body ,
)}
< span className = 'name-block-mobile' >
< span
2023-09-26 17:44:47 +02:00
className = 'name-mobile capcode'
2023-09-23 12:44:43 +02:00
style = {{ cursor : 'pointer' }}
onClick = {() => {
setIsAdminListOpen ( ! isAdminListOpen );
}}
>
2023-09-26 14:46:11 +02:00
## Board Mods
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
2023-10-04 17:25:42 +02:00
< span className = 'thread-icons-mobile' >
< img
src = 'assets/sticky.gif'
alt = 'Sticky'
title = 'Sticky'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
& nbsp ;
2023-10-04 17:25:42 +02:00
< img
src = 'assets/closed.gif'
alt = 'Closed'
title = 'Closed'
style = {{ all : 'unset' , marginBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
/>
2023-09-23 12:44:43 +02:00
</ span >
< br />
< span className = 'subject-mobile' style = {{ marginBottom : '-15px' }}>
Welcome to { subplebbit . title || subplebbit . address }
</ span >
& nbsp ;
</ span >
< span
className = 'date-time-mobile post-number-mobile'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( subplebbit . createdAt )}
data - tooltip - place = 'top'
>
{ getDate ( subplebbit . createdAt )}
</ span >
& nbsp ;
</ div >
{ subplebbit . suggested ? . avatarUrl ? (
< div className = 'file-mobile' >
< div className = 'img-container' >
< span className = 'file-thumb-mobile' >
< img src = { subplebbit . suggested ? . avatarUrl } alt = 'board avatar' onClick = { handleImageClick } style = {{ cursor : 'pointer' }} />
< div className = 'file-info-mobile' > image </ div >
</ span >
</ div >
</ div >
) : null }
< blockquote className = 'post-message-mobile' >
< div className = 'custom-paragraph' >
< Post content = { subplebbit . description } />
</ div >
</ blockquote >
</ div >
< div className = 'post-link-mobile' >
< Link to = { `/p/ ${ selectedAddress } /description` } className = 'button-mobile' >
View Thread
</ Link >
</ div >
</ div >
</ div >
</>
) : null }
< Virtuoso
2023-10-20 16:02:36 +02:00
increaseViewportBy = {{ bottom : 1200 , top : 1200 }}
2023-10-20 20:07:56 +02:00
overscan = { 600 }
2023-09-23 12:44:43 +02:00
data = { selectedFeed }
itemContent = {( index , thread ) => {
if ( editedComments [ thread . cid ]) {
thread = editedComments [ thread . cid ];
if ( thread . removed ) {
thread . content = '[removed]' ;
thread . link = undefined ;
}
}
const { displayedReplies , omittedCount } = filteredRepliesByThread [ thread . cid ] || {};
const commentMediaInfo = getCommentMediaInfo ( thread );
const fallbackImgUrl = 'assets/filedeleted-res.gif' ;
2023-09-29 11:44:05 +02:00
const isOwner = subplebbit ? . roles ? .[ thread . author . address ] ? . role === 'owner' ;
const isAdmin = subplebbit ? . roles ? .[ thread . author . address ] ? . role === 'admin' ;
const isModerator = subplebbit ? . roles ? .[ thread . author . address ] ? . role === 'moderator' ;
2023-09-23 12:44:43 +02:00
let displayWidth , displayHeight , displayWidthMobile , displayHeightMobile ;
if ( thread . linkWidth && thread . linkHeight ) {
let scale = Math . min ( 1 , 250 / Math . max ( thread . linkWidth , thread . linkHeight ));
displayWidth = ` ${ thread . linkWidth * scale } px` ;
displayHeight = ` ${ thread . linkHeight * scale } px` ;
2023-07-23 14:52:47 +02:00
2023-09-23 12:44:43 +02:00
scale = Math . min ( 1 , 100 / Math . max ( thread . linkWidth , thread . linkHeight ));
displayWidthMobile = ` ${ thread . linkWidth * scale } px` ;
displayHeightMobile = ` ${ thread . linkHeight * scale } px` ;
} else {
displayWidth = '250px' ;
displayHeight = '250px' ;
displayWidthMobile = '100px' ;
displayHeightMobile = '100px' ;
}
return (
< Fragment key = { `fr- ${ index } ` }>
< div key = { `t- ${ index } ` } className = 'thread' >
< div key = { `c- ${ index } ` } className = 'op-container' >
< div key = { `po- ${ index } ` } className = 'post op op-desktop' >
< hr key = { `hr- ${ index } ` } />
< div key = { `pi- ${ index } ` } className = 'post-info' >
{ commentMediaInfo ? . url ? (
2023-10-20 21:20:51 +02:00
< div key = { `f- ${ index } ` } className = 'file' >
2023-09-23 12:44:43 +02:00
< div key = { `ft- ${ index } ` } className = 'file-text' >
Link :& nbsp ;
< a key = { `fa- ${ index } ` } href = { commentMediaInfo . url } target = '_blank' rel = 'noopener noreferrer' >
{ commentMediaInfo ? . url . length > 30 ? commentMediaInfo ? . url . slice ( 0 , 30 ) + '(...)' : commentMediaInfo ? . url }
</ a >
{ commentMediaInfo ? . type === 'iframe' ? null : ` ( ${ commentMediaInfo ? . type } )` }
{(( isThreadThumbnailClicked [ index ] && ( commentMediaInfo . type === 'iframe' || commentMediaInfo . type === 'video' )) ||
( commentMediaInfo . type === 'iframe' && ! commentMediaInfo . thumbnail )) && (
< span >
[
< span
className = 'reply-link'
style = {{ textDecoration : 'underline' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'thread' );
}}
>
{ isThreadThumbnailClicked [ index ] ? 'Close' : 'Embed' }
</ span >
]
</ span >
)}
</ div >
{ commentMediaInfo ? . type === 'iframe' && (
< div key = { `enlarge- ${ index } ` } className = { `img-container ${ isThreadThumbnailClicked [ index ] ? 'expanded-container' : '' } ` }>
< span
key = { `fta- ${ index } ` }
className = 'file-thumb'
style = { isThreadThumbnailClicked [ index ] || ! commentMediaInfo . thumbnail ? {} : { width : displayWidth , height : displayHeight }}
>
{ isThreadThumbnailClicked [ index ] && commentMediaInfo . url ? (
< Embed url = { commentMediaInfo . url } key = { `fti- ${ index } ` } />
) : commentMediaInfo . thumbnail ? (
< img
key = { `fti- ${ index } ` }
src = { commentMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'thread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
</ span >
</ div >
)}
{ commentMediaInfo ? . type === 'webpage' ? (
< div key = { `enlarge- ${ index } ` } className = 'img-container' >
< span
key = { `fta- ${ index } ` }
className = 'file-thumb'
style = { isThreadThumbnailClicked [ index ] || ! thread . thumbnailUrl ? {} : { width : displayWidth , height : displayHeight }}
>
{ thread . thumbnailUrl ? (
< img
key = { `fti- ${ index } ` }
src = { commentMediaInfo . thumbnail }
alt = { commentMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'thread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
</ span >
</ div >
) : null }
{ commentMediaInfo ? . type === 'image' ? (
< div key = { `enlarge- ${ index } ` } className = 'img-container' >
< span
key = { `fta- ${ index } ` }
className = 'file-thumb'
style = { isThreadThumbnailClicked [ index ] ? {} : { width : displayWidth , height : displayHeight }}
>
< img
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'thread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
</ span >
</ div >
) : null }
{ commentMediaInfo ? . type === 'video' ? (
< div key = { `enlarge- ${ index } ` } className = { `img-container ${ isThreadThumbnailClicked [ index ] ? 'expanded-container' : '' } ` }>
< span
key = { `fta- ${ index } ` }
className = 'file-thumb'
style = { isThreadThumbnailClicked [ index ] ? {} : { width : displayWidth , height : displayHeight }}
>
{ isThreadThumbnailClicked [ index ] ? (
< video
className = 'enlarged'
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
controls
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< video
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'thread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
)}
</ span >
</ div >
) : null }
{ commentMediaInfo ? . type === 'audio' ? (
< span key = { `fta- ${ index } ` } className = 'file-thumb' >
< audio
controls
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
</ span >
) : null }
</ div >
) : null }
< span key = { `nb- ${ index } ` } className = 'name-block' >
{ thread . title ? (
thread . title . length > 75 ? (
< Fragment key = { `fragment2- ${ index } ` }>
< span key = { `q- ${ index } ` } 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- ${ index } ` } className = 'title' >
{ thread . title }
</ span >
)
) : null }
& nbsp ;
{ thread . author . displayName ? (
thread . author . displayName . length > 20 ? (
< Fragment key = { `fragment3- ${ index } ` }>
< span
key = { `n- ${ index } ` }
2023-09-29 11:44:05 +02:00
className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }
2023-09-23 12:44:43 +02:00
data - tooltip - id = 'tooltip'
data - tooltip - content = { thread . author . displayName }
data - tooltip - place = 'top'
>
{ thread . author . displayName . slice ( 0 , 20 ) + ' (...)' }
</ span >
</ Fragment >
) : (
2023-09-29 11:44:05 +02:00
< span key = { `n- ${ index } ` } className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
{ thread . author . displayName }
</ span >
)
) : (
2023-09-29 11:44:05 +02:00
< span key = { `n- ${ index } ` } className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
Anonymous
</ span >
)}
2023-09-29 11:44:05 +02:00
{ isOwner ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Owner
</ span >
) : isAdmin ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Admin
</ span >
) : isModerator ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode' >
& nbsp ; ## Board Mod
</ span >
) : null }
2023-09-26 14:46:11 +02:00
& nbsp ;( u /
2023-09-23 12:44:43 +02:00
< span
key = { `pa- ${ index } ` }
className = 'poster-address address-desktop'
id = 'reply-button'
style = {{ cursor : 'pointer' }}
onClick = {() =>
2023-09-26 13:40:28 +02:00
handleAddressClick (< VerifiedAuthor commentCid = { thread . cid }>{({ shortAuthorAddress }) => shortAuthorAddress }</ VerifiedAuthor >)
2023-09-23 12:44:43 +02:00
}
>
2023-09-26 13:40:28 +02:00
< VerifiedAuthor commentCid = { thread . cid }>
{({ shortAuthorAddress , authorAddressChanged }) => (
<>
< span className = 'author-address-hidden' >{ thread . author . shortAddress }</ span >
< span className = { `author-address-visible ${ authorAddressChanged ? 'authorAddressChanged' : '' } ` }>{ shortAuthorAddress }</ span >
</>
)}
</ VerifiedAuthor >
2023-09-23 12:44:43 +02:00
</ span >
2023-09-26 14:46:11 +02:00
) & nbsp ;
2023-09-23 12:44:43 +02:00
< span
key = { `dt- ${ index } ` }
className = 'date-time'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( thread ? . timestamp )}
data - tooltip - place = 'top'
>
{ getDate ( thread . timestamp )}
</ span >
& nbsp ;
< span key = { `pn- ${ index } ` } className = 'post-number post-number-desktop' >
< span key = { `pl1- ${ index } ` }> c / </ span >
< Link
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
id = 'reply-button'
key = { `pl2- ${ index } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setSelectedThreadCid ( thread . cid );
let text = document . getSelection (). toString ();
text = text ? `> ${ text } ` : text ;
setSelectedText ( text );
if ( isReplyOpen ) {
setReplyQuoteCid ( thread . shortCid );
setTriggerInsertion ( Date . now ());
} else {
setIsReplyOpen ( true );
setSelectedShortCid ( thread . shortCid );
setSelectedParentCid ( thread . cid );
}
}}
title = 'Reply to this post'
>
{ thread . shortCid }
</ Link >
& nbsp ;
{ thread . pinned ? (
<>
& nbsp ;
2023-10-20 21:20:51 +02:00
< img src = 'assets/sticky.gif' alt = 'Sticky' title = 'Sticky' style = {{ imageRendering : 'pixelated' }} />
2023-09-23 12:44:43 +02:00
</>
) : null }
{ thread . locked ? (
<>
& nbsp ;
< img
src = 'assets/closed.gif'
alt = 'Closed'
title = 'Closed'
style = {{
marginBottom : '-1px' ,
imageRendering : 'pixelated' ,
}}
/>
</>
) : null }
< span key = { `rl1- ${ index } ` }>
& nbsp ; [
< Link
key = { `rl2- ${ index } ` }
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
onClick = {() => setSelectedThread ( thread . cid )}
className = 'reply-link'
>
Reply
</ Link >
]
</ span >
</ span >
< PostMenu
key = { `pmb- ${ index } ` }
title = 'Post menu'
ref = {( el ) => {
threadMenuRefs . current [ thread . cid ] = el ;
}}
className = 'post-menu-button'
rotated = { openMenuCid === thread . cid }
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefs . current [ thread . cid ]. getBoundingClientRect ();
setMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMenuCid (( prevCid ) => ( prevCid === thread . cid ? null : thread . cid ));
}}
>
▶
</ PostMenu >
{ createPortal (
< PostMenuCatalog
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuCatalogRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{ position : 'absolute' , top : menuPosition . top + 7 , left : menuPosition . left }}
>
< div
className = { `post-menu-thread post-menu-thread- ${ thread . cid } ` }
style = {{ display : openMenuCid === thread . cid ? 'block' : 'none' }}
>
< ul className = 'post-menu-catalog' >
< li
onClick = {() => {
handleOptionClick ( thread . cid );
setNewSuccessMessage ( 'Link copied to clipboard' );
handleShareClick ( selectedAddress , thread . cid );
}}
>
Share thread
</ li >
< VerifiedAuthor commentCid = { thread . cid }>
{({ authorAddress }) => (
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? (
<>
< li onClick = {() => handleAuthorEditClick ( thread )}> Edit post </ li >
< li onClick = {() => handleAuthorDeleteClick ( thread )}> Delete post </ li >
</>
) : null }
2023-09-28 21:05:03 +02:00
{ canModerate ? (
2023-09-23 12:44:43 +02:00
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? null : (
< li
onClick = {() => {
setModeratingCommentCid ( thread . cid );
setIsModerationOpen ( true );
handleOptionClick ( thread . cid );
setDeletePost ( true );
}}
>
Delete post
</ li >
)}
< li
onClick = {() => {
setModeratingCommentCid ( thread . cid );
setIsModerationOpen ( true );
handleOptionClick ( thread . cid );
}}
>
Mod tools
</ li >
</>
) : null }
</>
)}
</ VerifiedAuthor >
{ commentMediaInfo &&
( commentMediaInfo . type === 'image' || ( commentMediaInfo . type === 'webpage' && commentMediaInfo . thumbnail )) ? (
< li
onMouseOver = {() => {
setIsImageSearchOpen ( true );
}}
onMouseLeave = {() => {
setIsImageSearchOpen ( false );
}}
>
Image search »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isImageSearchOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( thread . cid )}>
< a href = { `https://lens.google.com/uploadbyurl?url= ${ commentMediaInfo . url } ` } target = '_blank' rel = 'noreferrer' >
Google
</ a >
</ li >
< li onClick = {() => handleOptionClick ( thread . cid )}>
< a href = { `https://yandex.com/images/search?url= ${ commentMediaInfo . url } ` } target = '_blank' rel = 'noreferrer' >
Yandex
</ a >
</ li >
< li onClick = {() => handleOptionClick ( thread . cid )}>
< a href = { `https://saucenao.com/search.php?url= ${ commentMediaInfo . url } ` } target = '_blank' rel = 'noreferrer' >
SauceNAO
</ a >
</ li >
</ ul >
</ li >
) : null }
< li
onMouseOver = {() => {
setIsClientRedirectMenuOpen ( true );
}}
onMouseLeave = {() => {
setIsClientRedirectMenuOpen ( false );
}}
>
View on »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isClientRedirectMenuOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( thread . cid )}>
< a href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } target = '_blank' rel = 'noreferrer' >
Plebbit
</ a >
</ li >
2023-10-26 14:13:00 +02:00
< li onClick = {() => handleOptionClick ( thread . cid )}>
2023-12-15 12:58:47 +01:00
< a href = { `https://seedit.eth.limo/#/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } target = '_blank' rel = 'noreferrer' >
2023-10-26 14:13:00 +02:00
Seedit
</ a >
</ li >
2023-09-23 12:44:43 +02:00
< li onClick = {() => handleOptionClick ( thread . cid )}>
< a href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } /c/ ${ thread . cid } ` } target = '_blank' rel = 'noreferrer' >
Plebones
</ a >
</ li >
</ ul >
</ li >
</ ul >
</ div >
</ PostMenuCatalog >,
document . body ,
)}
< div key = { `bi- ${ index } ` } id = 'backlink-id' className = 'backlink' >
{ thread . replies ? . pages ? . topAll . comments
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply , index ) => (
< div
key = { `div- ${ index } ` }
style = {{ display : 'inline-block' }}
ref = {( el ) => {
backlinkRefs . current [ reply . cid ] = el ;
}}
>
< Link
key = { `ql- ${ index } ` }
to = {() => {}}
className = 'quote-link'
onClick = {( event ) => handleQuoteClick ( reply , null , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , null , () => {
const rect = backlinkRefs . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-07-09 16:03:46 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
setOutOfViewCid ( reply . cid );
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
c / { reply . shortCid }
</ Link >
& nbsp ;
</ div >
))}
</ div >
</ span >
{ thread . content ? (
thread . content ? . length > 1000 ? (
< Fragment key = { `fragment5- ${ index } ` }>
< blockquote key = { `bq- ${ index } ` }>
2023-10-02 12:55:42 +02:00
< Post
key = { `post- ${ thread . cid } ` }
content = { thread . content ? . slice ( 0 , 1000 )}
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( thread , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( thread , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
2023-09-23 12:44:43 +02:00
< span key = { `ttl-s- ${ index } ` } className = 'ttl' >
{ ' ' }
(...)
< br key = { `ttl-s-br1- ${ index } ` } />
< EditLabel key = { `edit-label-thread- ${ index } ` } commentCid = { thread . cid } className = 'ttl' />
< StateLabel key = { `state-label-thread- ${ index } ` } commentIndex = { thread . index } className = 'ttl ellipsis' />
< br key = { `ttl-s-br2 ${ index } ` } />
Post too long . & nbsp ;
< Link
key = { `ttl-l- ${ index } ` }
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
onClick = {() => setSelectedThread ( thread . cid )}
className = 'ttl-link'
>
Click here
</ Link >
& nbsp ; to view .
</ span >
</ blockquote >
</ Fragment >
) : (
< blockquote key = { `bq- ${ index } ` }>
2023-10-02 12:55:42 +02:00
< Post
key = { `post- ${ thread . cid } ` }
content = { thread . content }
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( thread , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( thread , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
2023-09-23 12:44:43 +02:00
< EditLabel key = { `edit-label-thread- ${ index } ` } commentCid = { thread . cid } className = 'ttl' />
< StateLabel key = { `state-label-thread- ${ index } ` } commentIndex = { thread . index } className = 'ttl ellipsis' />
</ blockquote >
)
) : null }
</ div >
</ div >
</ div >
< span key = { `summary- ${ index } ` } className = 'summary' >
{ omittedCount > 0 ? (
<>
{ thread . content ? null : < br />}
< span key = { `oc- ${ index } ` } className = 'ttl' >
< span key = { `oc1- ${ index } ` }>
{ omittedCount } post { omittedCount > 1 ? 's' : '' } omitted . Click & nbsp ;
< Link
key = { `oc2- ${ index } ` }
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
onClick = {() => setSelectedThread ( thread . cid )}
className = 'ttl-link'
>
here
</ Link >
& nbsp ; to view .
</ span >
</ span >
</>
) : null }
</ span >
{ displayedReplies ? . map (( reply , index ) => {
if ( editedComments [ reply . cid ]) {
reply = editedComments [ reply . cid ];
if ( reply . removed ) {
reply . content = '[removed]' ;
reply . link = undefined ;
}
}
const replyMediaInfo = getCommentMediaInfo ( reply );
const fallbackImgUrl = 'assets/filedeleted-res.gif' ;
2023-09-29 11:44:05 +02:00
const isOwner = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'owner' ;
const isAdmin = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'admin' ;
const isModerator = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'moderator' ;
2023-09-23 12:44:43 +02:00
const shortParentCid = findShortParentCid ( reply . parentCid , selectedFeed );
let storedSigners = JSON . parse ( localStorage . getItem ( 'storedSigners' )) || {};
let signerAddress ;
if ( storedSigners [ reply . parentCid ]) {
signerAddress = storedSigners [ reply . parentCid ]. address ;
}
let replyDisplayWidth , replyDisplayHeight ;
if ( reply . linkWidth && reply . linkHeight ) {
const scale = Math . min ( 1 , 125 / Math . max ( reply . linkWidth , reply . linkHeight ));
replyDisplayWidth = ` ${ reply . linkWidth * scale } px` ;
replyDisplayHeight = ` ${ reply . linkHeight * scale } px` ;
} else {
replyDisplayWidth = '125px' ;
replyDisplayHeight = '125px' ;
}
return (
< Fragment key = { `fr-reply- ${ index } ` }>
< div key = { `rc- ${ index } ` } className = 'reply-container' >
< div key = { `sa- ${ index } ` } className = 'side-arrows' >
{ '>>' }
</ div >
< div key = { `pr- ${ index } ` } className = 'post-reply post-reply-desktop' >
< div key = { `pi- ${ index } ` } className = 'post-info' >
< span key = { `nb- ${ index } ` } className = 'nameblock' >
{ reply . author . displayName ? (
reply . author . displayName . length > 20 ? (
< Fragment key = { `fragment6- ${ index } ` }>
< span
key = { `mob-n- ${ index } ` }
2023-09-29 11:44:05 +02:00
className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }
2023-09-23 12:44:43 +02:00
data - tooltip - id = 'tooltip'
data - tooltip - content = { reply . author . displayName }
data - tooltip - place = 'top'
>
{ reply . author . displayName . slice ( 0 , 20 ) + ' (...)' }
</ span >
</ Fragment >
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
{ reply . author . displayName }
</ span >
)
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
Anonymous
</ span >
)}
2023-09-29 11:44:05 +02:00
{ isOwner ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Owner
</ span >
) : isAdmin ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Admin
</ span >
) : isModerator ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode' >
& nbsp ; ## Board Mod
</ span >
) : null }
2023-09-26 13:40:28 +02:00
& nbsp ;( u /
2023-09-23 12:44:43 +02:00
< span
key = { `pa- ${ index } ` }
className = 'poster-address address-desktop'
id = 'reply-button'
style = {{ cursor : 'pointer' }}
onClick = {() =>
2023-09-26 13:40:28 +02:00
handleAddressClick (< VerifiedAuthor commentCid = { reply . cid }>{({ shortAuthorAddress }) => shortAuthorAddress }</ VerifiedAuthor >)
2023-09-23 12:44:43 +02:00
}
>
{ reply . author ? . shortAddress ? (
2023-09-26 13:40:28 +02:00
< VerifiedAuthor commentCid = { reply . cid }>
{({ shortAuthorAddress , authorAddressChanged }) => (
<>
< span className = 'author-address-hidden' >{ reply . author . shortAddress }</ span >
< span className = { `author-address-visible ${ authorAddressChanged ? 'authorAddressChanged' : '' } ` }>
{ shortAuthorAddress }
</ span >
</>
)}
</ VerifiedAuthor >
2023-09-23 12:44:43 +02:00
) : (
2023-09-26 13:40:28 +02:00
< span key = { `mob-ha- ${ index } ` }>{ signerAddress ? . slice ( 8 , 20 )}</ span >
2023-09-23 12:44:43 +02:00
)}
</ span >
2023-09-26 13:40:28 +02:00
)
2023-09-23 12:44:43 +02:00
</ span >
& nbsp ;
< span
key = { `dt- ${ index } ` }
className = 'date-time'
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( reply . timestamp )}
data - tooltip - place = 'top'
>
{ getDate ( reply . timestamp )}
</ span >
& nbsp ;
< span key = { `pn- ${ index } ` } className = 'post-number post-number-desktop' >
< span key = { `pl1- ${ index } ` }> c / </ span >
{ reply . shortCid ? (
< Link
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
id = 'reply-button'
key = { `pl2- ${ index } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setSelectedThreadCid ( thread . cid );
let text = document . getSelection (). toString ();
text = text ? `> ${ text } ` : text ;
setSelectedText ( text );
if ( isReplyOpen ) {
setReplyQuoteCid ( reply . shortCid );
setTriggerInsertion ( Date . now ());
} else {
setIsReplyOpen ( true );
setSelectedShortCid ( reply . shortCid );
setSelectedParentCid ( reply . cid );
}
}}
title = 'Reply to this post'
>
{ reply . shortCid }
</ Link >
) : (
< PendingLabel key = 'pending' commentIndex = { reply . index } />
)}
</ span >
& nbsp ;
< PostMenu
key = { `pmb- ${ index } ` }
title = 'Post menu'
ref = {( el ) => {
replyMenuRefs . current [ reply . cid ] = el ;
}}
className = 'post-menu-button'
rotated = { openMenuCid === reply . cid }
onClick = {( event ) => {
event . stopPropagation ();
const rect = replyMenuRefs . current [ reply . cid ]. getBoundingClientRect ();
setMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMenuCid (( prevCid ) => ( prevCid === reply . cid ? null : reply . cid ));
}}
>
▶
</ PostMenu >
{ createPortal (
< PostMenuCatalog
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuCatalogRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{ position : 'absolute' , top : menuPosition . top + 7 , left : menuPosition . left }}
>
< div
className = { `post-menu-reply post-menu-reply- ${ reply . cid } ` }
style = {{ display : openMenuCid === reply . cid ? 'block' : 'none' }}
>
< ul className = 'post-menu-catalog' >
< li
onClick = {() => {
handleOptionClick ( reply . cid );
setNewSuccessMessage ( 'Link copied to clipboard' );
2023-10-24 17:54:22 +02:00
handleShareClick ( thread . subplebbitAddress , thread . cid );
2023-09-23 12:44:43 +02:00
}}
>
Share post
</ li >
< VerifiedAuthor commentCid = { reply . cid }>
{({ authorAddress }) => (
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? (
<>
< li onClick = {() => handleAuthorEditClick ( reply )}> Edit post </ li >
< li onClick = {() => handleAuthorDeleteClick ( reply )}> Delete post </ li >
</>
) : null }
2023-09-28 21:05:03 +02:00
{ canModerate ? (
2023-09-23 12:44:43 +02:00
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? null : (
< li
onClick = {() => {
setModeratingCommentCid ( reply . cid );
setIsModerationOpen ( true );
handleOptionClick ( reply . cid );
setDeletePost ( true );
}}
>
Delete post
</ li >
)}
< li
onClick = {() => {
setModeratingCommentCid ( reply . cid );
setIsModerationOpen ( true );
handleOptionClick ( reply . cid );
}}
>
Mod tools
</ li >
</>
) : null }
</>
)}
</ VerifiedAuthor >
{ replyMediaInfo && ( replyMediaInfo . type === 'image' || ( replyMediaInfo . type === 'webpage' && replyMediaInfo . thumbnail )) ? (
< li
onMouseOver = {() => {
setIsImageSearchOpen ( true );
}}
onMouseLeave = {() => {
setIsImageSearchOpen ( false );
}}
>
Image search »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isImageSearchOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( reply . cid )}>
< a href = { `https://lens.google.com/uploadbyurl?url= ${ replyMediaInfo . url } ` } target = '_blank' rel = 'noreferrer' >
Google
</ a >
</ li >
< li onClick = {() => handleOptionClick ( reply . cid )}>
< a
href = { `https://yandex.com/images/search?img_url= ${ replyMediaInfo . url } &rpt=imageview` }
target = '_blank'
rel = 'noreferrer'
>
Yandex
</ a >
</ li >
< li onClick = {() => handleOptionClick ( reply . cid )}>
< a href = { `https://saucenao.com/search.php?url= ${ replyMediaInfo . url } ` } target = '_blank' rel = 'noreferrer' >
SauceNAO
</ a >
</ li >
</ ul >
</ li >
) : null }
< li
onMouseOver = {() => {
setIsClientRedirectMenuOpen ( true );
}}
onMouseLeave = {() => {
setIsClientRedirectMenuOpen ( false );
}}
>
View on »
< ul className = 'dropdown-menu post-menu-catalog' style = {{ display : isClientRedirectMenuOpen ? 'block' : 'none' }}>
< li onClick = {() => handleOptionClick ( reply . cid )}>
< a href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } /c/ ${ reply . cid } ` } target = '_blank' rel = 'noreferrer' >
Plebbit
</ a >
</ li >
{ /* <li onClick={() => handleOptionClick(reply.cid)}>
2023-08-09 13:03:20 +02:00
<a
href={`https://seedit.eth.limo/#/p/${selectedAddress}/c/${reply.cid}`}
target="_blank" rel="noreferrer"
>Seedit</a>
</li> */ }
2023-09-23 12:44:43 +02:00
< li onClick = {() => handleOptionClick ( reply . cid )}>
< a href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } /c/ ${ reply . cid } ` } target = '_blank' rel = 'noreferrer' >
Plebones
</ a >
</ li >
</ ul >
</ li >
</ ul >
</ div >
</ PostMenuCatalog >,
document . body ,
)}
< div key = { `bi- ${ index } ` } id = 'backlink-id' className = 'backlink' >
{ reply . replies ? . pages ? . topAll . comments
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply , index ) => (
< div
key = { `div- ${ index } ` }
style = {{ display : 'inline-block' }}
ref = {( el ) => {
backlinkRefs . current [ reply . cid ] = el ;
}}
>
< Link
key = { `ql- ${ index } ` }
to = {() => {}}
className = 'quote-link'
onClick = {( event ) => handleQuoteClick ( reply , null , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , null , () => {
setOutOfViewCid ( reply . cid );
const rect = backlinkRefs . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-07-09 16:03:46 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-07-09 16:03:46 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
c / { reply . shortCid }
</ Link >
& nbsp ;
</ div >
))}
</ div >
</ div >
{ replyMediaInfo ? . url ? (
2023-10-20 21:20:51 +02:00
< div key = { `f- ${ index } ` } className = 'file' >
2023-09-23 12:44:43 +02:00
< div key = { `ft- ${ index } ` } className = 'file-text' >
Link :& nbsp ;
< a key = { `fa- ${ index } ` } href = { replyMediaInfo . url } target = '_blank' rel = 'noopener noreferrer' >
{ replyMediaInfo ? . url . length > 30 ? replyMediaInfo ? . url . slice ( 0 , 30 ) + '(...)' : replyMediaInfo ? . url }
</ a >
{ replyMediaInfo ? . type === 'iframe' ? null : ` ( ${ replyMediaInfo ? . type } )` }
{(( isReplyThumbnailClicked [ index ] && ( replyMediaInfo . type === 'iframe' || replyMediaInfo . type === 'video' )) ||
( replyMediaInfo . type === 'iframe' && ! replyMediaInfo . thumbnail )) && (
< span >
[
< span
className = 'reply-link'
style = {{ textDecoration : 'underline' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'reply' );
}}
>
{ isReplyThumbnailClicked [ index ] ? 'Close' : 'Embed' }
</ span >
]
</ span >
)}
</ div >
{ replyMediaInfo ? . type === 'iframe' && (
< div key = { `enlarge- ${ index } ` } className = { `img-container ${ isReplyThumbnailClicked [ index ] ? 'expanded-container' : '' } ` }>
< span
key = { `fta- ${ index } ` }
className = 'file-thumb-reply'
style = {
isReplyThumbnailClicked [ index ] || ! replyMediaInfo . thumbnail
? {}
: { width : replyDisplayWidth , height : replyDisplayHeight }
}
>
{ isReplyThumbnailClicked [ index ] && replyMediaInfo . url ? (
< Embed url = { replyMediaInfo . url } key = { `fti- ${ index } ` } />
) : replyMediaInfo . thumbnail ? (
< img
key = { `fti- ${ index } ` }
src = { replyMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'reply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
</ span >
</ div >
)}
{ replyMediaInfo ? . type === 'webpage' && (
< div key = { `enlarge- ${ index } ` } className = 'img-container' >
< span
key = { `fta- ${ index } ` }
className = 'file-thumb-reply'
style = {
isReplyThumbnailClicked [ index ] || ! reply . thumbnailUrl ? {} : { width : replyDisplayWidth , height : replyDisplayHeight }
}
>
{ reply . thumbnailUrl ? (
< img
key = { `fti- ${ index } ` }
src = { replyMediaInfo . thumbnail }
alt = { replyMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'reply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
</ span >
</ div >
)}
{ replyMediaInfo ? . type === 'image' && (
< div key = { `enlarge- ${ index } ` } className = 'img-container' >
< span
key = { `fta- ${ index } ` }
className = 'file-thumb-reply'
style = { isReplyThumbnailClicked [ index ] ? {} : { width : replyDisplayWidth , height : replyDisplayHeight }}
>
< img
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'reply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
</ span >
</ div >
)}
{ replyMediaInfo ? . type === 'video' && (
< div key = { `enlarge- ${ index } ` } className = { `img-container ${ isReplyThumbnailClicked [ index ] ? 'expanded-container' : '' } ` }>
< span
key = { `fta- ${ index } ` }
className = 'file-thumb-reply'
style = { isReplyThumbnailClicked [ index ] ? {} : { width : replyDisplayWidth , height : replyDisplayHeight }}
>
{ isReplyThumbnailClicked [ index ] ? (
< video
className = 'enlarged'
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
controls
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< video
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'reply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
)}
</ span >
</ div >
)}
{ replyMediaInfo ? . type === 'audio' && (
< span key = { `fta- ${ index } ` } className = 'file-thumb-reply' >
< audio
controls
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
</ span >
)}
</ div >
) : null }
{ reply . content ? (
reply . content ? . length > 500 ? (
< Fragment key = { `fragment8- ${ index } ` }>
< blockquote key = { `pm- ${ index } ` } comment = { reply } className = 'post-message' >
< Link
to = {() => {}}
key = { `r-pm- ${ index } ` }
className = 'quotelink'
ref = {( el ) => {
quoteRefs . current [ reply . cid ] = el ;
}}
onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , shortParentCid , () => {
setOutOfViewCid ( reply . parentCid );
const rect = quoteRefs . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-07-09 16:03:46 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-06-17 12:51:30 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
{ `c/ ${ shortParentCid } ` }
{ shortParentCid === thread . shortCid ? ' (OP)' : null }
</ Link >
< Post
key = { `post- ${ index } ` }
content = { reply . content ? . slice ( 0 , 500 )}
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( reply , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( reply , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
2023-07-09 16:03:46 +02:00
2023-09-23 12:44:43 +02:00
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-06-11 13:59:56 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-01-31 23:08:57 +01:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
< span key = { `ttl-s- ${ index } ` } className = 'ttl' >
{ ' ' }
(...)
< br key = { `ttl-s-br1- ${ index } ` } />
< EditLabel key = { `edit-label-reply- ${ index } ` } commentCid = { reply . cid } className = 'ttl' />
< StateLabel key = { `state-label-reply- ${ index } ` } commentIndex = { reply . index } className = 'ttl ellipsis' />
< br key = { `ttl-s-br2 ${ index } ` } />
Comment too long . & nbsp ;
< Link
key = { `ttl-l- ${ index } ` }
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
onClick = {() => setSelectedThread ( thread . cid )}
className = 'ttl-link'
>
Click here
</ Link >
& nbsp ; to view .{ ' ' }
</ span >
</ blockquote >
</ Fragment >
) : (
< blockquote key = { `pm- ${ index } ` } className = 'post-message' >
< Link
to = {() => {}}
key = { `r-pm- ${ index } ` }
className = 'quotelink'
ref = {( el ) => {
quoteRefs . current [ reply . cid ] = el ;
}}
onClick = {() => handleQuoteClick ( reply , shortParentCid , null )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , shortParentCid , () => {
setOutOfViewCid ( reply . parentCid );
const rect = quoteRefs . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
{ `c/ ${ shortParentCid } ` }
{ shortParentCid === thread . shortCid ? ' (OP)' : null }
</ Link >
< Post
key = { `post- ${ index } ` }
content = { reply . content }
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( reply , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( reply , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
< EditLabel key = { `edit-label-reply- ${ index } ` } commentCid = { reply . cid } className = 'ttl' />
< StateLabel key = { `state-label-reply- ${ index } ` } commentIndex = { reply . index } className = 'ttl ellipsis' />
</ blockquote >
)
) : null }
</ div >
2023-09-26 20:18:17 +02:00
< div key = { `spacer-fix- ${ index } ` } style = {{ height : '2px' }} />
2023-09-23 12:44:43 +02:00
</ div >
</ Fragment >
);
})}
</ div >
< div key = { `mob-t- ${ index } ` } className = 'thread-mobile' >
2023-10-20 14:18:17 +02:00
< hr key = { `mob-hr- ${ index } ` } />
2023-09-23 12:44:43 +02:00
< div key = { `mob-c- ${ index } ` } className = 'op-container' >
< div key = { `mob-po- ${ index } ` } className = 'post op op-mobile' >
< div key = { `mob-pi- ${ index } ` } className = 'post-info-mobile' >
< button
key = { `mob-pb- ${ index } ` }
className = 'post-menu-button-mobile'
ref = {( el ) => {
threadMenuRefsMobile . current [ thread . cid ] = el ;
}}
onClick = {( event ) => {
event . stopPropagation ();
const rect = threadMenuRefsMobile . current [ thread . cid ]. getBoundingClientRect ();
setMobileMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMobileMenuCid (( prevCid ) => ( prevCid === thread . cid ? null : thread . cid ));
}}
style = {{ all : 'unset' , cursor : 'pointer' }}
>
...
</ button >
{ createPortal (
< PostMenuMobile
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuMobileRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{
position : 'absolute' ,
display : openMobileMenuCid === thread . cid ? 'block' : 'none' ,
top : mobileMenuPosition . top + 20 ,
left : mobileMenuPosition . left ,
}}
>
< ul className = { `post-menu-mobile-thread- ${ thread . cid } ` }>
< li
onClick = {() => {
handleMobileOptionClick ( thread . cid );
setNewSuccessMessage ( 'Link copied to clipboard' );
handleShareClick ( selectedAddress , thread . cid );
}}
>
Share thread
</ li >
< VerifiedAuthor commentCid = { thread . cid }>
{({ authorAddress }) => (
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? (
<>
< li onClick = {() => handleAuthorEditClick ( thread )}> Edit post </ li >
< li onClick = {() => handleAuthorDeleteClick ( thread )}> Delete post </ li >
</>
) : null }
2023-09-28 21:05:03 +02:00
{ canModerate ? (
2023-09-23 12:44:43 +02:00
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? null : (
< li
onClick = {() => {
setModeratingCommentCid ( thread . cid );
setIsModerationOpen ( true );
handleMobileOptionClick ( thread . cid );
setDeletePost ( true );
}}
>
Delete post
</ li >
)}
< li
onClick = {() => {
setModeratingCommentCid ( thread . cid );
setIsModerationOpen ( true );
handleMobileOptionClick ( thread . cid );
}}
>
Mod tools
</ li >
</>
) : null }
</>
)}
</ VerifiedAuthor >
{ commentMediaInfo && ( commentMediaInfo . type === 'image' || ( commentMediaInfo . type === 'webpage' && commentMediaInfo . thumbnail )) ? (
<>
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://lens.google.com/uploadbyurl?url= ${ commentMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( thread . cid )}> Search image on Google </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://yandex.com/images/search?url= ${ commentMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( thread . cid )}> Search image on Yandex </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://saucenao.com/search.php?url= ${ commentMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( thread . cid )}> Search image on SauceNAO </ li >
</ a >
</>
) : null }
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( thread . cid )}> View on plebbit </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( thread . cid )}> View on plebones </ li >
</ a >
</ ul >
</ PostMenuMobile >,
document . body ,
)}
< span key = { `mob-nbm- ${ index } ` } className = 'name-block-mobile' >
{ thread . author . displayName ? (
thread . author . displayName . length > 20 ? (
< Fragment key = { `fragment9- ${ index } ` }>
< span
key = { `mob-n- ${ index } ` }
2023-09-29 11:44:05 +02:00
className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }
2023-09-23 12:44:43 +02:00
data - tooltip - id = 'tooltip'
data - tooltip - content = { thread . author . displayName }
data - tooltip - place = 'top'
>
{ thread . author . displayName . slice ( 0 , 20 ) + ' (...)' }
</ span >
</ Fragment >
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
{ thread . author . displayName }
</ span >
)
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
Anonymous
</ span >
)}
2023-09-29 11:44:05 +02:00
{ isOwner ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Owner
</ span >
) : isAdmin ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Admin
</ span >
) : isModerator ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode' >
& nbsp ; ## Board Mod
</ span >
) : null }
2023-09-23 12:44:43 +02:00
& nbsp ;
< span
key = { `mob-pa- ${ index } ` }
className = 'poster-address-mobile address-mobile'
id = 'reply-button'
style = {{ cursor : 'pointer' }}
onClick = {() =>
2023-09-26 13:40:28 +02:00
handleAddressClick (< VerifiedAuthor commentCid = { thread . cid }>{({ shortAuthorAddress }) => shortAuthorAddress }</ VerifiedAuthor >)
2023-09-23 12:44:43 +02:00
}
>
( u /
2023-09-26 13:40:28 +02:00
< span key = { `mob-ha- ${ index } ` } className = 'highlight-address-mobile poster-address' >
< VerifiedAuthor commentCid = { thread . cid }>
{({ shortAuthorAddress , authorAddressChanged }) => (
<>
< span className = 'author-address-hidden' >{ thread . author . shortAddress }</ span >
< span className = { `author-address-visible ${ authorAddressChanged ? 'authorAddressChanged' : '' } ` }>{ shortAuthorAddress }</ span >
</>
)}
</ VerifiedAuthor >
2023-09-23 12:44:43 +02:00
</ span >
) & nbsp ;
2023-10-04 17:25:42 +02:00
< span key = { `ti-mob- ${ index } ` } className = 'thread-icons-mobile' >
{ thread . pinned ? (
< img
src = 'assets/sticky.gif'
alt = 'Sticky'
title = 'Sticky'
2023-10-20 15:59:05 +02:00
style = {{ all : 'unset' , paddingBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
2023-10-04 17:25:42 +02:00
/>
) : null }
{ thread . locked ? (
< img
src = 'assets/closed.gif'
alt = 'Closed'
title = 'Closed'
2023-10-20 15:59:05 +02:00
style = {{ all : 'unset' , paddingBottom : '-2px' , paddingLeft : '2px' , imageRendering : 'pixelated' }}
2023-10-04 17:25:42 +02:00
/>
) : null }
</ span >
2023-09-23 12:44:43 +02:00
</ span >
< br key = { `mob-br1- ${ index } ` } />
{ thread . title ? (
thread . title . length > 30 ? (
< Fragment key = { `fragment11- ${ index } ` }>
< span
key = { `mob-t- ${ index } ` }
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- ${ index } ` } className = 'subject-mobile' >
{ thread . title }
</ span >
)
) : null }
</ span >
< span key = { `mob-dt- ${ index } ` } className = 'date-time-mobile post-number-mobile' >
< span
key = { `tooltip-fix-mob- ${ index } ` }
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( thread . timestamp )}
data - tooltip - place = 'top'
>
{ getDate ( thread . timestamp )}
</ span >
& nbsp ;
< span key = { `mob-no- ${ index } ` }> c / </ span >
< Link
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
id = 'reply-button'
key = { `mob-no2- ${ index } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setSelectedThreadCid ( thread . cid );
let text = document . getSelection (). toString ();
text = text ? `> ${ text } ` : text ;
setSelectedText ( text );
if ( isReplyOpen ) {
setReplyQuoteCid ( thread . shortCid );
setTriggerInsertion ( Date . now ());
} else {
setIsReplyOpen ( true );
setSelectedShortCid ( thread . shortCid );
setSelectedParentCid ( thread . cid );
}
}}
title = 'Reply to this post'
>
{ thread . shortCid }
</ Link >
</ span >
</ div >
{ thread . link ? (
< div key = { `mob-f- ${ index } ` } className = 'file-mobile' >
{ commentMediaInfo ? . type === 'iframe' && (
< div key = { `enlarge-mob- ${ index } ` } className = 'img-container' >
< span
key = { `mob-fta- ${ index } ` }
className = 'file-thumb-mobile'
2023-10-20 15:59:05 +02:00
style = { isMobileThreadThumbnailClicked [ index ] ? {} : { paddingBottom : '25px' }}
2023-09-23 12:44:43 +02:00
>
{ isMobileThreadThumbnailClicked [ index ] && commentMediaInfo . url ? (
< div style = {{ width : '92vw' }}>
< Embed url = { commentMediaInfo . url } key = { `fti-mobile- ${ index } ` } />
</ div >
) : commentMediaInfo . thumbnail ? (
< img
key = { `mob-fti- ${ index } ` }
src = { commentMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'mobileThread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< span
onClick = {() => {
handleThumbnailClick ( index , 'mobileThread' );
}}
>
Embed
</ span >
)}
{ commentMediaInfo ? . type === 'video' || commentMediaInfo ? . type === 'iframe' ? (
isMobileThreadThumbnailClicked [ index ] ? (
2023-10-20 15:59:05 +02:00
< div style = {{ textAlign : 'center' , paddingTop : '15px' , paddingBottom : '15px' }}>
2023-09-23 12:44:43 +02:00
< span
className = 'button-mobile'
style = {{ float : 'none' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'mobileThread' );
}}
>
Close
</ span >
</ div >
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
webpage
</ div >
)
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
webpage
</ div >
)}
</ span >
</ div >
)}
{ commentMediaInfo ? . url ? (
commentMediaInfo . type === 'webpage' ? (
< div key = { `enlarge-mob- ${ index } ` } className = 'img-container' >
< span
key = { `mob-ft ${ thread . cid } ` }
className = 'file-thumb-mobile'
style = {
isMobileThreadThumbnailClicked [ index ] || ! thread . thumbnailUrl
? {}
2023-10-20 15:59:05 +02:00
: { width : displayWidthMobile , height : displayHeightMobile , paddingBottom : '25px' }
2023-09-23 12:44:43 +02:00
}
>
{ thread . thumbnailUrl ? (
< img
key = { `mob-img- ${ index } ` }
src = { commentMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'mobileThread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ commentMediaInfo ? . type }
</ div >
</ span >
</ div >
) : commentMediaInfo . type === 'image' ? (
< div key = { `enlarge-mob- ${ index } ` } className = 'img-container' >
< span
key = { `mob-ft ${ thread . cid } ` }
className = 'file-thumb-mobile'
style = {
2023-10-20 15:59:05 +02:00
isMobileThreadThumbnailClicked [ index ] ? {} : { width : displayWidthMobile , height : displayHeightMobile , paddingBottom : '25px' }
2023-09-23 12:44:43 +02:00
}
>
< img
key = { `mob-img- ${ index } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'mobileThread' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ commentMediaInfo ? . type }
</ div >
</ span >
</ div >
) : commentMediaInfo . type === 'video' ? (
< span
key = { `mob-ft ${ thread . cid } ` }
className = 'file-thumb-mobile'
2023-10-20 15:59:05 +02:00
style = { isMobileThreadThumbnailClicked [ index ] ? {} : { paddingBottom : '25px' }}
2023-09-23 12:44:43 +02:00
>
{ isMobileThreadThumbnailClicked [ index ] ? (
< video
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
controls
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< video
key = { `fti- ${ index } ` }
src = { commentMediaInfo . url }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'mobileThread' );
}}
style = {{ cursor : 'pointer' }}
id = 'video-thumbnail-mobile'
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
)}
{ commentMediaInfo ? . type === 'video' || commentMediaInfo ? . type === 'iframe' ? (
isMobileThreadThumbnailClicked [ index ] ? (
2023-10-20 15:59:05 +02:00
< div style = {{ textAlign : 'center' , paddingTop : '15px' , paddingBottom : '15px' }}>
2023-09-23 12:44:43 +02:00
< span
className = 'button-mobile'
style = {{ float : 'none' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'mobileThread' );
}}
>
Close
</ span >
</ div >
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
video
</ div >
)
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
video
</ div >
)}
</ span >
) : commentMediaInfo . type === 'audio' ? (
< span
key = { `mob-ft ${ thread . cid } ` }
className = 'file-thumb-mobile'
style = {
2023-10-20 15:59:05 +02:00
isMobileThreadThumbnailClicked [ index ] ? {} : { width : displayWidthMobile , height : displayHeightMobile , paddingBottom : '25px' }
2023-09-23 12:44:43 +02:00
}
>
< audio
key = { `mob-img- ${ index } ` }
src = { commentMediaInfo . url }
alt = { commentMediaInfo . type }
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ commentMediaInfo ? . type }
</ div >
</ span >
) : null
) : null }
</ div >
) : null }
{ thread . content ? (
thread . content ? . length > 500 ? (
< Fragment key = { `fragment12- ${ index } ` }>
< blockquote key = { `mob-bq- ${ index } ` } className = 'post-message-mobile' >
2023-10-02 12:55:42 +02:00
< Post
key = { `post-mobile- ${ thread . cid } ` }
content = { thread . content ? . slice ( 0 , 500 )}
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( thread , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( thread , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
2023-09-23 12:44:43 +02:00
< span key = { `mob-ttl-s- ${ index } ` } className = 'ttl' >
{ ' ' }
(...)
< br key = { `mob-ttl-s-br1- ${ index } ` } />
< EditLabel key = { `edit-label-thread-mob- ${ index } ` } commentCid = { thread . cid } className = 'ttl' />
< StateLabel key = { `state-label-thread-mob- ${ index } ` } commentIndex = { thread . index } className = 'ttl ellipsis' />
< br key = { `mob-ttl-s-br2 ${ thread . cid } ` } />
Post too long . & nbsp ;
< Link
key = { `mob-ttl-l- ${ index } ` }
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- ${ index } ` } className = 'post-message-mobile' >
2023-10-02 12:55:42 +02:00
< Post
key = { `post-mobile- ${ thread . cid } ` }
content = { thread . content ? . slice ( 0 , 1000 )}
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( thread , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( thread , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
2023-09-23 12:44:43 +02:00
< EditLabel key = { `edit-label-thread-mob- ${ index } ` } commentCid = { thread . cid } className = 'ttl' />
< StateLabel key = { `state-label-thread-mob- ${ index } ` } commentIndex = { thread . index } className = 'ttl ellipsis' />
</ blockquote >
)
) : null }
</ div >
< div key = { `mob-pl- ${ index } ` } className = 'post-link-mobile' >
< span key = { `mob-info- ${ index } ` } className = 'info-mobile' >
{ thread . replyCount + pendingReplyCounts [ thread . cid ] === 0
? 'No replies'
: thread . replyCount + pendingReplyCounts [ thread . cid ] === 1
? '1 reply'
: thread . replyCount + pendingReplyCounts [ thread . cid ] > 1
? thread . replyCount + pendingReplyCounts [ thread . cid ] + ' replies'
: null }
</ span >
< Link
key = { `rl2- ${ index } ` }
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
onClick = {() => {
setSelectedThread ( thread . cid );
}}
className = 'button-mobile'
>
View Thread
</ Link >
</ div >
</ div >
{ displayedReplies ? . map (( reply , index ) => {
const replyMediaInfo = getCommentMediaInfo ( reply );
const shortParentCid = findShortParentCid ( reply . parentCid , selectedFeed );
2023-09-29 11:44:05 +02:00
const isOwner = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'owner' ;
const isAdmin = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'admin' ;
const isModerator = subplebbit ? . roles ? .[ reply . author . address ] ? . role === 'moderator' ;
2023-09-23 12:44:43 +02:00
let storedSigners = JSON . parse ( localStorage . getItem ( 'storedSigners' )) || {};
let signerAddress ;
if ( storedSigners [ selectedThreadCidRef ]) {
signerAddress = storedSigners [ selectedThreadCidRef ]. address ;
}
return (
< div key = { `mob-rc- ${ index } ` } className = 'reply-container' >
< div key = { `mob-pr- ${ index } ` } className = 'post-reply post-reply-mobile' >
< div key = { `mob-pi- ${ index } ` } className = 'post-info-mobile' >
< button
key = { `mob-pb- ${ index } ` }
className = 'post-menu-button-mobile'
ref = {( el ) => {
replyMenuRefsMobile . current [ reply . cid ] = el ;
}}
onClick = {( event ) => {
event . stopPropagation ();
const rect = replyMenuRefsMobile . current [ reply . cid ]. getBoundingClientRect ();
setMobileMenuPosition ({ top : rect . top + window . scrollY , left : rect . left });
setOpenMobileMenuCid (( prevCid ) => ( prevCid === reply . cid ? null : reply . cid ));
}}
style = {{ all : 'unset' , cursor : 'pointer' }}
>
...
</ button >
{ createPortal (
< PostMenuMobile
selectedStyle = { selectedStyle }
ref = {( el ) => {
postMenuMobileRef . current = el ;
}}
onClick = {( event ) => event . stopPropagation ()}
style = {{
position : 'absolute' ,
display : openMobileMenuCid === reply . cid ? 'block' : 'none' ,
top : mobileMenuPosition . top + 20 ,
left : mobileMenuPosition . left ,
}}
>
< ul className = { `post-menu-mobile-thread- ${ reply . cid } ` }>
< li
onClick = {() => {
handleMobileOptionClick ( reply . cid );
setNewSuccessMessage ( 'Link copied to clipboard' );
2023-10-24 17:54:22 +02:00
handleShareClick ( thread . subplebbitAddress , thread . cid );
2023-09-23 12:44:43 +02:00
}}
>
Share post
</ li >
< VerifiedAuthor commentCid = { reply . cid }>
{({ authorAddress }) => (
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? (
<>
< li onClick = {() => handleAuthorEditClick ( reply )}> Edit post </ li >
< li onClick = {() => handleAuthorDeleteClick ( reply )}> Delete post </ li >
</>
) : null }
2023-09-28 21:05:03 +02:00
{ canModerate ? (
2023-09-23 12:44:43 +02:00
<>
{ authorAddress === account ? . author . address || authorAddress === account ? . signer . address ? null : (
< li
onClick = {() => {
setModeratingCommentCid ( reply . cid );
setIsModerationOpen ( true );
handleMobileOptionClick ( reply . cid );
setDeletePost ( true );
}}
>
Delete post
</ li >
)}
< li
onClick = {() => {
setModeratingCommentCid ( reply . cid );
setIsModerationOpen ( true );
handleMobileOptionClick ( reply . cid );
}}
>
Mod tools
</ li >
</>
) : null }
</>
)}
</ VerifiedAuthor >
{ replyMediaInfo && ( replyMediaInfo . type === 'image' || ( replyMediaInfo . type === 'webpage' && replyMediaInfo . thumbnail )) ? (
<>
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://lens.google.com/uploadbyurl?url= ${ replyMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( reply . cid )}> Search image on Google </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://yandex.com/images/search?url= ${ replyMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( reply . cid )}> Search image on Yandex </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://saucenao.com/search.php?url= ${ replyMediaInfo . url } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleMobileOptionClick ( reply . cid )}> Search image on SauceNAO </ li >
</ a >
</>
) : null }
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebbitapp.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( reply . cid )}> View on plebbit </ li >
</ a >
< a
style = {{ color : 'inherit' , textDecoration : 'none' }}
href = { `https://plebones.eth.limo/#/p/ ${ selectedAddress } ` }
target = '_blank'
rel = 'noreferrer'
>
< li onClick = {() => handleOptionClick ( reply . cid )}> View on plebones </ li >
</ a >
</ ul >
</ PostMenuMobile >,
document . body ,
)}
< span key = { `mob-nb- ${ index } ` } className = 'name-block-mobile' >
{ reply . author . displayName ? (
reply . author . displayName . length > 20 ? (
< Fragment key = { `fragment13- ${ index } ` }>
< span
key = { `mob-n- ${ index } ` }
2023-09-29 11:44:05 +02:00
className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }
2023-09-23 12:44:43 +02:00
data - tooltip - id = 'tooltip'
data - tooltip - content = { reply . author . displayName }
data - tooltip - place = 'top'
>
{ reply . author . displayName . slice ( 0 , 20 ) + ' (...)' }
</ span >
</ Fragment >
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
{ reply . author . displayName }
</ span >
)
) : (
2023-09-29 11:44:05 +02:00
< span key = { `mob-n- ${ index } ` } className = { `name-mobile ${ isAdmin || isOwner ? 'capcode-admin' : isModerator ? 'capcode' : '' } ` }>
2023-09-23 12:44:43 +02:00
Anonymous
</ span >
)}
2023-09-29 11:44:05 +02:00
{ isOwner ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Owner
</ span >
) : isAdmin ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode-admin' >
& nbsp ; ## Board Admin
</ span >
) : isModerator ? (
< span onClick = {() => setIsAdminListOpen ( true )} className = 'name capcode' >
& nbsp ; ## Board Mod
</ span >
) : null }
2023-09-23 12:44:43 +02:00
& nbsp ;
< span
key = { `mob-pa- ${ index } ` }
className = 'poster-address-mobile address-mobile'
id = 'reply-button'
style = {{ cursor : 'pointer' }}
onClick = {() =>
2023-09-26 13:40:28 +02:00
handleAddressClick (< VerifiedAuthor commentCid = { reply . cid }>{({ shortAuthorAddress }) => shortAuthorAddress }</ VerifiedAuthor >)
2023-09-23 12:44:43 +02:00
}
>
( u /
2023-09-26 13:40:28 +02:00
< span key = { `mob-ha- ${ index } ` } className = 'highlight-address-mobile poster-address' >
{ reply . author ? . shortAddress ? (
< VerifiedAuthor commentCid = { reply . cid }>
{({ shortAuthorAddress , authorAddressChanged }) => (
<>
< span className = 'author-address-hidden' >{ reply . author . shortAddress }</ span >
< span className = { `author-address-visible ${ authorAddressChanged ? 'authorAddressChanged' : '' } ` }>
{ shortAuthorAddress }
</ span >
</>
)}
</ VerifiedAuthor >
) : (
< span key = { `mob-ha- ${ index } ` }>{ signerAddress ? . slice ( 8 , 20 )}</ span >
)}
</ span >
2023-09-23 12:44:43 +02:00
) & nbsp ;
</ span >
< br key = { `mob-br- ${ index } ` } />
</ span >
< span key = { `mob-dt- ${ index } ` } className = 'date-time-mobile post-number-mobile' >
< span
key = { `tooltip-fix-mob-reply- ${ index } ` }
data - tooltip - id = 'tooltip'
data - tooltip - content = { getFormattedTime ( reply . timestamp )}
data - tooltip - place = 'top'
>
{ getDate ( reply . timestamp )} & nbsp ;
</ span >
< span key = { `mob-pl1- ${ index } ` }> c / </ span >
{ reply . shortCid ? (
< Link
to = { `/p/ ${ selectedAddress } /c/ ${ thread . cid } ` }
id = 'reply-button'
key = { `mob-pl2- ${ index } ` }
onClick = {( e ) => {
if ( e . button === 2 ) return ;
e . preventDefault ();
setSelectedThreadCid ( thread . cid );
let text = document . getSelection (). toString ();
text = text ? `> ${ text } ` : text ;
setSelectedText ( text );
if ( isReplyOpen ) {
setReplyQuoteCid ( reply . shortCid );
setTriggerInsertion ( Date . now ());
} else {
setIsReplyOpen ( true );
setSelectedShortCid ( reply . shortCid );
setSelectedParentCid ( reply . cid );
}
}}
title = 'Reply to this post'
>
{ reply . shortCid }
</ Link >
) : (
< PendingLabel key = 'pending-mob' commentIndex = { reply . index } />
)}
</ span >
</ div >
{ reply . link ? (
< div key = { `mob-f- ${ index } ` } className = 'file-mobile' >
{ replyMediaInfo ? . url ? (
replyMediaInfo . type === 'iframe' ? (
< div key = { `enlarge-mob- ${ index } ` } className = 'img-container' >
< span
key = { `mob-fta- ${ index } ` }
className = 'file-thumb-mobile'
2023-10-20 15:59:05 +02:00
style = { isMobileReplyThumbnailClicked [ index ] ? {} : { paddingBottom : '25px' }}
2023-09-23 12:44:43 +02:00
>
{ isMobileReplyThumbnailClicked [ index ] && replyMediaInfo . url ? (
< div style = {{ width : '92vw' }}>
< Embed url = { replyMediaInfo . url } key = { `fti-mobile-reply- ${ index } ` } />
</ div >
) : replyMediaInfo . thumbnail ? (
< img
key = { `mob-fti- ${ index } ` }
src = { replyMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'mobileReply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< span
onClick = {() => {
handleThumbnailClick ( index , 'mobileReply' );
}}
>
Embed
</ span >
)}
{ replyMediaInfo ? . type === 'video' || replyMediaInfo ? . type === 'iframe' ? (
isMobileReplyThumbnailClicked [ index ] ? (
2023-10-20 15:59:05 +02:00
< div style = {{ textAlign : 'center' , paddingTop : '15px' , paddingBottom : '15px' }}>
2023-09-23 12:44:43 +02:00
< span
className = 'button-mobile'
style = {{ float : 'none' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'mobileReply' );
}}
>
Close
</ span >
</ div >
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
webpage
</ div >
)
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
webpage
</ div >
)}
</ span >
</ div >
) : replyMediaInfo . type === 'webpage' ? (
< div key = { `enlarge-mob-reply- ${ index } ` } className = 'img-container' >
< span
key = { `mob-ft ${ reply . cid } ` }
className = 'file-thumb-mobile'
style = {
isMobileReplyThumbnailClicked [ index ] || ! reply . thumbnailUrl
? {}
: { width : displayWidthMobile , height : displayHeightMobile }
}
>
{ reply . thumbnailUrl ? (
< img
key = { `mob-img- ${ index } ` }
src = { replyMediaInfo . thumbnail }
alt = 'thumbnail'
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'mobileReply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : null }
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ replyMediaInfo . type }
</ div >
</ span >
</ div >
) : replyMediaInfo . type === 'image' ? (
< div key = { `enlarge-mob-reply- ${ index } ` } className = 'img-container' >
2023-09-23 15:57:34 +02:00
< span key = { `mob-ft ${ reply . cid } ` } className = 'file-thumb-mobile' >
2023-09-23 12:44:43 +02:00
< img
key = { `mob-img- ${ index } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
onClick = {( e ) => {
handleImageClick ( e );
handleThumbnailClick ( index , 'mobileReply' );
}}
style = {{ cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ replyMediaInfo . type }
</ div >
</ span >
</ div >
) : replyMediaInfo . type === 'video' ? (
< span
key = { `mob-ft ${ reply . cid } ` }
className = 'file-thumb-mobile'
2023-10-20 15:59:05 +02:00
style = { isMobileReplyThumbnailClicked [ index ] ? {} : { paddingBottom : '25px' }}
2023-09-23 12:44:43 +02:00
>
{ isMobileReplyThumbnailClicked [ index ] ? (
< video
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
controls
style = {{ all : 'unset' , width : '100%' , height : '100%' , cursor : 'pointer' }}
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
) : (
< video
key = { `fti- ${ index } ` }
src = { replyMediaInfo . url }
alt = 'thumbnail'
onClick = {() => {
handleThumbnailClick ( index , 'mobileReply' );
}}
style = {{ cursor : 'pointer' }}
id = 'video-thumbnail-mobile'
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
)}
{ replyMediaInfo ? . type === 'video' || replyMediaInfo ? . type === 'iframe' ? (
isMobileReplyThumbnailClicked [ index ] ? (
2023-10-20 15:59:05 +02:00
< div style = {{ textAlign : 'center' , paddingTop : '15px' , paddingBottom : '15px' }}>
2023-09-23 12:44:43 +02:00
< span
className = 'button-mobile'
style = {{ float : 'none' , cursor : 'pointer' }}
onClick = {() => {
handleThumbnailClick ( index , 'mobileReply' );
}}
>
Close
</ span >
</ div >
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
video
</ div >
)
) : (
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
video
</ div >
)}
</ span >
) : replyMediaInfo . type === 'audio' ? (
< span
key = { `mob-ft ${ reply . cid } ` }
className = 'file-thumb-mobile'
style = { isMobileReplyThumbnailClicked [ index ] ? {} : { width : displayWidthMobile , height : displayHeightMobile }}
>
< audio
key = { `mob-img- ${ index } ` }
src = { replyMediaInfo . url }
alt = { replyMediaInfo . type }
onError = {( e ) => ( e . target . src = fallbackImgUrl )}
/>
< div key = { `mob-fi- ${ index } ` } className = 'file-info-mobile' >
{ replyMediaInfo . type }
</ div >
</ span >
) : null
) : null }
</ div >
) : null }
{ reply . content ? (
reply . content ? . length > 500 ? (
< Fragment key = { `fragment15- ${ index } ` }>
< blockquote key = { `mob-pm- ${ index } ` } className = 'post-message' >
< Link
to = {() => {}}
key = { `mob-r-pm- ${ index } ` }
className = 'quotelink'
ref = {( el ) => {
quoteRefsMobile . current [ reply . cid ] = el ;
}}
onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , shortParentCid , () => {
setOutOfViewCid ( reply . parentCid );
const rect = quoteRefsMobile . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
{ `c/ ${ shortParentCid } ` }
{ shortParentCid === thread . shortCid ? ' (OP)' : null }
</ Link >
< Post
key = { `post-mobile- ${ index } ` }
content = { reply . content ? . slice ( 0 , 500 )}
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( reply , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( reply , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
< span key = { `mob-ttl-s- ${ index } ` } className = 'ttl' >
{ ' ' }
(...)
< br key = { `mob-ttl-s-br1- ${ index } ` } />
< EditLabel key = { `edit-label-reply-mob- ${ index } ` } commentCid = { reply . cid } className = 'ttl' />
< StateLabel key = { `state-label-reply-mob- ${ index } ` } commentIndex = { reply . index } className = 'ttl ellipsis' />
< br key = { `mob-ttl-s-br2 ${ reply . cid } ` } />
Comment too long . & nbsp ;
< Link
key = { `mob-ttl-l- ${ index } ` }
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- ${ index } ` } className = 'post-message' >
< Link
to = {() => {}}
key = { `mob-r-pm- ${ index } ` }
className = 'quotelink'
ref = {( el ) => {
quoteRefsMobile . current [ reply . cid ] = el ;
}}
onClick = {( event ) => handleQuoteClick ( reply , shortParentCid , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , shortParentCid , () => {
setOutOfViewCid ( reply . parentCid );
const rect = quoteRefsMobile . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
>
{ `c/ ${ shortParentCid } ` }
{ shortParentCid === thread . shortCid ? ' (OP)' : null }
</ Link >
< Post
key = { `post-mobile- ${ index } ` }
content = { reply . content }
postQuoteRef = {( quoteShortParentCid , ref ) => {
postRefs . current [ quoteShortParentCid ] = ref ;
}}
postQuoteOnClick = {( quoteShortParentCid ) => {
handleQuoteClick ( reply , quoteShortParentCid , null );
}}
postQuoteOnOver = {( quoteShortParentCid ) => {
const quoteParentCid = cidTracker [ quoteShortParentCid ];
if ( outOfViewCid !== quoteParentCid ) {
handleQuoteHover ( reply , quoteShortParentCid , () => {
setOutOfViewCid ( quoteParentCid );
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
const rect = postRefs . current [ quoteShortParentCid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}
}}
postQuoteOnLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
/>
< EditLabel key = { `edit-label-reply-mob- ${ index } ` } commentCid = { reply . cid } className = 'ttl' />
< StateLabel key = { `state-label-reply-mob- ${ index } ` } commentIndex = { reply . index } className = 'ttl ellipsis' />
</ blockquote >
)
) : null }
{ reply . replyCount > 0 ? (
< div key = { `back-mob- ${ index } ` } className = 'backlink backlink-mobile' >
{ reply . replies ? . pages ? . topAll . comments
. sort (( a , b ) => a . timestamp - b . timestamp )
. map (( reply , index ) => (
< div
key = { `div-back ${ index } ` }
style = {{ display : 'inline-block' }}
ref = {( el ) => {
backlinkRefsMobile . current [ reply . cid ] = el ;
}}
>
< Link
key = { `ql- ${ index } ` }
to = {() => {}}
onClick = {( event ) => handleQuoteClick ( reply , reply . shortCid , event )}
onMouseOver = {( event ) => {
event . stopPropagation ();
handleQuoteHover ( reply , reply . shortCid , () => {
setOutOfViewCid ( reply . cid );
const rect = backlinkRefsMobile . current [ reply . cid ]. getBoundingClientRect ();
const distanceToRight = window . innerWidth - rect . right ;
const distanceToTop = rect . top ;
const distanceToBottom = window . innerHeight - rect . bottom ;
let top ;
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToTop < postOnHoverHeight / 2 ) {
top = window . scrollY - 5 ;
} else if ( distanceToBottom < postOnHoverHeight / 2 ) {
top = window . scrollY - postOnHoverHeight + window . innerHeight - 10 ;
} else {
top = rect . top + window . scrollY - postOnHoverHeight / 2 ;
}
2023-09-21 14:38:12 +02:00
2023-09-23 12:44:43 +02:00
if ( distanceToRight < 200 ) {
setOutOfViewPosition ({
top ,
right : window . innerWidth - rect . left - 10 ,
maxWidth : rect . left - 5 ,
});
} else {
setOutOfViewPosition ({
top ,
left : rect . left + rect . width + 5 ,
maxWidth : window . innerWidth - rect . left - rect . width - 5 ,
});
}
});
}}
onMouseLeave = {() => {
removeHighlight ();
setOutOfViewCid ( null );
}}
className = 'quote-link'
>
c / { reply . shortCid }
</ Link >
& nbsp ;
</ div >
))}
</ div >
) : null }
</ div >
</ div >
);
})}
</ div >
</ Fragment >
);
}}
ref = { virtuosoRef }
restoreStateFrom = { lastVirtuosoState }
initialScrollTop = { lastVirtuosoState ? . scrollTop }
endReached = { tryLoadMore }
useWindowScroll = { true }
components = {{
Footer : () => {
return isFeedCached ? null : < PostLoader />;
},
}}
/>
</>
) : (
< PostLoader />
)}
</ div >
{ outOfViewCid &&
outOfViewPosition &&
createPortal (
< div
ref = { postOnHoverRef }
style = {{
display : 'block' ,
position : 'absolute' ,
top : outOfViewPosition . top ,
right : outOfViewPosition . right ,
left : outOfViewPosition . left ,
maxWidth : outOfViewPosition . maxWidth ,
}}
>
< PostOnHover cid = { outOfViewCid } feed = { feed } />
</ div >,
document . body ,
)}
</ BoardForm >
< 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' ,
}}
>
<>
< span className = 'boardList' >
2023-10-05 16:42:47 +02:00
[< Link to = {{ pathname : `/p/all` , state : { scrollToTop : true } }}> All </ Link >
2023-09-23 12:44:43 +02:00
/
2023-10-05 16:42:47 +02:00
< Link to = {{ pathname : `/p/subscriptions` , state : { scrollToTop : true } }}> Subscriptions </ Link >
2023-09-23 12:44:43 +02:00
] & nbsp ;
</ span >
{ defaultSubplebbits . map (( subplebbit , index ) => (
< span className = 'boardList' key = { `span- ${ subplebbit . address } ` }>
{ index === 0 ? null : '\u00a0' }
< Link to = { `/p/ ${ subplebbit . address } ` } key = { `a- ${ subplebbit . address } ` } onClick = {() => handleClickTitle ( subplebbit . title , subplebbit . address )}>
{ subplebbit . title ? subplebbit . title : subplebbit . address }
</ Link >
{ index !== defaultSubplebbits . length - 1 ? ' /' : null }
</ span >
))}
< span className = 'nav' >
[
< span
id = 'button-span'
style = {{ cursor : 'pointer' }}
onClick = {() => {
window . electron && window . electron . isElectron
? setIsCreateBoardOpen ( true )
: alert (
2023-10-01 18:28:00 +02:00
'HOW TO CREATE A BOARD (currently via CLI only):\n\n(1) have a computer you can use as server, because users will connect to your board peer-to-peer;\n\n(2) go to https://github.com/plebbit/plebbit-cli#install and follow the instructions to run your daemon, create a subplebbit (board) and edit its settings;\n\n(3) users can always connect to your board via its address, but it can be added below by submitting a pull request here: https://github.com/plebbit/temporary-default-subplebbits. This will be automated with a plebbit DAO using the plebbit token.' ,
2023-09-23 12:44:43 +02:00
);
}}
>
Create Board
</ span >
] [
< Link to = { `/p/ ${ selectedAddress } /settings` } onClick = {() => setIsSettingsOpen ( true )}>
Settings
</ Link >
] [
< Link to = '/' onClick = {() => handleStyleChange ({ target : { value : 'Yotsuba' } })}>
Home
</ Link >
]
</ span >
</>
</ NavBar >
< div id = 'version' >
plebchan v { version }
{ commitRef }. GPL - 2.0
</ div >
< div
className = 'footer-links'
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 >
& nbsp ; • & nbsp ;
< a style = {{ textDecoration : 'underline' }} href = 'https://github.com/plebbit/plebchan/releases/latest' target = '_blank' rel = 'noopener noreferrer' >
App
</ a >
& nbsp ; • & nbsp ;
< a style = {{ textDecoration : 'underline' }} href = 'https://twitter.com/plebchan_eth' target = '_blank' rel = 'noopener noreferrer' >
Twitter
</ a >
& nbsp ; • & nbsp ;
< a style = {{ textDecoration : 'underline' }} href = 'https://t.me/plebbit' target = '_blank' rel = 'noopener noreferrer' >
Telegram
</ a >
</ div >
</ Footer >
</ Container >
</>
);
2023-09-21 14:38:12 +02:00
};
export default Board ;