refactor(routing): migrate to directory-based board routing

This commit is contained in:
plebeius
2025-11-07 12:50:58 +01:00
parent e7e2a2fda7
commit d78fed1fda
22 changed files with 332 additions and 163 deletions
+31 -28
View File
@@ -8,6 +8,8 @@ import useCreateBoardModalStore from './stores/use-create-board-modal-store';
import useSpecialThemeStore from './stores/use-special-theme-store'; import useSpecialThemeStore from './stores/use-special-theme-store';
import useIsMobile from './hooks/use-is-mobile'; import useIsMobile from './hooks/use-is-mobile';
import useTheme from './hooks/use-theme'; import useTheme from './hooks/use-theme';
import { useDefaultSubplebbits } from './hooks/use-default-subplebbits';
import { getSubplebbitAddress } from './lib/utils/route-utils';
import styles from './app.module.css'; import styles from './app.module.css';
import Board from './views/board'; import Board from './views/board';
import Catalog from './views/catalog'; import Catalog from './views/catalog';
@@ -27,12 +29,14 @@ import TopBar from './components/topbar';
import SettingsModal from './components/settings-modal'; import SettingsModal from './components/settings-modal';
const BoardLayout = () => { const BoardLayout = () => {
const { accountCommentIndex, subplebbitAddress } = useParams(); const { accountCommentIndex, boardIdentifier } = useParams();
const location = useLocation(); const location = useLocation();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const defaultSubplebbits = useDefaultSubplebbits();
const subplebbitAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, defaultSubplebbits) : undefined;
const pendingPost = useAccountComment({ commentIndex: accountCommentIndex ? parseInt(accountCommentIndex) : undefined }); const pendingPost = useAccountComment({ commentIndex: accountCommentIndex ? parseInt(accountCommentIndex) : undefined });
const { closeCreateBoardModal } = useCreateBoardModalStore(); const { closeCreateBoardModal } = useCreateBoardModalStore();
@@ -130,42 +134,41 @@ const App = () => (
<Routes> <Routes>
<Route element={<GlobalLayout />}> <Route element={<GlobalLayout />}>
<Route path='/' element={<Home />} /> <Route path='/' element={<Home />} />
<Route path='/:tag' element={<Home />} />
<Route path='/faq' element={<FAQ />} /> <Route path='/faq' element={<FAQ />} />
<Route path='*' element={<NotFound />} />
<Route element={<BoardLayout />}> <Route element={<BoardLayout />}>
<Route path='/p/:subplebbitAddress' element={<Board />} /> <Route path='/all/:timeFilterName?' element={<Board />} />
<Route path='/p/:subplebbitAddress/settings' element={<Board />} /> <Route path='/all/:timeFilterName?/settings' element={<Board />} />
<Route path='/p/:subplebbitAddress/catalog' element={<Catalog />} /> <Route path='/all/description' element={<Post />} />
<Route path='/p/:subplebbitAddress/catalog/settings' element={<Catalog />} /> <Route path='/all/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/all/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/p/:subplebbitAddress/c/:commentCid' element={<Post />} /> <Route path='/subscriptions/:timeFilterName?' element={<Board />} />
<Route path='/p/:subplebbitAddress/c/:commentCid/settings' element={<Post />} /> <Route path='/subscriptions/:timeFilterName?/settings' element={<Board />} />
<Route path='/p/:subplebbitAddress/description' element={<Post />} /> <Route path='/subscriptions/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/p/:subplebbitAddress/description/settings' element={<Post />} /> <Route path='/subscriptions/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/p/:subplebbitAddress/rules' element={<Post />} />
<Route path='/p/:subplebbitAddress/rules/settings' element={<Post />} />
<Route path='/p/all/:timeFilterName?' element={<Board />} /> <Route path='/mod/:timeFilterName?' element={<Board />} />
<Route path='/p/all/:timeFilterName?/settings' element={<Board />} /> <Route path='/mod/:timeFilterName?/settings' element={<Board />} />
<Route path='/p/all/description' element={<Post />} /> <Route path='/mod/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/p/all/catalog/:timeFilterName?' element={<Catalog />} /> <Route path='/mod/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/p/all/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/p/subscriptions/:timeFilterName?' element={<Board />} /> <Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
<Route path='/p/subscriptions/:timeFilterName?/settings' element={<Board />} /> <Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
<Route path='/p/subscriptions/catalog/:timeFilterName?' element={<Catalog />} />
<Route path='/p/subscriptions/catalog/:timeFilterName?/settings' element={<Catalog />} />
<Route path='/p/mod/:timeFilterName?' element={<Board />} /> <Route path='/:boardIdentifier' element={<Board />} />
<Route path='/p/mod/:timeFilterName?/settings' element={<Board />} /> <Route path='/:boardIdentifier/settings' element={<Board />} />
<Route path='/p/mod/catalog/:timeFilterName?' element={<Catalog />} /> <Route path='/:boardIdentifier/catalog' element={<Catalog />} />
<Route path='/p/mod/catalog/:timeFilterName?/settings' element={<Catalog />} /> <Route path='/:boardIdentifier/catalog/settings' element={<Catalog />} />
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} /> <Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
<Route path='/profile/:accountCommentIndex/settings' element={<PendingPost />} /> <Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
<Route path='/:boardIdentifier/description' element={<Post />} />
<Route path='/:boardIdentifier/description/settings' element={<Post />} />
<Route path='/:boardIdentifier/rules' element={<Post />} />
<Route path='/:boardIdentifier/rules/settings' element={<Post />} />
</Route> </Route>
<Route path='/not-found' element={<NotFound />} /> <Route path='/not-found' element={<NotFound />} />
<Route path='*' element={<NotFound />} />
</Route> </Route>
</Routes> </Routes>
</div> </div>
+44 -31
View File
@@ -4,6 +4,9 @@ import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits'; import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages'; import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
import { isAllView, isCatalogView, isDescriptionView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isDescriptionView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store'; import useFeedResetStore from '../../stores/use-feed-reset-store';
@@ -29,19 +32,21 @@ interface BoardButtonsProps {
const CatalogButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => { const CatalogButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const params = useParams(); const params = useParams();
const defaultSubplebbits = useDefaultSubplebbits();
const createCatalogLink = () => { const createCatalogLink = () => {
if (isInAllView) { if (isInAllView) {
if (params?.timeFilterName) return `/p/all/catalog/${params.timeFilterName}`; if (params?.timeFilterName) return `/all/catalog/${params.timeFilterName}`;
return `/p/all/catalog`; return `/all/catalog`;
} else if (isInSubscriptionsView) { } else if (isInSubscriptionsView) {
if (params?.timeFilterName) return `/p/subscriptions/catalog/${params.timeFilterName}`; if (params?.timeFilterName) return `/subscriptions/catalog/${params.timeFilterName}`;
return `/p/subscriptions/catalog`; return `/subscriptions/catalog`;
} else if (isInModView) { } else if (isInModView) {
if (params?.timeFilterName) return `/p/mod/catalog/${params.timeFilterName}`; if (params?.timeFilterName) return `/mod/catalog/${params.timeFilterName}`;
return `/p/mod/catalog`; return `/mod/catalog`;
} }
return `/p/${address}/catalog`; const boardPath = address ? getBoardPath(address, defaultSubplebbits) : address;
return `/${boardPath}/catalog`;
}; };
return ( return (
@@ -65,19 +70,21 @@ const SubscribeButton = ({ address }: BoardButtonsProps) => {
const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => { const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isInModView }: BoardButtonsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const params = useParams(); const params = useParams();
const defaultSubplebbits = useDefaultSubplebbits();
const createReturnLink = () => { const createReturnLink = () => {
if (isInAllView) { if (isInAllView) {
if (params?.timeFilterName) return `/p/all/${params.timeFilterName}`; if (params?.timeFilterName) return `/all/${params.timeFilterName}`;
return `/p/all`; return `/all`;
} else if (isInSubscriptionsView) { } else if (isInSubscriptionsView) {
if (params?.timeFilterName) return `/p/subscriptions/${params.timeFilterName}`; if (params?.timeFilterName) return `/subscriptions/${params.timeFilterName}`;
return `/p/subscriptions`; return `/subscriptions`;
} else if (isInModView) { } else if (isInModView) {
if (params?.timeFilterName) return `/p/mod/${params.timeFilterName}`; if (params?.timeFilterName) return `/mod/${params.timeFilterName}`;
return `/p/mod`; return `/mod`;
} }
return `/p/${address}`; const boardPath = address ? getBoardPath(address, defaultSubplebbits) : address;
return `/${boardPath}`;
}; };
return ( return (
@@ -206,16 +213,16 @@ export const TimeFilter = ({ isInAllView, isInCatalogView, isInSubscriptionsView
const timeFilterName = event.target.value; const timeFilterName = event.target.value;
const link = isInAllView const link = isInAllView
? isInCatalogView ? isInCatalogView
? `/p/all/catalog/${timeFilterName}` ? `/all/catalog/${timeFilterName}`
: `/p/all/${timeFilterName}` : `/all/${timeFilterName}`
: isInSubscriptionsView : isInSubscriptionsView
? isInCatalogView ? isInCatalogView
? `/p/subscriptions/catalog/${timeFilterName}` ? `/subscriptions/catalog/${timeFilterName}`
: `/p/subscriptions/${timeFilterName}` : `/subscriptions/${timeFilterName}`
: isInModView : isInModView
? isInCatalogView ? isInCatalogView
? `/p/mod/catalog/${timeFilterName}` ? `/mod/catalog/${timeFilterName}`
: `/p/mod/${timeFilterName}` : `/mod/${timeFilterName}`
: null; : null;
link && navigate(link); link && navigate(link);
}; };
@@ -256,7 +263,8 @@ export const MobileBoardButtons = () => {
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const { filteredCount, searchText } = useCatalogFiltersStore(); const { filteredCount, searchText } = useCatalogFiltersStore();
@@ -319,11 +327,12 @@ const PostPageStats = () => {
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const isInDescriptionView = isDescriptionView(location.pathname, params); const isInDescriptionView = isDescriptionView(location.pathname, params);
const resolvedAddress = useResolvedSubplebbitAddress();
const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]); const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]);
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[params?.subplebbitAddress as string]); const subplebbit = useSubplebbitsStore((state) => state.subplebbits[resolvedAddress as string]);
const descriptionReplyCount = location?.pathname.startsWith('/p/all/') ? 0 : subplebbit?.rules?.length > 0 ? 1 : 0; const descriptionReplyCount = location?.pathname.startsWith('/all/') ? 0 : subplebbit?.rules?.length > 0 ? 1 : 0;
const { closed, pinned, replyCount } = comment || {}; const { closed, pinned, replyCount } = comment || {};
const linkCount = useCountLinksInReplies(comment); const linkCount = useCountLinksInReplies(comment);
@@ -344,7 +353,8 @@ export const DesktopBoardButtons = () => {
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const isInCatalogView = isCatalogView(location.pathname, params); const isInCatalogView = isCatalogView(location.pathname, params);
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInPendingPostPage = isPendingPostView(location.pathname, params); const isInPendingPostPage = isPendingPostView(location.pathname, params);
@@ -438,6 +448,9 @@ const SearchOPsBar = () => {
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const defaultSubplebbits = useDefaultSubplebbits();
const resolvedAddress = useResolvedSubplebbitAddress();
const boardPath = resolvedAddress ? getBoardPath(resolvedAddress, defaultSubplebbits) : params?.boardIdentifier || params?.subplebbitAddress;
const handleSearch = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleSearch = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -447,18 +460,18 @@ const SearchOPsBar = () => {
if (isInAllView) { if (isInAllView) {
catalogUrl = params?.timeFilterName catalogUrl = params?.timeFilterName
? `/p/all/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}` ? `/all/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/p/all/catalog?q=${encodeURIComponent(searchQuery)}`; : `/all/catalog?q=${encodeURIComponent(searchQuery)}`;
} else if (isInSubscriptionsView) { } else if (isInSubscriptionsView) {
catalogUrl = params?.timeFilterName catalogUrl = params?.timeFilterName
? `/p/subscriptions/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}` ? `/subscriptions/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/p/subscriptions/catalog?q=${encodeURIComponent(searchQuery)}`; : `/subscriptions/catalog?q=${encodeURIComponent(searchQuery)}`;
} else if (isInModView) { } else if (isInModView) {
catalogUrl = params?.timeFilterName catalogUrl = params?.timeFilterName
? `/p/mod/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}` ? `/mod/catalog/${params.timeFilterName}?q=${encodeURIComponent(searchQuery)}`
: `/p/mod/catalog?q=${encodeURIComponent(searchQuery)}`; : `/mod/catalog?q=${encodeURIComponent(searchQuery)}`;
} else { } else {
catalogUrl = `/p/${params?.subplebbitAddress}/catalog?q=${encodeURIComponent(searchQuery)}`; catalogUrl = `/${boardPath}/catalog?q=${encodeURIComponent(searchQuery)}`;
} }
navigate(catalogUrl); navigate(catalogUrl);
+4 -2
View File
@@ -7,6 +7,7 @@ import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subple
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import styles from './board-header.module.css'; import styles from './board-header.module.css';
import { useMultisubMetadata, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { useMultisubMetadata, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline';
import { shouldShowSnow } from '../../lib/snow'; import { shouldShowSnow } from '../../lib/snow';
@@ -28,7 +29,8 @@ const BoardHeader = () => {
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams()); const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]); const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
@@ -47,7 +49,7 @@ const BoardHeader = () => {
: isInModView : isInModView
? _.startCase(t('boards_you_moderate')) ? _.startCase(t('boards_you_moderate'))
: defaultSubplebbit?.title || subplebbit?.title; : defaultSubplebbit?.title || subplebbit?.title;
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : isInModView ? 'p/mod' : `p/${address}`; const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : isInModView ? 'p/mod' : `p/${address || subplebbitAddress || ''}`;
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit); const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
+5 -1
View File
@@ -9,6 +9,8 @@ import { shouldShowSnow } from '../../lib/snow';
import { getHasThumbnail } from '../../lib/utils/media-utils'; import { getHasThumbnail } from '../../lib/utils/media-utils';
import { getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useEditCommentPrivileges from '../../hooks/use-author-privileges'; import useEditCommentPrivileges from '../../hooks/use-author-privileges';
@@ -138,8 +140,10 @@ const CatalogPost = ({ post }: { post: Comment }) => {
const params = useParams(); const params = useParams();
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const postLink = isInAllView && isDescription ? `/p/all/description` : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`; const postLink = isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`;
const threadIcons = ( const threadIcons = (
<div className={styles.threadIcons}> <div className={styles.threadIcons}>
+11 -3
View File
@@ -172,9 +172,9 @@ const renderAnchorLink = (children: React.ReactNode, href: string) => {
shouldReplaceText = children[0] === href || children[0].trim() === href.trim(); shouldReplaceText = children[0] === href || children[0].trim() === href.trim();
} }
// For display purposes, remove leading slash from paths like "/p/something" // For display purposes, remove leading slash from paths like "/biz" or board identifiers
let displayText: React.ReactNode = children; let displayText: React.ReactNode = children;
if (shouldReplaceText && internalPath.startsWith('/p/')) { if (shouldReplaceText && internalPath.match(/^\/[^/]+$/)) {
displayText = internalPath.substring(1); // Remove leading slash displayText = internalPath.substring(1); // Remove leading slash
} else if (shouldReplaceText) { } else if (shouldReplaceText) {
displayText = internalPath; displayText = internalPath;
@@ -188,7 +188,15 @@ const renderAnchorLink = (children: React.ReactNode, href: string) => {
} }
// Handle hash routes and internal patterns (including routes that start with /#/) // Handle hash routes and internal patterns (including routes that start with /#/)
if (href.startsWith('#/') || href.startsWith('/#/') || href.startsWith('/p/') || href.match(/^\/p\/[^/]+(\/c\/[^/]+)?$/)) { // Support both old format (/p/...) for backward compatibility and new format (/{boardIdentifier}/...)
if (
href.startsWith('#/') ||
href.startsWith('/#/') ||
href.startsWith('/p/') ||
href.match(/^\/p\/[^/]+(\/c\/[^/]+)?$/) ||
href.match(/^\/[^/]+(\/thread\/[^/]+)?$/) ||
href.match(/^\/[^/]+\/(catalog|description|rules)(\/settings)?$/)
) {
return <Link to={href}>{children}</Link>; return <Link to={href}>{children}</Link>;
} }
+14 -6
View File
@@ -10,6 +10,8 @@ import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/po
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isValidURL } from '../../lib/utils/url-utils'; import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store'; import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
import useAuthorAddressClick from '../../hooks/use-author-address-click'; import useAuthorAddressClick from '../../hooks/use-author-address-click';
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info'; import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
@@ -62,6 +64,8 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
const { showOmittedReplies } = useShowOmittedReplies(); const { showOmittedReplies } = useShowOmittedReplies();
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author }); const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
const { hideAvatars } = useAvatarVisibilityStore(); const { hideAvatars } = useAvatarVisibilityStore();
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
@@ -165,13 +169,13 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && ( {subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && (
<span className={styles.postNumLink}> <span className={styles.postNumLink}>
{' '} {' '}
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>{' '} <Link to={`/${boardPath}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>{' '}
</span> </span>
)} )}
{!(isDescription || isRules) && {!(isDescription || isRules) &&
(cid ? ( (cid ? (
<span className={styles.postNumLink}> <span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}> <Link to={`/${boardPath}/thread/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/ c/
</Link> </Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}> <span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
@@ -200,7 +204,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
<span className={styles.replyButton}> <span className={styles.replyButton}>
[ [
<Link <Link
to={isInAllView && isDescription ? '/p/all/description' : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${postCid}`}`} to={isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${postCid}`}`}
onClick={(e) => !cid && !isDescription && !isRules && e.preventDefault()} onClick={(e) => !cid && !isDescription && !isRules && e.preventDefault()}
> >
{_.capitalize(t('reply'))} {_.capitalize(t('reply'))}
@@ -314,8 +318,10 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {}; const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`); const isRouteLinkToReply = useLocation().pathname.startsWith(`/${boardPath}/thread/${cid}`);
const { hidden } = useHide({ cid }); const { hidden } = useHide({ cid });
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
@@ -354,6 +360,8 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
const location = useLocation(); const location = useLocation();
const isInPendingPostView = isPendingPostView(location.pathname, params); const isInPendingPostView = isPendingPostView(location.pathname, params);
const isInPostPageView = isPostPageView(location.pathname, params); const isInPostPageView = isPostPageView(location.pathname, params);
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const { hidden, unhide, hide } = useHide({ cid }); const { hidden, unhide, hide } = useHide({ cid });
const isHidden = hidden && !isInPostPageView; const isHidden = hidden && !isInPostPageView;
@@ -430,14 +438,14 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
<Trans <Trans
i18nKey={'replies_and_links_omitted'} i18nKey={'replies_and_links_omitted'}
shouldUnescape={true} shouldUnescape={true}
components={{ 1: <Link key={cid} to={`/p/${subplebbitAddress}/c/${cid}`} /> }} components={{ 1: <Link key={cid} to={`/${boardPath}/thread/${cid}`} /> }}
values={{ repliesCount, linksCount }} values={{ repliesCount, linksCount }}
/> />
) : ( ) : (
<Trans <Trans
i18nKey={'replies_omitted'} i18nKey={'replies_omitted'}
shouldUnescape={true} shouldUnescape={true}
components={{ 1: <Link key={cid} to={`/p/${subplebbitAddress}/c/${cid}`} /> }} components={{ 1: <Link key={cid} to={`/${boardPath}/thread/${cid}`} /> }}
values={{ repliesCount }} values={{ repliesCount }}
/> />
)} )}
+6 -3
View File
@@ -11,6 +11,7 @@ import { isValidURL } from '../../lib/utils/url-utils';
import { isAllView, isDescriptionView, isModView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isDescriptionView, isModView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useAnonMode from '../../hooks/use-anon-mode'; import useAnonMode from '../../hooks/use-anon-mode';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline';
import usePublishPost from '../../hooks/use-publish-post'; import usePublishPost from '../../hooks/use-publish-post';
@@ -45,7 +46,8 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
const author = account?.author || {}; const author = account?.author || {};
const { displayName } = author || {}; const { displayName } = author || {};
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const { setPublishPostOptions, postIndex, publishPost, publishPostOptions, resetPublishPostOptions } = usePublishPost({ subplebbitAddress }); const { setPublishPostOptions, postIndex, publishPost, publishPostOptions, resetPublishPostOptions } = usePublishPost({ subplebbitAddress });
const textRef = useRef<HTMLTextAreaElement>(null); const textRef = useRef<HTMLTextAreaElement>(null);
@@ -160,7 +162,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
if (typeof postIndex === 'number') { if (typeof postIndex === 'number') {
resetPublishPostOptions(); resetPublishPostOptions();
resetFields(); resetFields();
navigate(`/profile/${postIndex}`); navigate(`/pending/${postIndex}`);
} }
}, [postIndex, resetPublishPostOptions, navigate]); }, [postIndex, resetPublishPostOptions, navigate]);
@@ -458,7 +460,8 @@ const PostForm = () => {
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]); const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsSubplebbitOffline(subplebbit); const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsSubplebbitOffline(subplebbit);
+12 -4
View File
@@ -10,6 +10,8 @@ import { getHasThumbnail } from '../../lib/utils/media-utils';
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils'; import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store'; import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
import useAuthorAddressClick from '../../hooks/use-author-address-click'; import useAuthorAddressClick from '../../hooks/use-author-address-click';
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info'; import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
@@ -29,6 +31,7 @@ import useReplyModalStore from '../../stores/use-reply-modal-store';
const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => { const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultSubplebbits = useDefaultSubplebbits();
const { const {
author, author,
cid, cid,
@@ -48,6 +51,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
timestamp, timestamp,
thumbnailUrl, thumbnailUrl,
} = post || {}; } = post || {};
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const isReply = parentCid; const isReply = parentCid;
const title = post?.title?.trim(); const title = post?.title?.trim();
const { isDescription, isRules } = post || {}; // custom properties, not from api const { isDescription, isRules } = post || {}; // custom properties, not from api
@@ -174,14 +178,14 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && ( {subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && (
<div className={styles.postNumLink}> <div className={styles.postNumLink}>
{' '} {' '}
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link> <Link to={`/${boardPath}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>
</div> </div>
)} )}
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '} <Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '}
{!(isDescription || isRules) && {!(isDescription || isRules) &&
(cid ? ( (cid ? (
<span className={styles.postNumLink}> <span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}> <Link to={`/${boardPath}/thread/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/ c/
</Link> </Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}> <span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
@@ -259,7 +263,9 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
post = editedComment; post = editedComment;
} }
const { author, cid, deleted, postCid, reason, removed, subplebbitAddress } = post || {}; const { author, cid, deleted, postCid, reason, removed, subplebbitAddress } = post || {};
const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`); const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const isRouteLinkToReply = useLocation().pathname.startsWith(`/${boardPath}/thread/${cid}`);
const { hidden } = useHide({ cid }); const { hidden } = useHide({ cid });
return ( return (
@@ -289,6 +295,8 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostPro
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInPendingPostView = isPendingPostView(location.pathname, params); const isInPendingPostView = isPendingPostView(location.pathname, params);
const isInPostView = isPostPageView(location.pathname, params); const isInPostView = isPostPageView(location.pathname, params);
const defaultSubplebbits = useDefaultSubplebbits();
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
const linksCount = useCountLinksInReplies(post); const linksCount = useCountLinksInReplies(post);
const replies = useReplies(post); const replies = useReplies(post);
@@ -345,7 +353,7 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostPro
{linksCount > 0 && ` / ${linksCount} Links`} {linksCount > 0 && ` / ${linksCount} Links`}
</span> </span>
<Link <Link
to={isInAllView && isDescription ? '/p/all/description' : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} to={isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`}
className='button' className='button'
> >
{t('view_thread')} {t('view_thread')}
@@ -3,6 +3,8 @@ import { createPortal } from 'react-dom';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Comment, useAccount } from '@plebbit/plebbit-react-hooks'; import { Comment, useAccount } from '@plebbit/plebbit-react-hooks';
import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react'; import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import useAnonModeStore from '../../stores/use-anon-mode-store'; import useAnonModeStore from '../../stores/use-anon-mode-store';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
@@ -58,6 +60,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null); const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
const placementRef = useRef<Placement>('right'); const placementRef = useRef<Placement>('right');
const availableWidthRef = useRef<number>(0); const availableWidthRef = useRef<number>(0);
const defaultSubplebbits = useDefaultSubplebbits();
const { refs, floatingStyles, update } = useFloating({ const { refs, floatingStyles, update } = useFloating({
placement: placementRef.current, placement: placementRef.current,
@@ -100,7 +103,8 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
const handleClick = (e: React.MouseEvent, cid: string | undefined, subplebbitAddress: string | undefined) => { const handleClick = (e: React.MouseEvent, cid: string | undefined, subplebbitAddress: string | undefined) => {
e.preventDefault(); e.preventDefault();
if (cid && subplebbitAddress) { if (cid && subplebbitAddress) {
navigate(`/p/${subplebbitAddress}/c/${cid}`); const boardPath = getBoardPath(subplebbitAddress, defaultSubplebbits);
navigate(`/${boardPath}/thread/${cid}`);
setTimeout(() => { setTimeout(() => {
const element = document.querySelector(`[data-cid="${cid}"]`); const element = document.querySelector(`[data-cid="${cid}"]`);
element?.scrollIntoView(); element?.scrollIntoView();
@@ -131,7 +135,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
<> <>
<Link <Link
className={styles.backlink} className={styles.backlink}
to={`/p/${backlinkReply?.subplebbitAddress}/c/${backlinkReply?.cid}`} to={`/${getBoardPath(backlinkReply?.subplebbitAddress || '', defaultSubplebbits)}/thread/${backlinkReply?.cid}`}
ref={refs.setReference} ref={refs.setReference}
onMouseOver={() => handleMouseOver(backlinkReply?.cid)} onMouseOver={() => handleMouseOver(backlinkReply?.cid)}
onMouseLeave={() => handleMouseLeave(backlinkReply?.cid)} onMouseLeave={() => handleMouseLeave(backlinkReply?.cid)}
@@ -158,7 +162,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
const replyQuotelink = ( const replyQuotelink = (
<> <>
<Link <Link
to={`/p/${quotelinkReply?.subplebbitAddress}/c/${quotelinkReply?.cid}`} to={`/${getBoardPath(quotelinkReply?.subplebbitAddress || '', defaultSubplebbits)}/thread/${quotelinkReply?.cid}`}
ref={refs.setReference} ref={refs.setReference}
className={styles.quoteLink} className={styles.quoteLink}
onMouseOver={() => handleMouseOver(quotelinkReply?.cid)} onMouseOver={() => handleMouseOver(quotelinkReply?.cid)}
@@ -186,6 +190,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => { const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
const [hoveredCid, setHoveredCid] = useState<string | null>(null); const [hoveredCid, setHoveredCid] = useState<string | null>(null);
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null); const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
const defaultSubplebbits = useDefaultSubplebbits();
const { refs, floatingStyles, update } = useFloating({ const { refs, floatingStyles, update } = useFloating({
placement: 'bottom', placement: 'bottom',
@@ -207,7 +212,8 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
const handleClick = (e: React.MouseEvent, cid: string | undefined, subplebbitAddress: string | undefined) => { const handleClick = (e: React.MouseEvent, cid: string | undefined, subplebbitAddress: string | undefined) => {
e.preventDefault(); e.preventDefault();
if (cid && subplebbitAddress) { if (cid && subplebbitAddress) {
navigate(`/p/${subplebbitAddress}/c/${cid}`); const boardPath = getBoardPath(subplebbitAddress, defaultSubplebbits);
navigate(`/${boardPath}/thread/${cid}`);
setTimeout(() => { setTimeout(() => {
const element = document.querySelector(`[data-cid="${cid}"]`); const element = document.querySelector(`[data-cid="${cid}"]`);
element?.scrollIntoView(); element?.scrollIntoView();
@@ -246,7 +252,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
</span> </span>
{backlinkReply?.shortCid && ( {backlinkReply?.shortCid && (
<Link <Link
to={`/p/${backlinkReply?.subplebbitAddress}/c/${backlinkReply?.cid}`} to={`/${getBoardPath(backlinkReply?.subplebbitAddress || '', defaultSubplebbits)}/thread/${backlinkReply?.cid}`}
className={styles.backlinkHash} className={styles.backlinkHash}
onClick={(e) => handleClick(e, backlinkReply?.cid, backlinkReply?.subplebbitAddress)} onClick={(e) => handleClick(e, backlinkReply?.cid, backlinkReply?.subplebbitAddress)}
> >
@@ -283,7 +289,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
{quotelinkReply?.shortCid && ( {quotelinkReply?.shortCid && (
<Link <Link
className={styles.quoteLink} className={styles.quoteLink}
to={`/p/${quotelinkReply?.subplebbitAddress}/c/${quotelinkReply?.cid}`} to={`/${getBoardPath(quotelinkReply?.subplebbitAddress || '', defaultSubplebbits)}/thread/${quotelinkReply?.cid}`}
onClick={(e) => handleClick(e, quotelinkReply?.cid, quotelinkReply?.subplebbitAddress)} onClick={(e) => handleClick(e, quotelinkReply?.cid, quotelinkReply?.subplebbitAddress)}
> >
{' '} {' '}
@@ -5,6 +5,7 @@ import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subple
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages'; import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store'; import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils'; import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import styles from './subplebbit-stats.module.css'; import styles from './subplebbit-stats.module.css';
const SubplebbitStats = () => { const SubplebbitStats = () => {
@@ -12,7 +13,8 @@ const SubplebbitStats = () => {
const params = useParams(); const params = useParams();
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]); const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
const { address, createdAt } = subplebbit || {}; const { address, createdAt } = subplebbit || {};
+39 -17
View File
@@ -4,7 +4,9 @@ import { useTranslation } from 'react-i18next';
import Plebbit from '@plebbit/plebbit-js'; import Plebbit from '@plebbit/plebbit-js';
import { useAccount, useAccountComment, useAccountSubplebbits } from '@plebbit/plebbit-react-hooks'; import { useAccount, useAccountComment, useAccountSubplebbits } from '@plebbit/plebbit-react-hooks';
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitAddresses, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useBoardPath, useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { getBoardPath } from '../../lib/utils/route-utils';
import { TimeFilter } from '../board-buttons'; import { TimeFilter } from '../board-buttons';
import useCreateBoardModalStore from '../../stores/use-create-board-modal-store'; import useCreateBoardModalStore from '../../stores/use-create-board-modal-store';
import styles from './topbar.module.css'; import styles from './topbar.module.css';
@@ -55,7 +57,7 @@ const SearchBar = ({ setShowSearchBar }: { setShowSearchBar: (show: boolean) =>
const searchInput = searchInputRef.current?.value; const searchInput = searchInputRef.current?.value;
if (searchInput) { if (searchInput) {
searchInputRef.current.value = ''; searchInputRef.current.value = '';
navigate(`/p/${searchInput}`); navigate(`/${searchInput}`);
setShowSearchBar(false); setShowSearchBar(false);
} }
}; };
@@ -77,6 +79,7 @@ const TopBarDesktop = () => {
const isInCatalogView = isCatalogView(location.pathname, params); const isInCatalogView = isCatalogView(location.pathname, params);
const [showSearchBar, setShowSearchBar] = useState(false); const [showSearchBar, setShowSearchBar] = useState(false);
const { openCreateBoardModal } = useCreateBoardModalStore(); const { openCreateBoardModal } = useCreateBoardModalStore();
const defaultSubplebbits = useDefaultSubplebbits();
const subscriptions = account?.subscriptions; const subscriptions = account?.subscriptions;
@@ -86,26 +89,28 @@ const TopBarDesktop = () => {
return ( return (
<div className={styles.boardNavDesktop}> <div className={styles.boardNavDesktop}>
<span className={styles.boardList}> <span className={styles.boardList}>
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link> [<Link to='/all'>all</Link> / <Link to='/subscriptions'>subscriptions</Link>
{accountSubplebbitAddresses.length > 0 && ( {accountSubplebbitAddresses.length > 0 && (
<> <>
{' '} {' '}
/ <Link to='/p/mod'>mod</Link> / <Link to='/mod'>mod</Link>
</> </>
)} )}
]{' '} ]{' '}
{subscriptions?.length > 0 && ( {subscriptions?.length > 0 && (
<> <>
[ [
{subscriptions.map((address: any, index: any) => ( {subscriptions.map((address: any, index: any) => {
<span key={index}> const boardPath = getBoardPath(address, defaultSubplebbits);
{index === 0 ? null : ' '} const displayText = address.endsWith('.eth') || address.endsWith('.sol') ? address : address.slice(0, 10).concat('...');
<Link to={`/p/${address}${isInCatalogView ? '/catalog' : ''}`}> return (
{address.endsWith('.eth') || address.endsWith('.sol') ? address : address.slice(0, 10).concat('...')} <span key={index}>
</Link> {index === 0 ? null : ' '}
{index !== subscriptions?.length - 1 ? ' /' : null} {boardPath && boardPath.trim() ? <Link to={`/${boardPath}${isInCatalogView ? '/catalog' : ''}`}>{displayText}</Link> : <span>{displayText}</span>}
</span> {index !== subscriptions?.length - 1 ? ' /' : null}
))} </span>
);
})}
]{' '} ]{' '}
</> </>
)} )}
@@ -128,6 +133,7 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const subplebbitAddresses = useDefaultSubplebbitAddresses(); const subplebbitAddresses = useDefaultSubplebbitAddresses();
const defaultSubplebbits = useDefaultSubplebbits();
const displaySubplebbitAddress = subplebbitAddress && subplebbitAddress.length > 30 ? subplebbitAddress.slice(0, 30).concat('...') : subplebbitAddress; const displaySubplebbitAddress = subplebbitAddress && subplebbitAddress.length > 30 ? subplebbitAddress.slice(0, 30).concat('...') : subplebbitAddress;
const [showSearchBar, setShowSearchBar] = useState(false); const [showSearchBar, setShowSearchBar] = useState(false);
@@ -138,21 +144,36 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInCatalogView = isCatalogView(location.pathname, params); const isInCatalogView = isCatalogView(location.pathname, params);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : subplebbitAddress; const boardPath = useBoardPath(subplebbitAddress);
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : boardPath || subplebbitAddress;
const { accountSubplebbits } = useAccountSubplebbits(); const { accountSubplebbits } = useAccountSubplebbits();
const accountSubplebbitAddresses = Object.keys(accountSubplebbits); const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
const boardSelect = ( const boardSelect = (
<select value={selectValue} onChange={(e) => navigate(`/p/${e.target.value}${isInCatalogView ? '/catalog' : ''}`)}> <select
value={selectValue}
onChange={(e) => {
const value = e.target.value;
// If it's a directory code or special route, use it directly
if (value === 'all' || value === 'subscriptions' || value === 'mod') {
navigate(`/${value}${isInCatalogView ? '/catalog' : ''}`);
} else {
// Otherwise, resolve to board path
const path = getBoardPath(value, defaultSubplebbits);
navigate(`/${path}${isInCatalogView ? '/catalog' : ''}`);
}
}}
>
{!currentSubplebbitIsInList && subplebbitAddress && <option value={subplebbitAddress}>{displaySubplebbitAddress}</option>} {!currentSubplebbitIsInList && subplebbitAddress && <option value={subplebbitAddress}>{displaySubplebbitAddress}</option>}
<option value='all'>all</option> <option value='all'>all</option>
<option value='subscriptions'>subscriptions</option> <option value='subscriptions'>subscriptions</option>
{accountSubplebbitAddresses.length > 0 && <option value='mod'>mod</option>} {accountSubplebbitAddresses.length > 0 && <option value='mod'>mod</option>}
{subplebbitAddresses.map((address: any, index: number) => { {subplebbitAddresses.map((address: any, index: number) => {
const subplebbitAddress = address?.includes('.') ? address : Plebbit.getShortAddress(address); const subplebbitAddress = address?.includes('.') ? address : Plebbit.getShortAddress(address);
const boardPath = getBoardPath(address, defaultSubplebbits);
return ( return (
<option key={index} value={address}> <option key={index} value={boardPath}>
{Plebbit.getShortAddress(subplebbitAddress)} {Plebbit.getShortAddress(subplebbitAddress)}
</option> </option>
); );
@@ -200,7 +221,8 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
const TopBar = () => { const TopBar = () => {
const params = useParams(); const params = useParams();
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any }); const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress; const resolvedSubplebbitAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedSubplebbitAddress || accountComment?.subplebbitAddress;
return ( return (
<> <>
+3 -1
View File
@@ -3,6 +3,7 @@ import { useLocation, useParams } from 'react-router-dom';
import { isAllView, isSubscriptionsView, isModView } from '../lib/utils/view-utils'; import { isAllView, isSubscriptionsView, isModView } from '../lib/utils/view-utils';
import useThemeStore from '../stores/use-theme-store'; import useThemeStore from '../stores/use-theme-store';
import { useDefaultSubplebbits } from './use-default-subplebbits'; import { useDefaultSubplebbits } from './use-default-subplebbits';
import { useResolvedSubplebbitAddress } from './use-resolved-subplebbit-address';
import { useAccountComment } from '@plebbit/plebbit-react-hooks'; import { useAccountComment } from '@plebbit/plebbit-react-hooks';
import useSpecialThemeStore from '../stores/use-special-theme-store'; import useSpecialThemeStore from '../stores/use-special-theme-store';
import { isChristmas } from '../lib/utils/time-utils'; import { isChristmas } from '../lib/utils/time-utils';
@@ -33,7 +34,8 @@ const useTheme = (): [string, (theme: string) => void] => {
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress; const resolvedAddress = useResolvedSubplebbitAddress();
const subplebbitAddress = resolvedAddress || pendingPostSubplebbitAddress;
// Check for Christmas and initialize special theme if needed // Check for Christmas and initialize special theme if needed
useEffect(() => { useEffect(() => {
+44 -5
View File
@@ -42,17 +42,30 @@ export const is5chanLink = (url: string): boolean => {
routePath = parsedUrl.hash.substring(1); // Remove the # to get the path routePath = parsedUrl.hash.substring(1); // Remove the # to get the path
} }
// For pleb.bz, only support the exact sharelink format // For pleb.bz, only support the exact sharelink format (legacy /p/... format)
if (hostname === 'pleb.bz') { if (hostname === 'pleb.bz') {
// Must match exactly: /p/{subplebbitAddress}/c/{cid} // Must match exactly: /p/{subplebbitAddress}/c/{cid}
// Allow redirect parameter since these are still valid internal links // Allow redirect parameter since these are still valid internal links
return /^\/p\/[^/]+\/c\/[^/]+$/.test(routePath); return /^\/p\/[^/]+\/c\/[^/]+$/.test(routePath);
} }
// For other 5chan hostnames, support: // For other 5chan hostnames, support both old and new formats:
// Old format (for backward compatibility):
// - /p/{subplebbitAddress} // - /p/{subplebbitAddress}
// - /p/{subplebbitAddress}/c/{commentCid} // - /p/{subplebbitAddress}/c/{commentCid}
return /^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath); // New format:
// - /{boardIdentifier} (directory code or address)
// - /{boardIdentifier}/thread/{commentCid}
// - /{boardIdentifier}/catalog
// - /{boardIdentifier}/description
// - /{boardIdentifier}/rules
// - /all, /subscriptions, /mod, /pending/{index}
return (
/^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath) ||
/^\/[^/]+(\/thread\/[^/]+|\/catalog|\/description|\/rules)?$/.test(routePath) ||
/^\/(all|subscriptions|mod)(\/catalog|\/thread\/[^/]+)?(\/[^/]+)?$/.test(routePath) ||
/^\/pending\/[^/]+$/.test(routePath)
);
} catch { } catch {
return false; return false;
} }
@@ -71,7 +84,8 @@ export const transform5chanLinkToInternal = (url: string): string | null => {
if (parsedUrl.hash && parsedUrl.hash.startsWith('#/')) { if (parsedUrl.hash && parsedUrl.hash.startsWith('#/')) {
// Extract the route from the hash, preserving any query params within the hash // Extract the route from the hash, preserving any query params within the hash
const hashPath = parsedUrl.hash.substring(1); // Remove the # const hashPath = parsedUrl.hash.substring(1); // Remove the #
return hashPath; // Transform old /p/... format to new format if needed
return transformOldPathToNew(hashPath);
} }
// For regular pathname-based routes, remove redirect parameter from query string // For regular pathname-based routes, remove redirect parameter from query string
@@ -81,12 +95,37 @@ export const transform5chanLinkToInternal = (url: string): string | null => {
const cleanSearch = searchParams.toString(); const cleanSearch = searchParams.toString();
const searchString = cleanSearch ? `?${cleanSearch}` : ''; const searchString = cleanSearch ? `?${cleanSearch}` : '';
return parsedUrl.pathname + searchString + parsedUrl.hash; // Transform old /p/... format to new format if needed
const transformedPath = transformOldPathToNew(parsedUrl.pathname);
return transformedPath + searchString + parsedUrl.hash;
} catch { } catch {
return null; return null;
} }
}; };
// Transform old URL format (/p/{address}/c/{cid}) to new format (/{boardIdentifier}/thread/{cid})
// Note: This function doesn't resolve directory codes - that's handled by the routing system
const transformOldPathToNew = (path: string): string => {
// Transform /p/{address}/c/{cid} to /{address}/thread/{cid}
const oldPostPattern = /^\/p\/([^/]+)\/c\/([^/]+)$/;
const postMatch = path.match(oldPostPattern);
if (postMatch) {
const [, address, cid] = postMatch;
return `/${address}/thread/${cid}`;
}
// Transform /p/{address} to /{address}
const oldBoardPattern = /^\/p\/([^/]+)$/;
const boardMatch = path.match(oldBoardPattern);
if (boardMatch) {
const [, address] = boardMatch;
return `/${address}`;
}
// Return path as-is if it doesn't match old patterns
return path;
};
// Check if a string is a valid IPNS public key (52 chars starting with 12D3KooW) // Check if a string is a valid IPNS public key (52 chars starting with 12D3KooW)
const isValidIPNSKey = (str: string): boolean => { const isValidIPNSKey = (str: string): boolean => {
return str.length === 52 && str.startsWith('12D3KooW'); return str.length === 52 && str.startsWith('12D3KooW');
+54 -32
View File
@@ -1,41 +1,60 @@
export type ParamsType = { export type ParamsType = {
accountCommentIndex?: string; accountCommentIndex?: string;
boardIdentifier?: string;
commentCid?: string; commentCid?: string;
subplebbitAddress?: string; subplebbitAddress?: string; // deprecated, kept for backward compatibility
timeFilterName?: string; timeFilterName?: string;
}; };
export const isAllView = (pathname: string): boolean => { export const isAllView = (pathname: string): boolean => {
return pathname.startsWith('/p/all'); return pathname.startsWith('/all');
}; };
export const isBoardView = (pathname: string, params: ParamsType): boolean => { export const isBoardView = (pathname: string, params: ParamsType): boolean => {
// some subs might use emojis in their address, so we need to decode the pathname // some subs might use emojis in their address, so we need to decode the pathname
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}`) : false; // Check if it's a board view (not all, subscriptions, mod, pending, or special routes)
if (
pathname.startsWith('/all') ||
pathname.startsWith('/subscriptions') ||
pathname.startsWith('/mod') ||
pathname.startsWith('/pending') ||
pathname === '/' ||
pathname.startsWith('/faq') ||
pathname.startsWith('/not-found')
) {
return false;
}
const identifier = params.boardIdentifier || params.subplebbitAddress;
return identifier ? decodedPathname.startsWith(`/${identifier}`) : false;
}; };
export const isCatalogView = (pathname: string, params: ParamsType): boolean => { export const isCatalogView = (pathname: string, params: ParamsType): boolean => {
const { subplebbitAddress, timeFilterName } = params; const { boardIdentifier, subplebbitAddress, timeFilterName } = params;
const identifier = boardIdentifier || subplebbitAddress;
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return ( return (
decodedPathname === `/p/${subplebbitAddress}/catalog` || (identifier && (decodedPathname === `/${identifier}/catalog` || decodedPathname === `/${identifier}/catalog/settings`)) ||
decodedPathname === `/p/${subplebbitAddress}/catalog/settings` || decodedPathname === `/all/catalog` ||
decodedPathname === `/p/all/catalog` || decodedPathname === `/all/catalog/settings` ||
decodedPathname === `/p/all/catalog/settings` || decodedPathname === `/all/catalog/${timeFilterName}` ||
decodedPathname === `/p/all/catalog/${timeFilterName}` || decodedPathname === `/all/catalog/${timeFilterName}/settings` ||
decodedPathname === `/p/all/catalog/${timeFilterName}/settings` || decodedPathname === `/subscriptions/catalog` ||
decodedPathname === `/p/subscriptions/catalog` || decodedPathname === `/subscriptions/catalog/settings` ||
decodedPathname === `/p/subscriptions/catalog/settings` || decodedPathname === `/subscriptions/catalog/${timeFilterName}` ||
decodedPathname === `/p/subscriptions/catalog/${timeFilterName}` || decodedPathname === `/subscriptions/catalog/${timeFilterName}/settings` ||
decodedPathname === `/p/subscriptions/catalog/${timeFilterName}/settings` decodedPathname === `/mod/catalog` ||
decodedPathname === `/mod/catalog/settings` ||
decodedPathname === `/mod/catalog/${timeFilterName}` ||
decodedPathname === `/mod/catalog/${timeFilterName}/settings`
); );
}; };
export const isDescriptionView = (pathname: string, params: ParamsType): boolean => { export const isDescriptionView = (pathname: string, params: ParamsType): boolean => {
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return pathname === '/p/all/description' ? true : params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/description`) : false; const identifier = params.boardIdentifier || params.subplebbitAddress;
return pathname === '/all/description' ? true : identifier ? decodedPathname.startsWith(`/${identifier}/description`) : false;
}; };
export const isHomeView = (pathname: string): boolean => { export const isHomeView = (pathname: string): boolean => {
@@ -43,11 +62,11 @@ export const isHomeView = (pathname: string): boolean => {
}; };
export const isModView = (pathname: string): boolean => { export const isModView = (pathname: string): boolean => {
return pathname === `/p/mod` || pathname.startsWith(`/p/mod/`); return pathname === `/mod` || pathname.startsWith(`/mod/`);
}; };
export const isPendingPostView = (pathname: string, params: ParamsType): boolean => { export const isPendingPostView = (pathname: string, params: ParamsType): boolean => {
return pathname === `/profile/${params.accountCommentIndex}` || pathname === `/profile/${params.accountCommentIndex}/settings`; return pathname === `/pending/${params.accountCommentIndex}` || pathname === `/pending/${params.accountCommentIndex}/settings`;
}; };
export const isPostPageView = (pathname: string, params: ParamsType): boolean => { export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
@@ -55,36 +74,39 @@ export const isPostPageView = (pathname: string, params: ParamsType): boolean =>
return true; return true;
} }
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress && params.commentCid ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/c/${params.commentCid}`) : false; const identifier = params.boardIdentifier || params.subplebbitAddress;
return identifier && params.commentCid ? decodedPathname.startsWith(`/${identifier}/thread/${params.commentCid}`) : false;
}; };
export const isRulesView = (pathname: string, params: ParamsType): boolean => { export const isRulesView = (pathname: string, params: ParamsType): boolean => {
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/rules`) : false; const identifier = params.boardIdentifier || params.subplebbitAddress;
return identifier ? decodedPathname.startsWith(`/${identifier}/rules`) : false;
}; };
export const isSettingsView = (pathname: string, params: ParamsType): boolean => { export const isSettingsView = (pathname: string, params: ParamsType): boolean => {
const { accountCommentIndex, commentCid, subplebbitAddress } = params; const { accountCommentIndex, boardIdentifier, commentCid, subplebbitAddress } = params;
const identifier = boardIdentifier || subplebbitAddress;
const decodedPathname = decodeURIComponent(pathname); const decodedPathname = decodeURIComponent(pathname);
return ( return (
decodedPathname === `/p/${subplebbitAddress}/c/${commentCid}/settings` || (identifier && commentCid && decodedPathname === `/${identifier}/thread/${commentCid}/settings`) ||
decodedPathname === `/p/${subplebbitAddress}/description/settings` || (identifier && decodedPathname === `/${identifier}/description/settings`) ||
decodedPathname === `/p/${subplebbitAddress}/rules/settings` || (identifier && decodedPathname === `/${identifier}/rules/settings`) ||
decodedPathname === `/profile/${accountCommentIndex}/settings` decodedPathname === `/pending/${accountCommentIndex}/settings`
); );
}; };
export const isSubscriptionsView = (pathname: string, params: ParamsType): boolean => { export const isSubscriptionsView = (pathname: string, params: ParamsType): boolean => {
const { timeFilterName } = params; const { timeFilterName } = params;
return ( return (
pathname === '/p/subscriptions' || pathname === '/subscriptions' ||
pathname === '/p/subscriptions/settings' || pathname === '/subscriptions/settings' ||
pathname === `/p/subscriptions/${timeFilterName}` || pathname === `/subscriptions/${timeFilterName}` ||
pathname === `/p/subscriptions/${timeFilterName}/settings` || pathname === `/subscriptions/${timeFilterName}/settings` ||
pathname === '/p/subscriptions/catalog' || pathname === '/subscriptions/catalog' ||
pathname === '/p/subscriptions/catalog/settings' || pathname === '/subscriptions/catalog/settings' ||
pathname === `/p/subscriptions/catalog/${timeFilterName}` || pathname === `/subscriptions/catalog/${timeFilterName}` ||
pathname === `/p/subscriptions/catalog/${timeFilterName}/settings` pathname === `/subscriptions/catalog/${timeFilterName}/settings`
); );
}; };
+13 -6
View File
@@ -6,7 +6,8 @@ export const DISCLAIMER_ACCEPTED_KEY = '5chan-disclaimer-accepted';
interface DisclaimerModalState { interface DisclaimerModalState {
showModal: boolean; showModal: boolean;
targetAddress: string | null; targetAddress: string | null;
showDisclaimerModal: (address: string, navigate: NavigateFunction) => void; targetBoardPath: string | null;
showDisclaimerModal: (address: string, navigate: NavigateFunction, boardPath?: string) => void;
closeDisclaimerModal: () => void; closeDisclaimerModal: () => void;
acceptDisclaimer: (navigate: NavigateFunction) => void; acceptDisclaimer: (navigate: NavigateFunction) => void;
} }
@@ -30,12 +31,14 @@ const setDisclaimerAccepted = (): void => {
const useDisclaimerModalStore = create<DisclaimerModalState>((set) => ({ const useDisclaimerModalStore = create<DisclaimerModalState>((set) => ({
showModal: false, showModal: false,
targetAddress: null, targetAddress: null,
targetBoardPath: null,
showDisclaimerModal: (address: string, navigate: NavigateFunction) => { showDisclaimerModal: (address: string, navigate: NavigateFunction, boardPath?: string) => {
// Check if user has already accepted the disclaimer // Check if user has already accepted the disclaimer
if (hasAcceptedDisclaimer()) { if (hasAcceptedDisclaimer()) {
// Navigate directly without showing modal // Navigate directly without showing modal
navigate(`/p/${address}`); const path = boardPath || address;
navigate(`/${path}`);
return; return;
} }
@@ -43,6 +46,7 @@ const useDisclaimerModalStore = create<DisclaimerModalState>((set) => ({
set({ set({
showModal: true, showModal: true,
targetAddress: address, targetAddress: address,
targetBoardPath: boardPath || null,
}); });
}, },
@@ -50,6 +54,7 @@ const useDisclaimerModalStore = create<DisclaimerModalState>((set) => ({
set({ set({
showModal: false, showModal: false,
targetAddress: null, targetAddress: null,
targetBoardPath: null,
}); });
}, },
@@ -59,15 +64,17 @@ const useDisclaimerModalStore = create<DisclaimerModalState>((set) => ({
// Save acceptance to localStorage // Save acceptance to localStorage
setDisclaimerAccepted(); setDisclaimerAccepted();
// Navigate to the target address // Navigate to the target board path (or address if no path)
if (state.targetAddress) { const path = state.targetBoardPath || state.targetAddress;
navigate(`/p/${state.targetAddress}`); if (path) {
navigate(`/${path}`);
} }
// Close the modal // Close the modal
set({ set({
showModal: false, showModal: false,
targetAddress: null, targetAddress: null,
targetBoardPath: null,
}); });
}, },
})); }));
+5 -3
View File
@@ -8,6 +8,7 @@ import { shouldShowSnow } from '../../lib/snow';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils'; import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string'; import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter from '../../hooks/use-time-filter'; import useTimeFilter from '../../hooks/use-time-filter';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store'; import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
@@ -35,7 +36,8 @@ const createThreadsWithoutImagesFilter = () => ({
const Board = () => { const Board = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation(); const location = useLocation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>(); const subplebbitAddress = useResolvedSubplebbitAddress();
const boardPath = useBoardPath(subplebbitAddress);
const { hideThreadsWithoutImages } = useInterfaceSettingsStore(); const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
@@ -194,7 +196,7 @@ const Board = () => {
i18nKey='more_threads_last_week' i18nKey='more_threads_last_week'
values={{ currentTimeFilterName, count: feed.length }} values={{ currentTimeFilterName, count: feed.length }}
components={{ components={{
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : isInModView ? '/p/mod' : `/p/${subplebbitAddress}`) + '/1w'} />, 1: <Link to={(isInAllView ? '/all' : isInSubscriptionsView ? '/subscriptions' : isInModView ? '/mod' : `/${boardPath}`) + '/1w'} />,
}} }}
/> />
</div> </div>
@@ -204,7 +206,7 @@ const Board = () => {
i18nKey='more_threads_last_month' i18nKey='more_threads_last_month'
values={{ currentTimeFilterName, count: feed.length }} values={{ currentTimeFilterName, count: feed.length }}
components={{ components={{
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : isInModView ? '/p/mod' : `/p/${subplebbitAddress}`) + '/1m'} />, 1: <Link to={(isInAllView ? '/all' : isInSubscriptionsView ? '/subscriptions' : isInModView ? '/mod' : `/${boardPath}`) + '/1m'} />,
}} }}
/> />
</div> </div>
+5 -3
View File
@@ -7,6 +7,7 @@ import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-util
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows'; import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string'; import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter from '../../hooks/use-time-filter'; import useTimeFilter from '../../hooks/use-time-filter';
import useWindowWidth from '../../hooks/use-window-width'; import useWindowWidth from '../../hooks/use-window-width';
@@ -125,7 +126,8 @@ const createCombinedFilter = (
const Catalog = () => { const Catalog = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation(); const location = useLocation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>(); const subplebbitAddress = useResolvedSubplebbitAddress();
const boardPath = useBoardPath(subplebbitAddress);
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const defaultSubplebbits = useDefaultSubplebbits(); const defaultSubplebbits = useDefaultSubplebbits();
@@ -366,7 +368,7 @@ const Catalog = () => {
i18nKey='more_threads_last_week' i18nKey='more_threads_last_week'
values={{ currentTimeFilterName, count: feed.length }} values={{ currentTimeFilterName, count: feed.length }}
components={{ components={{
1: <Link to={(isInAllView ? '/p/all/catalog' : isInSubscriptionsView ? '/p/subscriptions/catalog' : `/p/${subplebbitAddress}/catalog`) + '/1w'} />, 1: <Link to={(isInAllView ? '/all/catalog' : isInSubscriptionsView ? '/subscriptions/catalog' : `/${boardPath}/catalog`) + '/1w'} />,
}} }}
/> />
</div> </div>
@@ -376,7 +378,7 @@ const Catalog = () => {
i18nKey='more_threads_last_month' i18nKey='more_threads_last_month'
values={{ currentTimeFilterName, count: feed.length }} values={{ currentTimeFilterName, count: feed.length }}
components={{ components={{
1: <Link to={(isInAllView ? '/p/all/catalog' : isInSubscriptionsView ? '/p/subscriptions/catalog' : `/p/${subplebbitAddress}/catalog`) + '/1m'} />, 1: <Link to={(isInAllView ? '/all/catalog' : isInSubscriptionsView ? '/subscriptions/catalog' : `/${boardPath}/catalog`) + '/1m'} />,
}} }}
/> />
</div> </div>
+7 -3
View File
@@ -1,6 +1,7 @@
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDefaultSubplebbitsState, MultisubSubplebbit } from '../../../hooks/use-default-subplebbits'; import { useDefaultSubplebbitsState, useDefaultSubplebbits, MultisubSubplebbit } from '../../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../../lib/utils/route-utils';
import LoadingEllipsis from '../../../components/loading-ellipsis'; import LoadingEllipsis from '../../../components/loading-ellipsis';
import useDisclaimerModalStore from '../../../stores/use-disclaimer-modal-store'; import useDisclaimerModalStore from '../../../stores/use-disclaimer-modal-store';
import useDirectoryModalStore from '../../../stores/use-directory-modal-store'; import useDirectoryModalStore from '../../../stores/use-directory-modal-store';
@@ -34,16 +35,19 @@ const BoardsList = ({ multisub }: { multisub: MultisubSubplebbit[] }) => {
const { showDisclaimerModal } = useDisclaimerModalStore(); const { showDisclaimerModal } = useDisclaimerModalStore();
const { openDirectoryModal } = useDirectoryModalStore(); const { openDirectoryModal } = useDirectoryModalStore();
const { useCatalogLinks, boardFilter } = useBoardsFilterStore(); const { useCatalogLinks, boardFilter } = useBoardsFilterStore();
const defaultSubplebbits = useDefaultSubplebbits();
const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>, address: string) => { const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>, address: string) => {
e.preventDefault(); e.preventDefault();
showDisclaimerModal(address, navigate); const boardPath = getBoardPath(address, defaultSubplebbits);
showDisclaimerModal(address, navigate, boardPath);
}; };
// Helper to generate link URL with optional catalog suffix // Helper to generate link URL with optional catalog suffix
const getBoardLink = (address: string | null): string => { const getBoardLink = (address: string | null): string => {
if (!address) return '#'; if (!address) return '#';
return `/p/${address}${useCatalogLinks ? '/catalog' : ''}`; const boardPath = getBoardPath(address, defaultSubplebbits);
return `/${boardPath}${useCatalogLinks ? '/catalog' : ''}`;
}; };
// Handler for placeholder board links // Handler for placeholder board links
@@ -9,7 +9,8 @@ import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { CatalogPostMedia } from '../../../components/catalog-row'; import { CatalogPostMedia } from '../../../components/catalog-row';
import LoadingEllipsis from '../../../components/loading-ellipsis'; import LoadingEllipsis from '../../../components/loading-ellipsis';
import BoxModal from '../box-modal'; import BoxModal from '../box-modal';
import { MultisubSubplebbit } from '../../../hooks/use-default-subplebbits'; import { MultisubSubplebbit, useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../../lib/utils/route-utils';
import { removeMarkdown } from '../../../lib/utils/post-utils'; import { removeMarkdown } from '../../../lib/utils/post-utils';
interface PopularThreadProps { interface PopularThreadProps {
@@ -27,16 +28,18 @@ export const ContentPreview = ({ content, maxLength = 99 }: { content: string; m
const PopularThreadCard = ({ post, multisub }: PopularThreadProps) => { const PopularThreadCard = ({ post, multisub }: PopularThreadProps) => {
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {}; const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
const defaultSubplebbits = useDefaultSubplebbits();
// Find the matching MultisubSubplebbit entry and get its title // Find the matching MultisubSubplebbit entry and get its title
const multisubEntry = multisub.find((ms) => ms?.address === subplebbitAddress); const multisubEntry = multisub.find((ms) => ms?.address === subplebbitAddress);
const boardTitle = multisubEntry?.title?.replace(/^\/[^/]+\/\s*-\s*/, '') || ''; const boardTitle = multisubEntry?.title?.replace(/^\/[^/]+\/\s*-\s*/, '') || '';
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
return ( return (
<div className={styles.popularThread} key={cid}> <div className={styles.popularThread} key={cid}>
<div className={styles.title}>{boardTitle}</div> <div className={styles.title}>{boardTitle}</div>
<div className={styles.mediaContainer}> <div className={styles.mediaContainer}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`}> <Link to={`/${boardPath}/thread/${cid}`}>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={true} cid={cid} /> <CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={true} cid={cid} />
</Link> </Link>
</div> </div>
+5 -2
View File
@@ -13,7 +13,10 @@ const NotFoundImage = () => {
const NotFound = () => { const NotFound = () => {
const location = useLocation(); const location = useLocation();
const subplebbitAddress = location.pathname.startsWith('/p/') ? location.pathname.split('/')[2] : ''; // Extract boardIdentifier from pathname (could be directory code or address)
const pathParts = location.pathname.split('/').filter(Boolean);
const boardIdentifier = pathParts[0] && pathParts[0] !== 'not-found' && pathParts[0] !== 'faq' ? pathParts[0] : '';
const subplebbitAddress = boardIdentifier || '';
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]); const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
const { address, shortAddress } = subplebbit || {}; const { address, shortAddress } = subplebbit || {};
@@ -32,7 +35,7 @@ const NotFound = () => {
<> <>
<br /> <br />
<div className={styles.backToBoard}> <div className={styles.backToBoard}>
[<Link to={`/p/${subplebbitAddress}`}>Back to p/{shortAddress}</Link>] [<Link to={`/${boardIdentifier || subplebbitAddress}`}>Back to p/{shortAddress}</Link>]
</div> </div>
</> </>
)} )}
+6 -2
View File
@@ -1,6 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { useAccountComment, useAccountComments } from '@plebbit/plebbit-react-hooks'; import { useAccountComment, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { getBoardPath } from '../../lib/utils/route-utils';
import { Post } from '../post'; import { Post } from '../post';
const PendingPost = () => { const PendingPost = () => {
@@ -9,6 +11,7 @@ const PendingPost = () => {
const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined; const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined;
const post = useAccountComment({ commentIndex }); const post = useAccountComment({ commentIndex });
const navigate = useNavigate(); const navigate = useNavigate();
const defaultSubplebbits = useDefaultSubplebbits();
useEffect(() => window.scrollTo(0, 0), []); useEffect(() => window.scrollTo(0, 0), []);
@@ -28,9 +31,10 @@ const PendingPost = () => {
useEffect(() => { useEffect(() => {
if (post?.cid && post?.subplebbitAddress) { if (post?.cid && post?.subplebbitAddress) {
navigate(`/p/${post?.subplebbitAddress}/c/${post?.cid}`, { replace: true }); const boardPath = getBoardPath(post.subplebbitAddress, defaultSubplebbits);
navigate(`/${boardPath}/thread/${post.cid}`, { replace: true });
} }
}, [post, navigate]); }, [post, navigate, defaultSubplebbits]);
return <Post post={post} />; return <Post post={post} />;
}; };
+4 -2
View File
@@ -4,6 +4,7 @@ import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@ple
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits'; import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils'; import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import ErrorDisplay from '../../components/error-display/error-display'; import ErrorDisplay from '../../components/error-display/error-display';
import PostDesktop from '../../components/post-desktop'; import PostDesktop from '../../components/post-desktop';
@@ -53,7 +54,8 @@ const PostPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const { commentCid, subplebbitAddress } = params; const { commentCid } = params;
const subplebbitAddress = useResolvedSubplebbitAddress();
const isInAllView = isAllView(location.pathname); const isInAllView = isAllView(location.pathname);
const isInDescriptionView = isDescriptionView(location.pathname, params); const isInDescriptionView = isDescriptionView(location.pathname, params);
const isInRulesView = isRulesView(location.pathname, params); const isInRulesView = isRulesView(location.pathname, params);
@@ -101,7 +103,7 @@ const PostPage = () => {
avatarUrl={suggested?.avatarUrl} avatarUrl={suggested?.avatarUrl}
createdAt={createdAt} createdAt={createdAt}
description={description} description={description}
replyCount={location.pathname.startsWith('/p/all/') ? 0 : rules?.length > 0 ? 1 : 0} replyCount={location.pathname.startsWith('/all/') ? 0 : rules?.length > 0 ? 1 : 0}
subplebbitAddress={subplebbitAddress} subplebbitAddress={subplebbitAddress}
shortAddress={shortAddress} shortAddress={shortAddress}
title={title} title={title}