moved some repeated code

This commit is contained in:
plebbitor
2023-03-20 17:08:05 +01:00
parent 1885a105e4
commit 90b53d49fd
14 changed files with 299 additions and 586 deletions
+85 -3
View File
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes, useLocation } from 'react-router-dom';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import useBoardStore from './useBoardStore'; import useAppStore from './useAppStore';
import Home from './components/views/Home'; import Home from './components/views/Home';
import Board from './components/views/Board'; import Board from './components/views/Board';
import Thread from './components/views/Thread'; import Thread from './components/views/Thread';
@@ -48,8 +48,19 @@ const StyledContainer = styled(ToastContainer)`
export default function App() { export default function App() {
const { bodyStyle, setBodyStyle } = useBoardStore(state => state); const {
bodyStyle, setBodyStyle,
setDefaultSubplebbits,
setIsSettingsOpen,
setSelectedStyle,
setShowPostForm,
setShowPostFormLink,
} = useAppStore(state => state);
const location = useLocation();
// preload images
const imageUrls = Array.from({ length: 14 }, (_, index) => `/assets/banners/banner-${index + 1}.jpg`); const imageUrls = Array.from({ length: 14 }, (_, index) => `/assets/banners/banner-${index + 1}.jpg`);
useEffect(() => { useEffect(() => {
@@ -57,6 +68,77 @@ export default function App() {
}, []); }, []);
// automatic dark mode without interefering with user's selected style
useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkMode = darkModeMediaQuery.matches;
if (isDarkMode) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
const darkModeListener = (e) => {
if (e.matches) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
};
darkModeMediaQuery.addEventListener('change', darkModeListener);
return () => {
darkModeMediaQuery.removeEventListener('change', darkModeListener);
};
}, []);
// fetch default subplebbits
useEffect(() => {
let didCancel = false;
fetch(
"https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json",
{ cache: "no-cache" }
)
.then((res) => res.json())
.then(res => {
if (!didCancel) {
setDefaultSubplebbits(res);
}
});
return () => {
didCancel = true;
};
}, []);
// handle nested routes
useEffect(() => {
const path = location.pathname;
if (path.endsWith('/post')) {
setShowPostFormLink(false);
setShowPostForm(true);
} else {
setShowPostFormLink(true);
setShowPostForm(false);
}
if (path.endsWith('/settings')) {
setIsSettingsOpen(true);
} else {
setIsSettingsOpen(false);
}
}, [location.pathname]);
return ( return (
<div> <div>
<Helmet> <Helmet>
+3 -3
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import useBoardStore from '../useBoardStore'; import useAppStore from '../useAppStore';
import Modal from 'react-modal'; import Modal from 'react-modal';
import styled from 'styled-components'; import styled from 'styled-components';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
@@ -246,8 +246,8 @@ const StyledModal = styled(Modal)`
const CaptchaModal = ({ isOpen, closeModal, captchaImage }) => { const CaptchaModal = ({ isOpen, closeModal, captchaImage }) => {
const { captchaResponse, setCaptchaResponse } = useBoardStore(state => state); const { captchaResponse, setCaptchaResponse } = useAppStore(state => state);
const selectedStyle = useBoardStore(state => state.selectedStyle); const selectedStyle = useAppStore(state => state.selectedStyle);
const handleCloseModal = () => { const handleCloseModal = () => {
closeModal(); closeModal();
+2 -2
View File
@@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import ContentLoader from 'react-content-loader'; import ContentLoader from 'react-content-loader';
import useBoardStore from '../useBoardStore'; import useAppStore from '../useAppStore';
const SingleRectLoader = () => { const SingleRectLoader = () => {
const selectedStyle = useBoardStore((state) => state.selectedStyle); const selectedStyle = useAppStore((state) => state.selectedStyle);
const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3'; const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3';
const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb'; const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb';
+2 -2
View File
@@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import ContentLoader from 'react-content-loader'; import ContentLoader from 'react-content-loader';
import useBoardStore from '../useBoardStore'; import useAppStore from '../useAppStore';
const SinglePostLoader = () => { const SinglePostLoader = () => {
const selectedStyle = useBoardStore((state) => state.selectedStyle); const selectedStyle = useAppStore((state) => state.selectedStyle);
const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3'; const backgroundColor = selectedStyle === 'Tomorrow' ? '#333' : '#f3f3f3';
const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb'; const foregroundColor = selectedStyle === 'Tomorrow' ? '#555' : '#ecebeb';
+2 -2
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import useBoardStore from '../useBoardStore'; import useAppStore from '../useAppStore';
import Modal from 'react-modal'; import Modal from 'react-modal';
import styled from 'styled-components'; import styled from 'styled-components';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
@@ -248,7 +248,7 @@ const StyledModal = styled(Modal)`
const ReplyModal = ({ isOpen, closeModal }) => { const ReplyModal = ({ isOpen, closeModal }) => {
const selectedStyle = useBoardStore(state => state.selectedStyle); const selectedStyle = useAppStore(state => state.selectedStyle);
const handleCloseModal = () => { const handleCloseModal = () => {
closeModal(); closeModal();
+2 -2
View File
@@ -2,7 +2,7 @@ import React from "react";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import Modal from "react-modal"; import Modal from "react-modal";
import styled from "styled-components"; import styled from "styled-components";
import useBoardStore from "../useBoardStore"; import useAppStore from "../useAppStore";
const StyledModal = styled(Modal)` const StyledModal = styled(Modal)`
.panel { .panel {
@@ -140,7 +140,7 @@ const customOverlayStyles = {
}; };
const SettingsModal = ({ isOpen, closeModal }) => { const SettingsModal = ({ isOpen, closeModal }) => {
const selectedStyle = useBoardStore(state => state.selectedStyle); const selectedStyle = useAppStore(state => state.selectedStyle);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
+15 -185
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, Fragment } from 'react'; import React, { useState, useEffect, Fragment } from 'react';
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
import useBoardStore from '../../useBoardStore'; import useAppStore from '../../useAppStore';
import { Container, NavBar, Header, Break, PostFormLink, PostFormTable, PostForm, TopBar, BoardForm } from './styles/Board.styled'; import { Container, NavBar, Header, Break, PostFormLink, PostFormTable, PostForm, TopBar, BoardForm } from './styles/Board.styled';
import CaptchaModal from '../CaptchaModal'; import CaptchaModal from '../CaptchaModal';
import ImageBanner from '../ImageBanner'; import ImageBanner from '../ImageBanner';
@@ -10,39 +10,35 @@ import SettingsModal from '../SettingsModal';
import { useFeed, useAccountsActions } from '@plebbit/plebbit-react-hooks'; import { useFeed, useAccountsActions } from '@plebbit/plebbit-react-hooks';
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
import { Tooltip } from 'react-tooltip'; import { Tooltip } from 'react-tooltip';
import handleStyleChange from '../../utils/handleStyleChange';
import onError from '../../utils/onError'; import onError from '../../utils/onError';
import onSuccess from '../../utils/onSuccess'; import onSuccess from '../../utils/onSuccess';
import getDate from '../../utils/getDate'; import getDate from '../../utils/getDate';
import renderComments from '../../utils/renderComments'; import renderComments from '../../utils/renderComments';
import useClickForm from '../../hooks/useClickForm';
const Board = ({ setBodyStyle }) => { const Board = () => {
const { const {
selectedTitle, captchaResponse, setCaptchaResponse,
setSelectedTitle, defaultSubplebbits,
selectedAddress, isSettingsOpen, setIsSettingsOpen,
setSelectedAddress, selectedAddress, setSelectedAddress,
setSelectedThread,
selectedStyle, selectedStyle,
setSelectedStyle, setSelectedThread,
captchaResponse, selectedTitle, setSelectedTitle,
setCaptchaResponse showPostForm,
} = useBoardStore(state => state); showPostFormLink
} = useAppStore(state => state);
const [defaultSubplebbits, setDefaultSubplebbits] = useState([]);
const [showPostFormLink, setShowPostFormLink] = useState(true);
const [showPostForm, setShowPostForm] = useState(false);
const [name, setName] = useState(''); const [name, setName] = useState('');
const [subject, setSubject] = useState(''); const [subject, setSubject] = useState('');
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const { publishComment } = useAccountsActions(); const { publishComment } = useAccountsActions();
const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false);
const [isReplyOpen, setIsReplyOpen] = useState(false); const [isReplyOpen, setIsReplyOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [captchaImage, setCaptchaImage] = useState(''); const [captchaImage, setCaptchaImage] = useState('');
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [prevScrollPos, setPrevScrollPos] = useState(0); const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
const [endIndex, setEndIndex] = useState(2); const [endIndex, setEndIndex] = useState(2);
@@ -50,6 +46,7 @@ const Board = ({ setBodyStyle }) => {
const [selectedFeed, setSelectedFeed] = useState(feed); const [selectedFeed, setSelectedFeed] = useState(feed);
const renderedFeed = selectedFeed.slice(0, endIndex); const renderedFeed = selectedFeed.slice(0, endIndex);
const { subplebbitAddress } = useParams(); const { subplebbitAddress } = useParams();
const handleClickForm = useClickForm();
// temporary title from JSON, gets subplebbitAddress from URL // temporary title from JSON, gets subplebbitAddress from URL
@@ -66,24 +63,6 @@ const Board = ({ setBodyStyle }) => {
setSelectedFeed(feed); setSelectedFeed(feed);
}, [feed]); }, [feed]);
// fetches default subplebbits from JSON
useEffect(() => {
let didCancel = false;
fetch(
"https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json",
{ cache: "no-cache" }
)
.then((res) => res.json())
.then(res => {
if (!didCancel) {
setDefaultSubplebbits(res);
}
});
return () => {
didCancel = true;
};
}, []);
// mobile navbar scroll effect // mobile navbar scroll effect
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
@@ -103,60 +82,6 @@ const Board = ({ setBodyStyle }) => {
setEndIndex(2); setEndIndex(2);
}, [selectedAddress]); }, [selectedAddress]);
// nested routes handling
useEffect(() => {
const path = location.pathname;
if (path.endsWith('/post')) {
setShowPostFormLink(false);
setShowPostForm(true);
} else {
setShowPostFormLink(true);
setShowPostForm(false);
}
if (path.endsWith('/settings')) {
setIsSettingsOpen(true);
} else {
setIsSettingsOpen(false);
}
}, [location.pathname]);
// automatic dark mode without interefering with user's selected style
useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkMode = darkModeMediaQuery.matches;
if (isDarkMode) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
const darkModeListener = (e) => {
if (e.matches) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
};
darkModeMediaQuery.addEventListener('change', darkModeListener);
return () => {
darkModeMediaQuery.removeEventListener('change', darkModeListener);
};
}, []);
const tryLoadMore = async () => { const tryLoadMore = async () => {
@@ -260,13 +185,6 @@ const Board = ({ setBodyStyle }) => {
}; };
const handleClickForm = () => {
setShowPostFormLink(false);
setShowPostForm(true);
navigate(`/${selectedAddress}/post`);
};
const handleClickThread = (thread) => { const handleClickThread = (thread) => {
setSelectedThread(thread); setSelectedThread(thread);
}; };
@@ -323,94 +241,6 @@ const Board = ({ setBodyStyle }) => {
} }
const handleStyleChange = (event) => {
switch (event.target.value) {
case "Yotsuba":
const yotsubaBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle));
break;
case "Yotsuba-B":
const yotsubaBBodyStyle = {
background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x",
color: "#000",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBBodyStyle);
setSelectedStyle("Yotsuba-B");
localStorage.setItem("selectedStyle", "Yotsuba-B");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle));
break;
case "Futaba":
const futabaBodyStyle = {
background: "#ffe",
color: "maroon",
fontFamily: "times new roman, serif"
};
setBodyStyle(futabaBodyStyle);
setSelectedStyle("Futaba");
localStorage.setItem("selectedStyle", "Futaba");
localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle));
break;
case "Burichan":
const burichanBodyStyle = {
background: "#eef2ff",
color: "#000",
fontFamily: "times new roman, serif"
};
setBodyStyle(burichanBodyStyle);
setSelectedStyle("Burichan");
localStorage.setItem("selectedStyle", "Burichan");
localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle));
break;
case "Tomorrow":
const tomorrowBodyStyle = {
background: "#1d1f21 none",
color: "#c5c8c6",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(tomorrowBodyStyle);
setSelectedStyle("Tomorrow");
localStorage.setItem("selectedStyle", "Tomorrow");
localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle));
break;
case "Photon":
const photonBodyStyle = {
background: "#eee none",
color: "#333",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(photonBodyStyle);
setSelectedStyle("Photon");
localStorage.setItem("selectedStyle", "Photon");
localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle));
break;
default:
const defaultBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(defaultBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle));
}
}
return ( return (
<Container> <Container>
+15 -185
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
import useBoardStore from '../../useBoardStore'; import useAppStore from '../../useAppStore';
import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from './styles/Board.styled'; import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from './styles/Board.styled';
import { TopBar } from './styles/Thread.styled'; import { TopBar } from './styles/Thread.styled';
import { Threads } from './styles/Catalog.styled'; import { Threads } from './styles/Catalog.styled';
@@ -9,41 +9,38 @@ import ImageBanner from '../ImageBanner';
import CaptchaModal from '../CaptchaModal'; import CaptchaModal from '../CaptchaModal';
import CatalogLoader from '../CatalogLoader'; import CatalogLoader from '../CatalogLoader';
import SettingsModal from '../SettingsModal'; import SettingsModal from '../SettingsModal';
import handleStyleChange from '../../utils/handleStyleChange';
import onError from '../../utils/onError'; import onError from '../../utils/onError';
import onSuccess from '../../utils/onSuccess'; import onSuccess from '../../utils/onSuccess';
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
import useClickForm from '../../hooks/useClickForm';
const Catalog = ({ setBodyStyle }) => { const Catalog = () => {
const { const {
selectedTitle, captchaResponse, setCaptchaResponse,
setSelectedTitle, defaultSubplebbits,
selectedAddress, isSettingsOpen, setIsSettingsOpen,
setSelectedAddress, selectedAddress, setSelectedAddress,
setSelectedThread,
selectedStyle, selectedStyle,
setSelectedStyle, setSelectedThread,
captchaResponse, selectedTitle, setSelectedTitle,
setCaptchaResponse showPostForm,
} = useBoardStore(state => state); showPostFormLink
} = useAppStore(state => state);
const [defaultSubplebbits, setDefaultSubplebbits] = useState([]);
const [showPostFormLink, setShowPostFormLink] = useState(true);
const [showPostForm, setShowPostForm] = useState(false);
const [name, setName] = useState(''); const [name, setName] = useState('');
const [subject, setSubject] = useState(''); const [subject, setSubject] = useState('');
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const { publishComment } = useAccountsActions(); const { publishComment } = useAccountsActions();
const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [captchaImage, setCaptchaImage] = useState(''); const [captchaImage, setCaptchaImage] = useState('');
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [prevScrollPos, setPrevScrollPos] = useState(0); const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
const { feed, hasMore, loadMore } = useFeed([`${selectedAddress}`], 'new'); const { feed, hasMore, loadMore } = useFeed([`${selectedAddress}`], 'new');
const { subplebbitAddress } = useParams(); const { subplebbitAddress } = useParams();
const handleClickForm = useClickForm();
@@ -56,24 +53,6 @@ const Catalog = ({ setBodyStyle }) => {
}, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]);
useEffect(() => {
let didCancel = false;
fetch(
"https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json",
{ cache: "no-cache" }
)
.then((res) => res.json())
.then(res => {
if (!didCancel) {
setDefaultSubplebbits(res);
}
});
return () => {
didCancel = true;
};
}, []);
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const currentScrollPos = window.pageYOffset; const currentScrollPos = window.pageYOffset;
@@ -87,59 +66,6 @@ const Catalog = ({ setBodyStyle }) => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, [prevScrollPos, visible]); }, [prevScrollPos, visible]);
// nested routes handling
useEffect(() => {
const path = location.pathname;
if (path.endsWith('/post')) {
setShowPostFormLink(false);
setShowPostForm(true);
} else {
setShowPostFormLink(true);
setShowPostForm(false);
}
if (path.endsWith('/settings')) {
setIsSettingsOpen(true);
} else {
setIsSettingsOpen(false);
}
}, [location.pathname]);
// automatic dark mode without interefering with user's selected style
useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkMode = darkModeMediaQuery.matches;
if (isDarkMode) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
const darkModeListener = (e) => {
if (e.matches) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
};
darkModeMediaQuery.addEventListener('change', darkModeListener);
return () => {
darkModeMediaQuery.removeEventListener('change', darkModeListener);
};
}, []);
const tryLoadMore = async () => { const tryLoadMore = async () => {
@@ -225,14 +151,6 @@ const Catalog = ({ setBodyStyle }) => {
alert("- Embedding media is optional, posts can be text-only. \n- A CAPTCHA challenge will appear after posting. \n- The CAPTCHA is case-sensitive."); alert("- Embedding media is optional, posts can be text-only. \n- A CAPTCHA challenge will appear after posting. \n- The CAPTCHA is case-sensitive.");
}; };
const handleClickForm = () => {
setShowPostFormLink(false);
setShowPostForm(true);
navigate(`/${selectedAddress}/catalog/post`);
};
const handleClickThread = (thread) => { const handleClickThread = (thread) => {
setSelectedThread(thread); setSelectedThread(thread);
} }
@@ -271,94 +189,6 @@ const Catalog = ({ setBodyStyle }) => {
} }
const handleStyleChange = (event) => {
switch (event.target.value) {
case "Yotsuba":
const yotsubaBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle));
break;
case "Yotsuba-B":
const yotsubaBBodyStyle = {
background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x",
color: "#000",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBBodyStyle);
setSelectedStyle("Yotsuba-B");
localStorage.setItem("selectedStyle", "Yotsuba-B");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle));
break;
case "Futaba":
const futabaBodyStyle = {
background: "#ffe",
color: "maroon",
fontFamily: "times new roman, serif"
};
setBodyStyle(futabaBodyStyle);
setSelectedStyle("Futaba");
localStorage.setItem("selectedStyle", "Futaba");
localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle));
break;
case "Burichan":
const burichanBodyStyle = {
background: "#eef2ff",
color: "#000",
fontFamily: "times new roman, serif"
};
setBodyStyle(burichanBodyStyle);
setSelectedStyle("Burichan");
localStorage.setItem("selectedStyle", "Burichan");
localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle));
break;
case "Tomorrow":
const tomorrowBodyStyle = {
background: "#1d1f21 none",
color: "#c5c8c6",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(tomorrowBodyStyle);
setSelectedStyle("Tomorrow");
localStorage.setItem("selectedStyle", "Tomorrow");
localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle));
break;
case "Photon":
const photonBodyStyle = {
background: "#eee none",
color: "#333",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(photonBodyStyle);
setSelectedStyle("Photon");
localStorage.setItem("selectedStyle", "Photon");
localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle));
break;
default:
const defaultBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(defaultBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle));
}
}
return ( return (
<Container> <Container>
+2 -2
View File
@@ -1,12 +1,12 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import useBoardStore from '../../useBoardStore'; import useAppStore from '../../useAppStore';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Container, Header, Logo, Page, Search, About, AboutTitle, AboutContent, Boards, BoardsTitle, BoardsContent, Footer } from './styles/Home.styled'; import { Container, Header, Logo, Page, Search, About, AboutTitle, AboutContent, Boards, BoardsTitle, BoardsContent, Footer } from './styles/Home.styled';
const Home = ({ setBodyStyle }) => { const Home = ({ setBodyStyle }) => {
const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); const [defaultSubplebbits, setDefaultSubplebbits] = useState([]);
const { setSelectedTitle, setSelectedAddress, setSelectedStyle } = useBoardStore(state => state); const { setSelectedTitle, setSelectedAddress, setSelectedStyle } = useAppStore(state => state);
useEffect(() => { useEffect(() => {
setBodyStyle({ setBodyStyle({
+2 -2
View File
@@ -1,12 +1,12 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import useBoardStore from '../../useBoardStore'; import useAppStore from '../../useAppStore';
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Container, Header, Logo, Page, Boards, BoardsTitle } from './styles/Home.styled'; import { Container, Header, Logo, Page, Boards, BoardsTitle } from './styles/Home.styled';
const NotFound = ({ setBodyStyle }) => { const NotFound = ({ setBodyStyle }) => {
const { setSelectedStyle } = useBoardStore(state => state); const { setSelectedStyle } = useAppStore(state => state);
useEffect(() => { useEffect(() => {
setBodyStyle({ setBodyStyle({
+17 -186
View File
@@ -1,53 +1,50 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
import useBoardStore from '../../useBoardStore'; import useAppStore from '../../useAppStore';
import { Container, NavBar, Header, Break, PostForm, PostFormTable, BoardForm } from './styles/Board.styled'; import { Container, NavBar, Header, Break, PostForm, PostFormTable, BoardForm } from './styles/Board.styled';
import { ReplyFormLink, TopBar, BottomBar } from './styles/Thread.styled'; import { ReplyFormLink, TopBar, BottomBar } from './styles/Thread.styled';
import { useComment, useAccountsActions } from '@plebbit/plebbit-react-hooks'; import { useComment, useAccountsActions } from '@plebbit/plebbit-react-hooks';
import { Tooltip } from 'react-tooltip';
import CaptchaModal from '../CaptchaModal'; import CaptchaModal from '../CaptchaModal';
import ImageBanner from '../ImageBanner'; import ImageBanner from '../ImageBanner';
import PostLoader from '../PostLoader'; import PostLoader from '../PostLoader';
import ReplyModal from '../ReplyModal'; import ReplyModal from '../ReplyModal';
import SettingsModal from '../SettingsModal'; import SettingsModal from '../SettingsModal';
import { Tooltip } from 'react-tooltip'; import handleStyleChange from '../../utils/handleStyleChange';
import onError from '../../utils/onError'; import onError from '../../utils/onError';
import onSuccess from '../../utils/onSuccess'; import onSuccess from '../../utils/onSuccess';
import getDate from '../../utils/getDate'; import getDate from '../../utils/getDate';
import renderThreadComments from '../../utils/renderThreadComments'; import renderThreadComments from '../../utils/renderThreadComments';
import useClickForm from '../../hooks/useClickForm';
const Thread = ({ setBodyStyle }) => { const Thread = () => {
const { const {
selectedTitle, captchaResponse, setCaptchaResponse,
setSelectedTitle, defaultSubplebbits,
selectedAddress, isSettingsOpen, setIsSettingsOpen,
setSelectedAddress, selectedAddress, setSelectedAddress,
selectedThread,
setSelectedThread,
selectedStyle, selectedStyle,
setSelectedStyle, selectedThread, setSelectedThread,
captchaResponse, selectedTitle, setSelectedTitle,
setCaptchaResponse showPostForm,
} = useBoardStore(state => state); showPostFormLink
} = useAppStore(state => state);
const [defaultSubplebbits, setDefaultSubplebbits] = useState([]);
const [showPostFormLink, setShowPostFormLink] = useState(true);
const [showPostForm, setShowPostForm] = useState(false);
const [name, setName] = useState(''); const [name, setName] = useState('');
const [subject, setSubject] = useState(''); const [subject, setSubject] = useState('');
const [commentContent, setCommentContent] = useState(''); const [commentContent, setCommentContent] = useState('');
const { publishComment } = useAccountsActions(); const { publishComment } = useAccountsActions();
const [isCaptchaOpen, setIsCaptchaOpen] = useState(false); const [isCaptchaOpen, setIsCaptchaOpen] = useState(false);
const [isReplyOpen, setIsReplyOpen] = useState(false); const [isReplyOpen, setIsReplyOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [captchaImage, setCaptchaImage] = useState(''); const [captchaImage, setCaptchaImage] = useState('');
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [prevScrollPos, setPrevScrollPos] = useState(0); const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
const comment = useComment(`${selectedThread}`); const comment = useComment(`${selectedThread}`);
const { subplebbitAddress, threadCid } = useParams(); const { subplebbitAddress, threadCid } = useParams();
const handleClickForm = useClickForm();
// temporary title from JSON, gets subplebbitAddress and threadCid from URL // temporary title from JSON, gets subplebbitAddress and threadCid from URL
@@ -60,24 +57,6 @@ const Thread = ({ setBodyStyle }) => {
} }
}, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]); }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits]);
// fetches default subplebbits from JSON
useEffect(() => {
let didCancel = false;
fetch(
"https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json",
{ cache: "no-cache" }
)
.then((res) => res.json())
.then(res => {
if (!didCancel) {
setDefaultSubplebbits(res);
}
});
return () => {
didCancel = true;
};
}, []);
// mobile navbar scroll effect // mobile navbar scroll effect
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
@@ -92,59 +71,6 @@ const Thread = ({ setBodyStyle }) => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, [prevScrollPos, visible]); }, [prevScrollPos, visible]);
// nested routes handling
useEffect(() => {
const path = location.pathname;
if (path.endsWith('/post')) {
setShowPostFormLink(false);
setShowPostForm(true);
} else {
setShowPostFormLink(true);
setShowPostForm(false);
}
if (path.endsWith('/settings')) {
setIsSettingsOpen(true);
} else {
setIsSettingsOpen(false);
}
}, [location.pathname]);
// automatic dark mode without interefering with user's selected style
useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkMode = darkModeMediaQuery.matches;
if (isDarkMode) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
const darkModeListener = (e) => {
if (e.matches) {
setSelectedStyle('Tomorrow');
setBodyStyle({
background: '#1d1f21 none',
color: '#c5c8c6',
fontFamily: 'Arial, Helvetica, sans-serif'
});
localStorage.setItem('selectedStyle', 'Tomorrow');
}
};
darkModeMediaQuery.addEventListener('change', darkModeListener);
return () => {
darkModeMediaQuery.removeEventListener('change', darkModeListener);
};
}, []);
const onChallengeVerification = (challengeVerification) => { const onChallengeVerification = (challengeVerification) => {
@@ -227,13 +153,6 @@ const Thread = ({ setBodyStyle }) => {
}; };
const handleClickForm = () => {
setShowPostFormLink(false);
setShowPostForm(true);
navigate(`/${selectedAddress}/thread/${selectedThread}/post`);
};
const handleClickTop = () => { const handleClickTop = () => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
@@ -295,94 +214,6 @@ const Thread = ({ setBodyStyle }) => {
} }
const handleStyleChange = (event) => {
switch (event.target.value) {
case "Yotsuba":
const yotsubaBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle));
break;
case "Yotsuba-B":
const yotsubaBBodyStyle = {
background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x",
color: "#000",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBBodyStyle);
setSelectedStyle("Yotsuba-B");
localStorage.setItem("selectedStyle", "Yotsuba-B");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle));
break;
case "Futaba":
const futabaBodyStyle = {
background: "#ffe",
color: "maroon",
fontFamily: "times new roman, serif"
};
setBodyStyle(futabaBodyStyle);
setSelectedStyle("Futaba");
localStorage.setItem("selectedStyle", "Futaba");
localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle));
break;
case "Burichan":
const burichanBodyStyle = {
background: "#eef2ff",
color: "#000",
fontFamily: "times new roman, serif"
};
setBodyStyle(burichanBodyStyle);
setSelectedStyle("Burichan");
localStorage.setItem("selectedStyle", "Burichan");
localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle));
break;
case "Tomorrow":
const tomorrowBodyStyle = {
background: "#1d1f21 none",
color: "#c5c8c6",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(tomorrowBodyStyle);
setSelectedStyle("Tomorrow");
localStorage.setItem("selectedStyle", "Tomorrow");
localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle));
break;
case "Photon":
const photonBodyStyle = {
background: "#eee none",
color: "#333",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(photonBodyStyle);
setSelectedStyle("Photon");
localStorage.setItem("selectedStyle", "Photon");
localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle));
break;
default:
const defaultBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(defaultBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle));
}
}
return ( return (
<Container> <Container>
+32
View File
@@ -0,0 +1,32 @@
import { useNavigate, useLocation } from "react-router-dom";
import useAppStore from "../useAppStore";
const useClickForm = () => {
const navigate = useNavigate();
const location = useLocation();
const { setShowPostForm, setShowPostFormLink } = useAppStore.getState();
const handleClickForm = () => {
setShowPostForm(true);
setShowPostFormLink(false);
const subplebbitAddress = location.pathname.split("/")[1];
const threadCid = location.pathname.split("/")[3];
if (location.pathname === "/") {
navigate("/post");
} else if (location.pathname.endsWith("/catalog")) {
navigate(`/${subplebbitAddress}/catalog/post`);
} else if (location.pathname.endsWith("/thread")) {
navigate(`/${subplebbitAddress}/thread/post`);
} else if (threadCid) {
navigate(`/${subplebbitAddress}/thread/${threadCid}/post`);
} else {
navigate(`/${subplebbitAddress}/post`);
}
};
return handleClickForm;
};
export default useClickForm;
+22 -8
View File
@@ -1,6 +1,9 @@
import { create } from 'zustand'; import { create } from 'zustand';
const useBoardStore = create((set) => ({ const useAppStore = create((set) => ({
captchaResponse: '',
setCaptchaResponse: (response) => set({ captchaResponse: response }),
bodyStyle: JSON.parse(localStorage.getItem('bodyStyle')) || { bodyStyle: JSON.parse(localStorage.getItem('bodyStyle')) || {
background: '#ffe url(/assets/fade.png) top repeat-x', background: '#ffe url(/assets/fade.png) top repeat-x',
color: 'maroon', color: 'maroon',
@@ -11,14 +14,15 @@ const useBoardStore = create((set) => ({
set(() => ({ bodyStyle })); set(() => ({ bodyStyle }));
}, },
selectedTitle: '', defaultSubplebbits: [],
setSelectedTitle: (title) => set({ selectedTitle: title }), setDefaultSubplebbits: (subplebbits) => set({ defaultSubplebbits: subplebbits }),
isSettingsOpen: false,
setIsSettingsOpen: (isOpen) => set({ isSettingsOpen: isOpen }),
selectedAddress: '', selectedAddress: '',
setSelectedAddress: (address) => set({ selectedAddress: address }), setSelectedAddress: (address) => set({ selectedAddress: address }),
selectedThread: '',
setSelectedThread: (thread) => set({ selectedThread: thread }),
selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba', selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba',
setSelectedStyle: (style) => { setSelectedStyle: (style) => {
@@ -26,8 +30,18 @@ const useBoardStore = create((set) => ({
set({ selectedStyle: style }); set({ selectedStyle: style });
}, },
captchaResponse: '', selectedThread: '',
setCaptchaResponse: (response) => set({ captchaResponse: response }), 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 }),
})); }));
export default useBoardStore; export default useAppStore;
+94
View File
@@ -0,0 +1,94 @@
import useAppStore from "../useAppStore";
const handleStyleChange = (event) => {
const {
setBodyStyle, setSelectedStyle
} = useAppStore.getState();
switch (event.target.value) {
case "Yotsuba":
const yotsubaBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBodyStyle));
break;
case "Yotsuba-B":
const yotsubaBBodyStyle = {
background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x",
color: "#000",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(yotsubaBBodyStyle);
setSelectedStyle("Yotsuba-B");
localStorage.setItem("selectedStyle", "Yotsuba-B");
localStorage.setItem("bodyStyle", JSON.stringify(yotsubaBBodyStyle));
break;
case "Futaba":
const futabaBodyStyle = {
background: "#ffe",
color: "maroon",
fontFamily: "times new roman, serif"
};
setBodyStyle(futabaBodyStyle);
setSelectedStyle("Futaba");
localStorage.setItem("selectedStyle", "Futaba");
localStorage.setItem("bodyStyle", JSON.stringify(futabaBodyStyle));
break;
case "Burichan":
const burichanBodyStyle = {
background: "#eef2ff",
color: "#000",
fontFamily: "times new roman, serif"
};
setBodyStyle(burichanBodyStyle);
setSelectedStyle("Burichan");
localStorage.setItem("selectedStyle", "Burichan");
localStorage.setItem("bodyStyle", JSON.stringify(burichanBodyStyle));
break;
case "Tomorrow":
const tomorrowBodyStyle = {
background: "#1d1f21 none",
color: "#c5c8c6",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(tomorrowBodyStyle);
setSelectedStyle("Tomorrow");
localStorage.setItem("selectedStyle", "Tomorrow");
localStorage.setItem("bodyStyle", JSON.stringify(tomorrowBodyStyle));
break;
case "Photon":
const photonBodyStyle = {
background: "#eee none",
color: "#333",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(photonBodyStyle);
setSelectedStyle("Photon");
localStorage.setItem("selectedStyle", "Photon");
localStorage.setItem("bodyStyle", JSON.stringify(photonBodyStyle));
break;
default:
const defaultBodyStyle = {
background: "#ffe url(/assets/fade.png) top repeat-x",
color: "maroon",
fontFamily: "Arial, Helvetica, sans-serif"
};
setBodyStyle(defaultBodyStyle);
setSelectedStyle("Yotsuba");
localStorage.setItem("selectedStyle", "Yotsuba");
localStorage.setItem("bodyStyle", JSON.stringify(defaultBodyStyle));
}
}
export default handleStyleChange;