feat(post): add mobile footer to thread view

Post a Reply, Return/Catalog/Top, Update/Auto buttons, stats row (Replies/Images/Page), and license footer links. Matches mobile board view styling.
This commit is contained in:
plebeius
2026-03-03 15:07:14 +08:00
parent 9216791505
commit 84e6057bb8
4 changed files with 108 additions and 7 deletions
+33
View File
@@ -175,6 +175,19 @@
padding: 8px 8px;
}
.mobileFooterButtons a {
all: unset;
}
.mobileFooterButtons input[type='checkbox'] {
margin: 0 3px 0 0;
position: relative;
top: -2px;
border-radius: 0;
height: 10px;
width: 10px;
}
.mobileFooterPagination {
display: flex;
flex-wrap: wrap;
@@ -209,3 +222,23 @@
.mobileFooterLinks a:hover {
color: var(--boardsbar-desktop-link-text-color-hover);
}
.mobileFooterStats {
padding: 4px 8px;
font-size: 14px;
text-align: center;
}
.threadMobileFooterContent .mobileFooterButtons {
padding-top: 0;
padding-bottom: 6px;
}
.threadMobileFooterContent .mobileFooterButtons:first-child {
padding-top: 25px;
padding-bottom: 10px;
}
.threadMobileFooterContent .mobileFooterStats {
padding-top: 15px;
}
+65
View File
@@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom';
import { useComment } from '@bitsocialhq/pkc-react-hooks';
import BoardsBar from '../boards-bar';
import SiteLegalMeta from '../site-legal-meta';
import StyleSelector from '../style-selector/style-selector';
import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats, RefreshButton } from '../board-buttons/board-buttons';
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import useReplyModalStore from '../../stores/use-reply-modal-store';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import { usePostPageNumber } from '../../hooks/use-post-page-number';
import { useDirectoryByAddress } from '../../hooks/use-directories';
import capitalize from 'lodash/capitalize';
import styles from './footer.module.css';
/* -----------------------------------------------------------------------------
@@ -165,3 +170,63 @@ export const PageFooterMobile = ({ children }: { children: React.ReactNode }) =>
</div>
</footer>
);
/* -----------------------------------------------------------------------------
* ThreadFooterMobile
* Mobile thread page footer: Post a Reply, Return/Catalog/Top, Update/Auto, stats.
* -------------------------------------------------------------------------- */
export interface ThreadFooterMobileProps {
postCid: string;
threadNumber: number | undefined;
subplebbitAddress: string;
isThreadClosed?: boolean;
}
export const ThreadFooterMobile = ({ postCid, threadNumber, subplebbitAddress, isThreadClosed = false }: ThreadFooterMobileProps) => {
const { t } = useTranslation();
const location = useLocation();
const params = useParams();
const { openReplyModalEmpty } = useReplyModalStore();
const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const isInModView = isModView(location.pathname);
const post = useComment({ commentCid: postCid });
const { replyCount } = post || {};
const linkCount = useCountLinksInReplies(post);
const directoryEntry = useDirectoryByAddress(subplebbitAddress);
const requirePostLinkIsMedia = directoryEntry?.features?.requirePostLinkIsMedia === true;
const pageNumber = usePostPageNumber({ subplebbitAddress, postCid, enabled: true });
const handlePostReplyClick = () => {
if (isThreadClosed) return;
openReplyModalEmpty(postCid, threadNumber, subplebbitAddress);
};
return (
<PageFooterMobile>
<div className={styles.threadMobileFooterContent}>
<div className={styles.mobileFooterButtons}>
<button className='button' onClick={handlePostReplyClick} disabled={isThreadClosed}>
{t('post_a_reply')}
</button>
</div>
<div className={styles.mobileFooterButtons}>
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
<TopButton />
</div>
<div className={styles.mobileFooterButtons}>
<UpdateButton />
<AutoButton />
</div>
<div className={styles.mobileFooterStats}>
{capitalize(t('replies'))}: {replyCount ?? '?'} / {capitalize(requirePostLinkIsMedia ? t('images') : t('links'))}: {linkCount ?? '?'} /{' '}
{t('pagination.pageLabel')}: {pageNumber ?? '?'}
</div>
</div>
</PageFooterMobile>
);
};
+2 -2
View File
@@ -1,2 +1,2 @@
export { PageFooterDesktop, PageFooterMobile, CatalogFooterFirstRow, ThreadFooterFirstRow, ThreadFooterStyleRow } from './footer';
export type { PageFooterDesktopProps, CatalogFooterFirstRowProps, ThreadFooterFirstRowProps } from './footer';
export { PageFooterDesktop, PageFooterMobile, CatalogFooterFirstRow, ThreadFooterFirstRow, ThreadFooterStyleRow, ThreadFooterMobile } from './footer';
export type { PageFooterDesktopProps, CatalogFooterFirstRowProps, ThreadFooterFirstRowProps, ThreadFooterMobileProps } from './footer';
+8 -5
View File
@@ -10,7 +10,7 @@ import { useDirectories } from '../../hooks/use-directories';
import { isDirectoryBoard } from '../../lib/utils/route-utils';
import useIsMobile from '../../hooks/use-is-mobile';
import ErrorDisplay from '../../components/error-display/error-display';
import { PageFooterDesktop, ThreadFooterFirstRow, ThreadFooterStyleRow } from '../../components/footer';
import { PageFooterDesktop, ThreadFooterFirstRow, ThreadFooterStyleRow, ThreadFooterMobile } from '../../components/footer';
import PostDesktop from '../../components/post-desktop';
import PostMobile from '../../components/post-mobile';
import styles from './post.module.css';
@@ -203,10 +203,13 @@ const PostPage = () => {
</div>
)}
{post?.cid && subplebbitAddress ? (
<PageFooterDesktop
firstRow={<ThreadFooterFirstRow postCid={post.cid} threadNumber={post?.number} subplebbitAddress={subplebbitAddress} isThreadClosed={!!post?.locked} />}
styleRow={<ThreadFooterStyleRow />}
/>
<>
<PageFooterDesktop
firstRow={<ThreadFooterFirstRow postCid={post.cid} threadNumber={post?.number} subplebbitAddress={subplebbitAddress} isThreadClosed={!!post?.locked} />}
styleRow={<ThreadFooterStyleRow />}
/>
<ThreadFooterMobile postCid={post.cid} threadNumber={post?.number} subplebbitAddress={subplebbitAddress} isThreadClosed={!!post?.locked} />
</>
) : null}
</div>
);