Merge pull request #148 from plebbit/development

Development
This commit is contained in:
plebeius.eth
2023-07-09 16:05:37 +02:00
committed by GitHub
19 changed files with 2879 additions and 1995 deletions
+23 -19
View File
@@ -117,15 +117,18 @@ const createMainWindow = () => {
// only open valid https urls to prevent remote execution
// will throw if url isn't valid
const validatedUrl = new URL(originalUrl);
if (validatedUrl.protocol !== 'https:') {
throw Error(`can't open url '${originalUrl}' not https`);
let serializedUrl = '';
// make an exception for ipfs stats
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
serializedUrl = validatedUrl.toString();
} else if (validatedUrl.protocol === 'https:') {
// open serialized url to prevent remote execution
serializedUrl = validatedUrl.toString();
} else {
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`);
}
// do not open http protocol, not private
// do not open any other protocol, will lead to remote execution
// open serialized url to prevent remote execution
const serializedUrl = validatedUrl.toString();
shell.openExternal(serializedUrl);
} catch (e) {
console.warn(e);
@@ -136,7 +139,7 @@ const createMainWindow = () => {
// open links (with target="_blank") in external browser
// do not open links in plebchan or will lead to remote execution
mainWindow.webContents.setWindowOpenHandler(({url}) => {
const originalUrl = url
const originalUrl = url;
try {
// do not let the user open any url with shell.openExternal
// or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/
@@ -144,25 +147,26 @@ const createMainWindow = () => {
// only open valid https urls to prevent remote execution
// will throw if url isn't valid
const validatedUrl = new URL(originalUrl);
if (validatedUrl.protocol !== 'https:') {
throw Error(`can't open url '${originalUrl}' not https`);
let serializedUrl = '';
// make an exception for ipfs stats
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
serializedUrl = validatedUrl.toString();
} else if (validatedUrl.protocol === 'https:') {
// open serialized url to prevent remote execution
serializedUrl = validatedUrl.toString();
} else {
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`);
}
// do not open http protocol, not private
// do not open any other protocol, will lead to remote execution
// open serialized url to prevent remote execution
const serializedUrl = validatedUrl.toString();
shell.openExternal(serializedUrl);
setImmediate(() => {
shell.openExternal(serializedUrl)
})
} catch (e) {
console.warn(e);
}
return {action: 'deny'}
return {action: 'deny'};
})
// deny permissions like location, notifications, etc https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
// deny all permissions
+4 -1
View File
@@ -38,6 +38,9 @@ const defaultPlebbitOptions = {
ipfsGatewayUrls: ['http://localhost:11028'] || undefined,
};
// expose a flag to indicate that we are running in electron
contextBridge.exposeInMainWorld('electron', { isElectron: true });
// expose plebbit-js native functions into electron's renderer
contextBridge.exposeInMainWorld('plebbitJsNativeFunctions', require('@plebbit/plebbit-js').nativeFunctions.node)
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions)
@@ -47,4 +50,4 @@ try {
if (isDev) localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*';
} catch (e) {
console.log(e);
}
}
+10
View File
@@ -0,0 +1,10 @@
import {useComment, useAuthorAddress} from '@plebbit/plebbit-react-hooks';
function VerifiedAuthor({ commentCid, children }) {
const comment = useComment({commentCid});
const {authorAddress, shortAuthorAddress} = useAuthorAddress({comment});
return children({ authorAddress, shortAuthorAddress });
}
export default VerifiedAuthor;
+1
View File
@@ -95,6 +95,7 @@ const CaptchaModal = () => {
const handleCloseModal = () => {
setImageSources([]);
setChallengesArray([]);
setCurrentChallengeIndex(0);
setIsCaptchaOpen(false);
};
+14 -3
View File
@@ -50,7 +50,8 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
if (challengeVerification.challengeSuccess === true) {
setNewSuccessMessage('Challenge Success');
} else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -63,7 +64,7 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -113,9 +114,19 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
useEffect(() => {
if (selectedAddress) {
setPublishCommentEditOptions((prevOptions) => ({
...prevOptions,
subplebbitAddress: selectedAddress,
}));
}
}, [selectedAddress]);
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
+10 -8
View File
@@ -99,10 +99,10 @@ const ReplyModal = ({ isOpen, closeModal }) => {
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
console.log('challenge success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
return;
} else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -115,7 +115,7 @@ const ReplyModal = ({ isOpen, closeModal }) => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -136,7 +136,7 @@ const ReplyModal = ({ isOpen, closeModal }) => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -229,8 +229,10 @@ const ReplyModal = ({ isOpen, closeModal }) => {
}, [selectedParentCid, anonymousMode, account]);
useEffect(() => {
updateSigner();
}, [updateSigner]);
if (anonymousMode) {
updateSigner();
}
}, [updateSigner, anonymousMode]);
useEffect(() => {
+39 -17
View File
@@ -36,6 +36,17 @@ const SettingsModal = ({ isOpen, closeModal }) => {
const importRef = useRef();
const nameRef = useRef();
const defaultGatewayUrls = [
'https://ipfs.io',
'https://ipfsgateway.xyz',
'https://cloudflare-ipfs.com',
'https://plebpubsub.live'
];
const defaultPubsubHttpClientsOptions = [
'https://pubsubprovider.xyz/api/v0',
'https://plebpubsub.live/api/v0'
];
const isValidURL = (url) => {
try {
new URL(url);
@@ -46,14 +57,26 @@ const SettingsModal = ({ isOpen, closeModal }) => {
};
const handleSavePlebbitOptions = async () => {
const gatewayUrls = gatewayRef.current.value.split('\n').filter(url => url.trim());
const ipfsClientsOptions = ipfsRef.current.value.split('\n').filter(url => url.trim()) || undefined;
const pubsubClientsOptions = pubsubRef.current.value.split('\n').filter(url => url.trim());
let gatewayUrls = gatewayRef.current.value.split('\n').filter(url => url.trim());
let ipfsClientsOptions = ipfsRef.current.value.split('\n').filter(url => url.trim());
let pubsubClientsOptions = pubsubRef.current.value.split('\n').filter(url => url.trim());
if (!gatewayUrls.length) {
gatewayUrls = defaultGatewayUrls;
}
if (!ipfsClientsOptions.length) {
ipfsClientsOptions = undefined;
}
if (!pubsubClientsOptions.length) {
pubsubClientsOptions = defaultPubsubHttpClientsOptions;
}
const invalidUrls = [
...gatewayUrls,
...gatewayUrls || defaultGatewayUrls,
...(ipfsClientsOptions || []),
...pubsubClientsOptions,
...pubsubClientsOptions || defaultPubsubHttpClientsOptions,
].filter((url) => !isValidURL(url));
if (invalidUrls.length > 0) {
@@ -72,7 +95,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
localStorage.setItem("successToast", "Settings Saved");
window.location.reload();
} catch (error) {
setNewErrorMessage(error.message);
setNewErrorMessage(error.message); console.log(error);
}
};
@@ -81,13 +104,6 @@ const SettingsModal = ({ isOpen, closeModal }) => {
setNewErrorMessage(null);
setNewSuccessMessage(null);
const defaultGatewayUrls = [
'https://ipfs.io',
'https://ipfsgateway.xyz',
'https://cloudflare-ipfs.com'
];
const defaultPubsubHttpClientsOptions = ['https://pubsubprovider.xyz/api/v0'];
gatewayRef.current.value = defaultGatewayUrls.join('\n');
ipfsRef.current.value = "";
pubsubRef.current.value = defaultPubsubHttpClientsOptions.join('\n');
@@ -105,7 +121,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
localStorage.setItem("successToast", "Settings Reset");
window.location.reload();
} catch (error) {
setNewErrorMessage(error.message);
setNewErrorMessage(error.message); console.log(error);
}
};
@@ -169,7 +185,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
setNewSuccessMessage("Account Imported");
} catch (error) {
setNewErrorMessage(error.message);
setNewErrorMessage(error.message); console.log(error);
}
};
@@ -190,7 +206,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
}});
setNewSuccessMessage("Account Name Saved");
} catch (error) {
setNewErrorMessage(error.message);
setNewErrorMessage(error.message); console.log(error);
}
};
@@ -207,7 +223,13 @@ const SettingsModal = ({ isOpen, closeModal }) => {
<div className="panel">
<div className="panel-header">
<span id="version">
v{version}
v{version} -&nbsp;
{window.electron && window.electron.isElectron ? (
<Link className="all-button" id="node-stats" to="http://localhost:5001/webui/" target="_blank" rel="noreferrer">
full node
</Link>
) : (<span>web</span>)
}
</span>
Settings
<Link to="" onClick={handleCloseModal}>
@@ -417,4 +417,8 @@ export const StyledModal = styled(Modal)`
return '';
}
}}
#node-stats {
text-decoration: none;
}
`;
+12 -2
View File
@@ -267,7 +267,7 @@ export const Header = styled.div`
clear: both;
.banner {
border: 1px solid #800;
border: 1px solid #34345c;
margin: 5px auto;
width: 300px;
height: 100px;
@@ -2016,6 +2016,16 @@ export const BoardForm = styled.div`
}
video {
width: 100%;
height: auto;
}
iframe {
width: 100%;
height: auto;
}
#video-thumbnail-mobile {
width: 100px;
}
@@ -3804,7 +3814,7 @@ export const BoardForm = styled.div`
color: #707070;
font-weight: 700;
background-color: rgb(27,28,30);
border: 1px solid #282A2E;
border: 1px solid #2D2F33;
background-image: url(assets/buttonfade-dark.png);
background-repeat: repeat-x;
text-decoration: none;
@@ -1193,7 +1193,13 @@ export const BoardForm = styled.div`
}
video {
width: 100px;
width: 100%;
height: auto;
}
iframe {
width: 100%;
height: auto;
}
img {
@@ -2932,7 +2938,7 @@ export const BoardForm = styled.div`
color: #707070;
font-weight: 700;
background-color: rgb(27,28,30);
border: 1px solid #282A2E;
border: 1px solid #2D2F33;
background-image: url(assets/buttonfade-dark.png);
background-repeat: repeat-x;
text-decoration: none;
+300 -158
View File
@@ -1,4 +1,4 @@
import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useState, useRef } from 'react';
import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { confirmAlert } from 'react-confirm-alert';
import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async';
@@ -21,6 +21,7 @@ import Post from '../Post';
import PostLoader from '../PostLoader';
import PostOnHover from '../PostOnHover';
import StateLabel from '../StateLabel';
import VerifiedAuthor from '../VerifiedAuthor';
import ReplyModal from '../modals/ReplyModal';
import SettingsModal from '../modals/SettingsModal';
import findShortParentCid from '../../utils/findShortParentCid';
@@ -102,6 +103,8 @@ const All = () => {
const [moderatorPermissions, setModeratorPermissions] = useState({});
const [executeAnonMode, setExecuteAnonMode] = useState(false);
const [cidTracker, setCidTracker] = useState({});
const [isThumbnailClicked, setIsThumbnailClicked] = useState({});
const [isMobileThumbnailClicked, setIsMobileThumbnailClicked] = useState({});
const setSelectedThreadCid = (cid) => {
selectedThreadCidRef.current = cid;
@@ -117,6 +120,20 @@ const All = () => {
const stateString = useFeedStateString(subplebbits);
const handleThumbnailClick = (index, isMobile=false) => {
if (isMobile) {
setIsMobileThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
} else {
setIsThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
}
};
useEffect(() => {
let permissions = {};
@@ -163,6 +180,14 @@ const All = () => {
}, [openMenuCid, handleOutsideClick]);
useEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
const errorString = useMemo(() => {
for (const subplebbit of subplebbits) {
if (subplebbit?.updatingState !== 'failed') {
@@ -279,7 +304,8 @@ const All = () => {
setNewSuccessMessage('Challenge Success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -290,7 +316,7 @@ const All = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -347,7 +373,7 @@ const All = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -355,8 +381,8 @@ const All = () => {
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
const handleAuthorDeleteClick = (cid) => {
handleOptionClick(cid);
const handleAuthorDeleteClick = (comment) => {
handleOptionClick(comment.cid);
confirmAlert({
customUI: ({ onClose }) => {
@@ -370,9 +396,10 @@ const All = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -457,14 +484,6 @@ const All = () => {
};
useLayoutEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
return (
<>
<Helmet>
@@ -583,7 +602,7 @@ const All = () => {
<Link to={`/p/all/catalog`}>Catalog</Link>
]
</div>
{feed.length > 0 ? (
{feed ? (
null
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
@@ -599,7 +618,7 @@ const All = () => {
<Tooltip id="tooltip" className="tooltip" />
<BoardForm selectedStyle={selectedStyle}>
<div className="board">
{feed.length > 0 ? (
{feed ? (
<Virtuoso
increaseViewportBy={2000}
data={selectedFeed}
@@ -616,56 +635,110 @@ const All = () => {
<hr key={`hr-${index}`} />
<div key={`pi-${index}`} className="post-info">
{commentMediaInfo?.url ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<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>&nbsp;({commentMediaInfo?.type})
</div>
{commentMediaInfo?.type === "webpage" ? (
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{thread.thumbnailUrl ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<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>&nbsp;({commentMediaInfo?.type === "iframe" ? "video" : commentMediaInfo?.type})
{isThumbnailClicked[index] ? (
<span>
-[
<span className='reply-link'
style={{textDecoration: 'underline', cursor: 'pointer'}}
onClick={() => {handleThumbnailClick(index)}}>Close</span>
]
</span>
) : null}
</div>
{commentMediaInfo?.type === 'iframe' && (
<div key={`enlarge-${index}`}
className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{(isThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<iframe
className='enlarged'
key={`fti-${index}`}
src={commentMediaInfo.embedUrl}
width={commentMediaInfo.thumbnail ? "560" : "250"}
height="315"
style={{border: "none"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
) : (
<img
key={`fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
</span>
</div>
)}
{commentMediaInfo?.type === "webpage" ? (
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{thread.thumbnailUrl ? (
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
) : null}
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<div key={`enlarge-${index}`} className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{isThumbnailClicked[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)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
</span>
</div>
) : null}
{commentMediaInfo?.type === "image" ? (
<div key={`enlarge-${index}`} className="img-container">
) : null}
{commentMediaInfo?.type === "audio" ? (
<span key={`fta-${index}`} className="file-thumb">
<img key={`fti-${index}`}
<audio controls key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<span key={`fta-${index}`} className="file-thumb">
<video controls width="" key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
) : 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}
) : null}
</div>
) : null}
<span key={`nb-${index}`} className="name-block">
{thread.title ? (
thread.title.length > 75 ?
@@ -699,9 +772,9 @@ const All = () => {
(u/
<span key={`pa-${index}`} className="poster-address address-desktop"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(thread.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
{thread.author.shortAddress}
{<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>)
&nbsp;
<span key={`dt-${index}`} className="date-time" data-utc="data">{getDate(thread.timestamp)}</span>
@@ -776,38 +849,44 @@ const All = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(thread.cid)}>Hide thread</li>
{thread.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={thread.cid}>{({ authorAddress }) => (
<>
<li onClick={() => {handleAuthorEditClick(thread); setSelectedAddress(thread.subplebbitAddress);}}>Edit post</li>
<li onClick={() => {handleAuthorDeleteClick(thread.cid); setSelectedAddress(thread.subplebbitAddress);}}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
{isModerator ? (
<>
{thread.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
)}</VerifiedAuthor>
{(commentMediaInfo && (
commentMediaInfo.type === 'image' ||
(commentMediaInfo.type === 'webpage' &&
@@ -969,7 +1048,7 @@ const All = () => {
&nbsp;
<span key={`pa-${index}`} className="poster-address address-desktop"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(reply.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={reply.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
{reply.author?.shortAddress ?
@@ -1064,38 +1143,44 @@ const All = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(reply.cid)}>Hide post</li>
{reply.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={reply.cid}>{({ authorAddress }) => (
<>
<li onClick={() => {handleAuthorEditClick(reply); setSelectedAddress(thread.subplebbitAddress);}}>Edit post</li>
<li onClick={() => {handleAuthorDeleteClick(reply.cid); setSelectedAddress(thread.subplebbitAddress);}}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{reply.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(reply)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(reply)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
}}>
Delete post
Mod tools
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
)}</VerifiedAuthor>
{(replyMediaInfo && (
replyMediaInfo.type === 'image' ||
(replyMediaInfo.type === 'webpage' &&
@@ -1487,11 +1572,11 @@ const All = () => {
&nbsp;
<span key={`mob-pa-${index}`} className="poster-address-mobile address-mobile"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(thread.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
<span key={`mob-ha-${index}`} className="highlight-address-mobile">
{thread.author.shortAddress}
{<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>
)&nbsp;
</span>
@@ -1556,51 +1641,108 @@ const All = () => {
</span>
</div>
{thread.link ? (
<div key={`mob-f-${index}`} className="file-mobile">
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
{thread.thumbnailUrl ? (
<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">
{(isMobileThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<div style={{width: "92vw"}}>
<iframe
key={`mob-fti-${index}`}
src={commentMediaInfo.embedUrl}
style={{border: "none", height: "250px"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
</div>
) : (
<img
key={`mob-fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index, true)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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>
</div>
)}
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
{thread.thumbnailUrl ? (
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
onClick={handleImageClick}
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">
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<img key={`mob-img-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<video key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
style={{ pointerEvents: "none" }}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${index}`} className="file-info-mobile">{commentMediaInfo?.type}</div>
</span>
) : commentMediaInfo.type === "audio" ? (
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
<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}
<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">
{isMobileThumbnailClicked[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, true)}}
style={{cursor: "pointer"}}
id="video-thumbnail-mobile"
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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">
<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}`}>
@@ -1670,7 +1812,7 @@ const All = () => {
&nbsp;
<span key={`mob-pa-${index}`} className="poster-address-mobile address-mobile"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(reply.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={reply.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
{reply.author?.shortAddress ?
@@ -2062,7 +2204,7 @@ const All = () => {
}}
endReached={tryLoadMore}
useWindowScroll={true}
components={{ Footer: hasMore ? () => <PostLoader /> : null }}
components={{ Footer: hasMore && feed.length > 0 ? () => <PostLoader /> : null }}
/>
) : (
<PostLoader />
+45 -36
View File
@@ -13,6 +13,7 @@ import { TopBar, Footer, AuthorDeleteAlert } from '../styled/views/Thread.styled
import CatalogLoader from '../CatalogLoader';
import EditModal from '../modals/EditModal';
import ImageBanner from '../ImageBanner';
import VerifiedAuthor from '../VerifiedAuthor';
import ModerationModal from '../modals/ModerationModal';
import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal';
@@ -149,7 +150,8 @@ const AllCatalog = () => {
setNewSuccessMessage('Challenge Success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -160,7 +162,7 @@ const AllCatalog = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -209,7 +211,7 @@ const AllCatalog = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -232,9 +234,10 @@ const AllCatalog = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(comment.cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -440,7 +443,7 @@ const AllCatalog = () => {
<Link to={`/p/all`}>Return</Link>
</span>
</div>
{feed.length > 0 ? (
{feed ? (
null
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
@@ -451,7 +454,7 @@ const AllCatalog = () => {
</TopBar>
<Tooltip id="tooltip" className="tooltip" />
<Threads selectedStyle={selectedStyle}>
{feed.length > 0 ? (
{feed ? (
<InfiniteScroll
pageStart={0}
loadMore={tryLoadMore}
@@ -557,38 +560,44 @@ const AllCatalog = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(thread.cid)}>Hide thread</li>
{thread.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={thread.cid}>{({ authorAddress }) => (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
{isModerator ? (
<>
{thread.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
)}</VerifiedAuthor>
{(commentMediaInfo && (
commentMediaInfo.type === 'image' ||
(commentMediaInfo.type === 'webpage' &&
File diff suppressed because it is too large Load Diff
+45 -36
View File
@@ -13,6 +13,7 @@ import { TopBar, Footer, AuthorDeleteAlert } from '../styled/views/Thread.styled
import CatalogLoader from '../CatalogLoader';
import EditModal from '../modals/EditModal';
import ImageBanner from '../ImageBanner';
import VerifiedAuthor from '../VerifiedAuthor';
import ModerationModal from '../modals/ModerationModal';
import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal';
@@ -195,7 +196,8 @@ const Catalog = () => {
}
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -208,7 +210,7 @@ const Catalog = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -229,7 +231,7 @@ const Catalog = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -381,7 +383,7 @@ const Catalog = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -404,9 +406,10 @@ const Catalog = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(comment.cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -485,7 +488,7 @@ const Catalog = () => {
await unsubscribe(selectedAddress);
}
} catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
};
@@ -663,7 +666,7 @@ const Catalog = () => {
<Link to={`/p/${selectedAddress}`} onClick={()=> {window.scrollTo(0, 0)}}>Return</Link>
]
</div>
{feed.length > 0 ? (
{feed ? (
<>
<span className="subscribe-button-desktop">
[
@@ -697,7 +700,7 @@ const Catalog = () => {
{subplebbit?.state === "failed" ? (
null
) : (
feed.length > 0 ? (
feed ? (
<InfiniteScroll
pageStart={0}
loadMore={tryLoadMore}
@@ -1005,36 +1008,42 @@ const Catalog = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(thread.cid)}>Hide thread</li>
{thread.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={thread.cid}>{({ authorAddress }) => (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{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}
</>
) : null}
{isModerator ? (
<>
{thread.author.shortAddress === account?.author.shortAddress ? (
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' &&
+228 -65
View File
@@ -12,6 +12,7 @@ import PostLoader from '../PostLoader';
import SettingsModal from '../modals/SettingsModal';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getDate from '../../utils/getDate';
import handleImageClick from '../../utils/handleImageClick';
import handleQuoteClick from '../../utils/handleQuoteClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError';
@@ -35,6 +36,23 @@ const Pending = () => {
const account = useAccount();
const comment = useAccountComment({commentIndex: index});
const [, setNewErrorMessage] = useError();
const [isThumbnailClicked, setIsThumbnailClicked] = useState({});
const [isMobileThumbnailClicked, setIsMobileThumbnailClicked] = useState({});
const handleThumbnailClick = (index, isMobile=false) => {
if (isMobile) {
setIsMobileThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
} else {
setIsThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
}
};
useEffect(() => {
setSelectedAddress(comment?.subplebbitAddress);
@@ -138,28 +156,29 @@ const Pending = () => {
]
</span>
<div id="board-nav-mobile" style={{ top: visible ? 0 : '-23px' }}>
<div className="board-select">
<strong>Board</strong>
&nbsp;
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
<option value="all">All</option>
<option value="subscriptions">Subscriptions</option>
{defaultSubplebbits.map(subplebbit => (
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</option>
))}
</select>
<span style={{cursor: 'pointer'}} onClick={
<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.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={
() => alert(
'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n'
)
}>Create Board</span>
</div>
<div className="page-jump">
<Link to={`/profile/c/${index}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
&nbsp;
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
)}>Home</Link>
}>Create Board</span>
</div>
<div className="page-jump">
<Link to={`/p/${selectedAddress}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
&nbsp;
<Link to="/" onClick={() => {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>Home</Link>
</div>
</div>
</div>
<div id="separator-mobile">&nbsp;</div>
@@ -228,35 +247,107 @@ const Pending = () => {
<div className="op-container">
<div className="post op">
<div className="post-info">
{commentMediaInfo?.url ? (
<div key={`f-${"pending"}`} className="file" style={{marginBottom: "5px"}}>
<div key={`ft-${"pending"}`} className="file-text">
{commentMediaInfo?.url ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<div key={`ft-${index}`} className="file-text">
Link:&nbsp;
<a key={`fa-${"pending"}`} href={commentMediaInfo.url}
target="_blank" rel="noreferrer">{
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
rel="noopener noreferrer">{
commentMediaInfo?.url.length > 30 ?
commentMediaInfo?.url.slice(0, 30) + "(...)" :
commentMediaInfo?.url
}</a>&nbsp;({commentMediaInfo?.type})
}</a>&nbsp;({commentMediaInfo?.type === "iframe" ? "video" : commentMediaInfo?.type})
{isThumbnailClicked[index] ? (
<span>
-[
<span className='reply-link'
style={{textDecoration: 'underline', cursor: 'pointer'}}
onClick={() => {handleThumbnailClick(index)}}>Close</span>
]
</span>
) : null}
</div>
{commentMediaInfo?.type === 'iframe' && (
<div key={`enlarge-${index}`}
className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{(isThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<iframe
className='enlarged'
key={`fti-${index}`}
src={commentMediaInfo.embedUrl}
width={commentMediaInfo.thumbnail ? "560" : "250"}
height="315"
style={{border: "none"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
) : (
<img
key={`fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
</span>
</div>
)}
{commentMediaInfo?.type === "webpage" ? (
<span key={`fta-${"pending"}`} className="file-thumb">
<img key={`fti-${"pending"}`} src={comment.thumbnailUrl} alt="thumbnail" onError={(e) => e.target.src = fallbackImgUrl} />
</span>
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{comment.thumbnailUrl ? (
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
) : null}
</span>
</div>
) : null}
{commentMediaInfo?.type === "image" ? (
<span key={`fta-${"pending"}`} className="file-thumb">
<img key={`fti-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
</span>
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
<img key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<span key={`fta-${"pending"}`} className="file-thumb">
<video controls key={`fti-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`enlarge-${index}`} className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{isThumbnailClicked[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)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
</span>
</div>
) : null}
{commentMediaInfo?.type === "audio" ? (
<span key={`fta-${"pending"}`} className="file-thumb">
<audio controls key={`fti-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
<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>
@@ -360,37 +451,109 @@ const Pending = () => {
<span key="pending-mob" style={{color: 'red', fontWeight: '700'}}>Pending</span>
</span>
</div>
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`mob-f-${"pending"}`} className="file-mobile">
<span key={`mob-ft${"pending"}`} className="file-thumb-mobile">
<img key={`mob-img-${"pending"}`} src={comment.thumbnailUrl} alt="thumbnail" onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${"pending"}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
{comment.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">
{(isMobileThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<div style={{width: "92vw"}}>
<iframe
key={`mob-fti-${index}`}
src={commentMediaInfo.embedUrl}
style={{border: "none", height: "250px"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
</div>
) : (
<img
key={`mob-fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index, true)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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>
</div>
)}
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
{comment.thumbnailUrl ? (
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
onClick={handleImageClick}
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${comment.cid}`} className="file-thumb-mobile">
<img key={`mob-img-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
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${comment.cid}`} className="file-thumb-mobile">
{isMobileThumbnailClicked[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, true)}}
style={{cursor: "pointer"}}
id="video-thumbnail-mobile"
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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${comment.cid}`} className="file-thumb-mobile">
<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>
) : commentMediaInfo.type === "image" ? (
<div key={`mob-f-${"pending"}`} className="file-mobile">
<span key={`mob-ft${"pending"}`} className="file-thumb-mobile">
<img key={`mob-img-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${"pending"}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
) : commentMediaInfo.type === "video" ? (
<div key={`mob-f-${"pending"}`} className="file-mobile">
<span key={`mob-ft${"pending"}`} className="file-thumb-mobile">
<video controls key={`mob-img-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${"pending"}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
) : commentMediaInfo.type === "audio" ? (
<div key={`mob-f-${"pending"}`} className="file-mobile">
<span key={`mob-ft${"pending"}`} className="file-thumb-mobile">
<audio controls key={`mob-img-${"pending"}`} src={commentMediaInfo.url} alt={commentMediaInfo.type} onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${"pending"}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
) : null
) : null}
) : null}
<blockquote key={`mob-bq-${"pending"}`} className="post-message-mobile">
{comment.content ? (
<>
+306 -163
View File
@@ -1,4 +1,4 @@
import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useState, useRef } from 'react';
import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { confirmAlert } from 'react-confirm-alert';
import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async';
@@ -22,6 +22,7 @@ import Post from '../Post';
import PostLoader from '../PostLoader';
import PostOnHover from '../PostOnHover';
import StateLabel from '../StateLabel';
import VerifiedAuthor from '../VerifiedAuthor';
import ReplyModal from '../modals/ReplyModal';
import SettingsModal from '../modals/SettingsModal';
import findShortParentCid from '../../utils/findShortParentCid';
@@ -102,6 +103,8 @@ const Subscriptions = () => {
const [moderatorPermissions, setModeratorPermissions] = useState({});
const [executeAnonMode, setExecuteAnonMode] = useState(false);
const [cidTracker, setCidTracker] = useState({});
const [isThumbnailClicked, setIsThumbnailClicked] = useState({});
const [isMobileThumbnailClicked, setIsMobileThumbnailClicked] = useState({});
const setSelectedThreadCid = (cid) => {
selectedThreadCidRef.current = cid;
@@ -116,6 +119,21 @@ const Subscriptions = () => {
const stateString = useFeedStateString(subplebbits);
const handleThumbnailClick = (index, isMobile=false) => {
if (isMobile) {
setIsMobileThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
} else {
setIsThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
}
};
useEffect(() => {
let permissions = {};
@@ -161,6 +179,14 @@ const Subscriptions = () => {
};
}, [openMenuCid, handleOutsideClick]);
useEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
const errorString = useMemo(() => {
for (const subplebbit of subplebbits) {
@@ -280,7 +306,8 @@ const Subscriptions = () => {
setNewSuccessMessage('Challenge Success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -291,7 +318,7 @@ const Subscriptions = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -341,7 +368,7 @@ const Subscriptions = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -356,8 +383,8 @@ const Subscriptions = () => {
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
const handleAuthorDeleteClick = (cid) => {
handleOptionClick(cid);
const handleAuthorDeleteClick = (comment) => {
handleOptionClick(comment.cid);
confirmAlert({
customUI: ({ onClose }) => {
@@ -371,9 +398,10 @@ const Subscriptions = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -398,9 +426,9 @@ const Subscriptions = () => {
setCommentCid(comment.cid);
setOriginalCommentContent(comment.content);
setIsEditModalOpen(true);
}
};
useEffect(() => {
setPublishCommentEditOptions((prevOptions) => ({
...prevOptions,
@@ -458,14 +486,6 @@ const Subscriptions = () => {
navigate(`/p/${selected}`);
};
useLayoutEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
return (
<>
@@ -561,11 +581,11 @@ const Subscriptions = () => {
</div>
<>
<div className="board-title">Subscriptions</div>
{feed.length < 1 ? (
{account?.subscriptions.length < 1 ? (
<div className="board-address">You haven't subscribed to any board yet.</div>
) : (
<div className="board-address">
You have subscribed to {account.subscriptions.length} board{account.subscriptions.length > 1 ? "s" : null}.
You have subscribed to {account?.subscriptions.length} board{account?.subscriptions.length > 1 ? "s" : null}.
</div>
)}
</>
@@ -591,7 +611,7 @@ const Subscriptions = () => {
<Link to={`/p/subscriptions/catalog`}>Catalog</Link>
]
</div>
{feed.length > 0 ? (
{feed ? (
null
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
@@ -607,7 +627,7 @@ const Subscriptions = () => {
<Tooltip id="tooltip" className="tooltip" />
<BoardForm selectedStyle={selectedStyle}>
<div className="board">
{ feed.length < 1 ? (
{ !feed ? (
null
) : (
<Virtuoso
@@ -626,56 +646,110 @@ const Subscriptions = () => {
<hr key={`hr-${index}`} />
<div key={`pi-${index}`} className="post-info">
{commentMediaInfo?.url ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<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>&nbsp;({commentMediaInfo?.type})
</div>
{commentMediaInfo?.type === "webpage" ? (
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{thread.thumbnailUrl ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<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>&nbsp;({commentMediaInfo?.type === "iframe" ? "video" : commentMediaInfo?.type})
{isThumbnailClicked[index] ? (
<span>
-[
<span className='reply-link'
style={{textDecoration: 'underline', cursor: 'pointer'}}
onClick={() => {handleThumbnailClick(index)}}>Close</span>
]
</span>
) : null}
</div>
{commentMediaInfo?.type === 'iframe' && (
<div key={`enlarge-${index}`}
className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{(isThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<iframe
className='enlarged'
key={`fti-${index}`}
src={commentMediaInfo.embedUrl}
width={commentMediaInfo.thumbnail ? "560" : "250"}
height="315"
style={{border: "none"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
) : (
<img
key={`fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
</span>
</div>
)}
{commentMediaInfo?.type === "webpage" ? (
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{thread.thumbnailUrl ? (
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
) : null}
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<div key={`enlarge-${index}`} className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{isThumbnailClicked[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)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
</span>
</div>
) : null}
{commentMediaInfo?.type === "image" ? (
<div key={`enlarge-${index}`} className="img-container">
) : null}
{commentMediaInfo?.type === "audio" ? (
<span key={`fta-${index}`} className="file-thumb">
<img key={`fti-${index}`}
<audio controls key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<span key={`fta-${index}`} className="file-thumb">
<video controls width="" key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
) : 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}
) : null}
</div>
) : null}
<span key={`nb-${index}`} className="name-block">
{thread.title ? (
thread.title.length > 75 ?
@@ -709,9 +783,9 @@ const Subscriptions = () => {
(u/
<span key={`pa-${index}`} className="poster-address address-desktop"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(thread.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
{thread.author.shortAddress}
{<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>)
&nbsp;
<span key={`dt-${index}`} className="date-time" data-utc="data">{getDate(thread.timestamp)}</span>
@@ -786,38 +860,44 @@ const Subscriptions = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(thread.cid)}>Hide thread</li>
{thread.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={thread.cid}>{({ authorAddress }) => (
<>
<li onClick={() => {handleAuthorEditClick(thread); setSelectedAddress(thread.subplebbitAddress);}}>Edit post</li>
<li onClick={() => {handleAuthorDeleteClick(thread.cid); setSelectedAddress(thread.subplebbitAddress);}}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
{isModerator ? (
<>
{thread.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
)}</VerifiedAuthor>
{(commentMediaInfo && (
commentMediaInfo.type === 'image' ||
(commentMediaInfo.type === 'webpage' &&
@@ -979,7 +1059,7 @@ const Subscriptions = () => {
&nbsp;
<span key={`pa-${index}`} className="poster-address address-desktop"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(reply.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={reply.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
{reply.author?.shortAddress ?
@@ -1074,38 +1154,44 @@ const Subscriptions = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(reply.cid)}>Hide post</li>
{reply.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={reply.cid}>{({ authorAddress }) => (
<>
<li onClick={() => {handleAuthorEditClick(reply); setSelectedAddress(thread.subplebbitAddress);}}>Edit post</li>
<li onClick={() => {handleAuthorDeleteClick(reply.cid); setSelectedAddress(thread.subplebbitAddress);}}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{reply.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(reply)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(reply)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
}}>
Delete post
Mod tools
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(reply.cid)
setIsModerationOpen(true);
handleOptionClick(reply.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
)}</VerifiedAuthor>
{(replyMediaInfo && (
replyMediaInfo.type === 'image' ||
(replyMediaInfo.type === 'webpage' &&
@@ -1497,11 +1583,11 @@ const Subscriptions = () => {
&nbsp;
<span key={`mob-pa-${index}`} className="poster-address-mobile address-mobile"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(thread.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
<span key={`mob-ha-${index}`} className="highlight-address-mobile">
{thread.author.shortAddress}
{<VerifiedAuthor commentCid={thread.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>
)&nbsp;
</span>
@@ -1566,51 +1652,108 @@ const Subscriptions = () => {
</span>
</div>
{thread.link ? (
<div key={`mob-f-${index}`} className="file-mobile">
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
{thread.thumbnailUrl ? (
<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">
{(isMobileThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<div style={{width: "92vw"}}>
<iframe
key={`mob-fti-${index}`}
src={commentMediaInfo.embedUrl}
style={{border: "none", height: "250px"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
</div>
) : (
<img
key={`mob-fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index, true)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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>
</div>
)}
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
{thread.thumbnailUrl ? (
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
onClick={handleImageClick}
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">
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<img key={`mob-img-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
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">
<video key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
style={{ pointerEvents: "none" }}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${index}`} className="file-info-mobile">{commentMediaInfo?.type}</div>
</span>
) : commentMediaInfo.type === "audio" ? (
<span key={`mob-ft${thread.cid}`} className="file-thumb-mobile">
<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}
<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">
{isMobileThumbnailClicked[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, true)}}
style={{cursor: "pointer"}}
id="video-thumbnail-mobile"
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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">
<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}`}>
@@ -1680,7 +1823,7 @@ const Subscriptions = () => {
&nbsp;
<span key={`mob-pa-${index}`} className="poster-address-mobile address-mobile"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(reply.author.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={reply.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
{reply.author?.shortAddress ?
@@ -2072,7 +2215,7 @@ const Subscriptions = () => {
}}
endReached={tryLoadMore}
useWindowScroll={true}
components={{ Footer: hasMore ? () => <PostLoader /> : null }}
components={{ Footer: hasMore && feed.length > 0 ? () => <PostLoader /> : null }}
/>
)}
</div>
+46 -37
View File
@@ -14,6 +14,7 @@ import { TopBar, Footer, AuthorDeleteAlert } from '../styled/views/Thread.styled
import CatalogLoader from '../CatalogLoader';
import EditModal from '../modals/EditModal';
import ImageBanner from '../ImageBanner';
import VerifiedAuthor from '../VerifiedAuthor';
import ModerationModal from '../modals/ModerationModal';
import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal';
@@ -146,7 +147,8 @@ const SubscriptionsCatalog = () => {
setNewSuccessMessage('Challenge Success');
}
else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -157,7 +159,7 @@ const SubscriptionsCatalog = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -206,7 +208,7 @@ const SubscriptionsCatalog = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -229,9 +231,10 @@ const SubscriptionsCatalog = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(comment.cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -409,7 +412,7 @@ const SubscriptionsCatalog = () => {
</div>
<>
<div className="board-title">Subscriptions</div>
{feed.length < 1 ? (
{account.subscriptions.length < 1 ? (
<div className="board-address">You haven't subscribed to any board yet.</div>
) : (
<div className="board-address">
@@ -444,7 +447,7 @@ const SubscriptionsCatalog = () => {
<Link to={`/p/subscriptions`}>Return</Link>
</span>
</div>
{feed.length > 0 ? (
{feed ? (
null
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
@@ -455,7 +458,7 @@ const SubscriptionsCatalog = () => {
</TopBar>
<Tooltip id="tooltip" className="tooltip" />
<Threads selectedStyle={selectedStyle}>
{feed.length > 0 ? (
{feed ? (
<InfiniteScroll
pageStart={0}
loadMore={tryLoadMore}
@@ -561,38 +564,44 @@ const SubscriptionsCatalog = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(thread.cid)}>Hide thread</li>
{thread.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={thread.cid}>{({ authorAddress }) => (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(thread)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(thread)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
{isModerator ? (
<>
{thread.author.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setSelectedAddress(thread.subplebbitAddress);
setModeratingCommentCid(thread.cid)
setIsModerationOpen(true);
handleOptionClick(thread.cid);
}}>
Mod tools
</li>
</>
) : null}
)}</VerifiedAuthor>
{(commentMediaInfo && (
commentMediaInfo.type === 'image' ||
(commentMediaInfo.type === 'webpage' &&
+282 -140
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async';
import { Link, useNavigate, useParams } from 'react-router-dom';
@@ -20,6 +20,7 @@ import Post from '../Post';
import PostLoader from '../PostLoader';
import PostOnHover from '../PostOnHover';
import StateLabel from '../StateLabel';
import VerifiedAuthor from '../VerifiedAuthor';
import ReplyModal from '../modals/ReplyModal';
import SettingsModal from '../modals/SettingsModal';
import findShortParentCid from '../../utils/findShortParentCid';
@@ -111,6 +112,8 @@ const Thread = () => {
const [postOnHoverHeight, setPostOnHoverHeight] = useState(0);
const [executeAnonMode, setExecuteAnonMode] = useState(false);
const [cidTracker, setCidTracker] = useState({});
const [isThumbnailClicked, setIsThumbnailClicked] = useState({});
const [isMobileThumbnailClicked, setIsMobileThumbnailClicked] = useState({});
useAnonMode(selectedThread, anonymousMode && executeAnonMode);
@@ -126,6 +129,21 @@ const Thread = () => {
const fallbackImgUrl = "assets/filedeleted-res.gif";
const handleThumbnailClick = (index, isMobile=false) => {
if (isMobile) {
setIsMobileThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
} else {
setIsThumbnailClicked(prevState => ({
...prevState,
[index]: !prevState[index],
}));
}
};
useEffect(() => {
if (subplebbit.roles !== undefined) {
const role = subplebbit.roles[account?.author?.address]?.role;
@@ -161,6 +179,14 @@ const Thread = () => {
};
}, [openMenuCid, handleOutsideClick]);
useEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
useEffect(() => {
window.scrollTo(0, 0);
@@ -259,7 +285,8 @@ const Thread = () => {
setNewSuccessMessage('Challenge Success');
}
} else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
@@ -272,7 +299,7 @@ const Thread = () => {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
@@ -293,7 +320,7 @@ const Thread = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -388,8 +415,10 @@ const Thread = () => {
}, [selectedThread, anonymousMode, account]);
useEffect(() => {
updateSigner();
}, [updateSigner]);
if (anonymousMode) {
updateSigner();
}
}, [updateSigner, anonymousMode]);
useEffect(() => {
@@ -446,7 +475,7 @@ const Thread = () => {
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error);
setNewErrorMessage(error.message); console.log(error);
},
});
@@ -454,8 +483,8 @@ const Thread = () => {
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
const handleAuthorDeleteClick = (cid) => {
handleOptionClick(cid);
const handleAuthorDeleteClick = (comment) => {
handleOptionClick(comment.cid);
confirmAlert({
customUI: ({ onClose }) => {
@@ -469,9 +498,10 @@ const Thread = () => {
onClick={() => {
setIsAuthorDelete(true);
setIsAuthorEdit(false);
setCommentCid(cid);
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
commentCid: comment.cid,
subplebbitAddress: comment.subplebbitAddress,
deleted: true,
}));
setTriggerPublishCommentEdit(true);
@@ -549,14 +579,6 @@ const Thread = () => {
};
useLayoutEffect(() => {
if (postOnHoverRef.current) {
const rect = postOnHoverRef.current.getBoundingClientRect();
setPostOnHoverHeight(rect.height);
}
}, [outOfViewCid]);
return (
<>
<Helmet>
@@ -792,52 +814,106 @@ const Thread = () => {
<div className="op-container">
<div className="post op op-desktop">
<div className="post-info">
{commentMediaInfo?.url ? (
<div key={`f-${comment.cid}`} className="file" style={{marginBottom: "5px"}}>
<div key={`ft-${comment.cid}`} className="file-text">
{commentMediaInfo?.url ? (
<div key={`f-${index}`} className="file" style={{marginBottom: "5px"}}>
<div key={`ft-${index}`} className="file-text">
Link:&nbsp;
<a key={`fa-${comment.cid}`} href={commentMediaInfo.url}
target="_blank" rel="noreferrer">{
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
rel="noopener noreferrer">{
commentMediaInfo?.url.length > 30 ?
commentMediaInfo?.url.slice(0, 30) + "(...)" :
commentMediaInfo?.url
}</a>&nbsp;({commentMediaInfo?.type})
}</a>&nbsp;({commentMediaInfo?.type === "iframe" ? "video" : commentMediaInfo?.type})
{isThumbnailClicked[index] ? (
<span>
-[
<span className='reply-link'
style={{textDecoration: 'underline', cursor: 'pointer'}}
onClick={() => {handleThumbnailClick(index)}}>Close</span>
]
</span>
) : null}
</div>
{commentMediaInfo?.type === 'iframe' && (
<div key={`enlarge-${index}`}
className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{(isThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<iframe
className='enlarged'
key={`fti-${index}`}
src={commentMediaInfo.embedUrl}
width={commentMediaInfo.thumbnail ? "560" : "250"}
height="315"
style={{border: "none"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
) : (
<img
key={`fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
</span>
</div>
)}
{commentMediaInfo?.type === "webpage" ? (
<div key="enlarge" className="img-container">
<span key={`fta-${comment.cid}`} className="file-thumb">
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
{comment.thumbnailUrl ? (
<img key={`fti-${comment.cid}`}
<img key={`fti-${index}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
) : null}
</span>
</div>
) : null}
{commentMediaInfo?.type === "image" ? (
<div key="enlarge" className="img-container">
<span key={`fta-${comment.cid}`} className="file-thumb">
<img key={`fti-${comment.cid}`}
<div key={`enlarge-${index}`} className="img-container">
<span key={`fta-${index}`} className="file-thumb">
<img key={`fti-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
</span>
</div>
) : null}
{commentMediaInfo?.type === "video" ? (
<span key={`fta-${comment.cid}`} className="file-thumb">
<video controls key={`fti-${comment.cid}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`enlarge-${index}`} className={`img-container ${isThumbnailClicked[index] ? 'expanded-container' : ''}`}>
<span key={`fta-${index}`} className="file-thumb">
{isThumbnailClicked[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)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
</span>
</div>
) : null}
{commentMediaInfo?.type === "audio" ? (
<span key={`fta-${comment.cid}`} className="file-thumb">
<audio controls key={`fti-${comment.cid}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
<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}
@@ -877,10 +953,10 @@ const Thread = () => {
&nbsp;
<span className="poster-address address-desktop"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(comment?.author?.shortAddress)}>
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={comment.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}>
(u/
<span key={`pa-${comment.cid}`} className="poster-address">
{comment?.author?.shortAddress}
{<VerifiedAuthor commentCid={comment.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>)
</span>
&nbsp;
@@ -948,36 +1024,42 @@ const Thread = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(comment.cid)}>Hide thread</li>
{comment.author?.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={comment.cid}>{({ authorAddress }) => (
<>
<li onClick={() => handleAuthorEditClick(comment)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(comment.cid)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{comment.author?.shortAddress === account?.author.shortAddress ? (
null
) : (
<li onClick={() => {
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(comment)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(comment)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
null
) : (
<li onClick={() => {
setModeratingCommentCid(comment.cid)
setIsModerationOpen(true);
handleOptionClick(comment.cid);
setDeletePost(true);
}}>
Delete post
</li>
)}
<li
onClick={() => {
setModeratingCommentCid(comment.cid)
setIsModerationOpen(true);
handleOptionClick(comment.cid);
setDeletePost(true);
}}>
Delete post
Mod tools
</li>
)}
<li
onClick={() => {
setModeratingCommentCid(comment.cid)
setIsModerationOpen(true);
handleOptionClick(comment.cid);
}}>
Mod tools
</li>
</>
) : null}
</>
) : null}
</>
)}</VerifiedAuthor>
{(commentMediaInfo && (
commentMediaInfo.type === 'image' ||
(commentMediaInfo.type === 'webpage' &&
@@ -1194,36 +1276,42 @@ const Thread = () => {
>
<ul className="post-menu-catalog">
<li onClick={() => handleOptionClick(reply.cid)}>Hide post</li>
{reply.author.shortAddress === account?.author.shortAddress ? (
<VerifiedAuthor commentCid={reply.cid}>{({ authorAddress }) => (
<>
<li onClick={() => handleAuthorEditClick(reply)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(reply.cid)}>Delete post</li>
{authorAddress === account?.author.address ||
authorAddress === account?.signer.address ? (
<>
<li onClick={() => handleAuthorEditClick(reply)}>Edit post</li>
<li onClick={() => handleAuthorDeleteClick(reply)}>Delete post</li>
</>
) : null}
{isModerator ? (
<>
{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}
</>
) : null}
{isModerator ? (
<>
{reply.author.shortAddress === account?.author.shortAddress ? (
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' &&
@@ -1499,11 +1587,11 @@ const Thread = () => {
&nbsp;
<span key={`mob-pa-${comment.cid}`} className="poster-address-mobile address-mobile"
id="reply-button" style={{cursor: "pointer"}}
onClick={() => handleAddressClick(comment?.author?.shortAddress)}
onClick={() => handleAddressClick(<VerifiedAuthor commentCid={comment.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>)}
>
(u/
<span key={`mob-ha-${comment.cid}`} className="highlight-address-mobile">
{comment?.author?.shortAddress}
{<VerifiedAuthor commentCid={comment.cid}>{({ shortAuthorAddress }) => (shortAuthorAddress)}</VerifiedAuthor>}
</span>
)&nbsp;
</span>
@@ -1553,54 +1641,108 @@ const Thread = () => {
}}}>{comment.shortCid}</Link>
</span>
</div>
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`mob-f-${comment.cid}`} className="file-mobile">
<div key="enlarge-reply-mob" className="img-container">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
{comment.thumbnailUrl ? (
<img key={`mob-img-${comment.cid}`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
{comment.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">
{(isMobileThumbnailClicked[index] || !commentMediaInfo.thumbnail) && commentMediaInfo.embedUrl ? (
<div style={{width: "92vw"}}>
<iframe
key={`mob-fti-${index}`}
src={commentMediaInfo.embedUrl}
style={{border: "none", height: "250px"}}
title="Embedded content"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen />
</div>
) : (
<img
key={`mob-fti-${index}`}
src={commentMediaInfo.thumbnail}
alt="thumbnail"
onClick={() => {handleThumbnailClick(index, true)}}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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>
</div>
)}
{commentMediaInfo?.url ? (
commentMediaInfo.type === "webpage" ? (
<div key={`enlarge-mob-${index}`} className="img-container">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
{comment.thumbnailUrl ? (
<img key={`mob-img-${index}`}
src={commentMediaInfo.thumbnail} alt="thumbnail"
onClick={handleImageClick}
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${comment.cid}`} className="file-thumb-mobile">
<img key={`mob-img-${index}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
) : null}
<div key={`mob-fi-${comment.cid}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
</div>
) : commentMediaInfo.type === "image" ? (
<div key={`mob-f-${comment.cid}`} className="file-mobile">
<div key="enlarge-reply-mob" className="img-container">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
<img key={`mob-img-${comment.cid}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onClick={handleImageClick}
style={{cursor: "pointer"}}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${comment.cid}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
</div>
) : commentMediaInfo.type === "video" ? (
<div key={`mob-f-${comment.cid}`} className="file-mobile">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
<video controls key={`mob-img-${comment.cid}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${comment.cid}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
) : commentMediaInfo.type === "audio" ? (
<div key={`mob-f-${comment.cid}`} className="file-mobile">
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
<audio controls key={`mob-img-${comment.cid}`}
src={commentMediaInfo.url} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} />
<div key={`mob-fi-${comment.cid}`} className="file-info-mobile">{commentMediaInfo.type}</div>
</span>
</div>
) : null
<div key={`mob-fi-${index}`} className="file-info-mobile">{commentMediaInfo?.type}</div>
</span>
</div>
) : commentMediaInfo.type === "video" ? (
<span key={`mob-ft${comment.cid}`} className="file-thumb-mobile">
{isMobileThumbnailClicked[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, true)}}
style={{cursor: "pointer"}}
id="video-thumbnail-mobile"
onError={(e) => e.target.src = fallbackImgUrl}
/>
)}
{commentMediaInfo?.type === "video" || "iframe" ? (
isMobileThumbnailClicked[index] ? (
<div style={{textAlign: "center", marginTop: "15px", marginBottom: "15px"}}>
<span className='button-mobile' style={{float: "none", cursor: "pointer"}}
onClick={() => {handleThumbnailClick(index, true)}}
>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${comment.cid}`} className="file-thumb-mobile">
<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}
<blockquote key={`mob-bq-${comment.cid}`} className="post-message-mobile">
{comment.content ? (
+71 -25
View File
@@ -7,34 +7,80 @@ const getCommentMediaInfo = (comment) => {
if (comment?.link) {
try {
new URL(comment.link);
const url = new URL(comment.link);
const host = url.hostname;
let embedUrl = null;
if (['youtube.com', 'www.youtube.com', 'youtu.be'].includes(host)) {
const videoId = host === 'youtu.be' ? url.pathname.slice(1) : url.searchParams.get('v');
embedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`;
} else if (host.includes('odysee.com')) {
const pathComponents = url.pathname.split('/');
const channelAndId = pathComponents[1];
const videoAndId = pathComponents[2];
embedUrl = `https://odysee.com/$/embed/${channelAndId}/${videoAndId}`;
// TODO: add support for Rumble, video id is dynamic
// } else if (host.includes('rumble.com')) {
// const regex = /(\/v.+?)-/;
// const match = regex.exec(url.pathname);
// if (match) {
// const videoId = match[1].slice(1);
// embedUrl = `https://rumble.com/embed/${videoId}/?pub=4`;
// }
} else if (host.includes('bitchute.com')) {
const videoId = url.pathname.split('/')[2];
embedUrl = `https://www.bitchute.com/embed/${videoId}/`;
} else if (host.includes('streamable.com')) {
const videoId = url.pathname.split('/')[1];
embedUrl = `https://streamable.com/e/${videoId}?quality=highest`;
// TODO: Add support for Giphy (doesn't have thumbnails, size is variable)
// } else if (host.includes('giphy.com')) {
// const hyphenComponents = url.pathname.split('-');
// const gifId = hyphenComponents[hyphenComponents.length - 1];
// embedUrl = `https://giphy.com/embed/${gifId}`;
}
if (embedUrl) {
return {
url: comment.link,
embedUrl,
type: 'iframe',
thumbnail: comment.thumbnailUrl,
};
}
const mime = extName(url.pathname.replace('/', ''))[0]?.mime;
if (mime?.startsWith('image')) {
return {
url: comment.link,
type: 'image',
};
}
if (mime?.startsWith('video')) {
return {
url: comment.link,
type: 'video',
thumbnail: comment.thumbnailUrl,
};
}
if (mime?.startsWith('audio')) {
return {
url: comment.link,
type: 'audio',
};
}
} catch (error) {
// console.error("Invalid URL:", comment.link);
return;
}
const mime = extName(new URL(comment?.link).pathname.replace('/', ''))[0]?.mime;
if (mime?.startsWith('image')) {
return {
url: comment.link,
type: 'image',
};
}
if (mime?.startsWith('video')) {
return {
url: comment.link,
type: 'video',
};
}
if (mime?.startsWith('audio')) {
return {
url: comment.link,
type: 'audio',
};
}
}
if (comment?.thumbnailUrl && comment?.thumbnailUrl !== comment?.link) {