mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor home component
This commit is contained in:
@@ -29,7 +29,7 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isMediaShowed &&
|
isMediaShowed &&
|
||||||
(replyCount > 0 || postsPerSub > 1) &&
|
replyCount > 0 &&
|
||||||
!deleted &&
|
!deleted &&
|
||||||
!removed &&
|
!removed &&
|
||||||
!locked &&
|
!locked &&
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface HomeFiltersStore {
|
||||||
|
showNsfwBoardsOnly: boolean;
|
||||||
|
setShowNsfwBoardsOnly: (value: boolean) => void;
|
||||||
|
showWorksafeBoardsOnly: boolean;
|
||||||
|
setShowWorksafeBoardsOnly: (value: boolean) => void;
|
||||||
|
useCatalog: boolean;
|
||||||
|
setUseCatalog: (value: boolean) => void;
|
||||||
|
showWorksafeContentOnly: boolean;
|
||||||
|
setShowWorksafeContentOnly: (value: boolean) => void;
|
||||||
|
showNsfwContentOnly: boolean;
|
||||||
|
setShowNsfwContentOnly: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useHomeFiltersStore = create<HomeFiltersStore>((set) => ({
|
||||||
|
showNsfwBoardsOnly: localStorage.getItem('showNsfwBoardsOnly') === 'true' ? true : false,
|
||||||
|
setShowNsfwBoardsOnly: (value: boolean) => {
|
||||||
|
set({ showNsfwBoardsOnly: value });
|
||||||
|
localStorage.setItem('showNsfwBoardsOnly', value.toString());
|
||||||
|
},
|
||||||
|
showWorksafeBoardsOnly: localStorage.getItem('showWorksafeBoardsOnly') === 'true' ? true : false,
|
||||||
|
setShowWorksafeBoardsOnly: (value: boolean) => {
|
||||||
|
set({ showWorksafeBoardsOnly: value });
|
||||||
|
localStorage.setItem('showWorksafeBoardsOnly', value.toString());
|
||||||
|
},
|
||||||
|
useCatalog: localStorage.getItem('useCatalog') === 'true' ? true : false,
|
||||||
|
setUseCatalog: (value: boolean) => {
|
||||||
|
set({ useCatalog: value });
|
||||||
|
localStorage.setItem('useCatalog', value.toString());
|
||||||
|
},
|
||||||
|
showWorksafeContentOnly: localStorage.getItem('showWorksafeContentOnly') === 'true' ? true : false,
|
||||||
|
setShowWorksafeContentOnly: (value: boolean) => {
|
||||||
|
set({ showWorksafeContentOnly: value });
|
||||||
|
localStorage.setItem('showWorksafeContentOnly', value.toString());
|
||||||
|
},
|
||||||
|
showNsfwContentOnly: localStorage.getItem('showNsfwContentOnly') === 'true' ? true : false,
|
||||||
|
setShowNsfwContentOnly: (value: boolean) => {
|
||||||
|
set({ showNsfwContentOnly: value });
|
||||||
|
localStorage.setItem('showNsfwContentOnly', value.toString());
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useHomeFiltersStore;
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Subplebbit, useAccount, useAccountSubplebbits } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
|
import useHomeFiltersStore from '../../../stores/use-home-filters';
|
||||||
|
import { useMultisubMetadata } from '../../../hooks/use-default-subplebbits';
|
||||||
|
import styles from '../home.module.css';
|
||||||
|
import { nsfwTags } from '../home';
|
||||||
|
|
||||||
|
const BoxModal = ({ isBoardsBoxModal }: { isBoardsBoxModal: boolean }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
showNsfwBoardsOnly,
|
||||||
|
setShowNsfwBoardsOnly,
|
||||||
|
showWorksafeBoardsOnly,
|
||||||
|
setShowWorksafeBoardsOnly,
|
||||||
|
useCatalog,
|
||||||
|
setUseCatalog,
|
||||||
|
showWorksafeContentOnly,
|
||||||
|
setShowWorksafeContentOnly,
|
||||||
|
showNsfwContentOnly,
|
||||||
|
setShowNsfwContentOnly,
|
||||||
|
} = useHomeFiltersStore();
|
||||||
|
|
||||||
|
const handleClickOutside = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[modalRef, setShowFilterModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [handleClickOutside]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span ref={modalRef} onClick={() => setShowFilterModal(true)}>
|
||||||
|
{isBoardsBoxModal ? t('filter') : t('options')} ▼
|
||||||
|
</span>
|
||||||
|
{showFilterModal && (
|
||||||
|
<div ref={modalRef} className={styles.filterModal}>
|
||||||
|
{isBoardsBoxModal ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${!showNsfwBoardsOnly && !showWorksafeBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowNsfwBoardsOnly(false);
|
||||||
|
setShowWorksafeBoardsOnly(false);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show All Boards
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showNsfwBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showWorksafeBoardsOnly) {
|
||||||
|
setShowWorksafeBoardsOnly(false);
|
||||||
|
}
|
||||||
|
setShowNsfwBoardsOnly(!showNsfwBoardsOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show NSFW Boards Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showWorksafeBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showNsfwBoardsOnly) {
|
||||||
|
setShowNsfwBoardsOnly(false);
|
||||||
|
}
|
||||||
|
setShowWorksafeBoardsOnly(!showWorksafeBoardsOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show Worksafe Boards Only
|
||||||
|
</div>
|
||||||
|
<div className={styles.separator} />
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${useCatalog && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setUseCatalog(!useCatalog);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use Catalog
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showWorksafeContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showNsfwContentOnly) {
|
||||||
|
setShowNsfwContentOnly(false);
|
||||||
|
}
|
||||||
|
setShowWorksafeContentOnly(!showWorksafeContentOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show Worksafe Content Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showNsfwContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showWorksafeContentOnly) {
|
||||||
|
setShowWorksafeContentOnly(false);
|
||||||
|
}
|
||||||
|
setShowNsfwContentOnly(!showNsfwContentOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show NSFW Content Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${!showWorksafeContentOnly && !showNsfwContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowWorksafeContentOnly(false);
|
||||||
|
setShowNsfwContentOnly(false);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show All Content
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Board = ({ isOffline, subplebbit }: { isOffline: boolean; subplebbit: Subplebbit }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { address, title, tags } = subplebbit || {};
|
||||||
|
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
|
||||||
|
const { useCatalog } = useHomeFiltersStore();
|
||||||
|
|
||||||
|
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.subplebbit} key={address}>
|
||||||
|
{isOffline && <span className={styles.offlineIcon} />}
|
||||||
|
<Link to={boardLink}>{title || address}</Link>
|
||||||
|
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoardsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const account = useAccount();
|
||||||
|
const subscriptions = account?.subscriptions || [];
|
||||||
|
const { accountSubplebbits } = useAccountSubplebbits();
|
||||||
|
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
||||||
|
const { showNsfwBoardsOnly, showWorksafeBoardsOnly } = useHomeFiltersStore();
|
||||||
|
|
||||||
|
const filterSubs = (subs: Subplebbit[], includeTags: string[], excludeTags: string[] = []) =>
|
||||||
|
subs.filter((sub) => includeTags.every((tag) => sub.tags?.includes(tag)) && excludeTags.every((tag) => !sub.tags?.includes(tag)));
|
||||||
|
|
||||||
|
let plebbitSubs = filterSubs(multisub, ['plebbit']);
|
||||||
|
let interestsSubs = filterSubs(multisub, ['topic'], ['plebbit', 'country', 'international']);
|
||||||
|
let randomSubs = filterSubs(multisub, ['random'], ['plebbit']);
|
||||||
|
let internationalSubs = filterSubs(multisub, ['international']);
|
||||||
|
let projectsSubs = filterSubs(multisub, ['project'], ['plebbit', 'topic']);
|
||||||
|
|
||||||
|
const filterByNsfw = (subs: Subplebbit[], includeNsfw: boolean) => subs.filter((sub) => sub.tags.some((tag: string) => nsfwTags.includes(tag)) === includeNsfw);
|
||||||
|
|
||||||
|
const filterCategoriesByNsfw = (subsArray: Subplebbit[][], includeNsfw: boolean) => subsArray.map((subs) => filterByNsfw(subs, includeNsfw));
|
||||||
|
|
||||||
|
const includeNsfw = showNsfwBoardsOnly ? true : showWorksafeBoardsOnly ? false : null;
|
||||||
|
|
||||||
|
if (includeNsfw !== null) {
|
||||||
|
[plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs] = filterCategoriesByNsfw(
|
||||||
|
[plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs],
|
||||||
|
includeNsfw,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSubOffline = (address: string) => {
|
||||||
|
const subplebbit = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
||||||
|
const isOffline = subplebbit?.updatedAt && subplebbit.updatedAt < Date.now() / 1000 - 60 * 60;
|
||||||
|
return isOffline;
|
||||||
|
};
|
||||||
|
const multisubMetadata = useMultisubMetadata();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.box} ${styles.boardsBox}`}>
|
||||||
|
<div className={styles.boxBar}>
|
||||||
|
<h2 className={styles.capitalize}>{t('boards')}</h2>
|
||||||
|
<BoxModal isBoardsBoxModal={true} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.boardsBoxContent}>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<h3>Multiboards</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
<div className={styles.subplebbit}>
|
||||||
|
<Link to='/p/all'>{multisubMetadata?.title || 'All'}</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles.subplebbit}>
|
||||||
|
<Link to='/p/subscriptions'>Subscriptions</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{plebbitSubs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3>Plebbit</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
{plebbitSubs.map((sub) => (
|
||||||
|
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{projectsSubs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3>{t('projects')}</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
{projectsSubs.map((sub) => (
|
||||||
|
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.column}>
|
||||||
|
{interestsSubs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3>{t('interests')}</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
{interestsSubs.map((sub) => (
|
||||||
|
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.column}>
|
||||||
|
{randomSubs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3>{t('random')}</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
{randomSubs.map((sub) => (
|
||||||
|
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{internationalSubs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3>{t('international')}</h3>
|
||||||
|
<div className={styles.list}>
|
||||||
|
{internationalSubs.map((sub) => (
|
||||||
|
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<div className={styles.list}>
|
||||||
|
<h3>{t('subscriptions')}</h3>
|
||||||
|
{subscriptions.length > 0
|
||||||
|
? subscriptions.map((address: string, index: number) => (
|
||||||
|
<div className={styles.subplebbit} key={index}>
|
||||||
|
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: t('not_subscribed')}
|
||||||
|
</div>
|
||||||
|
<div className={styles.list}>
|
||||||
|
<h3>{t('moderating')}</h3>
|
||||||
|
{accountSubplebbitAddresses.length > 0
|
||||||
|
? accountSubplebbitAddresses.map((address: string, index: number) => (
|
||||||
|
<div className={styles.subplebbit} key={index}>
|
||||||
|
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: t('not_moderating')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoardsBox;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './boards-box';
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import useHomeFiltersStore from '../../../stores/use-home-filters';
|
||||||
|
import styles from '../home.module.css';
|
||||||
|
|
||||||
|
const BoxModal = ({ isBoardsBoxModal }: { isBoardsBoxModal: boolean }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
showNsfwBoardsOnly,
|
||||||
|
setShowNsfwBoardsOnly,
|
||||||
|
showWorksafeBoardsOnly,
|
||||||
|
setShowWorksafeBoardsOnly,
|
||||||
|
useCatalog,
|
||||||
|
setUseCatalog,
|
||||||
|
showWorksafeContentOnly,
|
||||||
|
setShowWorksafeContentOnly,
|
||||||
|
showNsfwContentOnly,
|
||||||
|
setShowNsfwContentOnly,
|
||||||
|
} = useHomeFiltersStore();
|
||||||
|
|
||||||
|
const handleClickOutside = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[modalRef, setShowFilterModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [handleClickOutside]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span ref={modalRef} onClick={() => setShowFilterModal(true)}>
|
||||||
|
{isBoardsBoxModal ? t('filter') : t('options')} ▼
|
||||||
|
</span>
|
||||||
|
{showFilterModal && (
|
||||||
|
<div ref={modalRef} className={styles.filterModal}>
|
||||||
|
{isBoardsBoxModal ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${!showNsfwBoardsOnly && !showWorksafeBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowNsfwBoardsOnly(false);
|
||||||
|
setShowWorksafeBoardsOnly(false);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show All Boards
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showNsfwBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showWorksafeBoardsOnly) {
|
||||||
|
setShowWorksafeBoardsOnly(false);
|
||||||
|
}
|
||||||
|
setShowNsfwBoardsOnly(!showNsfwBoardsOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show NSFW Boards Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showWorksafeBoardsOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showNsfwBoardsOnly) {
|
||||||
|
setShowNsfwBoardsOnly(false);
|
||||||
|
}
|
||||||
|
setShowWorksafeBoardsOnly(!showWorksafeBoardsOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show Worksafe Boards Only
|
||||||
|
</div>
|
||||||
|
<div className={styles.separator} />
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${useCatalog && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setUseCatalog(!useCatalog);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Use Catalog
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showWorksafeContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showNsfwContentOnly) {
|
||||||
|
setShowNsfwContentOnly(false);
|
||||||
|
}
|
||||||
|
setShowWorksafeContentOnly(!showWorksafeContentOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show Worksafe Content Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${showNsfwContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (showWorksafeContentOnly) {
|
||||||
|
setShowWorksafeContentOnly(false);
|
||||||
|
}
|
||||||
|
setShowNsfwContentOnly(!showNsfwContentOnly);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show NSFW Content Only
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${styles.option} ${!showWorksafeContentOnly && !showNsfwContentOnly && styles.selected}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowWorksafeContentOnly(false);
|
||||||
|
setShowNsfwContentOnly(false);
|
||||||
|
setShowFilterModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show All Content
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoxModal;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './box-modal';
|
||||||
+9
-406
@@ -1,58 +1,17 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Comment, Subplebbit, useAccount, useAccountSubplebbits, useSubplebbits } from '@plebbit/plebbit-react-hooks';
|
import { useSubplebbits } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
|
||||||
import { create } from 'zustand';
|
|
||||||
import styles from './home.module.css';
|
import styles from './home.module.css';
|
||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
import useDefaultSubplebbits, { useMultisubMetadata, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import usePopularPosts from '../../hooks/use-popular-posts';
|
|
||||||
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
||||||
import { getCommentMediaInfo } from '../../lib/utils/media-utils';
|
|
||||||
import { CatalogPostMedia } from '../../components/catalog-row';
|
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
|
import PopularThreadsBox from './popular-threads-box';
|
||||||
|
import BoardsBox from './boards-box';
|
||||||
|
|
||||||
interface BoxModalStore {
|
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
|
||||||
showNsfwBoardsOnly: boolean;
|
export const nsfwTags = ['adult', 'anti', 'gore'];
|
||||||
setShowNsfwBoardsOnly: (value: boolean) => void;
|
|
||||||
showWorksafeBoardsOnly: boolean;
|
|
||||||
setShowWorksafeBoardsOnly: (value: boolean) => void;
|
|
||||||
useCatalog: boolean;
|
|
||||||
setUseCatalog: (value: boolean) => void;
|
|
||||||
showWorksafeContentOnly: boolean;
|
|
||||||
setShowWorksafeContentOnly: (value: boolean) => void;
|
|
||||||
showNsfwContentOnly: boolean;
|
|
||||||
setShowNsfwContentOnly: (value: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useBoxModalStore = create<BoxModalStore>((set) => ({
|
|
||||||
showNsfwBoardsOnly: localStorage.getItem('showNsfwBoardsOnly') === 'true' ? true : false,
|
|
||||||
setShowNsfwBoardsOnly: (value: boolean) => {
|
|
||||||
set({ showNsfwBoardsOnly: value });
|
|
||||||
localStorage.setItem('showNsfwBoardsOnly', value.toString());
|
|
||||||
},
|
|
||||||
showWorksafeBoardsOnly: localStorage.getItem('showWorksafeBoardsOnly') === 'true' ? true : false,
|
|
||||||
setShowWorksafeBoardsOnly: (value: boolean) => {
|
|
||||||
set({ showWorksafeBoardsOnly: value });
|
|
||||||
localStorage.setItem('showWorksafeBoardsOnly', value.toString());
|
|
||||||
},
|
|
||||||
useCatalog: localStorage.getItem('useCatalog') === 'true' ? true : false,
|
|
||||||
setUseCatalog: (value: boolean) => {
|
|
||||||
set({ useCatalog: value });
|
|
||||||
localStorage.setItem('useCatalog', value.toString());
|
|
||||||
},
|
|
||||||
showWorksafeContentOnly: localStorage.getItem('showWorksafeContentOnly') === 'true' ? true : false,
|
|
||||||
setShowWorksafeContentOnly: (value: boolean) => {
|
|
||||||
set({ showWorksafeContentOnly: value });
|
|
||||||
localStorage.setItem('showWorksafeContentOnly', value.toString());
|
|
||||||
},
|
|
||||||
showNsfwContentOnly: localStorage.getItem('showNsfwContentOnly') === 'true' ? true : false,
|
|
||||||
setShowNsfwContentOnly: (value: boolean) => {
|
|
||||||
set({ showNsfwContentOnly: value });
|
|
||||||
localStorage.setItem('showNsfwContentOnly', value.toString());
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -101,362 +60,6 @@ const InfoBox = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
|
|
||||||
const nsfwTags = ['adult', 'anti', 'gore'];
|
|
||||||
|
|
||||||
const Board = ({ isOffline, subplebbit }: { isOffline: boolean; subplebbit: Subplebbit }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { address, title, tags } = subplebbit || {};
|
|
||||||
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
|
|
||||||
const { useCatalog } = useBoxModalStore();
|
|
||||||
|
|
||||||
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.subplebbit} key={address}>
|
|
||||||
{isOffline && <span className={styles.offlineIcon} />}
|
|
||||||
<Link to={boardLink}>{title || address}</Link>
|
|
||||||
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BoxModal = ({ isBoardsBoxModal }: { isBoardsBoxModal: boolean }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
showNsfwBoardsOnly,
|
|
||||||
setShowNsfwBoardsOnly,
|
|
||||||
showWorksafeBoardsOnly,
|
|
||||||
setShowWorksafeBoardsOnly,
|
|
||||||
useCatalog,
|
|
||||||
setUseCatalog,
|
|
||||||
showWorksafeContentOnly,
|
|
||||||
setShowWorksafeContentOnly,
|
|
||||||
showNsfwContentOnly,
|
|
||||||
setShowNsfwContentOnly,
|
|
||||||
} = useBoxModalStore();
|
|
||||||
|
|
||||||
const handleClickOutside = useCallback(
|
|
||||||
(event: MouseEvent) => {
|
|
||||||
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[modalRef, setShowFilterModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [handleClickOutside]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span ref={modalRef} onClick={() => setShowFilterModal(true)}>
|
|
||||||
{isBoardsBoxModal ? t('filter') : t('options')} ▼
|
|
||||||
</span>
|
|
||||||
{showFilterModal && (
|
|
||||||
<div ref={modalRef} className={styles.filterModal}>
|
|
||||||
{isBoardsBoxModal ? (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${!showNsfwBoardsOnly && !showWorksafeBoardsOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
setShowNsfwBoardsOnly(false);
|
|
||||||
setShowWorksafeBoardsOnly(false);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show All Boards
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${showNsfwBoardsOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (showWorksafeBoardsOnly) {
|
|
||||||
setShowWorksafeBoardsOnly(false);
|
|
||||||
}
|
|
||||||
setShowNsfwBoardsOnly(!showNsfwBoardsOnly);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show NSFW Boards Only
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${showWorksafeBoardsOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (showNsfwBoardsOnly) {
|
|
||||||
setShowNsfwBoardsOnly(false);
|
|
||||||
}
|
|
||||||
setShowWorksafeBoardsOnly(!showWorksafeBoardsOnly);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show Worksafe Boards Only
|
|
||||||
</div>
|
|
||||||
<div className={styles.separator} />
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${useCatalog && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
setUseCatalog(!useCatalog);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Use Catalog
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${showWorksafeContentOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (showNsfwContentOnly) {
|
|
||||||
setShowNsfwContentOnly(false);
|
|
||||||
}
|
|
||||||
setShowWorksafeContentOnly(!showWorksafeContentOnly);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show Worksafe Content Only
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${showNsfwContentOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (showWorksafeContentOnly) {
|
|
||||||
setShowWorksafeContentOnly(false);
|
|
||||||
}
|
|
||||||
setShowNsfwContentOnly(!showNsfwContentOnly);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show NSFW Content Only
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${styles.option} ${!showWorksafeContentOnly && !showNsfwContentOnly && styles.selected}`}
|
|
||||||
onClick={() => {
|
|
||||||
setShowWorksafeContentOnly(false);
|
|
||||||
setShowNsfwContentOnly(false);
|
|
||||||
setShowFilterModal(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show All Content
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Boards = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const account = useAccount();
|
|
||||||
const subscriptions = account?.subscriptions || [];
|
|
||||||
const { accountSubplebbits } = useAccountSubplebbits();
|
|
||||||
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
|
||||||
const { showNsfwBoardsOnly, showWorksafeBoardsOnly } = useBoxModalStore();
|
|
||||||
|
|
||||||
const filterSubs = (subs: Subplebbit[], includeTags: string[], excludeTags: string[] = []) =>
|
|
||||||
subs.filter((sub) => includeTags.every((tag) => sub.tags?.includes(tag)) && excludeTags.every((tag) => !sub.tags?.includes(tag)));
|
|
||||||
|
|
||||||
let plebbitSubs = filterSubs(multisub, ['plebbit']);
|
|
||||||
let interestsSubs = filterSubs(multisub, ['topic'], ['plebbit', 'country', 'international']);
|
|
||||||
let randomSubs = filterSubs(multisub, ['random'], ['plebbit']);
|
|
||||||
let internationalSubs = filterSubs(multisub, ['international']);
|
|
||||||
let projectsSubs = filterSubs(multisub, ['project'], ['plebbit', 'topic']);
|
|
||||||
|
|
||||||
const filterByNsfw = (subs: Subplebbit[], includeNsfw: boolean) => subs.filter((sub) => sub.tags.some((tag: string) => nsfwTags.includes(tag)) === includeNsfw);
|
|
||||||
|
|
||||||
const filterCategoriesByNsfw = (subsArray: Subplebbit[][], includeNsfw: boolean) => subsArray.map((subs) => filterByNsfw(subs, includeNsfw));
|
|
||||||
|
|
||||||
const includeNsfw = showNsfwBoardsOnly ? true : showWorksafeBoardsOnly ? false : null;
|
|
||||||
|
|
||||||
if (includeNsfw !== null) {
|
|
||||||
[plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs] = filterCategoriesByNsfw(
|
|
||||||
[plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs],
|
|
||||||
includeNsfw,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubOffline = (address: string) => {
|
|
||||||
const subplebbit = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
|
||||||
const isOffline = subplebbit?.updatedAt && subplebbit.updatedAt < Date.now() / 1000 - 60 * 60;
|
|
||||||
return isOffline;
|
|
||||||
};
|
|
||||||
const multisubMetadata = useMultisubMetadata();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${styles.box} ${styles.boardsBox}`}>
|
|
||||||
<div className={styles.boxBar}>
|
|
||||||
<h2 className={styles.capitalize}>{t('boards')}</h2>
|
|
||||||
<BoxModal isBoardsBoxModal={true} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.boardsBoxContent}>
|
|
||||||
<div className={styles.column}>
|
|
||||||
<h3>Multiboards</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
<div className={styles.subplebbit}>
|
|
||||||
<Link to='/p/all'>{multisubMetadata?.title || 'All'}</Link>
|
|
||||||
</div>
|
|
||||||
<div className={styles.subplebbit}>
|
|
||||||
<Link to='/p/subscriptions'>Subscriptions</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{plebbitSubs.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h3>Plebbit</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
{plebbitSubs.map((sub) => (
|
|
||||||
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{projectsSubs.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h3>{t('projects')}</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
{projectsSubs.map((sub) => (
|
|
||||||
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.column}>
|
|
||||||
{interestsSubs.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h3>{t('interests')}</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
{interestsSubs.map((sub) => (
|
|
||||||
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.column}>
|
|
||||||
{randomSubs.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h3>{t('random')}</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
{randomSubs.map((sub) => (
|
|
||||||
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{internationalSubs.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h3>{t('international')}</h3>
|
|
||||||
<div className={styles.list}>
|
|
||||||
{internationalSubs.map((sub) => (
|
|
||||||
<Board key={sub.address} subplebbit={sub} isOffline={sub.address && isSubOffline(sub.address)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.column}>
|
|
||||||
<div className={styles.list}>
|
|
||||||
<h3>{t('subscriptions')}</h3>
|
|
||||||
{subscriptions.length > 0
|
|
||||||
? subscriptions.map((address: string, index: number) => (
|
|
||||||
<div className={styles.subplebbit} key={index}>
|
|
||||||
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: t('not_subscribed')}
|
|
||||||
</div>
|
|
||||||
<div className={styles.list}>
|
|
||||||
<h3>{t('moderating')}</h3>
|
|
||||||
{accountSubplebbitAddresses.length > 0
|
|
||||||
? accountSubplebbitAddresses.map((address: string, index: number) => (
|
|
||||||
<div className={styles.subplebbit} key={index}>
|
|
||||||
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: t('not_moderating')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface PopularThreadProps {
|
|
||||||
post: Comment;
|
|
||||||
boardTitle: string | undefined;
|
|
||||||
boardShortAddress: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
|
||||||
const { cid, content, subplebbitAddress, title } = post || {};
|
|
||||||
const commentMediaInfo = getCommentMediaInfo(post);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.popularThread} key={cid}>
|
|
||||||
<div className={styles.title}>{boardTitle || boardShortAddress}</div>
|
|
||||||
<div className={styles.mediaContainer}>
|
|
||||||
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>
|
|
||||||
<CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={true} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className={styles.threadContent}>
|
|
||||||
{title && <b>{title}</b>}
|
|
||||||
{content && (content.length > 99 ? `: ${content.substring(0, 99)}...` : `: ${content}`)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PopularThreads = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { showWorksafeContentOnly, showNsfwContentOnly } = useBoxModalStore();
|
|
||||||
|
|
||||||
const getFilteredSubplebbits = () => {
|
|
||||||
if (showWorksafeContentOnly) {
|
|
||||||
return subplebbits.filter((sub: Subplebbit) => {
|
|
||||||
const multisubEntry = multisub.find((ms) => ms.address === sub.address);
|
|
||||||
return multisubEntry ? !multisubEntry.tags.some((tag: string) => nsfwTags.includes(tag)) : true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (showNsfwContentOnly) {
|
|
||||||
return subplebbits.filter((sub: Subplebbit) => {
|
|
||||||
const multisubEntry = multisub.find((ms) => ms.address === sub.address);
|
|
||||||
return multisubEntry ? multisubEntry.tags.some((tag: string) => nsfwTags.includes(tag)) : false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return subplebbits;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredSubplebbits = getFilteredSubplebbits();
|
|
||||||
const popularPosts = usePopularPosts(filteredSubplebbits);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.box}>
|
|
||||||
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
|
|
||||||
<h2 className={styles.capitalize}>{t('popular_threads')}</h2>
|
|
||||||
<BoxModal isBoardsBoxModal={false} />
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.boxContent} ${popularPosts.length === 8 ? styles.popularThreads : ''}`}>
|
|
||||||
{popularPosts.length < 8 ? (
|
|
||||||
<LoadingEllipsis string={t('loading')} />
|
|
||||||
) : (
|
|
||||||
popularPosts.map((post: any) => (
|
|
||||||
<PopularThreadCard key={post.cid} post={post} boardTitle={post.subplebbitTitle || post.subplebbitAddress} boardShortAddress={post.subplebbitAddress} />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Stats = ({ multisub, subplebbitAddresses }: { multisub: any; subplebbitAddresses: string[] }) => {
|
const Stats = ({ multisub, subplebbitAddresses }: { multisub: any; subplebbitAddresses: string[] }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const stats = useSubplebbitsStats({ subplebbitAddresses });
|
const stats = useSubplebbitsStats({ subplebbitAddresses });
|
||||||
@@ -620,8 +223,8 @@ const Home = () => {
|
|||||||
<HomeLogo />
|
<HomeLogo />
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<InfoBox />
|
<InfoBox />
|
||||||
<Boards multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
<BoardsBox multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||||
<PopularThreads multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
<PopularThreadsBox multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||||
<Stats multisub={defaultSubplebbits} subplebbitAddresses={subplebbitAddresses} />
|
<Stats multisub={defaultSubplebbits} subplebbitAddresses={subplebbitAddresses} />
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './popular-threads-box';
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Comment, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import styles from '../home.module.css';
|
||||||
|
import usePopularPosts from '../../../hooks/use-popular-posts';
|
||||||
|
import useHomeFiltersStore from '../../../stores/use-home-filters';
|
||||||
|
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
|
||||||
|
import { CatalogPostMedia } from '../../../components/catalog-row';
|
||||||
|
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
||||||
|
import BoxModal from '../box-modal';
|
||||||
|
import { nsfwTags } from '../home';
|
||||||
|
|
||||||
|
interface PopularThreadProps {
|
||||||
|
post: Comment;
|
||||||
|
boardTitle: string | undefined;
|
||||||
|
boardShortAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
||||||
|
const { cid, content, subplebbitAddress, title } = post || {};
|
||||||
|
const commentMediaInfo = getCommentMediaInfo(post);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.popularThread} key={cid}>
|
||||||
|
<div className={styles.title}>{boardTitle || boardShortAddress}</div>
|
||||||
|
<div className={styles.mediaContainer}>
|
||||||
|
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>
|
||||||
|
<CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={true} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles.threadContent}>
|
||||||
|
{title && <b>{title}</b>}
|
||||||
|
{content && (content.length > 99 ? `: ${content.substring(0, 99)}...` : `: ${content}`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { showWorksafeContentOnly, showNsfwContentOnly } = useHomeFiltersStore();
|
||||||
|
|
||||||
|
const getFilteredSubplebbits = () => {
|
||||||
|
if (showWorksafeContentOnly) {
|
||||||
|
return subplebbits.filter((sub: Subplebbit) => {
|
||||||
|
const multisubEntry = multisub.find((ms) => ms?.address === sub?.address);
|
||||||
|
return multisubEntry ? !multisubEntry.tags.some((tag: string) => nsfwTags.includes(tag)) : true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (showNsfwContentOnly) {
|
||||||
|
return subplebbits.filter((sub: Subplebbit) => {
|
||||||
|
const multisubEntry = multisub.find((ms) => ms?.address === sub?.address);
|
||||||
|
return multisubEntry ? multisubEntry.tags.some((tag: string) => nsfwTags.includes(tag)) : false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return subplebbits;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredSubplebbits = useMemo(getFilteredSubplebbits, [subplebbits, showWorksafeContentOnly, showNsfwContentOnly, multisub]);
|
||||||
|
const popularPosts = usePopularPosts(filteredSubplebbits);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.box}>
|
||||||
|
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
|
||||||
|
<h2 className={styles.capitalize}>{t('popular_threads')}</h2>
|
||||||
|
<BoxModal isBoardsBoxModal={false} />
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.boxContent} ${popularPosts.length === 8 ? styles.popularThreads : ''}`}>
|
||||||
|
{popularPosts.length < 8 ? (
|
||||||
|
<LoadingEllipsis string={t('loading')} />
|
||||||
|
) : (
|
||||||
|
popularPosts.map((post: any) => (
|
||||||
|
<PopularThreadCard key={post.cid} post={post} boardTitle={post.subplebbitTitle || post.subplebbitAddress} boardShortAddress={post.subplebbitAddress} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PopularThreadsBox;
|
||||||
Reference in New Issue
Block a user