Merge pull request #48 from plebbit/development

Development
This commit is contained in:
plebeius.eth
2023-05-10 18:01:34 +02:00
committed by GitHub
21 changed files with 508 additions and 200 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
"@babel/plugin-transform-react-jsx": "7.21.0",
"@capacitor/android": "3.6.0",
"@capacitor/core": "3.6.0",
"@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#91ea5a494fde74836b085e912f671cc5489ecf09",
"@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#e602eda8080f2b3449388c2d5868d6e6da60c8f6",
"@testing-library/dom": "9.0.1",
"@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "14.0.0",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+1 -1
View File
@@ -25,7 +25,7 @@ const BoardAvatar = ({ address }) => {
{!isOnline && (
<img className="offline-indicator" alt="offline" src="assets/offline.png"
data-tooltip-id="tooltip"
data-tooltip-content="OFFLINE"
data-tooltip-content="Offline"
data-tooltip-place="top" />
)}
</div>
+1 -1
View File
@@ -16,7 +16,7 @@ const OfflineIndicator = ({ address, className, tooltipPlace }) => {
alt="offline"
src="assets/offline.png"
data-tooltip-id="tooltip"
data-tooltip-content="OFFLINE"
data-tooltip-content="Offline"
data-tooltip-place={tooltipPlace}
/>
</>
+2 -4
View File
@@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { usePublishComment } from '@plebbit/plebbit-react-hooks';
import { StyledModal } from './styled/ReplyModal.styled';
import useGeneralStore from '../hooks/stores/useGeneralStore';
@@ -33,8 +32,7 @@ const ReplyModal = ({ isOpen, closeModal }) => {
const [selectedText, setSelectedText] = useState('');
useError(errorMessage, [errorMessage]);
const location = useLocation();
const onModalOpen = () => {
@@ -109,7 +107,7 @@ const ReplyModal = ({ isOpen, closeModal }) => {
if (index !== undefined) {
setPendingCommentIndex(index);
}
}, [index, location, setPendingCommentIndex]);
}, [index, setPendingCommentIndex]);
const resetFields = useCallback(() => {
+91 -19
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import Modal from "react-modal";
import { deleteCaches, setAccount, useAccount } from "@plebbit/plebbit-react-hooks";
import { deleteCaches, exportAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from "@plebbit/plebbit-react-hooks";
import { StyledModal } from "./styled/SettingsModal.styled";
import useGeneralStore from "../hooks/stores/useGeneralStore";
import useError from "../hooks/useError";
@@ -10,28 +10,27 @@ import packageJson from '../../package.json'
const {version} = packageJson
const customOverlayStyles = {
overlay: {
backgroundColor: 'rgba(0,0,0,.25)'
}
};
const SettingsModal = ({ isOpen, closeModal }) => {
const selectedStyle = useGeneralStore(state => state.selectedStyle);
const navigate = useNavigate();
const location = useLocation();
const [expanded, setExpanded] = useState([]);
const [accountJson, setAccountJson] = useState(null);
const [setImportSuccess] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
useError(errorMessage, [errorMessage]);
useSuccess(successMessage, [successMessage]);
const account = useAccount();
const { accounts } = useAccounts();
const gatewayRef = useRef();
const ipfsRef = useRef();
const pubsubRef = useRef();
const dataPathRef = useRef();
const importRef = useRef();
const isValidURL = (url) => {
try {
@@ -108,6 +107,8 @@ const SettingsModal = ({ isOpen, closeModal }) => {
const handleCloseModal = () => {
setAccountJson(null);
if (location.pathname.endsWith("/settings")) {
const newPath = location.pathname.slice(0, -9);
closeModal();
@@ -146,7 +147,31 @@ const SettingsModal = ({ isOpen, closeModal }) => {
localStorage.removeItem("cacheCleared");
}
}, []);
const handleExport = async () => {
const activeAccountJson = await exportAccount();
setAccountJson(activeAccountJson);
};
const handleImport = async () => {
const accountJson = importRef.current.value;
try {
await importAccount(accountJson);
setImportSuccess(true);
localStorage.setItem("toastMessage", "Account Imported");
window.location.reload();
} catch (error) {
setErrorMessage(error.message);
}
};
const handleAccountChange = (e) => {
setActiveAccount(e.target.value);
};
return (
@@ -154,7 +179,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
isOpen={isOpen}
onRequestClose={handleCloseModal}
contentLabel="Settings"
style={customOverlayStyles}
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
selectedStyle={selectedStyle}
expanded={expanded}
>
@@ -189,7 +214,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
<li>
</li>
</ul>
</ul>
</ul>*/}
<ul>
<li className="settings-cat-lbl">
<span className={`${expanded.includes(1) ? 'minus' : 'plus'}`}
@@ -197,13 +222,55 @@ const SettingsModal = ({ isOpen, closeModal }) => {
/>
<span className="settings-pointer" style={{cursor: "pointer"}}
onClick={() => toggleExpanded(1)}
>Profile</span>
>Account</span>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(1) ? 'block' : 'none' }}
>
</div>
</li>
<ul className="settings-cat" style={{ display: expanded.includes(1) ? 'block' : 'none' }}>
<li>
<li className="settings-option disc">
Export or Import Your Data
</li>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(1) ? 'block' : 'none' }}
>
<button className="save-button"
onClick={handleExport}>Export</button>
<button className="reset-button"
onClick={handleImport}
>Import</button>
</div>
<li className="settings-tip">
To export, click "Export", then save your account data displayed below in a safe place. To import, paste your account data into the box below, then click "Import".
</li>
<div className="settings-input">
<textarea ref={importRef} value={accountJson} />
</div>
<li className="settings-option disc">
Current Account: {account?.name}
</li>
<li className="settings-tip">
Select a different account to use in the dropdown below.
</li>
</ul>
</ul> */}
<ul className="settings-cat" style={{ display: expanded.includes(1) ? 'block' : 'none' }}>
<li>
<div className="settings-input">
<select className="settings-select"
value={account?.name}
onChange={handleAccountChange}
>
{accounts.map((account) => (
<option key={account?.name} value={account?.name}>
{account?.name}
</option>
))}
</select>
</div>
</li>
</ul>
</ul>
<ul>
<li className="settings-cat-lbl">
<span className={`${expanded.includes(2) ? 'minus' : 'plus'}`}
@@ -213,16 +280,21 @@ const SettingsModal = ({ isOpen, closeModal }) => {
onClick={() => toggleExpanded(2)}
>Plebbit Options</span>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(2) ? 'block' : 'none' }}
style={{ display: expanded.includes(2) ? 'block' : 'none' }}
>
<button className="save-button" onClick={handleSavePlebbitOptions}>Save</button>
<button className="reset-button"onClick={handleResetPlebbitOptions}>Reset</button>
</div>
<button className="save-button"
onClick={handleSavePlebbitOptions}>Save</button>
<button className="reset-button"
onClick={handleResetPlebbitOptions}>Reset</button>
</div>
</li>
<ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
IPFS Gateway URLs</li>
<li className="settings-tip">Optional URLs of IPFS gateways.</li>
IPFS Gateway URLs
</li>
<li className="settings-tip">
Optional URLs of IPFS gateways.
</li>
<div className="settings-input">
<textarea placeholder="IPFS Gateway URLs"
defaultValue={account?.plebbitOptions?.ipfsGatewayUrls.join("\n")}
+53 -39
View File
@@ -30,12 +30,6 @@ export const NavBar = styled.div`
#board-nav-mobile {
display: none;
}
.offline-nav {
width: 12px;
position: relative;
margin: 2px 2px -2px -2px;
}
}
@media (max-width: 480px) {
@@ -244,13 +238,14 @@ export const Header = styled.div`
margin: 5px auto;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
img {
border: none;
width: 300px;
height: 100px;
}
.board-title {
font-family: Tahoma, sans-serif;
@@ -276,13 +271,14 @@ export const Header = styled.div`
margin: 5px auto;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
img {
border: none;
width: 300px;
height: 100px;
}
.board-title {
font-family: Tahoma, sans-serif;
@@ -307,13 +303,14 @@ export const Header = styled.div`
margin: 5px auto;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
img {
border: none;
width: 300px;
height: 100px;
}
.board-title {
font-size: 24pt;
@@ -336,13 +333,14 @@ export const Header = styled.div`
margin: 5px auto;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
img {
border: none;
width: 300px;
height: 100px;
}
.board-title {
font-size: 24pt;
@@ -366,12 +364,12 @@ export const Header = styled.div`
width: 300px;
height: 100px;
max-width: 100%;
}
img {
border: none;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
.board-title {
@@ -397,13 +395,14 @@ export const Header = styled.div`
margin: 5px auto;
width: 300px;
height: 100px;
img {
border: none;
width: 300px;
height: 100px;
}
}
img {
border: none;
width: 300px;
height: 100px;
}
.board-title {
font-family: Tahoma, sans-serif;
@@ -424,6 +423,12 @@ export const Header = styled.div`
return '';
}
}}
.offline {
width: 16px;
position: relative;
margin: 2px 2px -4px 2px;
}
`;
export const Break = styled.hr`
@@ -1626,7 +1631,14 @@ export const BoardForm = styled.div`
.offline-sub {
width: 13px;
margin-right: -5px;
margin-bottom: -2px;
margin-bottom: -3px;
position: relative;
}
.offline-reply {
width: 13px;
margin-right: 2px;
margin-bottom: -3px;
position: relative;
}
@@ -2981,12 +2993,14 @@ export const BoardForm = styled.div`
margin-bottom: 335px;
.offline-mobile-sub {
width: 18px;
width: 13px;
margin-top: 3px;
margin-right: 5px;
}
.offline-mobile-sub-reply {
width: 15px;
width: 13px;
margin-top: 3px;
}
.thread {
+12
View File
@@ -39,6 +39,18 @@ export const Threads = styled.div`
text-align: right;
}
.offline-icon {
height: 16px;
margin: 2px;
}
.offline-icon-no-link {
height: 13px;
position: absolute;
margin-left: 13px;
margin-top: -2px;
}
.sticky-icon {
background-image: url(assets/sticky.gif);
width: 16px;
+1 -4
View File
@@ -240,14 +240,11 @@ export const BoardsContent = styled.div`
object-fit: cover;
}
.board-avatar-container img.offline {
filter: brightness(50%);
}
.offline-indicator {
position: absolute;
top: 5px;
right: 5px;
width: 16px;
}
.board-text {
@@ -172,6 +172,11 @@ export const StyledModal = styled(Modal)`
top: 1px;
}
.settings-select {
position: relative;
left: 23px;
}
${({ selectedStyle }) => {
switch (selectedStyle) {
+32 -20
View File
@@ -16,7 +16,6 @@ import PostLoader from '../PostLoader';
import ReplyModal from '../ReplyModal';
import SettingsModal from '../SettingsModal';
import findShortParentCid from '../../utils/findShortParentCid';
import formatState from '../../utils/formatState';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getDate from '../../utils/getDate';
import handleAddressClick from '../../utils/handleAddressClick';
@@ -25,6 +24,7 @@ import handleQuoteClick from '../../utils/handleQuoteClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useClickForm from '../../hooks/useClickForm';
import useError from '../../hooks/useError';
import useStateString from '../../hooks/useStateString';
import useSuccess from '../../hooks/useSuccess';
import packageJson from '../../../package.json'
const {version} = packageJson
@@ -66,12 +66,7 @@ const Board = () => {
const { subplebbitAddress } = useParams();
const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
useEffect(() => {
if (subplebbit.error) {
const errorMessage = formatState(subplebbit.error);
setErrorMessage(errorMessage);
}
}, [subplebbit.error]);
const stateString = useStateString(subplebbit?.clients);
const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress});
@@ -82,6 +77,22 @@ const Board = () => {
const [triggerPublishComment, setTriggerPublishComment] = useState(false);
const errorString = useMemo(() => {
if (subplebbit?.state === 'failed') {
let errorString = 'Failed fetching board "' + selectedAddress + '".';
if (subplebbit.error) {
errorString += `: ${subplebbit.error.toString().slice(0, 300)}`
}
return errorString
}
}, [subplebbit?.state, subplebbit?.error, selectedAddress])
useEffect(() => {
if (errorString) {
setErrorMessage(errorString);
}
}, [errorString]);
const flattenedRepliesByThread = useMemo(() => {
return selectedFeed.reduce((acc, thread) => {
@@ -370,10 +381,6 @@ const Board = () => {
{index === 0 ? null : "\u00a0"}
<Link to={`/p/${subplebbit.address}`} key={`a-${subplebbit.address}`} onClick={() => handleClickTitle(subplebbit.title, subplebbit.address)}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
<OfflineIndicator
address={subplebbit.address}
className="offline-nav"
tooltipPlace="bottom" />
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
@@ -428,10 +435,13 @@ const Board = () => {
<div className="banner">
<ImageBanner />
</div>
<>
<div className="board-title">{selectedTitle}</div>
<div className="board-address">p/{selectedAddress}</div>
</>
<div className="board-title">{selectedTitle}</div>
<div className="board-address">p/{selectedAddress}
<OfflineIndicator
address={selectedAddress}
className="offline"
tooltipPlace="top" />
</div>
</>
</Header>
<Break selectedStyle={selectedStyle} />
@@ -516,7 +526,7 @@ const Board = () => {
</>
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
<span>{formatState(subplebbit.state)}</span>
<span>{stateString}</span>
</div>
)}
<div id="catalog-button-mobile">
@@ -528,10 +538,11 @@ const Board = () => {
<Tooltip id="tooltip" className="tooltip" />
<BoardForm selectedStyle={selectedStyle}>
<div className="board">
{feed.length < 1 ? (
<PostLoader />
{subplebbit?.state === "failed" ? (
null
) : (
<Virtuoso
feed.length > 0 ? (
<Virtuoso
increaseViewportBy={2000}
data={selectedFeed}
itemContent={(index, thread) => {
@@ -1162,7 +1173,8 @@ const Board = () => {
endReached={tryLoadMore}
useWindowScroll={true}
components={{ Footer: hasMore ? () => <PostLoader /> : null }}
/>
/>
) : (<PostLoader />)
)}
</div>
</BoardForm>
+32 -17
View File
@@ -1,7 +1,8 @@
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import InfiniteScroll from 'react-infinite-scroller';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Tooltip } from 'react-tooltip';
import { useFeed, usePublishComment, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks';
import { debounce } from 'lodash';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
@@ -12,11 +13,11 @@ import CatalogLoader from '../CatalogLoader';
import ImageBanner from '../ImageBanner';
import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../SettingsModal';
import formatState from '../../utils/formatState';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import handleStyleChange from '../../utils/handleStyleChange';
import useClickForm from '../../hooks/useClickForm';
import useError from '../../hooks/useError';
import useStateString from '../../hooks/useStateString';
import useSuccess from '../../hooks/useSuccess';
import packageJson from '../../../package.json'
const {version} = packageJson
@@ -52,13 +53,23 @@ const Catalog = () => {
const { subplebbitAddress } = useParams();
const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
useEffect(() => {
if (subplebbit.error) {
const errorMessage = formatState(subplebbit.error);
setErrorMessage(errorMessage);
}
}, [subplebbit.error]);
const stateString = useStateString(subplebbit?.clients);
const errorString = useMemo(() => {
if (subplebbit?.state === 'failed') {
let errorString = 'Failed fetching board "' + selectedAddress + '".';
if (subplebbit.error) {
errorString += `: ${subplebbit.error.toString().slice(0, 300)}`
}
return errorString
}
}, [subplebbit?.state, subplebbit?.error, selectedAddress])
useEffect(() => {
if (errorString) {
setErrorMessage(errorString);
}
}, [errorString]);
const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress});
const [errorMessage, setErrorMessage] = useState(null);
@@ -282,10 +293,6 @@ const Catalog = () => {
setSelectedAddress(subplebbit.address);
}}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
<OfflineIndicator
address={subplebbit.address}
className="offline-nav"
tooltipPlace="bottom" />
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
@@ -342,7 +349,12 @@ const Catalog = () => {
</div>
<>
<div className="board-title">{selectedTitle}</div>
<div className="board-address">p/{selectedAddress}</div>
<div className="board-address">p/{selectedAddress}
<OfflineIndicator
address={selectedAddress}
className="offline"
tooltipPlace="top" />
</div>
</>
</>
</Header>
@@ -428,7 +440,7 @@ const Catalog = () => {
</>
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
<span>{formatState(subplebbit.state)}</span>
<span>{stateString}</span>
</div>
)}
<div id="return-button-mobile">
@@ -438,10 +450,12 @@ const Catalog = () => {
</div>
<hr />
</TopBar>
<Tooltip id="tooltip" className="tooltip" />
<Threads selectedStyle={selectedStyle}>
{feed.length < 1 ? (
<CatalogLoader />
{subplebbit?.state === "failed" ? (
null
) : (
feed.length > 0 ? (
<InfiniteScroll
pageStart={0}
loadMore={tryLoadMore}
@@ -504,7 +518,8 @@ const Catalog = () => {
</Link>
)})}
</InfiniteScroll>
)}
) : (<CatalogLoader />)
)}
</Threads>
<Footer selectedStyle={selectedStyle}>
<Break id="break" selectedStyle={selectedStyle} style={{
+5 -5
View File
@@ -83,7 +83,7 @@ const Home = () => {
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 a board</button>. Search for any board address above, or feel free to click on a popular board below that interests you and jump right in! </p>
>create a board</button>. All the content is just text, and plebchan's speed depends on how many users are <a href="https://github.com/plebbit/whitepaper/blob/master/contribute.md" target="_blank" rel="noreferrer">seeding</a>. Search for any board address above, or feel free to click on a popular board below that interests you and jump right in! </p>
<br />
<p>There are no global rules, each board is completely independent and its owner decides how it should be moderated.</p>
</div>
@@ -145,16 +145,16 @@ const Home = () => {
<ul>
<li className="fill"></li>
<li className="first">
<a href="https://plebbitapp.eth.limo/#/" target="_blank" rel="noopener noreferrer">Plebbit</a>
<a href="https://snowtrace.io/token/0x625fc9bb971bb305a2ad63252665dcfe9098bee9" target="_blank" rel="noopener noreferrer">Token</a>
</li>
<li>
<a href="https://gitcoin.co/grants/5515/plebbit-a-serverless-adminless-decentralized-redd" target="_blank" rel="noopener noreferrer">Donate</a>
</li>
<li>
<a href="https://plebbit.com/whitepaper" target="_blank" rel="noopener noreferrer">Whitepaper</a>
<a href="https://github.com/plebbit/whitepaper/discussions/2" target="_blank" rel="noopener noreferrer">Whitepaper</a>
</li>
<li>
<a href="https://snowtrace.io/token/0x625fc9bb971bb305a2ad63252665dcfe9098bee9" target="_blank" rel="noopener noreferrer">Token</a>
<a href="https://github.com/plebbit/plebchan" target="_blank" rel="noopener noreferrer">GitHub</a>
</li>
<li>
<a href="https://matrix.to/#/#plebbit:plebbitchat.org" target="_blank" rel="noopener noreferrer">Matrix</a>
@@ -163,7 +163,7 @@ const Home = () => {
<a href="https://t.me/plebbit" target="_blank" rel="noopener noreferrer">Telegram</a>
</li>
<li>
<a href="https://twitter.com/getplebbit" target="_blank" rel="noopener noreferrer">Twitter</a>
<a href="https://twitter.com/plebchan_eth" target="_blank" rel="noopener noreferrer">Twitter</a>
</li>
<li>
<a href="https://discord.gg/E7ejphwzGW" target="_blank" rel="noopener noreferrer">Discord</a>
+40 -23
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Tooltip } from 'react-tooltip';
@@ -10,12 +10,12 @@ import ImageBanner from '../ImageBanner';
import Post from '../Post';
import PostLoader from '../PostLoader';
import SettingsModal from '../SettingsModal';
import formatState from '../../utils/formatState';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getDate from '../../utils/getDate';
import handleQuoteClick from '../../utils/handleQuoteClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError';
import useStateString from '../../hooks/useStateString';
import packageJson from '../../../package.json'
const {version} = packageJson
@@ -26,7 +26,6 @@ const Pending = () => {
isSettingsOpen, setIsSettingsOpen,
selectedAddress, setSelectedAddress,
selectedStyle,
selectedThread,
selectedTitle, setSelectedTitle,
showPostFormLink
} = useGeneralStore(state => state);
@@ -35,20 +34,34 @@ const Pending = () => {
const account = useAccount();
const comment = useAccountComment({commentIndex: index});
const { publishingState, error } = useAccountComment({commentIndex: index});
useEffect(() => {
setSelectedAddress(comment?.subplebbitAddress);
}, [comment, setSelectedAddress]);
const stateString = useStateString(comment?.clients)
const errorString = useMemo(() => {
if (comment?.state === 'failed') {
let errorString = 'Failed fetching pending thread. Pending index: ' + index;
if (comment.error) {
errorString += `: ${comment.error.toString().slice(0, 300)}`
}
return errorString
}
}, [comment?.state, comment?.error, index])
useEffect(() => {
if (errorString) {
setErrorMessage(errorString);
}
}, [errorString]);
const [visible] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
useError(errorMessage, [errorMessage]);
useEffect(() => {
if (error) {
const errorMessage = formatState(error);
setErrorMessage(errorMessage);
}
}, [error]);
const navigate = useNavigate();
const [commentMediaInfo, setCommentMediaInfo] = useState(null);
const fallbackImgUrl = "assets/filedeleted-res.gif";
@@ -83,22 +96,26 @@ const Pending = () => {
closeModal={() => setIsSettingsOpen(false)} />
<NavBar selectedStyle={selectedStyle}>
<>
{defaultSubplebbits.map(subplebbit => (
<span className="boardList">
[
<Link to={`/p/subscriptions`}>Subscriptions</Link>
]&nbsp;[
{defaultSubplebbits.map((subplebbit, index) => (
<span className="boardList" key={`span-${subplebbit.address}`}>
[
<Link key={`a-${subplebbit.address}`}
to={`/p/${subplebbit.address}`}
onClick={() => {
setSelectedTitle(subplebbit.title);
setSelectedAddress(subplebbit.address);
{index === 0 ? null : "\u00a0"}
<Link to={`/p/${subplebbit.address}`} key={`a-${subplebbit.address}`} onClick={() => {
setSelectedTitle(subplebbit.title);
setSelectedAddress(subplebbit.address);
}}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
]&nbsp;
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
]
</span>
<span className="nav">
[
<Link to={`/p/${selectedAddress}/c/${selectedThread}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
<Link to={`/profile/c/${index}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
]
[
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
@@ -117,7 +134,7 @@ const Pending = () => {
</select>
</div>
<div className="page-jump">
<Link to={`/p/${selectedAddress}/c/${selectedThread}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
<Link to={`/profile/c/${index}/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
&nbsp;
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
)}>Home</Link>
@@ -178,7 +195,7 @@ const Pending = () => {
<Link to={`/p/${selectedAddress}/catalog`}>Catalog</Link>
]
</span>
<span className="reply-stat">{formatState(publishingState)}</span>
<span className="reply-stat">{stateString}</span>
<hr />
</TopBar>
<Tooltip id="tooltip" className="tooltip" />
+57 -14
View File
@@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet-async';
import { Link, useNavigate } from 'react-router-dom';
import { Tooltip } from 'react-tooltip';
import { Virtuoso } from 'react-virtuoso';
import { useAccount, useAccountComments, useFeed } from '@plebbit/plebbit-react-hooks';
import { useAccount, useAccountComments, useFeed, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils'
import { debounce } from 'lodash';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
@@ -23,6 +23,7 @@ import handleImageClick from '../../utils/handleImageClick';
import handleQuoteClick from '../../utils/handleQuoteClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError';
import useFeedStateString from '../../hooks/useFeedStateString';
import packageJson from '../../../package.json'
const {version} = packageJson
@@ -45,15 +46,39 @@ const Subscriptions = () => {
const navigate = useNavigate();
const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
useError(errorMessage, [errorMessage]);
const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'new'});
const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp));
const {subplebbits} = useSubplebbits({subplebbitAddresses: account?.subscriptions, sortType: 'new'});
const stateString = useFeedStateString(subplebbits);
const errorString = useMemo(() => {
for (const subplebbit of subplebbits) {
if (subplebbit?.updatingState !== 'failed') {
return
}
}
for (const subplebbit of subplebbits) {
if (subplebbit?.error) {
return `Failed fetching subplebbit: ${subplebbit?.error.toString().slice(0, 300)}`
}
}
}, [subplebbits])
useEffect(() => {
if (errorString) {
setErrorMessage(errorString)
}
}, [errorString])
useEffect(() => {
setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp));
}, [feed]);
const [errorMessage] = useState(null);
useError(errorMessage, [errorMessage]);
const flattenedRepliesByThread = useMemo(() => {
@@ -172,10 +197,6 @@ const Subscriptions = () => {
{index === 0 ? null : "\u00a0"}
<Link to={`/p/${subplebbit.address}`} key={`a-${subplebbit.address}`} onClick={() => handleClickTitle(subplebbit.title, subplebbit.address)}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
<OfflineIndicator
address={subplebbit.address}
className="offline-nav"
tooltipPlace="bottom" />
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
@@ -262,6 +283,13 @@ const Subscriptions = () => {
<Link to={`/p/subscriptions/catalog`}>Catalog</Link>
]
</div>
{feed.length > 0 ? (
null
) : (
<div id="stats" style={{float: "right", marginTop: "5px"}}>
<span>{stateString}</span>
</div>
)}
<div id="catalog-button-mobile">
<span className="btn-wrap">
<Link to={`/p/subscriptions/catalog`}>Catalog</Link>
@@ -523,12 +551,27 @@ const Subscriptions = () => {
)}
&nbsp;p/
<Link key={`p-t-${index}`} to={`/p/${thread.subplebbitAddress}`} id="reply-button" title="Visit this board">
{thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : (
<span key={`short-add${index}`}
data-tooltip-id="tooltip"
data-tooltip-content={thread.subplebbitAddress}
data-tooltip-place="top"
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
{thread.subplebbitAddress.includes(".eth") ?
(
<>
{thread.subplebbitAddress}
<OfflineIndicator key={`offline-reply-${index}`}
address={thread.subplebbitAddress}
tooltipPlace="top"
className="offline-reply" />
</>
) : (
<>
<span key={`short-add${index}`}
data-tooltip-id="tooltip"
data-tooltip-content={thread.subplebbitAddress}
data-tooltip-place="top"
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
<OfflineIndicator key={`offline-reply-${index}`}
address={thread.subplebbitAddress}
tooltipPlace="top"
className="offline-reply" />
</>
)}
</Link>
</span>&nbsp;
+17 -7
View File
@@ -2,6 +2,7 @@ import React, { Fragment, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import InfiniteScroll from 'react-infinite-scroller';
import { Link, useNavigate } from 'react-router-dom';
import { Tooltip } from 'react-tooltip';
import { useAccount, useFeed } from '@plebbit/plebbit-react-hooks';
import { debounce } from 'lodash';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
@@ -106,10 +107,6 @@ const SubscriptionsCatalog = () => {
setSelectedAddress(subplebbit.address);
}}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
<OfflineIndicator
address={subplebbit.address}
className="offline-nav"
tooltipPlace="bottom" />
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
@@ -203,6 +200,7 @@ const SubscriptionsCatalog = () => {
</div>
<hr />
</TopBar>
<Tooltip id="tooltip" className="tooltip" />
<Threads selectedStyle={selectedStyle}>
{selectedFeed.length < 1 ? (
<CatalogLoader />
@@ -254,9 +252,21 @@ const SubscriptionsCatalog = () => {
) : null}
</Fragment>
) : null}
{/* <div key={`ti-${index}`} className="thread-icons" >
<span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky"></span>
</div> */}
<div key={`ti-${index}`} className="thread-icons" >
{thread.link ? (
// <span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky"></span> */
<OfflineIndicator
address={thread.subplebbitAddress}
className="thread-icon offline-icon"
tooltipPlace="top" />
) : (
// <span key={`si-${index}`} className="thread-icon sticky-icon-no-link" title="Sticky"></span> */
<OfflineIndicator
address={thread.subplebbitAddress}
className="thread-icon offline-icon-no-link"
tooltipPlace="top" />
) }
</div>
<div key={`meta-${index}`} className="meta" title="(R)eplies / (I)mage Replies" >
R:
<b key={`b-${index}`}>{thread.replyCount}</b>
+35 -13
View File
@@ -15,7 +15,6 @@ import PostLoader from '../PostLoader';
import ReplyModal from '../ReplyModal';
import SettingsModal from '../SettingsModal';
import findShortParentCid from '../../utils/findShortParentCid';
import formatState from '../../utils/formatState';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getDate from '../../utils/getDate';
import handleAddressClick from '../../utils/handleAddressClick';
@@ -24,6 +23,7 @@ import handleQuoteClick from '../../utils/handleQuoteClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useClickForm from '../../hooks/useClickForm';
import useError from '../../hooks/useError';
import useStateString from '../../hooks/useStateString';
import packageJson from '../../../package.json'
const {version} = packageJson
@@ -52,6 +52,11 @@ const Thread = () => {
const commentRef = useRef();
const linkRef = useRef();
const [triggerPublishComment, setTriggerPublishComment] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
useError(errorMessage, [errorMessage]);
const [isReplyOpen, setIsReplyOpen] = useState(false);
const navigate = useNavigate();
const [prevScrollPos, setPrevScrollPos] = useState(0);
@@ -61,13 +66,28 @@ const Thread = () => {
const { subplebbitAddress, threadCid } = useParams();
const handleClickForm = useClickForm();
const stateString = useStateString(comment?.clients);
const commentMediaInfo = getCommentMediaInfo(comment);
const fallbackImgUrl = "assets/filedeleted-res.gif";
const [errorMessage, setErrorMessage] = useState(null);
useError(errorMessage, [errorMessage]);
const [triggerPublishComment, setTriggerPublishComment] = useState(false);
const errorString = useMemo(() => {
if (comment?.state === 'failed') {
let errorString = 'Failed fetching thread.'
if (comment.error) {
errorString += `: ${comment.error.toString().slice(0, 300)}`
}
return errorString
}
}, [comment?.state, comment?.error])
useEffect(() => {
if (errorString) {
setErrorMessage(errorString);
}
}, [errorString]);
const flattenedReplies = useMemo(() =>
@@ -210,6 +230,7 @@ const Thread = () => {
await publishComment();
resetFields();
})();
setTriggerPublishComment(false);
}
}, [publishCommentOptions, triggerPublishComment, publishComment, resetFields]);
@@ -298,10 +319,6 @@ const Thread = () => {
setSelectedAddress(subplebbit.address);
}}
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
<OfflineIndicator
address={subplebbit.address}
className="offline-nav"
tooltipPlace="bottom" />
{index !== defaultSubplebbits.length - 1 ? " /" : null}
</span>
))}
@@ -358,7 +375,12 @@ const Thread = () => {
</div>
<>
<div className="board-title">{selectedTitle}</div>
<div className="board-address">p/{selectedAddress}</div>
<div className="board-address">p/{selectedAddress}
<OfflineIndicator
address={selectedAddress}
className="offline"
tooltipPlace="top" />
</div>
</>
</>
</Header>
@@ -463,10 +485,10 @@ const Thread = () => {
<span className="reply-stat">No replies yet</span>
)
) : (
<span className="reply-stat">{formatState(comment.state)}</span>
<span className="reply-stat">{stateString}</span>
)
) : (
<span className="reply-stat">{formatState(comment.state)}</span>
<span className="reply-stat">{stateString}</span>
)}
<hr />
</TopBar>
@@ -1036,10 +1058,10 @@ const Thread = () => {
<span className="reply-stat">No replies yet</span>
)
) : (
<span className="reply-stat">{formatState(comment.state)}</span>
<span className="reply-stat">{stateString}</span>
)
) : (
<span className="reply-stat">{formatState(comment.state)}</span>
<span className="reply-stat">{stateString}</span>
)}
<hr />
</div>
+64
View File
@@ -0,0 +1,64 @@
import { useMemo } from "react"
const useFeedStateString = (subplebbits) => {
return useMemo(() => {
const getClientUrls = (regex) => {
const clientUrls = new Set()
for (const clientType in subplebbits.clients) {
for (const clientUrl in subplebbits.clients[clientType]) {
const client = subplebbits.clients[clientType][clientUrl]
if (client.state.match(regex)) {
clientUrls.add(clientUrl)
}
}
}
return [...clientUrls]
}
if (!subplebbits) {
return undefined;
}
const states = {}
for (const subplebbit of subplebbits) {
states[subplebbit?.updatingState] = (states[subplebbit?.updatingState] || 0) + 1
}
// e.g. Resolving 2 addresses from infura.io, fetching 2 IPNS, 1 IPFS from cloudflare-ipfs.com, ipfs.io
let stateString = ''
if (states['resolving-address']) {
stateString += `resolving ${states['resolving-address']} addresses`
const clientUrls = getClientUrls(/address/)
if (clientUrls.length) {
stateString += ` from ${clientUrls.join(', ')}`
}
}
if (states['fetching-ipns'] || states['fetching-ipfs']) {
if (stateString) {
stateString += ', '
}
stateString += `fetching `
if (states['fetching-ipns']) {
stateString += `${states['fetching-ipns']} IPNS`
}
if (states['fetching-ipfs']) {
if (states['fetching-ipns']) {
stateString += ', '
}
stateString += `${states['fetching-ipfs']} IPNS`
}
const clientUrls = getClientUrls(/ipfs|ipns/)
if (clientUrls.length) {
stateString += ` from ${clientUrls.join(', ')}`
}
}
// capitalize first letter
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1)
// if string is empty, return undefined instead
return stateString || undefined
}, [subplebbits])
}
export default useFeedStateString
+54
View File
@@ -0,0 +1,54 @@
import { useMemo } from 'react'
import isValidUrl from '../utils/isValidUrl'
const useStateString = (clients) => {
return useMemo(() => {
if (!clients) {
return
}
const states = {}
for (const clientType in clients) {
for (const clientUrl in clients[clientType]) {
const state = clients[clientType][clientUrl].state
if (state === 'stopped') {
continue
}
if (!states[state]) {
states[state] = []
}
states[state].push(clientUrl)
}
}
let stateString = ''
for (const state in states) {
const clientUrls = states[state]
const clientHosts = clientUrls.filter(isValidUrl).map(clientUrl => new URL(clientUrl).hostname)
// if there are no valid hosts, skip this state
if (clientHosts.length === 0) {
continue
}
// separate 2 different states using ', '
if (stateString) {
stateString += ', '
}
// e.g. 'fetching IPFS from cloudflare-ipfs.com, ipfs.io'
const formattedState = state.replaceAll('-', ' ').replace('ipfs', 'IPFS').replace('ipns', 'IPNS')
stateString += `${formattedState} from ${clientHosts.join(', ')}`
}
// capitalize first letter
if (stateString) {
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1)
}
// if string is empty, return undefined instead
return stateString === '' ? undefined : stateString
}, [clients])
}
export default useStateString
-27
View File
@@ -1,27 +0,0 @@
function formatState(str) {
if (!str) {
return null;
}
const formattedWords = str
.replace(/-/g, " ")
.split(" ")
.map((word, index) => {
word = word.toLowerCase();
if (word === "ipfs" || word === "ipns") {
return word.toUpperCase();
} else if (index === 0) {
return word.charAt(0).toUpperCase() + word.slice(1);
} else {
return word;
}
});
if (formattedWords.includes("Failed")) {
return formattedWords.join(" ") + ".";
}
return formattedWords.join(" ") + "...";
}
export default formatState;
+5 -5
View File
@@ -2613,9 +2613,9 @@
mkdirp "^1.0.4"
rimraf "^3.0.2"
"@plebbit/plebbit-js@https://github.com/plebbit/plebbit-js.git#1073e876f52514195e87512281a9d2d7bbefbd6c":
"@plebbit/plebbit-js@https://github.com/plebbit/plebbit-js.git#bcf4ec7ba024fdd7dc0bb2fab76c27c93597dd50":
version "0.0.3"
resolved "https://github.com/plebbit/plebbit-js.git#1073e876f52514195e87512281a9d2d7bbefbd6c"
resolved "https://github.com/plebbit/plebbit-js.git#bcf4ec7ba024fdd7dc0bb2fab76c27c93597dd50"
dependencies:
"@keyv/sqlite" "3.6.2"
"@plebbit/plebbit-logger" "github:plebbit/plebbit-logger"
@@ -2668,11 +2668,11 @@
dependencies:
debug "4.3.3"
"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#91ea5a494fde74836b085e912f671cc5489ecf09":
"@plebbit/plebbit-react-hooks@https://github.com/plebbit/plebbit-react-hooks.git#e602eda8080f2b3449388c2d5868d6e6da60c8f6":
version "0.0.1"
resolved "https://github.com/plebbit/plebbit-react-hooks.git#91ea5a494fde74836b085e912f671cc5489ecf09"
resolved "https://github.com/plebbit/plebbit-react-hooks.git#e602eda8080f2b3449388c2d5868d6e6da60c8f6"
dependencies:
"@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#1073e876f52514195e87512281a9d2d7bbefbd6c"
"@plebbit/plebbit-js" "https://github.com/plebbit/plebbit-js.git#bcf4ec7ba024fdd7dc0bb2fab76c27c93597dd50"
"@plebbit/plebbit-logger" "https://github.com/plebbit/plebbit-logger.git"
assert "2.0.0"
ethers "5.6.9"