mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf: rewrite plebchan completely
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
const useAnonModeStore = create(
|
||||
persist(
|
||||
(set) => ({
|
||||
anonymousMode: true,
|
||||
setAnonymousMode: (mode) => set({ anonymousMode: mode }),
|
||||
}),
|
||||
{
|
||||
name: 'anonmode_store',
|
||||
getStorage: () => localStorage,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export default useAnonModeStore;
|
||||
@@ -1,123 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
const useGeneralStore = create((set) => ({
|
||||
bodyStyle: JSON.parse(localStorage.getItem('bodyStyle')) || {
|
||||
background: '#ffe url(assets/fade.png) top repeat-x',
|
||||
color: 'maroon',
|
||||
fontFamily: 'Helvetica, Arial, sans-serif',
|
||||
},
|
||||
setBodyStyle: (bodyStyle) => {
|
||||
localStorage.setItem('bodyStyle', JSON.stringify(bodyStyle));
|
||||
set(() => ({ bodyStyle }));
|
||||
},
|
||||
|
||||
captchaResponse: '',
|
||||
setCaptchaResponse: (response) => set({ captchaResponse: response }),
|
||||
|
||||
challengesArray: [],
|
||||
setChallengesArray: (challengesArray) => set({ challengesArray }),
|
||||
|
||||
defaultSubplebbits: [],
|
||||
setDefaultSubplebbits: (subplebbits) => set({ defaultSubplebbits: subplebbits }),
|
||||
|
||||
defaultNsfwSubplebbits: [],
|
||||
setDefaultNsfwSubplebbits: (subplebbits) => set({ defaultNsfwSubplebbits: subplebbits }),
|
||||
|
||||
deletePost: false,
|
||||
setDeletePost: (deletePost) => set({ deletePost }),
|
||||
|
||||
editedComment: '',
|
||||
setEditedComment: (comment) => set({ editedComment: comment }),
|
||||
|
||||
editedComments: {},
|
||||
setEditedComments: (comments) => set({ editedComments: comments }),
|
||||
|
||||
feedCacheStates: {},
|
||||
setFeedCacheState: (address, isCached) =>
|
||||
set((prev) => ({
|
||||
feedCacheStates: {
|
||||
...prev.feedCacheStates,
|
||||
[address]: isCached,
|
||||
},
|
||||
})),
|
||||
|
||||
isAuthorDelete: false,
|
||||
setIsAuthorDelete: (isAuthorDelete) => set({ isAuthorDelete }),
|
||||
|
||||
isAuthorEdit: false,
|
||||
setIsAuthorEdit: (isAuthorEdit) => set({ isAuthorEdit }),
|
||||
|
||||
isCaptchaOpen: false,
|
||||
setIsCaptchaOpen: (isOpen) => set({ isCaptchaOpen: isOpen }),
|
||||
|
||||
isEditModalOpen: false,
|
||||
setIsEditModalOpen: (isOpen) => set({ isEditModalOpen: isOpen }),
|
||||
|
||||
canModerate: false,
|
||||
setCanModerate: (canModerate) => set({ canModerate }),
|
||||
|
||||
isModerationOpen: false,
|
||||
setIsModerationOpen: (isOpen) => set({ isModerationOpen: isOpen }),
|
||||
|
||||
isModEdit: false,
|
||||
setIsModEdit: (isModEdit) => set({ isModEdit }),
|
||||
|
||||
isSettingsOpen: false,
|
||||
setIsSettingsOpen: (isOpen) => set({ isSettingsOpen: isOpen }),
|
||||
|
||||
moderatingCommentCid: '',
|
||||
setModeratingCommentCid: (cid) => set({ moderatingCommentCid: cid }),
|
||||
|
||||
originalCommentContent: null,
|
||||
setOriginalCommentContent: (content) => set({ originalCommentContent: content }),
|
||||
|
||||
pendingComment: '',
|
||||
setPendingComment: (comment) => set({ pendingComment: comment }),
|
||||
|
||||
pendingCommentIndex: null,
|
||||
setPendingCommentIndex: (index) => set({ pendingCommentIndex: index }),
|
||||
|
||||
publishedComment: '',
|
||||
setPublishedComment: (comment) => set({ publishedComment: comment }),
|
||||
|
||||
replyQuoteCid: '',
|
||||
setReplyQuoteCid: (cid) => set({ replyQuoteCid: cid }),
|
||||
|
||||
resolveCaptchaPromise: null,
|
||||
setResolveCaptchaPromise: (resolve) => set({ resolveCaptchaPromise: resolve }),
|
||||
|
||||
selectedAddress: '',
|
||||
setSelectedAddress: (address) => set({ selectedAddress: address }),
|
||||
|
||||
selectedParentCid: '',
|
||||
setSelectedParentCid: (parentCid) => set({ selectedParentCid: parentCid }),
|
||||
|
||||
selectedShortCid: '',
|
||||
setSelectedShortCid: (shortCid) => set({ selectedShortCid: shortCid }),
|
||||
|
||||
selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba',
|
||||
setSelectedStyle: (style) => {
|
||||
localStorage.setItem('selectedStyle', style);
|
||||
set({ selectedStyle: style });
|
||||
},
|
||||
|
||||
selectedText: '',
|
||||
setSelectedText: (text) => set({ selectedText: text }),
|
||||
|
||||
selectedThread: '',
|
||||
setSelectedThread: (thread) => set({ selectedThread: thread }),
|
||||
|
||||
selectedTitle: '',
|
||||
setSelectedTitle: (title) => set({ selectedTitle: title }),
|
||||
|
||||
showPostForm: false,
|
||||
setShowPostForm: (show) => set({ showPostForm: show }),
|
||||
|
||||
showPostFormLink: true,
|
||||
setShowPostFormLink: (show) => set({ showPostFormLink: show }),
|
||||
|
||||
triggerInsertion: 0,
|
||||
setTriggerInsertion: (trigger) => set({ triggerInsertion: trigger }),
|
||||
}));
|
||||
|
||||
export default useGeneralStore;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||
import useAnonModeStore from './stores/useAnonModeStore';
|
||||
|
||||
const useAnonMode = (threadCid, execute) => {
|
||||
const account = useAccount();
|
||||
const { anonymousMode } = useAnonModeStore();
|
||||
|
||||
useEffect(() => {
|
||||
const handleAnonMode = async () => {
|
||||
let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
|
||||
|
||||
if (!anonymousMode) {
|
||||
if (execute && storedSigners[threadCid]) {
|
||||
const signerPrivateKey = storedSigners[threadCid];
|
||||
if (account) {
|
||||
await account.plebbit.createSigner({ type: 'ed25519', privateKey: signerPrivateKey });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAnonMode();
|
||||
}, [threadCid, execute, account, anonymousMode]);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
export default useAnonMode;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||
import useAnonModeStore from './stores/useAnonModeStore';
|
||||
|
||||
const useAnonModeRef = (threadCidRef, execute) => {
|
||||
const account = useAccount();
|
||||
const { anonymousMode } = useAnonModeStore();
|
||||
|
||||
useEffect(() => {
|
||||
const handleAnonMode = async () => {
|
||||
let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
|
||||
|
||||
if (!anonymousMode) {
|
||||
if (execute && storedSigners[threadCidRef]) {
|
||||
const signerPrivateKey = storedSigners[threadCidRef];
|
||||
if (account) {
|
||||
await account.plebbit.createSigner({ type: 'ed25519', privateKey: signerPrivateKey });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAnonMode();
|
||||
}, [threadCidRef, execute, account, anonymousMode]);
|
||||
|
||||
return;
|
||||
};
|
||||
export default useAnonModeRef;
|
||||
@@ -1,14 +0,0 @@
|
||||
import useGeneralStore from './stores/useGeneralStore';
|
||||
|
||||
const useClickForm = () => {
|
||||
const { setShowPostForm, setShowPostFormLink } = useGeneralStore.getState();
|
||||
|
||||
const handleClickForm = () => {
|
||||
setShowPostForm(true);
|
||||
setShowPostFormLink(false);
|
||||
};
|
||||
|
||||
return handleClickForm;
|
||||
};
|
||||
|
||||
export default useClickForm;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const useError = () => {
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (errorMessage && errorMessage.length > 0) {
|
||||
const showErrorToast = () => {
|
||||
const toastId = toast.error(errorMessage.toString(), {
|
||||
position: 'top-right',
|
||||
autoClose: false,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: false,
|
||||
draggable: false,
|
||||
progress: undefined,
|
||||
theme: 'dark',
|
||||
});
|
||||
|
||||
return () => {
|
||||
toast.dismiss(toastId);
|
||||
};
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(showErrorToast, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
}, [errorMessage, renderCount]);
|
||||
|
||||
const setNewErrorMessage = (error) => {
|
||||
let message;
|
||||
if (typeof error === 'string') {
|
||||
message = error;
|
||||
} else if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else {
|
||||
message = JSON.stringify(error);
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setRenderCount((prevCount) => prevCount + 1);
|
||||
};
|
||||
|
||||
return [errorMessage, setNewErrorMessage];
|
||||
};
|
||||
|
||||
export default useError;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
|
||||
const useFeedRows = (feedWithDescriptionAndRules, columnCount) => {
|
||||
const rowsRef = useRef([]);
|
||||
return useMemo(() => {
|
||||
const rows = [];
|
||||
for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) {
|
||||
if (rowsRef.current?.[rows.length] && rowsRef.current[rows.length].length === columnCount) {
|
||||
rows.push(rowsRef.current[rows.length]);
|
||||
} else {
|
||||
rows.push(feedWithDescriptionAndRules.slice(i, i + columnCount));
|
||||
}
|
||||
}
|
||||
rowsRef.current = rows;
|
||||
return rows;
|
||||
}, [feedWithDescriptionAndRules, columnCount]);
|
||||
};
|
||||
|
||||
export default useFeedRows;
|
||||
@@ -1,98 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import useStateString from './useStateString';
|
||||
import { useSubplebbit, useSubplebbitsStates } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
const clientHosts = {};
|
||||
const getClientHost = (clientUrl) => {
|
||||
if (!clientHosts[clientUrl]) {
|
||||
try {
|
||||
clientHosts[clientUrl] = new URL(clientUrl).hostname || clientUrl;
|
||||
} catch (e) {
|
||||
clientHosts[clientUrl] = clientUrl;
|
||||
}
|
||||
}
|
||||
return clientHosts[clientUrl];
|
||||
};
|
||||
|
||||
const useFeedStateString = (subplebbitAddresses) => {
|
||||
// single subplebbit feed state string
|
||||
const subplebbitAddress = subplebbitAddresses?.length === 1 ? subplebbitAddresses[0] : undefined;
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||
const singleSubplebbitFeedStateString = useStateString(subplebbit);
|
||||
|
||||
// multiple subplebbit feed state string
|
||||
const { states } = useSubplebbitsStates({ subplebbitAddresses });
|
||||
|
||||
const multipleSubplebbitsFeedStateString = useMemo(() => {
|
||||
if (subplebbitAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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']) {
|
||||
const { subplebbitAddresses, clientUrls } = states['resolving-address'];
|
||||
if (subplebbitAddresses.length && clientUrls.length) {
|
||||
stateString += `resolving ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? 'address' : 'addresses'} from ${clientUrls
|
||||
.map(getClientHost)
|
||||
.join(', ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
// find all page client and sub addresses
|
||||
const pagesStatesClientHosts = new Set();
|
||||
const pagesStatesSubplebbitAddresses = new Set();
|
||||
for (const state in states) {
|
||||
if (state.match('page')) {
|
||||
states[state].clientUrls.forEach((clientUrl) => pagesStatesClientHosts.add(getClientHost(clientUrl)));
|
||||
states[state].subplebbitAddresses.forEach((subplebbitAddress) => pagesStatesSubplebbitAddresses.add(subplebbitAddress));
|
||||
}
|
||||
}
|
||||
|
||||
if (states['fetching-ipns'] || states['fetching-ipfs'] || pagesStatesSubplebbitAddresses.size) {
|
||||
// separate 2 different states using ', '
|
||||
if (stateString) {
|
||||
stateString += ', ';
|
||||
}
|
||||
|
||||
// find all client urls
|
||||
const clientHosts = new Set([...pagesStatesClientHosts]);
|
||||
states['fetching-ipns']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl)));
|
||||
states['fetching-ipfs']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl)));
|
||||
|
||||
if (clientHosts.size) {
|
||||
stateString += 'fetching ';
|
||||
if (states['fetching-ipns']) {
|
||||
stateString += `${states['fetching-ipns'].subplebbitAddresses.length} IPNS`;
|
||||
}
|
||||
if (states['fetching-ipfs']) {
|
||||
if (states['fetching-ipns']) {
|
||||
stateString += ', ';
|
||||
}
|
||||
stateString += `${states['fetching-ipfs'].subplebbitAddresses.length} IPFS`;
|
||||
}
|
||||
if (pagesStatesSubplebbitAddresses.size) {
|
||||
if (states['fetching-ipns'] || states['fetching-ipfs']) {
|
||||
stateString += ', ';
|
||||
}
|
||||
stateString += `${pagesStatesSubplebbitAddresses.size} ${pagesStatesSubplebbitAddresses.size === 1 ? 'page' : 'pages'}`;
|
||||
}
|
||||
stateString += ` from ${[...clientHosts].join(', ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
// capitalize first letter
|
||||
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1);
|
||||
|
||||
// if string is empty, return undefined instead
|
||||
return stateString === '' ? undefined : stateString;
|
||||
}, [states, subplebbitAddress]);
|
||||
|
||||
if (singleSubplebbitFeedStateString) {
|
||||
return singleSubplebbitFeedStateString;
|
||||
}
|
||||
return multipleSubplebbitsFeedStateString;
|
||||
};
|
||||
|
||||
export default useFeedStateString;
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const useInfo = () => {
|
||||
const [infoMessage, setInfoMessage] = useState('');
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (infoMessage && infoMessage.length > 0) {
|
||||
const showInfoToast = () => {
|
||||
const toastId = toast.info(infoMessage.toString(), {
|
||||
position: 'top-right',
|
||||
autoClose: false,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: false,
|
||||
draggable: false,
|
||||
progress: undefined,
|
||||
theme: 'dark',
|
||||
});
|
||||
|
||||
return () => {
|
||||
toast.dismiss(toastId);
|
||||
};
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(showInfoToast, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
}, [infoMessage, renderCount]);
|
||||
|
||||
const setNewInfoMessage = (message) => {
|
||||
setInfoMessage(message);
|
||||
setRenderCount((prevCount) => prevCount + 1);
|
||||
};
|
||||
|
||||
return [infoMessage, setNewInfoMessage];
|
||||
};
|
||||
|
||||
export default useInfo;
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useClientsStates } from '@plebbit/plebbit-react-hooks';
|
||||
|
||||
const clientHosts = {};
|
||||
const getClientHost = (clientUrl) => {
|
||||
if (!clientHosts[clientUrl]) {
|
||||
try {
|
||||
clientHosts[clientUrl] = new URL(clientUrl).hostname || clientUrl;
|
||||
} catch (e) {
|
||||
clientHosts[clientUrl] = clientUrl;
|
||||
}
|
||||
}
|
||||
return clientHosts[clientUrl];
|
||||
};
|
||||
|
||||
const useStateString = (commentOrSubplebbit) => {
|
||||
const { states } = useClientsStates({ comment: commentOrSubplebbit });
|
||||
return useMemo(() => {
|
||||
let stateString = '';
|
||||
for (const state in states) {
|
||||
const clientUrls = states[state];
|
||||
const clientHosts = clientUrls.map((clientUrl) => getClientHost(clientUrl));
|
||||
|
||||
// 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(', ')}`;
|
||||
}
|
||||
|
||||
// fallback to comment or subplebbit state when possible
|
||||
if (!stateString && commentOrSubplebbit?.state !== 'succeeded') {
|
||||
if (commentOrSubplebbit?.publishingState && commentOrSubplebbit?.publishingState !== 'stopped' && commentOrSubplebbit?.publishingState !== 'succeeded') {
|
||||
stateString = commentOrSubplebbit.publishingState;
|
||||
} else if (commentOrSubplebbit?.updatingState !== 'stopped' && commentOrSubplebbit?.updatingState !== 'succeeded') {
|
||||
stateString = commentOrSubplebbit.updatingState;
|
||||
}
|
||||
if (stateString) {
|
||||
stateString = stateString.replaceAll('-', ' ').replace('ipfs', 'IPFS').replace('ipns', 'IPNS');
|
||||
}
|
||||
}
|
||||
|
||||
// capitalize first letter
|
||||
if (stateString) {
|
||||
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1);
|
||||
}
|
||||
|
||||
// if string is empty, return undefined instead
|
||||
return stateString === '' ? undefined : stateString;
|
||||
}, [states, commentOrSubplebbit]);
|
||||
};
|
||||
|
||||
export default useStateString;
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const useSuccess = () => {
|
||||
const [successMessage, setSuccessMessage] = useState('');
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (successMessage && successMessage.length > 0) {
|
||||
const showSuccessToast = () => {
|
||||
const toastId = toast.success(successMessage.toString(), {
|
||||
position: 'top-right',
|
||||
autoClose: 3000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: false,
|
||||
draggable: false,
|
||||
progress: undefined,
|
||||
theme: 'dark',
|
||||
});
|
||||
|
||||
return () => {
|
||||
toast.dismiss(toastId);
|
||||
};
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(showSuccessToast, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
}, [successMessage, renderCount]);
|
||||
|
||||
const setNewSuccessMessage = (message) => {
|
||||
setSuccessMessage(message);
|
||||
setRenderCount((prevCount) => prevCount + 1);
|
||||
};
|
||||
|
||||
return [successMessage, setNewSuccessMessage];
|
||||
};
|
||||
|
||||
export default useSuccess;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function useWindowWidth() {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowWidth(window.innerWidth);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
return windowWidth;
|
||||
}
|
||||
Reference in New Issue
Block a user