mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+23
-19
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -95,6 +95,7 @@ const CaptchaModal = () => {
|
||||
const handleCloseModal = () => {
|
||||
setImageSources([]);
|
||||
setChallengesArray([]);
|
||||
setCurrentChallengeIndex(0);
|
||||
setIsCaptchaOpen(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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} -
|
||||
{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;
|
||||
}
|
||||
`;
|
||||
@@ -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
@@ -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:
|
||||
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
|
||||
rel="noopener noreferrer">{
|
||||
commentMediaInfo?.url.length > 30 ?
|
||||
commentMediaInfo?.url.slice(0, 30) + "(...)" :
|
||||
commentMediaInfo?.url
|
||||
}</a> ({commentMediaInfo?.type})
|
||||
</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:
|
||||
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
|
||||
rel="noopener noreferrer">{
|
||||
commentMediaInfo?.url.length > 30 ?
|
||||
commentMediaInfo?.url.slice(0, 30) + "(...)" :
|
||||
commentMediaInfo?.url
|
||||
}</a> ({commentMediaInfo?.type === "iframe" ? "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>)
|
||||
|
||||
<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 = () => {
|
||||
|
||||
<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 = () => {
|
||||
|
||||
<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>
|
||||
)
|
||||
</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 = () => {
|
||||
|
||||
<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 />
|
||||
|
||||
@@ -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' &&
|
||||
|
||||
+1431
-1283
File diff suppressed because it is too large
Load Diff
@@ -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' &&
|
||||
|
||||
@@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<Link to="/" onClick={() => {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>Home</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="separator-mobile"> </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:
|
||||
<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> ({commentMediaInfo?.type})
|
||||
}</a> ({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 ? (
|
||||
<>
|
||||
|
||||
@@ -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:
|
||||
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
|
||||
rel="noopener noreferrer">{
|
||||
commentMediaInfo?.url.length > 30 ?
|
||||
commentMediaInfo?.url.slice(0, 30) + "(...)" :
|
||||
commentMediaInfo?.url
|
||||
}</a> ({commentMediaInfo?.type})
|
||||
</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:
|
||||
<a key={`fa-${index}`} href={commentMediaInfo.url} target="_blank"
|
||||
rel="noopener noreferrer">{
|
||||
commentMediaInfo?.url.length > 30 ?
|
||||
commentMediaInfo?.url.slice(0, 30) + "(...)" :
|
||||
commentMediaInfo?.url
|
||||
}</a> ({commentMediaInfo?.type === "iframe" ? "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>)
|
||||
|
||||
<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 = () => {
|
||||
|
||||
<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 = () => {
|
||||
|
||||
<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>
|
||||
)
|
||||
</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 = () => {
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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
@@ -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:
|
||||
<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> ({commentMediaInfo?.type})
|
||||
}</a> ({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 = () => {
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
<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>
|
||||
)
|
||||
</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 ? (
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user