mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(home): add boards list more similar to vichan, which is better than 4chan's boards box for a potentially infinite number of boards
This commit is contained in:
@@ -41,32 +41,9 @@ const useDefaultSubplebbits = () => {
|
||||
return cacheSubplebbits || subplebbits;
|
||||
};
|
||||
|
||||
export const categorizeSubplebbits = (subplebbits: MultisubSubplebbit[]) => {
|
||||
const plebbitSubs = subplebbits.filter((sub) => sub.tags?.includes('plebbit'));
|
||||
const interestsSubs = subplebbits.filter(
|
||||
(sub) => sub.tags?.includes('topic') && !sub.tags?.includes('plebbit') && !sub.tags?.includes('country') && !sub.tags?.includes('international'),
|
||||
);
|
||||
const randomSubs = subplebbits.filter((sub) => sub.tags?.includes('random') && !sub.tags?.includes('plebbit'));
|
||||
const internationalSubs = subplebbits.filter((sub) => sub.tags?.includes('international') || sub.tags?.includes('country'));
|
||||
const projectsSubs = subplebbits.filter((sub) => sub.tags?.includes('project') && !sub.tags?.includes('plebbit') && !sub.tags?.includes('topic'));
|
||||
|
||||
return { plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs };
|
||||
};
|
||||
|
||||
export const useDefaultSubplebbitAddresses = () => {
|
||||
const defaultSubplebbits = useDefaultSubplebbits();
|
||||
const categorizedSubplebbits = useMemo(() => categorizeSubplebbits(defaultSubplebbits), [defaultSubplebbits]);
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
...categorizedSubplebbits.plebbitSubs,
|
||||
...categorizedSubplebbits.projectsSubs,
|
||||
...categorizedSubplebbits.interestsSubs,
|
||||
...categorizedSubplebbits.randomSubs,
|
||||
...categorizedSubplebbits.internationalSubs,
|
||||
].map((subplebbit) => subplebbit.address),
|
||||
[categorizedSubplebbits],
|
||||
);
|
||||
return useMemo(() => defaultSubplebbits.map((subplebbit) => subplebbit.address), [defaultSubplebbits]);
|
||||
};
|
||||
|
||||
export const useMultisubMetadata = () => {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
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' || localStorage.getItem('showWorksafeContentOnly') === null ? 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,23 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface PopularThreadsOptionsStore {
|
||||
showWorksafeContentOnly: boolean;
|
||||
setShowWorksafeContentOnly: (value: boolean) => void;
|
||||
showNsfwContentOnly: boolean;
|
||||
setShowNsfwContentOnly: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const usePopularThreadsOptionsStore = create<PopularThreadsOptionsStore>((set) => ({
|
||||
showWorksafeContentOnly: localStorage.getItem('showWorksafeContentOnly') === 'true' || localStorage.getItem('showWorksafeContentOnly') === null ? 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 usePopularThreadsOptionsStore;
|
||||
@@ -1,164 +0,0 @@
|
||||
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-store';
|
||||
import { useMultisubMetadata } from '../../../hooks/use-default-subplebbits';
|
||||
import styles from '../home.module.css';
|
||||
import { nsfwTags } from '../home';
|
||||
import BoxModal from '../box-modal';
|
||||
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
|
||||
|
||||
const Board = ({ subplebbit, subplebbits, useCatalog }: { subplebbit: Subplebbit; subplebbits: any; useCatalog?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const { address, title, tags } = subplebbit || {};
|
||||
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
|
||||
|
||||
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
||||
|
||||
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
||||
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
||||
|
||||
return (
|
||||
<div className={styles.subplebbit} key={address}>
|
||||
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||
<Link to={boardLink}>{title || (address.endsWith('.eth') || address.endsWith('.sol') ? address.slice(0, -4) : address && Plebbit.getShortAddress(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 { useCatalog } = 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 multisubMetadata = useMultisubMetadata();
|
||||
|
||||
return (
|
||||
<div className={`${styles.box} ${styles.boardsBox}`}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2 className='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={useCatalog ? '/p/all/catalog' : '/p/all'}>{multisubMetadata?.title || 'All'}</Link>
|
||||
</div>
|
||||
<div className={styles.subplebbit}>
|
||||
<Link to={useCatalog ? '/p/subscriptions/catalog' : '/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} subplebbits={subplebbits} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{projectsSubs.length > 0 && (
|
||||
<>
|
||||
<h3>{t('projects')}</h3>
|
||||
<div className={styles.list}>
|
||||
{projectsSubs.map((sub) => (
|
||||
<Board key={sub.address} subplebbit={sub} subplebbits={subplebbits} />
|
||||
))}
|
||||
</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} subplebbits={subplebbits} />
|
||||
))}
|
||||
</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} subplebbits={subplebbits} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{internationalSubs.length > 0 && (
|
||||
<>
|
||||
<h3>{t('international')}</h3>
|
||||
<div className={styles.list}>
|
||||
{internationalSubs.map((sub) => (
|
||||
<Board key={sub.address} subplebbit={sub} subplebbits={subplebbits} />
|
||||
))}
|
||||
</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={useCatalog ? `/p/${address}/catalog` : `/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={useCatalog ? `/p/${address}/catalog` : `/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
|
||||
</div>
|
||||
))
|
||||
: t('not_moderating')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardsBox;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './boards-box';
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
|
||||
import styles from '../home.module.css';
|
||||
import { nsfwTags } from '../home';
|
||||
|
||||
const Board = ({ subplebbit, subplebbits }: { subplebbit: Subplebbit; subplebbits: any; useCatalog?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const { address, title, tags } = subplebbit || {};
|
||||
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
|
||||
|
||||
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
||||
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
||||
|
||||
return (
|
||||
<tr className={styles.subplebbit} key={address}>
|
||||
<td className={styles.boardAddress}>
|
||||
<p className={styles.boardCell}>
|
||||
<Link to={`/p/${address}`}>{address && Plebbit.getShortAddress(address)}</Link>
|
||||
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
||||
</p>
|
||||
</td>
|
||||
<td className={styles.boardTitle}>
|
||||
<p className={styles.boardCell}>{title || (address && Plebbit.getShortAddress(address))}</p>
|
||||
</td>
|
||||
<td className={styles.boardTags}>
|
||||
<p className={styles.boardCell}>{tags.join(', ')}</p>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const BoardsList = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.boardsBox}>
|
||||
<table className={styles.boardsList}>
|
||||
<colgroup>
|
||||
<col className={styles.boardAddress} />
|
||||
<col className={styles.boardTitle} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('board')}</th>
|
||||
<th>{t('title')}</th>
|
||||
<th>{t('tags')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{multisub.map((sub) => (
|
||||
<Board key={sub.address} subplebbit={sub} subplebbits={subplebbits} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoardsList;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './boards-list';
|
||||
@@ -1,26 +1,15 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useHomeFiltersStore from '../../../stores/use-home-filters-store';
|
||||
import useHomeFiltersStore from '../../../stores/use-popular-threads-options-store';
|
||||
import styles from '../home.module.css';
|
||||
|
||||
const BoxModal = ({ isBoardsBoxModal }: { isBoardsBoxModal: boolean }) => {
|
||||
const BoxModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
const {
|
||||
showNsfwBoardsOnly,
|
||||
setShowNsfwBoardsOnly,
|
||||
showWorksafeBoardsOnly,
|
||||
setShowWorksafeBoardsOnly,
|
||||
useCatalog,
|
||||
setUseCatalog,
|
||||
showWorksafeContentOnly,
|
||||
setShowWorksafeContentOnly,
|
||||
showNsfwContentOnly,
|
||||
setShowNsfwContentOnly,
|
||||
} = useHomeFiltersStore();
|
||||
const { showWorksafeContentOnly, setShowWorksafeContentOnly, showNsfwContentOnly, setShowNsfwContentOnly } = useHomeFiltersStore();
|
||||
|
||||
const handleClickOutside = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
@@ -41,95 +30,44 @@ const BoxModal = ({ isBoardsBoxModal }: { isBoardsBoxModal: boolean }) => {
|
||||
return (
|
||||
<>
|
||||
<span ref={buttonRef} onClick={() => !showFilterModal && setShowFilterModal(true)}>
|
||||
{isBoardsBoxModal ? t('filter') : t('options')} ▼
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
{t('show_all_boards')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${showNsfwBoardsOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showWorksafeBoardsOnly) {
|
||||
setShowWorksafeBoardsOnly(false);
|
||||
}
|
||||
setShowNsfwBoardsOnly(!showNsfwBoardsOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_nsfw_boards_only')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${showWorksafeBoardsOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showNsfwBoardsOnly) {
|
||||
setShowNsfwBoardsOnly(false);
|
||||
}
|
||||
setShowWorksafeBoardsOnly(!showWorksafeBoardsOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_worksafe_boards_only')}
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div
|
||||
className={`${styles.option} ${useCatalog && styles.selected}`}
|
||||
onClick={() => {
|
||||
setUseCatalog(!useCatalog);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('use_catalog')}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`${styles.option} ${showWorksafeContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showNsfwContentOnly) {
|
||||
setShowNsfwContentOnly(false);
|
||||
}
|
||||
setShowWorksafeContentOnly(!showWorksafeContentOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_worksafe_content_only')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${showNsfwContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showWorksafeContentOnly) {
|
||||
setShowWorksafeContentOnly(false);
|
||||
}
|
||||
setShowNsfwContentOnly(!showNsfwContentOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_nsfw_content_only')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${!showWorksafeContentOnly && !showNsfwContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
setShowWorksafeContentOnly(false);
|
||||
setShowNsfwContentOnly(false);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_all_content')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={`${styles.option} ${showWorksafeContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showNsfwContentOnly) {
|
||||
setShowNsfwContentOnly(false);
|
||||
}
|
||||
setShowWorksafeContentOnly(!showWorksafeContentOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_worksafe_content_only')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${showNsfwContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
if (showWorksafeContentOnly) {
|
||||
setShowWorksafeContentOnly(false);
|
||||
}
|
||||
setShowNsfwContentOnly(!showNsfwContentOnly);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_nsfw_content_only')}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.option} ${!showWorksafeContentOnly && !showNsfwContentOnly && styles.selected}`}
|
||||
onClick={() => {
|
||||
setShowWorksafeContentOnly(false);
|
||||
setShowNsfwContentOnly(false);
|
||||
setShowFilterModal(false);
|
||||
}}
|
||||
>
|
||||
{t('show_all_content')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -34,6 +34,61 @@
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.offlineIcon {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
margin-left: 2px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.boardsBox table {
|
||||
display: table;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
table-layout: fixed;
|
||||
width: calc(100% + 4px);
|
||||
margin-left: -2px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 2px 0;
|
||||
}
|
||||
|
||||
.boardsBox table thead th {
|
||||
background: #fca;
|
||||
color: #800;
|
||||
border: 1px solid #800;
|
||||
padding: 4px 15px 5px 5px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.boardsBox table tbody td {
|
||||
margin: 0;
|
||||
padding: 4px 15px 4px 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.boardsBox table tbody tr:nth-of-type( even ) {
|
||||
background: #ede2d4;
|
||||
}
|
||||
|
||||
.boardCell {
|
||||
position: static;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.boardCell a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.boxBar, .infoboxBar {
|
||||
padding-left: 0.5em;
|
||||
line-height: 2em;
|
||||
@@ -158,16 +213,6 @@
|
||||
content: '✔ ';
|
||||
}
|
||||
|
||||
.list, .subplebbit a {
|
||||
color: var(--homepage-box-link-text-color);
|
||||
text-decoration: var(--homepage-box-link-text-decoration);
|
||||
}
|
||||
|
||||
.list a:hover {
|
||||
color: var(--homepage-box-link-text-color-hover);
|
||||
text-decoration: var(--homepage-box-link-text-decoration-hover);
|
||||
}
|
||||
|
||||
.nsfw {
|
||||
color: red;
|
||||
font-weight: 700;
|
||||
@@ -175,47 +220,6 @@
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.offlineIcon {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
margin-right: 2px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.boardsBoxContent {
|
||||
font-size: 93%;
|
||||
padding: 0.5em;
|
||||
padding-top: 0.25em;
|
||||
padding-bottom: 0;
|
||||
line-height: 130%;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.boardsBoxContent .subplebbit {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.boardsBoxContent .column {
|
||||
float: left;
|
||||
width: 168px;
|
||||
margin-right: 15px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.boardsBoxContent h3 {
|
||||
font-size: 100%;
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
text-transform: capitalize;
|
||||
color: var(--homepage-box-bar-text-color);
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import packageJson from '../../../package.json';
|
||||
import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
||||
import PopularThreadsBox from './popular-threads-box';
|
||||
import BoardsBox from './boards-box';
|
||||
import BoardsList from './boards-list';
|
||||
|
||||
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
|
||||
// plebchan shouldn't consider 'vulgar' or 'anti' as nsfw tags, unlike more sfw-oriented clients
|
||||
@@ -214,7 +214,7 @@ const Home = () => {
|
||||
<HomeLogo />
|
||||
<SearchBar />
|
||||
<InfoBox />
|
||||
<BoardsBox multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||
<BoardsList multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||
<PopularThreadsBox multisub={defaultSubplebbits} subplebbits={subplebbits} />
|
||||
<Stats subplebbitAddresses={subplebbitAddresses} />
|
||||
<Footer />
|
||||
|
||||
@@ -4,7 +4,7 @@ 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-store';
|
||||
import usePopularThreadsOptionsStore from '../../../stores/use-popular-threads-options-store';
|
||||
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
|
||||
import { CatalogPostMedia } from '../../../components/catalog-row';
|
||||
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
||||
@@ -52,7 +52,7 @@ const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThrea
|
||||
|
||||
const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebbits: any }) => {
|
||||
const { t } = useTranslation();
|
||||
const { showWorksafeContentOnly, showNsfwContentOnly } = useHomeFiltersStore();
|
||||
const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore();
|
||||
|
||||
const getFilteredSubplebbits = () => {
|
||||
if (showWorksafeContentOnly) {
|
||||
@@ -77,7 +77,7 @@ const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[];
|
||||
<div className={styles.box}>
|
||||
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
|
||||
<h2 className='capitalize'>{t('popular_threads')}</h2>
|
||||
<BoxModal isBoardsBoxModal={false} />
|
||||
<BoxModal />
|
||||
</div>
|
||||
<div className={`${styles.boxContent} ${popularPosts.length !== 0 ? styles.popularThreads : ''}`}>
|
||||
{popularPosts.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user