mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+1
-1
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
] [
|
||||
{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>
|
||||
]
|
||||
{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>
|
||||
|
||||
<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" />
|
||||
|
||||
@@ -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 = () => {
|
||||
)}
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user