refactor(core): remove legacy plebbit terminology

This commit is contained in:
Tommaso Casaburi
2026-04-17 10:48:27 +07:00
parent e366dac25c
commit ee7a5b5778
158 changed files with 626 additions and 836 deletions
+22 -23
View File
@@ -12,15 +12,15 @@ type ReplyModalShape = {
parentNumber: number | null;
scrollY: number;
showReplyModal: boolean;
subplebbitAddress: string | null;
communityAddress: string | null;
threadCid: string | null;
threadNumber: number | null;
};
const testState = vi.hoisted(() => ({
account: { author: { address: '0x123' } } as unknown,
accountComments: {} as Record<number, { subplebbitAddress?: string }>,
accountSubplebbitAddresses: [] as string[],
accountComments: {} as Record<number, { communityAddress?: string }>,
accountCommunityAddresses: [] as string[],
closeCreateBoardModalMock: vi.fn(),
directories: [
{ address: 'music-posting.eth', title: '/mu/ - Music', nsfw: false },
@@ -36,12 +36,12 @@ const testState = vi.hoisted(() => ({
parentNumber: null,
scrollY: 0,
showReplyModal: false,
subplebbitAddress: null,
communityAddress: null,
threadCid: null,
threadNumber: null,
} as ReplyModalShape,
resolvedSubplebbitAddress: undefined as string | undefined,
subplebbits: {} as Record<string, unknown>,
resolvedCommunityAddress: undefined as string | undefined,
communities: {} as Record<string, unknown>,
useThemeMock: vi.fn(),
}));
@@ -50,16 +50,15 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccountComment: ({ commentIndex }: { commentIndex?: number }) => (typeof commentIndex === 'number' ? testState.accountComments[commentIndex] : undefined),
useCommunity: (options?: { communityAddress?: string; community?: { name?: string; publicKey?: string } }) => {
const communityAddress = options?.communityAddress ?? options?.community?.name ?? options?.community?.publicKey;
return communityAddress ? testState.subplebbits[communityAddress] : undefined;
return communityAddress ? testState.communities[communityAddress] : undefined;
},
useAccountCommunities: () => ({
accountCommunities: Object.fromEntries(testState.accountSubplebbitAddresses.map((address) => [address, { address }])),
accountCommunities: Object.fromEntries(testState.accountCommunityAddresses.map((address) => [address, { address }])),
}),
useSubplebbit: ({ subplebbitAddress }: { subplebbitAddress?: string }) => (subplebbitAddress ? testState.subplebbits[subplebbitAddress] : undefined),
}));
vi.mock('../hooks/use-account-subplebbit-addresses', () => ({
useAccountSubplebbitAddresses: () => testState.accountSubplebbitAddresses,
vi.mock('../hooks/use-account-community-addresses', () => ({
useAccountCommunityAddresses: () => testState.accountCommunityAddresses,
}));
vi.mock('../hooks/use-directories', () => ({
@@ -72,8 +71,8 @@ vi.mock('../hooks/use-is-mobile', () => ({
default: () => testState.isMobile,
}));
vi.mock('../hooks/use-resolved-subplebbit-address', () => ({
useResolvedSubplebbitAddress: () => testState.resolvedSubplebbitAddress,
vi.mock('../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedCommunityAddress,
}));
vi.mock('../hooks/use-theme', () => ({
@@ -255,7 +254,7 @@ describe('App', () => {
latestLocation = '';
testState.account = { author: { address: '0x123' } };
testState.accountComments = {};
testState.accountSubplebbitAddresses = [];
testState.accountCommunityAddresses = [];
testState.isMobile = false;
testState.isSpecialEnabled = false;
testState.replyModalState = {
@@ -264,12 +263,12 @@ describe('App', () => {
parentNumber: null,
scrollY: 0,
showReplyModal: false,
subplebbitAddress: null,
communityAddress: null,
threadCid: null,
threadNumber: null,
} as ReplyModalShape;
testState.resolvedSubplebbitAddress = undefined;
testState.subplebbits = {};
testState.resolvedCommunityAddress = undefined;
testState.communities = {};
testState.useThemeMock.mockReset();
testState.closeCreateBoardModalMock.mockReset();
testState.initSnowMock.mockReset();
@@ -292,7 +291,7 @@ describe('App', () => {
parentNumber: 12,
scrollY: 32,
showReplyModal: true,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
threadCid: 'thread-cid',
threadNumber: 99,
} as ReplyModalShape;
@@ -333,7 +332,7 @@ describe('App', () => {
});
it('allows the global mod queue only when the account moderates at least one board', async () => {
testState.accountSubplebbitAddresses = ['music-posting.eth'];
testState.accountCommunityAddresses = ['music-posting.eth'];
await renderApp('/mod/queue');
expect(container.querySelector('[data-testid="mod-queue-view"]')).toBeTruthy();
@@ -341,7 +340,7 @@ describe('App', () => {
act(() => root.unmount());
root = createRoot(container);
testState.accountSubplebbitAddresses = [];
testState.accountCommunityAddresses = [];
await renderApp('/mod/queue');
expect(latestLocation).toBe('/not-allowed');
@@ -373,8 +372,8 @@ describe('App', () => {
});
it('enforces board-scoped mod queue access by account role', async () => {
testState.resolvedSubplebbitAddress = 'music-posting.eth';
testState.subplebbits = {
testState.resolvedCommunityAddress = 'music-posting.eth';
testState.communities = {
'music-posting.eth': {
state: 'succeeded',
roles: {
@@ -389,7 +388,7 @@ describe('App', () => {
act(() => root.unmount());
root = createRoot(container);
testState.subplebbits = {
testState.communities = {
'music-posting.eth': {
state: 'succeeded',
roles: {
+5 -4
View File
@@ -15,9 +15,10 @@ import { useDirectories } from './hooks/use-directories';
import { useCommunityIdentifier } from './hooks/use-community-identifiers';
import { useResolvedCommunityAddress } from './hooks/use-resolved-community-address';
import useSafeAccountComment from './hooks/use-safe-account-comment';
import { getCommentCommunityAddress } from './lib/utils/comment-utils';
import {
getBoardPath,
getSubplebbitAddress,
getCommunityAddress,
isBoardModRoute,
isDirectoryBoard,
isArchiveRoute,
@@ -72,9 +73,9 @@ const BoardLayout = () => {
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const isInModView = isModView(location.pathname);
const directories = useDirectories();
const communityAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, directories) : undefined;
const communityAddress = boardIdentifier ? getCommunityAddress(boardIdentifier, directories) : undefined;
const pendingPost = useSafeAccountComment({ commentIndex: accountCommentIndex });
const pendingPostCommunityAddress = pendingPost?.communityAddress || pendingPost?.subplebbitAddress;
const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost);
const { closeCreateBoardModal } = useCreateBoardModalStore();
const isOnPostRoute = isPostRoute(location.pathname);
const isOnPendingPostRoute = isPendingPostRoute(location.pathname);
@@ -178,7 +179,7 @@ const BoardLayout = () => {
const GlobalLayout = () => {
useTheme();
const { activeCid, parentNumber, threadNumber, threadCid, subplebbitAddress: activeCommunityAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore();
const { activeCid, parentNumber, threadNumber, threadCid, communityAddress: activeCommunityAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore();
const location = useLocation();
const isInSettingsView = location.pathname.endsWith('/settings');
@@ -37,7 +37,6 @@ type TestComment = {
>;
};
state?: string;
subplebbitAddress?: string;
thumbnailUrl?: string;
timestamp?: number;
updatedAt?: number;
@@ -417,13 +416,13 @@ const makeLegacyThread = (): TestComment => ({
number: 2,
parentCid: 'post-1',
postCid: 'post-1',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
},
],
},
},
},
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 1_710_000_000,
});
@@ -456,7 +455,7 @@ describe('post community address compatibility', () => {
container.remove();
});
it('renders desktop multiboard posts with only subplebbitAddress and still fetches replies', async () => {
it('renders desktop multiboard posts with only communityAddress and still fetches replies', async () => {
await renderWithRoute(createElement(PostDesktop, { post: makeLegacyThread() }));
const primaryRepliesComment = testState.replyComments.find((comment) => comment?.cid === 'post-1');
@@ -468,7 +467,7 @@ describe('post community address compatibility', () => {
expect(container.textContent).toContain('reply-1');
});
it('renders mobile multiboard posts with only subplebbitAddress and still fetches replies', async () => {
it('renders mobile multiboard posts with only communityAddress and still fetches replies', async () => {
await renderWithRoute(createElement(PostMobile, { post: makeLegacyThread() }));
const primaryRepliesComment = testState.replyComments.find((comment) => comment?.cid === 'post-1');
@@ -28,14 +28,14 @@ describe('BlotterMessage', () => {
container.remove();
});
it('normalizes legacy subplebbit wording without rewriting plain community text', async () => {
it('renders manual messages unchanged', async () => {
await act(async () => {
root.render(
createElement(BlotterMessage, {
entry: {
id: 'manual-1',
kind: 'manual',
message: 'Moved a subplebbit into a community spotlight',
message: 'Moved a board into a community spotlight',
timestamp: 1_710_000_000,
},
}),
@@ -43,18 +43,16 @@ describe('BlotterMessage', () => {
});
expect(container.textContent).toContain('Moved a board into a community spotlight');
expect(container.textContent).not.toContain('subplebbit');
expect(container.textContent).not.toContain('board spotlight');
});
it('normalizes release one-liners after the version prefix', async () => {
it('renders release one-liners after the version prefix', async () => {
await act(async () => {
root.render(
createElement(BlotterMessage, {
entry: {
id: 'release-1',
kind: 'release',
message: 'v0.7.0: Fix subplebbit loading in plebchan',
message: 'v0.7.0: Fix community loading in 5chan',
timestamp: 1_710_000_000,
version: '0.7.0',
},
@@ -63,6 +61,6 @@ describe('BlotterMessage', () => {
});
expect(container.querySelector('a')?.getAttribute('href')).toBe('https://github.com/bitsocialnet/5chan/releases/tag/v0.7.0');
expect(container.textContent).toContain('Fix board loading in 5chan');
expect(container.textContent).toContain('Fix community loading in 5chan');
});
});
@@ -3,10 +3,6 @@ import styles from './blotter-message.module.css';
const RELEASES_BASE = 'https://github.com/bitsocialnet/5chan/releases/tag/v';
function normalizeMessage(text: string): string {
return text.replace(/subplebbit/gi, 'board').replace(/plebchan/gi, '5chan');
}
const BlotterMessage = ({ entry }: { entry: BlotterEntry }) => {
if (entry.kind === 'release' && entry.version) {
const idx = entry.message.indexOf(': ');
@@ -16,11 +12,11 @@ const BlotterMessage = ({ entry }: { entry: BlotterEntry }) => {
<a href={`${RELEASES_BASE}${entry.version}`} className={styles.versionLink} target='_blank' rel='noopener noreferrer'>
v{entry.version}
</a>
: {normalizeMessage(oneLiner)}
: {oneLiner}
</>
);
}
return <>{normalizeMessage(entry.message)}</>;
return <>{entry.message}</>;
};
export default BlotterMessage;
@@ -66,7 +66,7 @@ export const ArchiveButton = ({ address, isInAllView, isInSubscriptionsView, isI
const directories = useDirectories();
const isInvalidArchiveContext = isInAllView || isInSubscriptionsView || isInModView;
const boardIdentifier = params.boardIdentifier || params.subplebbitAddress;
const boardIdentifier = params.boardIdentifier;
const archiveBoardIdentifier = address ? getBoardPath(address, directories) : boardIdentifier ? getBoardPath(boardIdentifier, directories) : '';
const archivePath = archiveBoardIdentifier ? `/${archiveBoardIdentifier}/archive` : '';
@@ -130,7 +130,7 @@ const VoteButton = () => {
const directories = useDirectories();
// Get the boardIdentifier from params (try boardIdentifier first, then communityAddress for backward compatibility)
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
// Only render the vote button if we're on a directory board route
if (!boardIdentifier || !isDirectoryBoard(boardIdentifier, directories)) {
@@ -400,7 +400,7 @@ export const MobileBoardButtons = () => {
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
return (
@@ -507,7 +507,7 @@ export const PostPageStats = () => {
const isThreadView = isPostPageView(location.pathname, params);
const pageNumber = usePostPageNumber({
subplebbitAddress: communityAddress,
communityAddress,
postCid,
enabled: isThreadView,
});
@@ -555,7 +555,7 @@ export const DesktopBoardButtons = () => {
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier || params.communityAddress;
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
return (
@@ -9,7 +9,7 @@ import BoardsBar from '../boards-bar';
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
const testState = vi.hoisted(() => ({
accountComment: undefined as { communityAddress?: string; subplebbitAddress?: string } | undefined,
accountComment: undefined as { communityAddress?: string } | undefined,
accountCommunityAddresses: ['music-posting.eth'] as string[],
directories: [
{ address: 'music-posting.eth', title: '/mu/ - Music' },
@@ -262,7 +262,7 @@ describe('BoardsBar', () => {
it('keeps the mobile board context for legacy account comments', async () => {
testState.resolvedCommunityAddress = undefined;
testState.accountComment = { subplebbitAddress: 'music-posting.eth' };
testState.accountComment = { communityAddress: 'music-posting.eth' };
await renderBoardsBar('/pending/7');
@@ -15,15 +15,12 @@ type FilterItem = {
hide: boolean;
communityCounts: Map<string, number>;
communityFilteredCids: Map<string, Set<string>>;
subplebbitCounts?: Map<string, number>;
subplebbitFilteredCids?: Map<string, Set<string>>;
text: string;
top: boolean;
};
const testState = vi.hoisted(() => ({
currentCommunityAddress: 'music-posting.eth' as string | null,
currentSubplebbitAddress: 'music-posting.eth' as string | null,
filterItems: [] as FilterItem[],
resetCountsMock: vi.fn(),
resetFeedMock: vi.fn(),
@@ -38,8 +35,6 @@ const createFilterItem = (overrides: Partial<FilterItem> = {}): FilterItem => ({
hide: true,
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: undefined,
subplebbitFilteredCids: undefined,
text: '',
top: false,
...overrides,
@@ -48,7 +43,6 @@ const createFilterItem = (overrides: Partial<FilterItem> = {}): FilterItem => ({
function getCatalogFiltersState() {
return {
currentCommunityAddress: testState.currentCommunityAddress,
currentSubplebbitAddress: testState.currentSubplebbitAddress,
filterItems: testState.filterItems,
saveAndApplyFilters: testState.saveAndApplyFiltersMock,
};
@@ -141,7 +135,6 @@ describe('CatalogFilters', () => {
vi.clearAllMocks();
vi.useRealTimers();
testState.currentCommunityAddress = 'music-posting.eth';
testState.currentSubplebbitAddress = 'music-posting.eth';
testState.filterItems = [
createFilterItem({
count: 2,
@@ -278,40 +271,4 @@ describe('CatalogFilters', () => {
expect(testState.resetFeedMock).toHaveBeenCalledTimes(1);
expect(container.querySelector('[title="close"]')).toBeNull();
});
it('shows filter hit counts when only the legacy currentSubplebbitAddress is populated', async () => {
testState.currentCommunityAddress = null;
renderCatalogFilters();
await openModal();
expect(container.textContent).toContain('x2');
expect(container.textContent).toContain('x4');
});
it('shows filter hit counts when only the legacy subplebbit count payload is populated', async () => {
testState.currentCommunityAddress = null;
testState.filterItems = [
createFilterItem({
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map([['music-posting.eth', 2]]),
subplebbitFilteredCids: new Map([['music-posting.eth', new Set(['alpha-cid'])]]),
text: 'alpha',
}),
createFilterItem({
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map([['music-posting.eth', 4]]),
subplebbitFilteredCids: new Map([['music-posting.eth', new Set(['beta-cid'])]]),
text: 'beta',
}),
];
renderCatalogFilters();
await openModal();
expect(container.textContent).toContain('x2');
expect(container.textContent).toContain('x4');
});
});
@@ -11,8 +11,6 @@ type CatalogFilterItemInput = {
enabled: boolean;
count: number;
filteredCids: Set<string>;
subplebbitCounts?: Map<string, number>;
subplebbitFilteredCids?: Map<string, Set<string>>;
communityCounts?: Map<string, number>;
communityFilteredCids?: Map<string, Set<string>>;
hide?: boolean;
@@ -28,32 +26,19 @@ type CatalogFilterItemStore = {
filteredCids: Set<string>;
communityCounts: Map<string, number>;
communityFilteredCids: Map<string, Set<string>>;
subplebbitCounts: Map<string, number>;
subplebbitFilteredCids: Map<string, Set<string>>;
hide: boolean;
top: boolean;
color: string;
id?: string;
};
const selectFilterMap = <K, V>(preferred?: Map<K, V>, legacy?: Map<K, V>) => {
if (preferred && preferred.size > 0) return preferred;
if (legacy && legacy.size > 0) return legacy;
return preferred || legacy || new Map<K, V>();
};
const toCatalogFilterItem = (item: CatalogFilterItemInput): CatalogFilterItemStore => {
const counts = selectFilterMap(item.communityCounts, item.subplebbitCounts);
const filteredByCommunity = selectFilterMap(item.communityFilteredCids, item.subplebbitFilteredCids);
return {
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set<string>(),
communityCounts: counts,
communityFilteredCids: filteredByCommunity,
subplebbitCounts: counts,
subplebbitFilteredCids: filteredByCommunity,
communityCounts: item.communityCounts || new Map<string, number>(),
communityFilteredCids: item.communityFilteredCids || new Map<string, Set<string>>(),
hide: item.hide ?? true,
top: item.top ?? false,
color: item.color || '',
@@ -62,14 +47,11 @@ const toCatalogFilterItem = (item: CatalogFilterItemInput): CatalogFilterItemSto
const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const { t } = useTranslation();
const { currentSubplebbitAddress, currentCommunityAddress, filterItems, saveAndApplyFilters } = useCatalogFiltersStore((state) => ({
currentSubplebbitAddress: state.currentSubplebbitAddress,
// legacy fallback kept for compatibility while worker B/store migration is in progress
currentCommunityAddress: (state as { currentCommunityAddress?: string | null }).currentCommunityAddress ?? null,
const { currentCommunityAddress, filterItems, saveAndApplyFilters } = useCatalogFiltersStore((state) => ({
currentCommunityAddress: state.currentCommunityAddress,
filterItems: state.filterItems as CatalogFilterItemInput[],
saveAndApplyFilters: state.saveAndApplyFilters,
}));
const currentCommunityAddressResolved = currentCommunityAddress ?? currentSubplebbitAddress;
const resetFeed = useFeedResetStore((state) => state.reset);
const [localFilterItems, setLocalFilterItems] = useState(() =>
@@ -97,8 +79,6 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
filteredCids: new Set<string>(),
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map<string, number>(),
subplebbitFilteredCids: new Map<string, Set<string>>(),
hide: true,
top: false,
color: '',
@@ -112,12 +92,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
saveAndApplyFilters(nonEmptyFilters);
const filtersState = useCatalogFiltersStore.getState() as {
resetCountsForCurrentCommunity?: () => void;
resetCountsForCurrentSubplebbit?: () => void;
};
filtersState.resetCountsForCurrentCommunity?.();
filtersState.resetCountsForCurrentSubplebbit?.();
useCatalogFiltersStore.getState().resetCountsForCurrentCommunity();
if (resetFeed) {
resetFeed();
@@ -234,9 +209,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
</span>
</td>
<td className={styles.filterHits}>
{currentCommunityAddressResolved &&
item.communityFilteredCids?.has(currentCommunityAddressResolved) &&
`x${item.communityCounts?.get(currentCommunityAddressResolved) ?? 0}`}
{currentCommunityAddress && item.communityFilteredCids?.has(currentCommunityAddress) && `x${item.communityCounts?.get(currentCommunityAddress) ?? 0}`}
</td>
</tr>
))}
@@ -260,12 +233,7 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
const [showHelp, setShowHelp] = useState(false);
const { currentSubplebbitAddress, currentCommunityAddress } = useCatalogFiltersStore((state) => ({
currentSubplebbitAddress: state.currentSubplebbitAddress,
// legacy fallback kept for compatibility while worker B/store migration is in progress
currentCommunityAddress: (state as { currentCommunityAddress?: string | null }).currentCommunityAddress ?? null,
}));
const currentCommunityAddressResolved = currentCommunityAddress ?? currentSubplebbitAddress;
const currentCommunityAddress = useCatalogFiltersStore((state) => state.currentCommunityAddress);
const openHelp = () => setShowHelp(true);
const closeHelp = () => setShowHelp(false);
@@ -292,7 +260,11 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
showHelp ? closeHelp() : closeModal();
if (showHelp) {
closeHelp();
} else {
closeModal();
}
}
}}
onClick={showHelp ? closeHelp : closeModal}
@@ -329,7 +301,7 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
onClick={closeModal}
/>
</div>
{showHelp ? <FiltersProtip /> : <FiltersTable key={currentCommunityAddressResolved ?? 'none'} onSave={closeModal} />}
{showHelp ? <FiltersProtip /> : <FiltersTable key={currentCommunityAddress ?? 'none'} onSave={closeModal} />}
</div>
</>
);
@@ -37,7 +37,6 @@ type TestComment = {
};
spoiler?: boolean;
communityAddress?: string;
subplebbitAddress?: string;
thumbnailUrl?: string;
timestamp?: number;
title?: string;
@@ -275,7 +274,7 @@ describe('CatalogRow', () => {
},
content: 'Archived thread',
replyCount: 3,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'Old thread',
};
@@ -422,14 +421,14 @@ describe('CatalogRow', () => {
{
author: { address: 'author-2', displayName: 'Bob' },
cid: 'reply-legacy',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 200,
},
],
},
},
},
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
timestamp: 100,
title: 'Legacy title',
};
+3 -3
View File
@@ -23,7 +23,7 @@ import PostMenuDesktop from '../post-desktop/post-menu-desktop';
import styles from './catalog-row.module.css';
import capitalize from 'lodash/capitalize';
import { selectPostMenuProps } from '../../lib/utils/post-menu-props';
import { withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getCommentCommunityAddress, withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
interface CatalogPostMediaProps {
cid: string;
@@ -325,8 +325,8 @@ const CatalogPost = memo(
(prevProps, nextProps) => {
const prev = prevProps.post;
const next = nextProps.post;
const prevCommunityAddress = prev?.communityAddress ?? prev?.subplebbitAddress;
const nextCommunityAddress = next?.communityAddress ?? next?.subplebbitAddress;
const prevCommunityAddress = getCommentCommunityAddress(prev);
const nextCommunityAddress = getCommentCommunityAddress(next);
// Compare all fields that affect rendering to avoid stale displays
return (
prev?.cid === next?.cid &&
@@ -12,9 +12,6 @@ type TestComment = {
community?: {
banExpiresAt?: number;
};
subplebbit?: {
banExpiresAt?: number;
};
};
cid?: string;
commentModeration?: {
@@ -276,10 +273,10 @@ describe('CommentContent', () => {
expect(queryMarkdownText()[0]).toHaveLength(1105);
});
it('keeps the ban indicator for legacy author subplebbit data', async () => {
it('shows the ban indicator from the canonical author community data', async () => {
await renderContent({
author: {
subplebbit: {
community: {
banExpiresAt: 1700000000,
},
},
@@ -75,8 +75,7 @@ const CommentContent = ({ comment: post, prependContent }: { comment: Comment; p
const { cid, content, deleted, edit, original, parentCid, postCid, pendingApproval, quotedCids, reason, removed, state } = resolvedPost || {};
const communityAddress = getCommentCommunityAddress(resolvedPost);
const purged = resolvedPost?.commentModeration?.purged;
const banExpiresAt =
resolvedPost?.author?.community?.banExpiresAt ?? (resolvedPost?.author as { subplebbit?: { banExpiresAt?: number } } | undefined)?.subplebbit?.banExpiresAt;
const banExpiresAt = resolvedPost?.author?.community?.banExpiresAt;
const banned = !!banExpiresAt;
const [showFullComment, setShowFullComment] = useState(false);
@@ -12,13 +12,13 @@
}
.floatingEmbed,
.subplebbitAvatar {
.communityAvatar {
max-width: 250px;
max-height: 250px;
display: inline-flex;
}
.subplebbitAvatar {
.communityAvatar {
margin: 3px 20px 5px 20px;
}
@@ -29,7 +29,7 @@
}
@media (max-width: 640px) {
.subplebbitAvatar {
.communityAvatar {
max-width: 125px;
max-height: 125px;
margin: 3px 10px 5px 5px;
@@ -438,7 +438,7 @@ const CommentMedia = ({
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
// use the dimensions from the plebbit-js api
// use the dimensions from the pkc-js API
let scale = Math.min(1, maxThumbnailSize / Math.max(linkWidth, linkHeight));
displayWidth = `${linkWidth * scale}px`;
displayHeight = `${linkHeight * scale}px`;
@@ -61,7 +61,7 @@ const CreateBoardModal = () => {
<p>
Directory assignments will use gasless pubsub votingcommunities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
directory&apos;s voting page lists all competing boards (even low-voted ones), giving visibility without winning or dev approval. See{' '}
<a href='https://github.com/plebbit/plebbit-js/issues/25' target='_blank' rel='noopener noreferrer'>
<a href='https://github.com/pkcprotocol/pkc-js/issues/25' target='_blank' rel='noopener noreferrer'>
design draft
</a>
.
@@ -70,7 +70,7 @@ const DirectoryModal = () => {
<p>
Directory assignments will use gasless pubsub votingcommunities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
directory&apos;s voting page lists all competing boards (even low-voted ones), giving visibility without winning or dev approval. See{' '}
<a href='https://github.com/plebbit/plebbit-js/issues/25' target='_blank' rel='noopener noreferrer'>
<a href='https://github.com/pkcprotocol/pkc-js/issues/25' target='_blank' rel='noopener noreferrer'>
design draft
</a>
.
@@ -264,7 +264,6 @@ describe('EditMenu', () => {
communityAddress: 'music-posting.eth',
postCid: 'post-1',
});
expect(testState.authorPrivilegesOptions).not.toHaveProperty('subplebbitAddress');
});
it('allows pseudonymous boards to attempt author-side deletion without a local author address match', async () => {
+1 -1
View File
@@ -217,7 +217,7 @@ export const ThreadFooterMobile = ({ postCid, threadNumber, communityAddress, is
const linkCount = useCountLinksInReplies(post);
const directoryEntry = useDirectoryByAddress(communityAddress);
const requirePostLinkIsMedia = directoryEntry?.features?.requirePostLinkIsMedia === true;
const pageNumber = usePostPageNumber({ subplebbitAddress: communityAddress, postCid, enabled: true });
const pageNumber = usePostPageNumber({ communityAddress: communityAddress, postCid, enabled: true });
const handlePostReplyClick = () => {
if (isThreadClosed) return;
@@ -163,13 +163,9 @@ type PostMenuDesktopProps = {
postMenu: PostMenuProps;
};
type PostMenuLegacyAddress = Pick<PostMenuProps, 'subplebbitAddress'> & { communityAddress?: string };
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
const { t } = useTranslation();
const { authorAddress, cid, link, thumbnailUrl, linkWidth, linkHeight, postCid } = postMenu || {};
const postMenuLegacyAddress = (postMenu as PostMenuLegacyAddress) || {};
const resolvedCommunityAddress = postMenuLegacyAddress.communityAddress || postMenuLegacyAddress.subplebbitAddress;
const { authorAddress, cid, communityAddress, link, thumbnailUrl, linkWidth, linkHeight, postCid } = postMenu || {};
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
@@ -267,7 +263,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
{hidden ? (postCid === cid ? t('unhide_thread') : t('unhide_post')) : postCid === cid ? t('hide_thread') : t('hide_post')}
</div>
)}
{cid && resolvedCommunityAddress && <CopyLinkButton cid={cid} communityAddress={resolvedCommunityAddress} linkType='thread' onClose={handleClose} />}
{cid && communityAddress && <CopyLinkButton cid={cid} communityAddress={communityAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
@@ -300,17 +300,13 @@ type PostMenuMobileProps = {
editMenuPost: Comment;
};
type PostMenuLegacyAddress = Pick<PostMenuProps, 'subplebbitAddress'> & { communityAddress?: string };
const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
const { authorAddress, cid, deleted, link, linkHeight, linkWidth, parentCid, postCid, removed, thumbnailUrl } = postMenu || {};
const postMenuLegacyAddress = (postMenu as PostMenuLegacyAddress) || {};
const resolvedCommunityAddress = postMenuLegacyAddress.communityAddress || postMenuLegacyAddress.subplebbitAddress;
const { authorAddress, cid, communityAddress, deleted, link, linkHeight, linkWidth, parentCid, postCid, removed, thumbnailUrl } = postMenu || {};
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({
commentAuthorAddress: authorAddress || '',
subplebbitAddress: resolvedCommunityAddress || '',
communityAddress: communityAddress || '',
});
const pseudonymityMode = useBoardPseudonymityMode(resolvedCommunityAddress);
const pseudonymityMode = useBoardPseudonymityMode(communityAddress);
const canAttemptAuthorDelete = pseudonymityMode !== undefined && pseudonymityMode !== 'none';
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth || 0, linkHeight || 0);
const { thumbnail, type, url } = commentMediaInfo || {};
@@ -365,9 +361,9 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
<ReportPostButton onClose={handleClose} />
{cid && resolvedCommunityAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{cid && communityAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{(isAccountCommentAuthor || canAttemptAuthorDelete) && cid && <DeletePostButton post={editMenuPost} onClose={handleClose} />}
{cid && resolvedCommunityAddress && <CopyLinkButton cid={cid} communityAddress={resolvedCommunityAddress} linkType='thread' onClose={handleClose} />}
{cid && communityAddress && <CopyLinkButton cid={cid} communityAddress={communityAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
@@ -23,7 +23,7 @@ const testState = vi.hoisted(() => ({
rpcSettings: {
pkcRpcSettings: {
pkcOptions: {
dataPath: '/tmp/plebbit-data',
dataPath: '/tmp/pkc-data',
},
},
state: 'disconnected',
@@ -100,7 +100,7 @@ describe('AdvancedSettings', () => {
testState.rpcSettings = {
pkcRpcSettings: {
pkcOptions: {
dataPath: '/tmp/plebbit-data',
dataPath: '/tmp/pkc-data',
},
},
state: 'disconnected',
@@ -149,7 +149,7 @@ describe('AdvancedSettings', () => {
await dispatchInput(textareas[3], ' https://eth.one.example \n');
await dispatchInput(textareas[4], ' https://sol.one.example \n');
await dispatchInput(textInputs[1], ' ws://127.0.0.1:9138/secret ');
await dispatchInput(textInputs[2], ' /tmp/next-plebbit ');
await dispatchInput(textInputs[2], ' /tmp/next-pkc ');
await clickButton('save_advanced_settings');
expect(testState.setAccountMock).toHaveBeenCalledWith({
@@ -159,7 +159,7 @@ describe('AdvancedSettings', () => {
sol: { chainId: 101, urls: ['https://sol.one.example'] },
},
pkcOptions: {
dataPath: '/tmp/next-plebbit',
dataPath: '/tmp/next-pkc',
httpRoutersOptions: ['https://router.one.example'],
ipfsGatewayUrls: ['https://ipfs.one.example', 'https://ipfs.two.example'],
pkcRpcClientsOptions: ['ws://127.0.0.1:9138/secret'],
@@ -20,7 +20,6 @@ type AccountProtocolOptions = {
httpRoutersOptions?: string[];
ipfsGatewayUrls?: string[];
pkcRpcClientsOptions?: string[];
plebbitRpcClientsOptions?: string[];
pubsubHttpClientsOptions?: string[];
pubsubKuboRpcClientsOptions?: string[];
};
@@ -29,24 +28,22 @@ type AccountShape = {
chainProviders?: AccountProtocolOptions['chainProviders'];
mediaIpfsGatewayUrl?: string;
pkcOptions?: AccountProtocolOptions;
plebbitOptions?: AccountProtocolOptions;
};
type RpcSettingsShape = {
pkcOptions?: { dataPath?: string };
plebbitOptions?: { dataPath?: string };
};
const getProtocolOptions = (account?: AccountShape) => account?.pkcOptions ?? account?.plebbitOptions;
const getProtocolOptions = (account?: AccountShape) => account?.pkcOptions;
const getChainProviders = (account?: AccountShape) => account?.chainProviders ?? getProtocolOptions(account)?.chainProviders;
const getNodeRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) => protocolOptions?.pkcRpcClientsOptions ?? protocolOptions?.plebbitRpcClientsOptions;
const getNodeRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) => protocolOptions?.pkcRpcClientsOptions;
const getPubsubRpcClientsOptions = (protocolOptions?: AccountProtocolOptions) =>
protocolOptions?.pubsubKuboRpcClientsOptions ?? protocolOptions?.pubsubHttpClientsOptions;
const getRpcSettingsDataPath = (rpcSettings?: RpcSettingsShape) => rpcSettings?.pkcOptions?.dataPath ?? rpcSettings?.plebbitOptions?.dataPath ?? '';
const getRpcSettingsDataPath = (rpcSettings?: RpcSettingsShape) => rpcSettings?.pkcOptions?.dataPath ?? '';
const IPFSGatewaysSettings = ({ ipfsGatewayUrlsRef, mediaIpfsGatewayUrlRef }: SettingsProps) => {
const account = useAccount() as AccountShape | undefined;
+17 -17
View File
@@ -2,8 +2,8 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useAccountSubplebbitAddresses } from '../use-account-subplebbit-addresses';
import { useAccountSubplebbitsWithMetadata } from '../use-account-subplebbits-with-metadata';
import { useAccountCommunityAddresses } from '../use-account-community-addresses';
import { useAccountCommunitiesWithMetadata } from '../use-account-communities-with-metadata';
import useAuthorPrivileges from '../use-author-privileges';
import { useBoardFeedPageSize } from '../use-board-feed-page-size';
import { useBoardPseudonymityMode } from '../use-board-pseudonymity-mode';
@@ -16,16 +16,16 @@ const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise
const testState = vi.hoisted(() => ({
account: undefined as unknown,
accountSubplebbits: {} as Record<string, unknown>,
accountCommunities: {} as Record<string, unknown>,
directories: [] as Array<{ address: string; nsfw?: boolean }>,
directoryLookup: {} as Record<string, unknown>,
flattenedReplies: [] as unknown[],
subplebbitSnapshot: undefined as unknown,
communitySnapshot: undefined as unknown,
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccount: () => testState.account,
useAccountCommunities: () => ({ accountCommunities: testState.accountSubplebbits }),
useAccountCommunities: () => ({ accountCommunities: testState.accountCommunities }),
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/lib/utils', () => ({
@@ -38,7 +38,7 @@ vi.mock('../use-directories', () => ({
}));
vi.mock('../use-stable-community', () => ({
useCommunityField: (_address: string | undefined, selector: (community: unknown) => unknown) => selector(testState.subplebbitSnapshot),
useCommunityField: (_address: string | undefined, selector: (community: unknown) => unknown) => selector(testState.communitySnapshot),
}));
let latestValue: unknown;
@@ -67,11 +67,11 @@ describe('selector hooks', () => {
localStorage.clear();
vi.clearAllMocks();
testState.account = undefined;
testState.accountSubplebbits = {};
testState.accountCommunities = {};
testState.directories = [];
testState.directoryLookup = {};
testState.flattenedReplies = [];
testState.subplebbitSnapshot = undefined;
testState.communitySnapshot = undefined;
useAllFeedFilterStore.getState().setFilter('all');
container = document.createElement('div');
@@ -84,14 +84,14 @@ describe('selector hooks', () => {
container.remove();
});
it('derives account board addresses and metadata from cached account subplebbits', () => {
testState.accountSubplebbits = {
it('derives account board addresses and metadata from cached account communities', () => {
testState.accountCommunities = {
'music.eth': { address: 'music.eth', title: '/mu/ - Music' },
'tech.eth': { address: 'tech.eth', title: '/g/ - Technology' },
};
expect(renderHookValue(() => useAccountSubplebbitAddresses())).toEqual(['music.eth', 'tech.eth']);
expect(renderHookValue(() => useAccountSubplebbitsWithMetadata())).toEqual([
expect(renderHookValue(() => useAccountCommunityAddresses())).toEqual(['music.eth', 'tech.eth']);
expect(renderHookValue(() => useAccountCommunitiesWithMetadata())).toEqual([
{ address: 'music.eth', title: '/mu/ - Music' },
{ address: 'tech.eth', title: '/g/ - Technology' },
]);
@@ -99,14 +99,14 @@ describe('selector hooks', () => {
it('computes moderator privileges and whether the current account authored the comment', () => {
testState.account = { author: { address: '0xme' } };
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
roles: {
'0xauthor': { role: 'moderator' },
'0xme': { role: 'admin' },
},
};
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xauthor', subplebbitAddress: 'music.eth' }))).toEqual({
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xauthor', communityAddress: 'music.eth' }))).toEqual({
isCommentAuthorMod: true,
isAccountMod: true,
isAccountCommentAuthor: false,
@@ -114,7 +114,7 @@ describe('selector hooks', () => {
accountAuthorRole: 'admin',
});
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xme', subplebbitAddress: 'music.eth' }))).toEqual({
expect(renderHookValue(() => useAuthorPrivileges({ commentAuthorAddress: '0xme', communityAddress: 'music.eth' }))).toEqual({
isCommentAuthorMod: true,
isAccountMod: true,
isAccountCommentAuthor: true,
@@ -146,13 +146,13 @@ describe('selector hooks', () => {
features: { pseudonymityMode: 'directory-mode' },
},
};
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
features: { pseudonymityMode: 'live-mode' },
};
expect(renderHookValue(() => useBoardPseudonymityMode('music.eth'))).toBe('live-mode');
testState.subplebbitSnapshot = {
testState.communitySnapshot = {
features: {},
};
@@ -12,7 +12,7 @@ type TestComment = {
content?: string;
index?: number;
number?: number;
subplebbitAddress?: string;
communityAddress?: string;
};
const testState = vi.hoisted(() => ({
@@ -79,12 +79,12 @@ describe('useFreshReplies', () => {
content: 'stale reply',
index: 3,
number: undefined,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
{
cid: 'network-reply-cid',
content: 'network reply',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
testState.accountComments = [
@@ -93,7 +93,7 @@ describe('useFreshReplies', () => {
content: 'fresh reply',
index: 3,
number: 27,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -121,12 +121,12 @@ describe('useFreshReplies', () => {
{
content: 'stale failed reply',
index: 0,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
{
content: 'duplicate stale failed reply',
index: 0,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
testState.accountComments = [
@@ -134,7 +134,7 @@ describe('useFreshReplies', () => {
content: 'retried pending reply',
index: 0,
number: 99,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -2,7 +2,7 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useIsSubplebbitOffline from '../use-is-subplebbit-offline';
import useIsCommunityOffline from '../use-is-community-offline';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -12,7 +12,7 @@ const testState = vi.hoisted(() => ({
loadingTimestamps: [0] as number[],
requestedAddresses: undefined as string[] | undefined,
setOfflineStateMock: vi.fn(),
subplebbitOfflineState: {} as Record<string, { initialLoad: boolean; state?: string; updatedAt?: number; updatingState?: string }>,
communityOfflineState: {} as Record<string, { initialLoad: boolean; state?: string; updatedAt?: number; updatingState?: string }>,
}));
vi.mock('react-i18next', () => ({
@@ -30,7 +30,7 @@ vi.mock('../../stores/use-community-offline-store', () => ({
default: () => ({
initializeCommunityOfflineState: testState.initializeMock,
setCommunityOfflineState: testState.setOfflineStateMock,
communityOfflineState: testState.subplebbitOfflineState,
communityOfflineState: testState.communityOfflineState,
}),
}));
@@ -45,12 +45,12 @@ vi.mock('../../lib/utils/time-utils', () => ({
getFormattedTimeAgo: (timestamp: number) => `ago:${timestamp}`,
}));
let latestValue: ReturnType<typeof useIsSubplebbitOffline>;
let latestValue: ReturnType<typeof useIsCommunityOffline>;
let container: HTMLDivElement;
let root: Root;
const HookHarness = ({ subplebbit }: { subplebbit?: { address?: string; state?: string; updatedAt?: number; updatingState?: string } }) => {
latestValue = useIsSubplebbitOffline(subplebbit as never);
const HookHarness = ({ community }: { community?: { address?: string; state?: string; updatedAt?: number; updatingState?: string } }) => {
latestValue = useIsCommunityOffline(community as never);
return null;
};
@@ -62,14 +62,14 @@ const flushEffects = async (count = 3) => {
}
};
const renderHook = async (subplebbit?: { address?: string; state?: string; updatedAt?: number; updatingState?: string }) => {
const renderHook = async (community?: { address?: string; state?: string; updatedAt?: number; updatingState?: string }) => {
await act(async () => {
root.render(createElement(HookHarness, { subplebbit }));
root.render(createElement(HookHarness, { community }));
});
await flushEffects();
};
describe('useIsSubplebbitOffline', () => {
describe('useIsCommunityOffline', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
@@ -83,7 +83,7 @@ describe('useIsSubplebbitOffline', () => {
};
testState.loadingTimestamps = [1_704_067_200];
testState.requestedAddresses = undefined;
testState.subplebbitOfflineState = {};
testState.communityOfflineState = {};
container = document.createElement('div');
document.body.appendChild(container);
@@ -116,7 +116,7 @@ describe('useIsSubplebbitOffline', () => {
it('reports boards with stale updates as offline and includes the last synced time', async () => {
const staleUpdatedAt = 1_704_052_000;
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
updatedAt: staleUpdatedAt,
@@ -136,7 +136,7 @@ describe('useIsSubplebbitOffline', () => {
});
it('marks boards without an update timestamp as offline once the loading timeout has elapsed', async () => {
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
},
@@ -149,13 +149,13 @@ describe('useIsSubplebbitOffline', () => {
isOffline: true,
isOnlineStatusLoading: false,
offlineIconClass: 'redOfflineIcon',
offlineTitle: 'subplebbit_offline_info',
offlineTitle: 'community_offline_info',
});
});
it('treats recently updated boards as online', async () => {
const freshUpdatedAt = 1_704_067_205;
testState.subplebbitOfflineState = {
testState.communityOfflineState = {
'music.eth': {
initialLoad: false,
updatedAt: freshUpdatedAt,
+19 -19
View File
@@ -39,13 +39,13 @@ const createPost = (boardAddress: string, suffix: string, replyCount: number, ti
content: `${suffix} content`,
link: `https://cdn.example/${boardAddress}/${suffix}.jpg`,
replyCount,
subplebbitAddress: boardAddress,
communityAddress: boardAddress,
thumbnailUrl: `https://cdn.example/${boardAddress}/${suffix}.thumb.jpg`,
timestamp,
title: `${suffix} title`,
}) as never;
const createSubplebbit = (boardAddress: string, posts: Array<{ cid: string }>, updatedAt = 1_704_067_150) =>
const createCommunity = (boardAddress: string, posts: Array<{ cid: string }>, updatedAt = 1_704_067_150) =>
({
address: boardAddress,
updatedAt,
@@ -58,14 +58,14 @@ const createSubplebbit = (boardAddress: string, posts: Array<{ cid: string }>, u
},
}) as never;
const HookHarness = ({ addresses, subplebbits }: { addresses: string[]; subplebbits: Array<unknown> }) => {
latestValue = usePopularPosts(subplebbits as never, addresses);
const HookHarness = ({ addresses, communities }: { addresses: string[]; communities: Array<unknown> }) => {
latestValue = usePopularPosts(communities as never, addresses);
return null;
};
const renderHook = async (addresses: string[], subplebbits: Array<unknown>) => {
const renderHook = async (addresses: string[], communities: Array<unknown>) => {
await act(async () => {
root.render(createElement(HookHarness, { addresses, subplebbits }));
root.render(createElement(HookHarness, { addresses, communities }));
});
};
@@ -112,8 +112,8 @@ describe('usePopularPosts', () => {
testState.loadingTimestamps = addresses.map(() => 1_704_067_180);
await renderHook(addresses, [
createSubplebbit(addresses[0], [createPost(addresses[0], 'top', 20), createPost(addresses[0], 'backup', 10)]),
createSubplebbit(addresses[1], [createPost(addresses[1], 'top', 18), createPost(addresses[1], 'backup', 9)]),
createCommunity(addresses[0], [createPost(addresses[0], 'top', 20), createPost(addresses[0], 'backup', 10)]),
createCommunity(addresses[1], [createPost(addresses[1], 'top', 18), createPost(addresses[1], 'backup', 9)]),
undefined,
undefined,
undefined,
@@ -128,12 +128,12 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'top', 20 - index), createPost(address, 'backup', 5 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'top', 20 - index), createPost(address, 'backup', 5 - index)])),
);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts).toHaveLength(8);
expect(new Set(latestValue.popularPosts.map((post) => post.subplebbitAddress)).size).toBe(8);
expect(new Set(latestValue.popularPosts.map((post) => post.communityAddress)).size).toBe(8);
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
});
@@ -143,7 +143,7 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'initial', 30 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'initial', 30 - index)])),
);
const initialCids = latestValue.popularPosts.map((post) => post.cid);
@@ -151,7 +151,7 @@ describe('usePopularPosts', () => {
await renderHook(
addresses,
addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'replacement', 100 - index), createPost(address, 'initial', 30 - index)])),
addresses.map((address, index) => createCommunity(address, [createPost(address, 'replacement', 100 - index), createPost(address, 'initial', 30 - index)])),
);
expect(latestValue.isLoading).toBe(false);
@@ -162,13 +162,13 @@ describe('usePopularPosts', () => {
const addresses = ['board-0.eth', 'board-1.eth'];
testState.loadingTimestamps = [1_704_067_180, 1_704_067_180];
await renderHook(addresses, [createSubplebbit(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
await renderHook(addresses, [createCommunity(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
expect(latestValue.isLoading).toBe(true);
expect(latestValue.popularPosts).toEqual([]);
testState.currentTime = 1_704_067_211;
await renderHook(addresses, [createSubplebbit(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
await renderHook(addresses, [createCommunity(addresses[0], [createPost(addresses[0], 'only', 12)]), undefined]);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.cid)).toEqual([`${addresses[0]}-only`]);
@@ -187,23 +187,23 @@ describe('usePopularPosts', () => {
it('reshuffles the selected boards on each mount while keeping one top thread per board', async () => {
const addresses = Array.from({ length: 10 }, (_, index) => `board-${index}.eth`);
const subplebbits = addresses.map((address, index) => createSubplebbit(address, [createPost(address, 'top', 30 - index), createPost(address, 'backup', 10 - index)]));
const communities = addresses.map((address, index) => createCommunity(address, [createPost(address, 'top', 30 - index), createPost(address, 'backup', 10 - index)]));
const keepOrderRandom = mockRandomSequence(Array.from({ length: addresses.length - 1 }, () => 0.999_999));
await renderHook(addresses, subplebbits);
await renderHook(addresses, communities);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.subplebbitAddress)).toEqual(addresses.slice(0, 8));
expect(latestValue.popularPosts.map((post) => post.communityAddress)).toEqual(addresses.slice(0, 8));
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
keepOrderRandom.mockRestore();
resetHookRoot();
const rotateOrderRandom = mockRandomSequence(Array.from({ length: addresses.length - 1 }, () => 0));
await renderHook(addresses, subplebbits);
await renderHook(addresses, communities);
expect(latestValue.isLoading).toBe(false);
expect(latestValue.popularPosts.map((post) => post.subplebbitAddress)).toEqual(addresses.slice(1, 9));
expect(latestValue.popularPosts.map((post) => post.communityAddress)).toEqual(addresses.slice(1, 9));
expect(latestValue.popularPosts.every((post) => post.cid.endsWith('-top'))).toBe(true);
rotateOrderRandom.mockRestore();
@@ -48,12 +48,12 @@ let latestValue: number | undefined;
let container: HTMLDivElement;
let root: Root;
const HookHarness = ({ enabled = true, postCid, subplebbitAddress }: { enabled?: boolean; postCid?: string; subplebbitAddress?: string }) => {
latestValue = usePostPageNumber({ enabled, postCid, subplebbitAddress });
const HookHarness = ({ enabled = true, postCid, communityAddress }: { enabled?: boolean; postCid?: string; communityAddress?: string }) => {
latestValue = usePostPageNumber({ enabled, postCid, communityAddress });
return null;
};
const renderHook = (props: { enabled?: boolean; postCid?: string; subplebbitAddress?: string }) => {
const renderHook = (props: { enabled?: boolean; postCid?: string; communityAddress?: string }) => {
act(() => {
root.render(createElement(HookHarness, props));
});
@@ -95,7 +95,7 @@ describe('usePostPageNumber', () => {
boardFeed: [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }],
};
expect(renderHook({ postCid: 'post-3', subplebbitAddress: 'music.eth' })).toBe(2);
expect(renderHook({ postCid: 'post-3', communityAddress: 'music.eth' })).toBe(2);
expect(testState.preloadOptions).toEqual({
communities: [{ name: 'music.eth' }],
postsPerPage: 20,
@@ -106,7 +106,7 @@ describe('usePostPageNumber', () => {
it('falls back to the preloaded feed when cached feeds do not contain the post yet', () => {
testState.preloadFeed = [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }, { cid: 'post-4' }];
expect(renderHook({ postCid: 'post-4', subplebbitAddress: 'music.eth' })).toBe(2);
expect(renderHook({ postCid: 'post-4', communityAddress: 'music.eth' })).toBe(2);
expect(testState.preloadOptions).toEqual({
communities: [{ name: 'music.eth' }],
postsPerPage: 20,
@@ -117,10 +117,10 @@ describe('usePostPageNumber', () => {
it('skips resolution entirely when the hook is disabled or required inputs are missing', () => {
testState.preloadFeed = [{ cid: 'post-1' }];
expect(renderHook({ enabled: false, postCid: 'post-1', subplebbitAddress: 'music.eth' })).toBeUndefined();
expect(renderHook({ enabled: false, postCid: 'post-1', communityAddress: 'music.eth' })).toBeUndefined();
expect(testState.preloadOptions).toBeUndefined();
expect(renderHook({ enabled: true, postCid: undefined, subplebbitAddress: 'music.eth' })).toBeUndefined();
expect(renderHook({ enabled: true, postCid: undefined, communityAddress: 'music.eth' })).toBeUndefined();
expect(testState.preloadOptions).toBeUndefined();
});
});
@@ -53,7 +53,7 @@ describe('useQuotedByMap', () => {
cid: 'reply-cid',
content: 'replying to >>1',
state: 'succeeded',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
},
];
@@ -85,7 +85,7 @@ describe('useQuotedByMap', () => {
content: 'replying to >>1',
number: 42,
state: 'succeeded',
subplebbitAddress: 'music.bso',
communityAddress: 'music.bso',
},
];
@@ -2,7 +2,7 @@ import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useStableSubplebbit, useSubplebbitField } from '../use-stable-subplebbit';
import { useStableCommunity, useCommunityField } from '../use-stable-community';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
@@ -36,7 +36,7 @@ const renderHookValue = (useValue: () => unknown) => {
return latestValue;
};
describe('use-stable-subplebbit', () => {
describe('use-stable-community', () => {
beforeEach(() => {
latestValue = undefined;
renderCount = 0;
@@ -63,11 +63,11 @@ describe('use-stable-subplebbit', () => {
},
};
expect(renderHookValue(() => useSubplebbitField('international-sfw.eth', (subplebbit) => subplebbit?.roles))).toEqual({
expect(renderHookValue(() => useCommunityField('international-sfw.eth', (community) => community?.roles))).toEqual({
'plebeius.eth': { role: 'owner' },
});
expect(renderHookValue(() => useStableSubplebbit('international-sfw.eth'))).toMatchObject({
expect(renderHookValue(() => useStableCommunity('international-sfw.eth'))).toMatchObject({
address: 'international-sfw.bso',
title: '/int/ - International',
});
@@ -85,6 +85,6 @@ describe('use-stable-subplebbit', () => {
},
};
expect(renderHookValue(() => useSubplebbitField('business.eth', (subplebbit) => subplebbit?.title))).toBe('/biz/ - Exact');
expect(renderHookValue(() => useCommunityField('business.eth', (community) => community?.title))).toBe('/biz/ - Exact');
});
});
@@ -1 +0,0 @@
export { useAccountCommunityAddresses as useAccountSubplebbitAddresses } from './use-account-community-addresses';
@@ -1 +0,0 @@
export { useAccountCommunitiesWithMetadata as useAccountSubplebbitsWithMetadata } from './use-account-communities-with-metadata';
+2 -4
View File
@@ -4,17 +4,15 @@ import { useCommunityField } from './use-stable-community';
interface AuthorPrivilegesProps {
commentAuthorAddress: string;
subplebbitAddress?: string;
communityAddress?: string;
postCid?: string;
}
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress, communityAddress }: AuthorPrivilegesProps) => {
const useAuthorPrivileges = ({ commentAuthorAddress, communityAddress }: AuthorPrivilegesProps) => {
const account = useAccount();
const targetAddress = communityAddress ?? subplebbitAddress;
const accountAuthorAddress = account?.author?.address;
// Only subscribe to roles field to avoid rerenders from updatingState changes
const roles = useCommunityField(targetAddress, (community) => community?.roles);
const roles = useCommunityField(communityAddress, (community) => community?.roles);
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor, commentAuthorRole, accountAuthorRole } = useMemo(() => {
const commentAuthorRole = roles?.[commentAuthorAddress]?.role;
const isCommentAuthorMod = commentAuthorRole === 'admin' || commentAuthorRole === 'owner' || commentAuthorRole === 'moderator';
-6
View File
@@ -29,9 +29,3 @@ export const CommunityStatsCollector = ({ communityAddress }: { communityAddress
return null;
};
/**
* Back-compat exports for old naming.
*/
export const useSubplebbitsStatsStore = useCommunitiesStatsStore;
export const SubplebbitStatsCollector = CommunityStatsCollector;
+4 -1
View File
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react';
import { ChallengeVerification, Comment, PublishCommentOptions, deleteComment, usePublishComment } from '@bitsocialnet/bitsocial-react-hooks';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import useChallengesStore from '../stores/use-challenges-store';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
const retryExcludedFields = new Set([
'accountId',
@@ -57,9 +58,11 @@ export const getFailedPostRetryPublishOptions = (post?: FailedPost): PublishComm
retryOptions.author = author;
}
if (!retryOptions.communityAddress && !retryOptions.subplebbitAddress) {
const communityAddress = retryOptions.communityAddress ?? getCommentCommunityAddress(post);
if (!communityAddress) {
return undefined;
}
retryOptions.communityAddress = communityAddress;
return retryOptions;
};
+1 -3
View File
@@ -35,11 +35,9 @@ const useIsCommunityOffline = (community?: Community | undefined) => {
? 'downloading board...'
: updatedAt
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
: t('subplebbit_offline_info');
: t('community_offline_info');
return { isOffline: !isOnline && isOffline, isOnlineStatusLoading: !isOnline && isLoading, offlineIconClass, offlineTitle };
};
export const useIsSubplebbitOffline = useIsCommunityOffline;
export default useIsCommunityOffline;
-4
View File
@@ -1,4 +0,0 @@
import useIsCommunityOffline from './use-is-community-offline';
export { useIsCommunityOffline as useIsSubplebbitOffline };
export default useIsCommunityOffline;
+1 -10
View File
@@ -7,10 +7,7 @@ import { useCommunityIdentifier } from './use-community-identifiers';
import { findPostPageInFeed, findPostPageInLoadedBoardFeeds, type FeedsOptionsLike, type LoadedFeedsLike } from '../lib/utils/post-page-resolution';
interface UsePostPageNumberOptions {
/** Canonical name. Kept for backward compatibility with older call sites. */
communityAddress?: string;
/** Legacy name kept for backwards compatibility. */
subplebbitAddress?: string;
postCid: string | undefined;
/** When false, page segment is excluded (e.g. pending-post view). When true, resolve and show page. */
enabled?: boolean;
@@ -23,13 +20,7 @@ interface UsePostPageNumberOptions {
*
* @returns 1-based page number, or undefined when unresolved (render as "?")
*/
export function usePostPageNumber({
communityAddress: requestedCommunityAddress,
subplebbitAddress: legacyCommunityAddress,
postCid,
enabled = true,
}: UsePostPageNumberOptions): number | undefined {
const communityAddress = requestedCommunityAddress ?? legacyCommunityAddress;
export function usePostPageNumber({ communityAddress, postCid, enabled = true }: UsePostPageNumberOptions): number | undefined {
const communityIdentifier = useCommunityIdentifier(communityAddress);
const community = useDirectoryByAddress(communityAddress);
+2 -9
View File
@@ -5,14 +5,12 @@ import { getCommunityAddress, getBoardPath } from '../lib/utils/route-utils';
/**
* Resolve a board identifier from URL params to canonical community address.
* Supports both current route params (`boardIdentifier`) and legacy
* compatibility params (`subplebbitAddress`).
*/
export const useResolvedCommunityAddress = (): string | undefined => {
const params = useParams<{ boardIdentifier?: string; subplebbitAddress?: string }>();
const params = useParams<{ boardIdentifier?: string }>();
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier || params.subplebbitAddress;
const boardIdentifier = params.boardIdentifier;
return useMemo(() => {
if (!boardIdentifier) {
@@ -23,11 +21,6 @@ export const useResolvedCommunityAddress = (): string | undefined => {
}, [boardIdentifier, directories]);
};
/**
* Back-compat export kept for callers still importing the legacy hook name.
*/
export const useResolvedSubplebbitAddress = useResolvedCommunityAddress;
/**
* Resolve a community address to board path (directory code or address) for links.
*/
@@ -1 +0,0 @@
export { useBoardPath, useResolvedSubplebbitAddress } from './use-resolved-community-address';
-6
View File
@@ -72,9 +72,3 @@ export const useCommunityField = <T>(communityAddress: string | undefined, selec
return field;
};
/**
* Back-compat exports for old hook names.
*/
export const useStableSubplebbit = useStableCommunity;
export const useSubplebbitField = useCommunityField;
-1
View File
@@ -1 +0,0 @@
export { useStableSubplebbit, useSubplebbitField } from './use-stable-community';
-3
View File
@@ -27,12 +27,9 @@ const friendlyStateNames: Record<string, string> = {
'fetching-ipfs': 'downloading thread',
'fetching-community-ipns': 'downloading board',
'fetching-community-ipfs': 'downloading board',
'fetching-subplebbit-ipns': 'downloading board',
'fetching-subplebbit-ipfs': 'downloading board',
'fetching-update-ipfs': 'downloading update',
'resolving-address': 'resolving address',
'resolving-community-address': 'resolving board address',
'resolving-subplebbit-address': 'resolving board address',
'resolving-author-address': 'resolving author address',
};
-1
View File
@@ -1 +0,0 @@
export { SubplebbitStatsCollector, useSubplebbitsStatsStore } from './use-communities-stats';
+5 -7
View File
@@ -8,6 +8,7 @@ import useSpecialThemeStore from '../stores/use-special-theme-store';
import { isChristmas } from '../lib/utils/time-utils';
import { isSfwBoard, updateFavicon } from '../lib/update-favicon';
import useSafeAccountComment from './use-safe-account-comment';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'];
@@ -20,14 +21,11 @@ const updateThemeClass = (newTheme: string) => {
const useTheme = (): [string, (theme: string) => void] => {
const location = useLocation();
const params = useParams<{ boardIdentifier?: string; subplebbitAddress?: string }>();
const params = useParams<{ boardIdentifier?: string }>();
const pendingPostParams = useParams<{ accountCommentIndex?: string }>();
const pendingPostCommentIndex = pendingPostParams?.accountCommentIndex ? parseInt(pendingPostParams.accountCommentIndex, 10) : undefined;
const pendingPost = useSafeAccountComment({ commentIndex: pendingPostCommentIndex });
const pendingPostCommunityAddress =
(pendingPost as { communityAddress?: string }).communityAddress ||
// compatibility fallback for legacy inbound/persisted comment payloads
(pendingPost as { subplebbitAddress?: string }).subplebbitAddress;
const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost);
const { isEnabled, setIsEnabled } = useSpecialThemeStore();
@@ -38,7 +36,7 @@ const useTheme = (): [string, (theme: string) => void] => {
const isInAllView = isAllView(location.pathname);
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
const isInModView = isModView(location.pathname);
const routeIdentifier = params.boardIdentifier || params.subplebbitAddress;
const routeIdentifier = params.boardIdentifier;
const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || pendingPostCommunityAddress || routeIdentifier;
@@ -86,7 +84,7 @@ const useTheme = (): [string, (theme: string) => void] => {
isInAllView,
isInSubscriptionsView,
isInModView,
subplebbitAddress: communityAddress,
communityAddress,
directories,
});
+3 -3
View File
@@ -36,7 +36,7 @@ describe('update-favicon', () => {
isInAllView: false,
isInSubscriptionsView: false,
isInModView: false,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
directories: [{ address: 'music.eth', nsfw: false }],
}),
).toBe(false);
@@ -48,7 +48,7 @@ describe('update-favicon', () => {
isInAllView: false,
isInSubscriptionsView: false,
isInModView: false,
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
directories: [
{ address: 'music.eth', nsfw: false },
{ address: 'flash.eth', nsfw: true },
@@ -63,7 +63,7 @@ describe('update-favicon', () => {
isInAllView: false,
isInSubscriptionsView: false,
isInModView: false,
subplebbitAddress: 'flash.eth',
communityAddress: 'flash.eth',
directories: [{ address: 'flash.eth', nsfw: true }],
}),
).toBe(false);
+4 -4
View File
@@ -42,7 +42,7 @@ export const isSfwBoard = ({
isInAllView,
isInSubscriptionsView,
isInModView,
subplebbitAddress,
communityAddress,
directories,
}: {
pathname: string;
@@ -50,15 +50,15 @@ export const isSfwBoard = ({
isInAllView: boolean;
isInSubscriptionsView: boolean;
isInModView: boolean;
subplebbitAddress: string | undefined;
communityAddress: string | undefined;
directories: { address: string; nsfw?: boolean }[];
}): boolean => {
if (pathname === '/' || pathname.startsWith('/rules')) return false;
if (isSpecialTheme) return false;
if (isInAllView || isInSubscriptionsView || isInModView) return false;
if (!subplebbitAddress) return false;
if (!communityAddress) return false;
const entry = directories.find((d) => d.address === subplebbitAddress);
const entry = directories.find((d) => d.address === communityAddress);
return !entry?.nsfw;
};
@@ -9,9 +9,7 @@ describe('buildEditableAccountJson', () => {
author: { address: '0x123', shortAddress: '0x1...3', avatar: { url: 'https://example.com' } },
pkc: { someOption: true },
pkcReactOptions: { foo: 'baz' },
plebbit: { someOption: true },
karma: 42,
plebbitReactOptions: { foo: 'bar' },
unreadNotificationCount: 5,
};
const result = JSON.parse(buildEditableAccountJson(account));
@@ -21,9 +19,7 @@ describe('buildEditableAccountJson', () => {
expect(result.account.author.avatar).toBeUndefined();
expect(result.account.pkc).toBeUndefined();
expect(result.account.pkcReactOptions).toBeUndefined();
expect(result.account.plebbit).toBeUndefined();
expect(result.account.karma).toBeUndefined();
expect(result.account.plebbitReactOptions).toBeUndefined();
expect(result.account.unreadNotificationCount).toBeUndefined();
});
@@ -26,14 +26,14 @@ describe('challenge-utils', () => {
challengeSuccess: false,
reason: 'try again later',
} as never,
{ subplebbitAddress: 'business-and-finance.bso' },
{ communityAddress: 'business-and-finance.bso' },
);
expect(warnSpy).toHaveBeenCalledWith(
'Challenge Verification Failed:',
expect.objectContaining({ challengeSuccess: false }),
'Publication:',
expect.objectContaining({ subplebbitAddress: 'business-and-finance.bso' }),
expect.objectContaining({ communityAddress: 'business-and-finance.bso' }),
);
expect(alertMock).toHaveBeenCalledWith('Error from /biz/: invalid captcha try again later');
});
@@ -44,13 +44,13 @@ describe('challenge-utils', () => {
challengeErrors: ['first error', 'second error'],
challengeSuccess: false,
} as never,
{ subplebbitAddress: 'unknown-board.eth' },
{ communityAddress: 'unknown-board.eth' },
);
expect(alertMock).toHaveBeenCalledWith('Error from unknown-board.eth: first error second error');
});
it('uses communityAddress when the upgraded publication no longer exposes subplebbitAddress', () => {
it('uses communityAddress when the upgraded publication no longer exposes communityAddress', () => {
alertChallengeVerificationFailed(
{
challengeErrors: ['first error'],
@@ -76,7 +76,7 @@ describe('challenge-utils', () => {
});
it('logs successful challenge verification instead of alerting', () => {
alertChallengeVerificationFailed({ challengeSuccess: true } as never, { subplebbitAddress: 'business-and-finance.bso' });
alertChallengeVerificationFailed({ challengeSuccess: true } as never, { communityAddress: 'business-and-finance.bso' });
expect(logSpy).toHaveBeenCalledWith('Challenge verification succeeded:', expect.objectContaining({ challengeSuccess: true }));
expect(alertMock).not.toHaveBeenCalled();
@@ -72,18 +72,18 @@ describe('pattern-utils', () => {
it('matches roles with moderator aliases and rejects missing role metadata', () => {
const modComment = {
author: { address: 'author-1' },
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
};
const ownerComment = {
author: { address: 'author-2' },
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
};
expect(userHasRole(modComment as never, 'moderator')).toBe(true);
expect(userHasRole(modComment as never, 'mod')).toBe(true);
expect(userHasRole(ownerComment as never, 'owner')).toBe(true);
expect(userHasRole(ownerComment as never, 'admin')).toBe(false);
expect(userHasRole({ author: { address: 'missing' }, subplebbitAddress: 'unknown.eth' } as never, 'moderator')).toBe(false);
expect(userHasRole({ author: { address: 'missing' }, communityAddress: 'unknown.eth' } as never, 'moderator')).toBe(false);
});
it('parses mixed special filters and content filters', () => {
@@ -109,7 +109,7 @@ describe('pattern-utils', () => {
shortAddress: 'auth1',
},
content: 'That feel when the girlfriend texts back',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'TFW',
};
@@ -14,13 +14,13 @@ describe('post-page-resolution', () => {
it('only accepts strict board feed options for the active single-board feed', () => {
const baseOptions = {
sortType: 'active',
subplebbitAddresses: ['music.eth'],
communityAddresses: ['music.eth'],
};
expect(isBoardFeedOptions(baseOptions, 'music.eth')).toBe(true);
expect(isBoardFeedOptions({ ...baseOptions, sortType: 'new' }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, subplebbitAddresses: ['music.eth', 'tech.eth'] }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, subplebbitAddresses: ['tech.eth'] }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, communityAddresses: ['music.eth', 'tech.eth'] }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, communityAddresses: ['tech.eth'] }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, filter: { title: 'test' } }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, newerThan: 3600 }, 'music.eth')).toBe(false);
expect(isBoardFeedOptions({ ...baseOptions, modQueue: true }, 'music.eth')).toBe(false);
@@ -31,16 +31,16 @@ describe('post-page-resolution', () => {
const feedsOptions = {
allFeed: {
sortType: 'active',
subplebbitAddresses: ['all.eth'],
communityAddresses: ['all.eth'],
},
catalogFilterFeed: {
filter: { title: 'match' },
sortType: 'active',
subplebbitAddresses: ['music.eth'],
communityAddresses: ['music.eth'],
},
boardFeed: {
sortType: 'active',
subplebbitAddresses: ['music.eth'],
communityAddresses: ['music.eth'],
},
};
const loadedFeeds = {
@@ -6,7 +6,7 @@ const createReply = (overrides: Partial<Comment> = {}) =>
({
cid: 'reply-cid',
parentCid: 'target-cid',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
...overrides,
}) as Comment;
+4 -4
View File
@@ -6,7 +6,7 @@ import {
getFeedCacheKey,
getFeedType,
getPageFromFeedPath,
getSubplebbitAddress,
getCommunityAddress,
isArchiveRoute,
isBoardModRoute,
isDirectoryBoard,
@@ -44,9 +44,9 @@ describe('directory mapping helpers', () => {
expect(getBoardPath('12D3KooWQdQ6TkVA1Xe9zzaFP6vXBgsLeMAewpLpLwbsAYKivnQy', communities)).toBe('mu');
expect(getBoardPath('unknown.example', communities)).toBe('unknown.example');
expect(getSubplebbitAddress('biz', communities)).toBe('business.eth');
expect(getSubplebbitAddress('b', communities)).toBe('random.eth');
expect(getSubplebbitAddress('unknown.example', communities)).toBe('unknown.example');
expect(getCommunityAddress('biz', communities)).toBe('business.eth');
expect(getCommunityAddress('b', communities)).toBe('random.eth');
expect(getCommunityAddress('unknown.example', communities)).toBe('unknown.example');
});
it('compares aliases and directory identifiers correctly', () => {
+2 -2
View File
@@ -38,9 +38,9 @@ describe('view-utils', () => {
expect(isSettingsView('/music.eth/thread/cid-123/settings', params)).toBe(true);
});
it('supports deprecated subplebbitAddress params and marks unknown routes as not found', () => {
it('supports emoji board identifiers and marks unknown routes as not found', () => {
const params = {
subplebbitAddress: 'emoji-🎵.eth',
boardIdentifier: 'emoji-🎵.eth',
commentCid: 'cid-123',
};
-4
View File
@@ -6,9 +6,7 @@ type AccountLike = {
author?: { address?: string; shortAddress?: string; avatar?: unknown };
pkc?: unknown;
pkcReactOptions?: unknown;
plebbit?: unknown;
karma?: unknown;
plebbitReactOptions?: unknown;
unreadNotificationCount?: unknown;
[key: string]: unknown;
};
@@ -23,9 +21,7 @@ export const buildEditableAccountJson = (account: AccountLike | undefined): stri
author: { ...account?.author, avatar: undefined },
pkc: undefined,
pkcReactOptions: undefined,
plebbit: undefined,
karma: undefined,
plebbitReactOptions: undefined,
unreadNotificationCount: undefined,
},
});
+2 -1
View File
@@ -1,5 +1,6 @@
import { ChallengeVerification } from '@bitsocialnet/bitsocial-react-hooks';
import { getFallbackDirectoriesData } from '../../hooks/use-directories';
import { getCommentCommunityAddress } from './comment-utils';
import { getBoardPath } from './route-utils';
const resolveBoardIdentifier = (communityAddress: unknown): string => {
@@ -35,7 +36,7 @@ export const alertChallengeVerificationFailed = (challengeVerification: Challeng
}
const finalMessage = errorMessages.filter(Boolean).join(' ');
const publicationCommunityAddress = publication?.communityAddress || publication?.subplebbitAddress;
const publicationCommunityAddress = getCommentCommunityAddress(publication);
alert(`Error from ${resolveBoardIdentifier(publicationCommunityAddress)}: ${finalMessage || 'unknown error'}`);
} else {
+5 -9
View File
@@ -1,15 +1,14 @@
type CommentWithLegacyCommunityAddress = {
type CommentWithCommunityAddress = {
communityAddress?: string;
replies?: {
pages?: Record<
string,
| {
comments?: Array<CommentWithLegacyCommunityAddress | undefined>;
comments?: Array<CommentWithCommunityAddress | undefined>;
}
| undefined
>;
};
subplebbitAddress?: string;
};
export const getCommentCommunityAddress = (comment?: unknown) => {
@@ -17,18 +16,15 @@ export const getCommentCommunityAddress = (comment?: unknown) => {
return undefined;
}
const record = comment as { communityAddress?: unknown; subplebbitAddress?: unknown };
const record = comment as { communityAddress?: unknown };
if (typeof record.communityAddress === 'string' && record.communityAddress) {
return record.communityAddress;
}
if (typeof record.subplebbitAddress === 'string' && record.subplebbitAddress) {
return record.subplebbitAddress;
}
return undefined;
};
const withResolvedReplyPages = (replies?: CommentWithLegacyCommunityAddress['replies']) => {
const withResolvedReplyPages = (replies?: CommentWithCommunityAddress['replies']) => {
if (!replies?.pages) {
return replies;
}
@@ -82,7 +78,7 @@ const withResolvedReplyPages = (replies?: CommentWithLegacyCommunityAddress['rep
};
};
export const withResolvedCommentCommunityAddress = <T extends CommentWithLegacyCommunityAddress | undefined | null>(comment: T): T => {
export const withResolvedCommentCommunityAddress = <T extends CommentWithCommunityAddress | undefined | null>(comment: T): T => {
if (!comment) {
return comment;
}
+2 -1
View File
@@ -6,6 +6,7 @@ import type { DirectoryCommunity } from '../../hooks/use-directories';
import usePostNumberStore from '../../stores/use-post-number-store';
import type { ExternalQuoteReference, ExternalQuoteSearchStatus } from './external-quote-utils';
import { getExternalQuoteBoardAddress, getExternalQuoteBoardLabel } from './external-quote-utils';
import { getCommentCommunityAddress } from './comment-utils';
import { getBoardPath } from './route-utils';
const BOARD_FEED_SORT_TYPE = 'new';
@@ -63,7 +64,7 @@ const findLoadedCommentByNumber = ({ number, communityAddress }: { number: numbe
const comments = Object.values(communitiesPagesStore.getState().comments) as Array<Comment | undefined>;
return comments.find((comment) => {
const address = (comment as { communityAddress?: string }).communityAddress || comment?.subplebbitAddress;
const address = getCommentCommunityAddress(comment);
return address === communityAddress && comment?.number === number && comment?.cid;
});
};
+1 -1
View File
@@ -163,7 +163,7 @@ const fetchWebpageThumbnail = async (url: string): Promise<string | undefined> =
});
html = response.data.slice(0, MAX_HTML_SIZE);
} else {
// some sites have CORS access, from which the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
// some sites have CORS access, so the thumbnail can be fetched client-side when community thumbnail fetching is disabled
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
+5 -4
View File
@@ -1,5 +1,6 @@
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
import communitiesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities';
import { getCommentCommunityAddress } from './comment-utils';
type CommunityLike = {
roles?: Record<string, { role?: string }>;
@@ -142,20 +143,20 @@ export const displayNameMatchesPattern = (comment: Comment, pattern: string): bo
* @returns True if the user has the specified role, false otherwise
*/
export const userHasRole = (comment: Comment, role: string): boolean => {
const communityAddress = (comment as { communityAddress?: string }).communityAddress ?? comment?.subplebbitAddress;
const communityAddress = getCommentCommunityAddress(comment);
if (!role || !comment?.author?.address || !communityAddress) {
return false;
}
const communities = communitiesStore.getState().communities;
const subplebbit = communities[communityAddress] as CommunityLike | undefined;
const community = communities[communityAddress] as CommunityLike | undefined;
if (!subplebbit?.roles) {
if (!community?.roles) {
return false;
}
const userRole = subplebbit.roles[comment.author.address]?.role;
const userRole = community.roles[comment.author.address]?.role;
// Handle different role names (moderator/mod)
if ((role.toLowerCase() === 'moderator' || role.toLowerCase() === 'mod') && userRole === 'moderator') {
+1 -1
View File
@@ -1,6 +1,6 @@
export const approvePendingCommentModeration = { approved: true } as const;
// plebbit-js clears pendingApproval only when rejection is published as approved:false.
// pkc-js clears pendingApproval only when rejection is published as approved:false.
// Sending removed:true marks the comment removed but can leave it in the mod queue.
export const rejectPendingCommentModeration = { approved: false } as const;
-2
View File
@@ -6,7 +6,6 @@ export type PostMenuProps = {
postCid?: string;
parentCid?: string;
communityAddress?: string;
subplebbitAddress?: string;
authorAddress?: string;
link?: string;
linkWidth?: number;
@@ -24,7 +23,6 @@ export const selectPostMenuProps = (post?: Comment): PostMenuProps => {
postCid: post?.postCid,
parentCid: post?.parentCid,
communityAddress,
subplebbitAddress: post?.subplebbitAddress,
authorAddress: post?.author?.address,
link: post?.link,
linkWidth: post?.linkWidth,
-4
View File
@@ -16,7 +16,6 @@ type CommunityIdentifierLike = {
};
type LegacyFeedOptionsLike = {
subplebbitAddresses?: string[];
sortType: string;
postsPerPage?: number;
filter?: unknown;
@@ -72,9 +71,6 @@ const getCommunityIdentifiers = (opts: FeedOptionsLike | LegacyFeedOptionsLike):
if ('communityAddresses' in opts && Array.isArray(opts.communityAddresses)) {
return opts.communityAddresses.map((communityAddress) => ({ name: communityAddress }));
}
if ('subplebbitAddresses' in opts && Array.isArray(opts.subplebbitAddresses)) {
return opts.subplebbitAddresses.map((communityAddress) => ({ name: communityAddress }));
}
return [];
};
-5
View File
@@ -104,11 +104,6 @@ export const getCommunityAddress = (boardIdentifier: string, communities: Direct
return boardIdentifier;
};
/**
* Back-compat alias kept for route params and comments.
*/
export const getSubplebbitAddress = getCommunityAddress;
/**
* Compare two addresses; returns true if they refer to the same board (handles .bso/.eth aliases).
*/
+3 -3
View File
@@ -64,15 +64,15 @@ export const is5chanLink = (url: string): boolean => {
// For pleb.bz, only support the exact sharelink format (legacy /p/... format)
if (hostname === 'pleb.bz') {
// Must match exactly: /p/{subplebbitAddress}/c/{cid}
// Must match exactly: /p/{communityAddress}/c/{cid}
// Allow redirect parameter since these are still valid internal links
return /^\/p\/[^/]+\/c\/[^/]+$/.test(routePath);
}
// For other 5chan hostnames, support both old and new formats:
// Old format (for backward compatibility):
// - /p/{subplebbitAddress}
// - /p/{subplebbitAddress}/c/{commentCid}
// - /p/{communityAddress}
// - /p/{communityAddress}/c/{commentCid}
// New format:
// - /{boardIdentifier} (directory code or address)
// - /{boardIdentifier}/thread/{commentCid}
+8 -9
View File
@@ -4,7 +4,6 @@ type ParamsType = {
accountCommentIndex?: string;
boardIdentifier?: string;
commentCid?: string;
subplebbitAddress?: string; // deprecated, kept for backward compatibility
};
export const isAllView = (pathname: string): boolean => {
@@ -28,13 +27,13 @@ export const isBoardView = (pathname: string, params: ParamsType): boolean => {
) {
return false;
}
const identifier = params.boardIdentifier || params.subplebbitAddress;
const identifier = params.boardIdentifier;
return identifier ? decodedPathname.startsWith(`/${identifier}`) : false;
};
export const isCatalogView = (pathname: string, params: ParamsType): boolean => {
const { boardIdentifier, subplebbitAddress } = params;
const identifier = boardIdentifier || subplebbitAddress;
const { boardIdentifier } = params;
const identifier = boardIdentifier;
const decodedPathname = decodeURIComponent(pathname);
return (
@@ -66,13 +65,13 @@ export const isPendingPostView = (pathname: string, params: ParamsType): boolean
export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
const decodedPathname = decodeURIComponent(pathname);
const identifier = params.boardIdentifier || params.subplebbitAddress;
const identifier = params.boardIdentifier;
return identifier && params.commentCid ? decodedPathname.startsWith(`/${identifier}/thread/${params.commentCid}`) : false;
};
export const isSettingsView = (pathname: string, params: ParamsType): boolean => {
const { accountCommentIndex, boardIdentifier, commentCid, subplebbitAddress } = params;
const identifier = boardIdentifier || subplebbitAddress;
const { accountCommentIndex, boardIdentifier, commentCid } = params;
const identifier = boardIdentifier;
const decodedPathname = decodeURIComponent(pathname);
return (
(identifier && commentCid && decodedPathname === `/${identifier}/thread/${commentCid}/settings`) || decodedPathname === `/pending/${accountCommentIndex}/settings`
@@ -84,8 +83,8 @@ export const isSubscriptionsView = (pathname: string, params: ParamsType): boole
};
export const isArchiveView = (pathname: string, params: ParamsType): boolean => {
const { boardIdentifier, subplebbitAddress } = params;
const identifier = boardIdentifier || subplebbitAddress;
const { boardIdentifier } = params;
const identifier = boardIdentifier;
const decodedPathname = decodeURIComponent(pathname);
return Boolean(identifier && isArchiveRoute(decodedPathname) && decodedPathname === `/${identifier}/archive`);
@@ -18,7 +18,7 @@ const resetReplyModalStore = () => {
parentNumber: null,
threadNumber: null,
threadCid: null,
subplebbitAddress: null,
communityAddress: null,
scrollY: 0,
quoteInsertRequestId: 0,
quoteInsertNumber: null,
@@ -210,11 +210,11 @@ describe('interaction stores', () => {
it('registers post numbers per board and ignores unchanged or invalid comments', () => {
const comments = [
{ cid: 'cid-1', number: 1, subplebbitAddress: 'music.eth' },
{ cid: 'cid-2', number: 2, subplebbitAddress: 'music.eth' },
{ cid: 'cid-1-tech', number: 1, subplebbitAddress: 'tech.eth' },
{ cid: '', number: 3, subplebbitAddress: 'music.eth' },
{ cid: 'cid-no-number', subplebbitAddress: 'music.eth' },
{ cid: 'cid-1', number: 1, communityAddress: 'music.eth' },
{ cid: 'cid-2', number: 2, communityAddress: 'music.eth' },
{ cid: 'cid-1-tech', number: 1, communityAddress: 'tech.eth' },
{ cid: '', number: 3, communityAddress: 'music.eth' },
{ cid: 'cid-no-number', communityAddress: 'music.eth' },
] as never[];
usePostNumberStore.getState().registerComments(comments);
@@ -278,7 +278,7 @@ describe('interaction stores', () => {
parentNumber: 12,
threadNumber: 34,
threadCid: 'thread-cid',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
scrollY: 140,
});
});
@@ -314,7 +314,7 @@ describe('interaction stores', () => {
activeCid: 'thread-cid',
threadNumber: 34,
threadCid: 'thread-cid',
subplebbitAddress: 'music.eth',
communityAddress: 'music.eth',
scrollY: 32,
quoteInsertNumber: null,
quoteInsertSelectedText: null,
+2 -4
View File
@@ -41,7 +41,7 @@ describe('publish stores', () => {
displayName: 'Poster Alias',
link: 'https://example.com',
spoiler: true,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'Hello',
};
@@ -52,7 +52,6 @@ describe('publish stores', () => {
expect(state.author).toEqual({ address: '0x123', role: 'mod', displayName: 'Poster Alias' });
expect(state.publishCommentOptions.author).toEqual({ address: '0x123', role: 'mod', displayName: 'Poster Alias' });
expect(state.publishCommentOptions.communityAddress).toBe('music-posting.eth');
expect('subplebbitAddress' in state.publishCommentOptions).toBe(false);
expect(state.publishCommentOptions.title).toBe('Hello');
state.publishCommentOptions.onChallengeVerification?.({} as never, comment);
@@ -75,7 +74,7 @@ describe('publish stores', () => {
link: 'https://example.com/reply',
parentCid: 'parent-1',
spoiler: false,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
};
usePublishReplyStore.getState().setPublishReplyStore(comment);
@@ -86,7 +85,6 @@ describe('publish stores', () => {
expect(state.publishCommentOptions['parent-1']?.communityAddress).toBe('music-posting.eth');
expect(state.publishCommentOptions['parent-1']?.parentCid).toBe('parent-1');
expect(state.publishCommentOptions['parent-1']?.postCid).toBe('parent-1');
expect('subplebbitAddress' in (state.publishCommentOptions['parent-1'] || {})).toBe(false);
state.publishCommentOptions['parent-1']?.onChallengeVerification?.({ token: 'challenge' } as never, comment);
expect(testState.alertChallengeVerificationFailedMock).toHaveBeenCalledWith({ token: 'challenge' }, comment);
+7 -7
View File
@@ -47,21 +47,21 @@ describe('ui state stores', () => {
expect(localStorage.getItem('expanded-media-store')).toContain('unmuteExpandedVideoSound');
});
it('useSubplebbitOfflineStore merges updates and clears initialLoad after the timeout', async () => {
it('useCommunityOfflineStore merges updates and clears initialLoad after the timeout', async () => {
vi.useFakeTimers();
const store = (await import('../use-subplebbit-offline-store')).default;
const store = (await import('../use-community-offline-store')).default;
store.getState().initializesubplebbitOfflineState('music-posting.eth');
expect(store.getState().subplebbitOfflineState['music-posting.eth']).toEqual({ initialLoad: true });
store.getState().initializeCommunityOfflineState('music-posting.eth');
expect(store.getState().communityOfflineState['music-posting.eth']).toEqual({ initialLoad: true });
store.getState().setSubplebbitOfflineState('music-posting.eth', {
store.getState().setCommunityOfflineState('music-posting.eth', {
state: 'offline',
updatedAt: 123,
updatingState: 'recovering',
});
expect(store.getState().subplebbitOfflineState['music-posting.eth']).toEqual({
expect(store.getState().communityOfflineState['music-posting.eth']).toEqual({
initialLoad: true,
state: 'offline',
updatedAt: 123,
@@ -70,7 +70,7 @@ describe('ui state stores', () => {
vi.advanceTimersByTime(30_000);
expect(store.getState().subplebbitOfflineState['music-posting.eth']).toEqual({
expect(store.getState().communityOfflineState['music-posting.eth']).toEqual({
initialLoad: false,
state: 'offline',
updatedAt: 123,
@@ -12,7 +12,6 @@ vi.mock('../../lib/utils/pattern-utils', () => ({
}));
type CatalogFiltersStoreModule = typeof import('../use-catalog-filters-store');
type CatalogFiltersStore = Awaited<ReturnType<typeof loadStore>>;
const STORAGE_KEY = 'catalog-filters-storage';
@@ -28,8 +27,8 @@ const createFilterItem = (text: string, overrides: Record<string, unknown> = {})
enabled: true,
count: 0,
filteredCids: new Set<string>(),
subplebbitCounts: new Map<string, number>(),
subplebbitFilteredCids: new Map<string, Set<string>>(),
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
hide: true,
top: false,
color: '',
@@ -73,7 +72,7 @@ describe('useCatalogFiltersStore', () => {
color: 'red',
});
expect(filterItems[0].filteredCids).toEqual(new Set());
expect(filterItems[0].subplebbitCounts).toEqual(new Map());
expect(filterItems[0].communityCounts).toEqual(new Map());
expect(filterItems[1]).toMatchObject({
text: 'news',
enabled: false,
@@ -92,17 +91,17 @@ describe('useCatalogFiltersStore', () => {
const useCatalogFiltersStore = await loadStore();
useCatalogFiltersStore.getState().setFilterItems([createFilterItem('spam', { hide: true }), createFilterItem('highlight', { hide: false, top: true })] as never);
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress('music.eth');
useCatalogFiltersStore.getState().setCurrentCommunityAddress('music.eth');
useCatalogFiltersStore.getState().setSearchFilter('topic');
const filter = useCatalogFiltersStore.getState().filter;
expect(filter).toBeTypeOf('function');
expect(filter?.({ cid: 'cid-1', content: 'topic spam', subplebbitAddress: 'music.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-1', content: 'topic spam', subplebbitAddress: 'music.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-2', content: 'topic spam', subplebbitAddress: 'tech.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-3', content: 'topic highlight', subplebbitAddress: 'music.eth' } as never)).toBe(true);
expect(filter?.({ cid: 'cid-4', content: 'ordinary update', subplebbitAddress: 'music.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-1', content: 'topic spam', communityAddress: 'music.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-1', content: 'topic spam', communityAddress: 'music.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-2', content: 'topic spam', communityAddress: 'tech.eth' } as never)).toBe(false);
expect(filter?.({ cid: 'cid-3', content: 'topic highlight', communityAddress: 'music.eth' } as never)).toBe(true);
expect(filter?.({ cid: 'cid-4', content: 'ordinary update', communityAddress: 'music.eth' } as never)).toBe(false);
vi.runAllTimers();
@@ -110,16 +109,16 @@ describe('useCatalogFiltersStore', () => {
expect(testState.commentMatchesPatternMock).toHaveBeenCalled();
expect(state.filterItems[0].count).toBe(1);
expect(state.filterItems[0].filteredCids).toEqual(new Set(['cid-1']));
expect(state.filterItems[0].subplebbitCounts.get('music.eth')).toBe(1);
expect(state.filterItems[0].communityCounts.get('music.eth')).toBe(1);
expect(state.filteredCount).toBe(1);
expect(state.getFilteredCountForCurrentSubplebbit()).toBe(1);
expect(state.getFilteredCountForCurrentCommunity()).toBe(1);
});
it('preserves counts for unchanged filters when saving and clears matched filters', async () => {
const useCatalogFiltersStore = await loadStore();
useCatalogFiltersStore.getState().setFilterItems([createFilterItem('spam')] as never);
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress('music.eth');
useCatalogFiltersStore.getState().setCurrentCommunityAddress('music.eth');
useCatalogFiltersStore.getState().incrementFilterCount(0, 'cid-1', 'music.eth');
useCatalogFiltersStore.getState().setMatchedFilter('cid-1', 'red');
@@ -131,7 +130,7 @@ describe('useCatalogFiltersStore', () => {
expect(filterItems).toHaveLength(2);
expect(filterItems[0].text).toBe('spam');
expect(filterItems[0].count).toBe(1);
expect(filterItems[0].subplebbitCounts.get('music.eth')).toBe(1);
expect(filterItems[0].communityCounts.get('music.eth')).toBe(1);
expect(filterItems[0].color).toBe('orange');
expect(filterItems[1]).toMatchObject({
text: 'eggs',
@@ -139,7 +138,7 @@ describe('useCatalogFiltersStore', () => {
enabled: false,
top: true,
});
expect(filterItems[1].subplebbitCounts).toEqual(new Map());
expect(filterItems[1].communityCounts).toEqual(new Map());
expect(filteredCids).toEqual(new Set());
expect(matchedFilters.size).toBe(0);
});
@@ -148,23 +147,58 @@ describe('useCatalogFiltersStore', () => {
const useCatalogFiltersStore = await loadStore();
useCatalogFiltersStore.getState().setFilterItems([createFilterItem('spam')] as never);
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress('music.eth');
useCatalogFiltersStore.getState().setCurrentCommunityAddress('music.eth');
useCatalogFiltersStore.getState().incrementFilterCount(0, 'cid-1', 'music.eth');
useCatalogFiltersStore.getState().incrementFilterCount(0, 'cid-2', 'tech.eth');
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentSubplebbit()).toBe(1);
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentCommunity()).toBe(1);
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress('tech.eth');
expect(useCatalogFiltersStore.getState().currentSubplebbitAddress).toBe('tech.eth');
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentSubplebbit()).toBe(1);
useCatalogFiltersStore.getState().setCurrentCommunityAddress('tech.eth');
expect(useCatalogFiltersStore.getState().currentCommunityAddress).toBe('tech.eth');
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentCommunity()).toBe(1);
useCatalogFiltersStore.getState().resetCountsForCurrentSubplebbit();
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentSubplebbit()).toBe(0);
useCatalogFiltersStore.getState().resetCountsForCurrentCommunity();
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentCommunity()).toBe(0);
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress('music.eth');
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentSubplebbit()).toBe(1);
useCatalogFiltersStore.getState().setCurrentCommunityAddress('music.eth');
expect(useCatalogFiltersStore.getState().getFilteredCountForCurrentCommunity()).toBe(1);
useCatalogFiltersStore.getState().clearSearchFilter();
expect(useCatalogFiltersStore.getState().searchText).toBe('');
});
it('rehydrates filter items from the persisted zustand wrapper', async () => {
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
state: {
filterItems: [
{
text: 'spam',
enabled: false,
hide: false,
top: true,
},
],
},
version: 0,
}),
);
const useCatalogFiltersStore = await loadStore();
expect(useCatalogFiltersStore.getState().filterItems).toEqual([
{
text: 'spam',
enabled: false,
count: 0,
filteredCids: new Set(),
communityCounts: new Map(),
communityFilteredCids: new Map(),
hide: false,
top: true,
color: '',
},
]);
});
});
@@ -18,7 +18,7 @@ const flushEffects = async (count = 3) => {
}
};
describe('useSubplebbitsLoadingStartTimestamps', () => {
describe('useCommunitiesLoadingStartTimestamps', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-01-01T00:00:00Z'));
@@ -36,10 +36,10 @@ describe('useSubplebbitsLoadingStartTimestamps', () => {
});
it('stores first-seen timestamps per board and only adds new addresses on rerender', async () => {
const useSubplebbitsLoadingStartTimestamps = (await import('../use-subplebbits-loading-start-timestamps-store')).default;
const useCommunitiesLoadingStartTimestamps = (await import('../use-communities-loading-start-timestamps-store')).default;
const HookHarness = ({ addresses }: { addresses?: string[] }) => {
const value = useSubplebbitsLoadingStartTimestamps(addresses);
const value = useCommunitiesLoadingStartTimestamps(addresses);
React.useLayoutEffect(() => {
latestValue = value;
}, [value]);
@@ -13,16 +13,16 @@ describe('useFeedCacheStore', () => {
it('accessFeed adds entries', () => {
const { accessFeed } = useFeedCacheStore.getState();
accessFeed('plebbit/board', 'board');
accessFeed('pkc/board', 'board');
const afterFirst = useFeedCacheStore.getState().cachedFeeds;
expect(afterFirst.length).toBe(1);
expect(afterFirst[0].key).toBe('plebbit/board');
expect(afterFirst[0].key).toBe('pkc/board');
expect(afterFirst[0].type).toBe('board');
accessFeed('plebbit/catalog', 'catalog');
accessFeed('pkc/catalog', 'catalog');
const afterSecond = useFeedCacheStore.getState().cachedFeeds;
expect(afterSecond.length).toBe(2);
expect(afterSecond.some((f) => f.key === 'plebbit/catalog' && f.type === 'catalog')).toBe(true);
expect(afterSecond.some((f) => f.key === 'pkc/catalog' && f.type === 'catalog')).toBe(true);
});
it('evicts least recently accessed when cache exceeds maxCacheSize (2)', () => {
@@ -45,7 +45,7 @@ describe('useFeedCacheStore', () => {
it('clearFeeds empties cache', () => {
const { accessFeed, clearFeeds } = useFeedCacheStore.getState();
accessFeed('plebbit/board', 'board');
accessFeed('pkc/board', 'board');
accessFeed('other/board', 'catalog');
expect(useFeedCacheStore.getState().cachedFeeds.length).toBe(2);
+16 -45
View File
@@ -1,6 +1,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
import { commentMatchesPattern } from '../lib/utils/pattern-utils';
interface FilterItem {
@@ -10,9 +11,6 @@ interface FilterItem {
filteredCids: Set<string>;
communityCounts: Map<string, number>;
communityFilteredCids: Map<string, Set<string>>;
// legacy read/write compatibility
subplebbitCounts: Map<string, number>;
subplebbitFilteredCids: Map<string, Set<string>>;
hide: boolean;
top: boolean;
color?: string;
@@ -38,11 +36,6 @@ interface CatalogFiltersStore {
setSearchFilter: (text: string) => void;
clearSearchFilter: () => void;
resetCountsForCurrentCommunity: () => void;
// legacy compatibility aliases
currentSubplebbitAddress: string | null;
setCurrentSubplebbitAddress: (address: string | null) => void;
getFilteredCountForCurrentSubplebbit: () => number;
resetCountsForCurrentSubplebbit: () => void;
matchedFilters: Map<string, string>;
setMatchedFilter: (cid: string, color: string) => void;
clearMatchedFilters: () => void;
@@ -55,22 +48,16 @@ type RawFilterItem = {
filteredCids?: Set<string>;
communityCounts?: Map<string, number>;
communityFilteredCids?: Map<string, Set<string>>;
subplebbitCounts?: Map<string, number>;
subplebbitFilteredCids?: Map<string, Set<string>>;
hide?: boolean;
top?: boolean;
color?: string;
};
const getCommentCommunityAddress = (comment: Comment): string | undefined => {
return ((comment as { communityAddress?: string }).communityAddress || (comment as { subplebbitAddress?: string }).subplebbitAddress) as string | undefined;
};
const toMap = (value: unknown, fallback: Map<any, any>): Map<string, any> => (value instanceof Map ? (value as Map<string, any>) : fallback);
const normalizeFilterItem = (item: RawFilterItem): FilterItem => {
const communityCounts = toMap(item.communityCounts ?? item.subplebbitCounts, new Map<string, number>());
const communityFilteredCids = toMap(item.communityFilteredCids ?? item.subplebbitFilteredCids, new Map<string, Set<string>>());
const communityCounts = toMap(item.communityCounts, new Map<string, number>());
const communityFilteredCids = toMap(item.communityFilteredCids, new Map<string, Set<string>>());
return {
text: item.text || '',
@@ -79,8 +66,6 @@ const normalizeFilterItem = (item: RawFilterItem): FilterItem => {
filteredCids: item.filteredCids || new Set<string>(),
communityCounts,
communityFilteredCids,
subplebbitCounts: communityCounts,
subplebbitFilteredCids: communityFilteredCids,
hide: item.hide ?? true,
top: item.top ?? false,
color: item.color || '',
@@ -96,7 +81,6 @@ const useCatalogFiltersStore = create(
filteredCount: 0,
filteredCids: new Set<string>(),
currentCommunityAddress: null,
currentSubplebbitAddress: null,
matchedFilters: new Map<string, string>(),
setMatchedFilter: (cid: string, color: string) => {
set((state) => {
@@ -132,10 +116,6 @@ const useCatalogFiltersStore = create(
newItem.communityFilteredCids = new Map();
}
// keep legacy aliases in sync
newItem.subplebbitCounts = newItem.communityCounts;
newItem.subplebbitFilteredCids = newItem.communityFilteredCids;
if (!newItem.communityFilteredCids.has(address)) {
newItem.communityFilteredCids.set(address, new Set<string>());
}
@@ -148,7 +128,6 @@ const useCatalogFiltersStore = create(
return {
currentCommunityAddress: address,
currentSubplebbitAddress: address,
filterItems: updatedFilterItems,
filteredCount: 0, // This will be recalculated below
};
@@ -156,7 +135,6 @@ const useCatalogFiltersStore = create(
return {
currentCommunityAddress: address,
currentSubplebbitAddress: address,
};
});
@@ -164,10 +142,9 @@ const useCatalogFiltersStore = create(
get().recalcFilteredCount();
get().updateFilter();
} else {
set({ currentCommunityAddress: address, currentSubplebbitAddress: address });
set({ currentCommunityAddress: address });
}
},
setCurrentSubplebbitAddress: (address: string | null) => get().setCurrentCommunityAddress(address),
searchText: '',
setSearchFilter: (text: string) => {
set({ searchText: text });
@@ -199,8 +176,6 @@ const useCatalogFiltersStore = create(
filteredCids: existingItem.filteredCids,
communityCounts: existingItem.communityCounts,
communityFilteredCids: existingItem.communityFilteredCids,
subplebbitCounts: existingItem.communityCounts,
subplebbitFilteredCids: existingItem.communityFilteredCids,
color: newItem.color || '',
};
}
@@ -212,8 +187,6 @@ const useCatalogFiltersStore = create(
filteredCids: new Set<string>(),
communityCounts: new Map<string, number>(),
communityFilteredCids: new Map<string, Set<string>>(),
subplebbitCounts: new Map<string, number>(),
subplebbitFilteredCids: new Map<string, Set<string>>(),
color: newItem.color || '',
};
});
@@ -309,8 +282,6 @@ const useCatalogFiltersStore = create(
filteredCids: newItemFilteredCids,
communityCounts,
communityFilteredCids,
subplebbitCounts: communityCounts,
subplebbitFilteredCids: communityFilteredCids,
};
return { filterItems: newFilterItems };
@@ -352,7 +323,6 @@ const useCatalogFiltersStore = create(
return filteredCount;
},
getFilteredCountForCurrentSubplebbit: () => get().getFilteredCountForCurrentCommunity(),
resetCountsForCurrentCommunity: () => {
const currentCommunityAddress = get().currentCommunityAddress;
if (!currentCommunityAddress) return;
@@ -369,10 +339,6 @@ const useCatalogFiltersStore = create(
newItem.communityFilteredCids = new Map();
}
// keep legacy aliases in sync
newItem.subplebbitCounts = newItem.communityCounts;
newItem.subplebbitFilteredCids = newItem.communityFilteredCids;
// Reset counts for current community
newItem.communityCounts.set(currentCommunityAddress, 0);
newItem.communityFilteredCids.set(currentCommunityAddress, new Set<string>());
@@ -389,7 +355,6 @@ const useCatalogFiltersStore = create(
// Trigger filter reapplication to start counting again
get().updateFilter();
},
resetCountsForCurrentSubplebbit: () => get().resetCountsForCurrentCommunity(),
}),
{
name: 'catalog-filters-storage',
@@ -405,17 +370,23 @@ const useCatalogFiltersStore = create(
},
deserialize: (persisted) => {
const persistedObj = typeof persisted === 'string' ? JSON.parse(persisted) : persisted;
const persistedState =
persistedObj && typeof persistedObj === 'object' && 'state' in persistedObj ? (persistedObj as { state?: CatalogFiltersStore }).state : persistedObj;
if (persistedObj && persistedObj.filterItems) {
return {
...persistedObj,
currentCommunityAddress: persistedObj.currentCommunityAddress || persistedObj.currentSubplebbitAddress || null,
currentSubplebbitAddress: persistedObj.currentCommunityAddress || persistedObj.currentSubplebbitAddress || null,
filterItems: persistedObj.filterItems.map((item: RawFilterItem) => ({
if (persistedState && typeof persistedState === 'object' && 'filterItems' in persistedState) {
const migratedState = {
...persistedState,
filterItems: persistedState.filterItems.map((item: RawFilterItem) => ({
...normalizeFilterItem(item),
})),
filteredCount: 0,
};
if (persistedObj && typeof persistedObj === 'object' && 'state' in persistedObj) {
return { ...persistedObj, state: migratedState };
}
return migratedState;
}
return persistedObj || persisted;
@@ -41,6 +41,4 @@ const useCommunitiesLoadingStartTimestamps = (communityAddresses?: string[]) =>
return communitiesLoadingStartTimestamps;
};
export const useSubplebbitsLoadingStartTimestamps = useCommunitiesLoadingStartTimestamps;
export default useCommunitiesLoadingStartTimestamps;
@@ -49,10 +49,4 @@ const useCommunityOfflineStore = create<CommunityOfflineStore>((set) => ({
},
}));
/**
* Back-compat exports for old naming.
*/
export const useSubplebbitOfflineStore = useCommunityOfflineStore;
export const useCommunityOfflineStoreForLegacy = useCommunityOfflineStore;
export default useCommunityOfflineStore;
+2 -1
View File
@@ -1,6 +1,7 @@
import { create } from 'zustand';
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
import { normalizeBoardAddress } from '../hooks/use-directories';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
interface PostNumberState {
// Post numbers are only unique within a board, so scope by canonical community address.
@@ -55,7 +56,7 @@ const usePostNumberStore = create<PostNumberState>((set) => ({
for (const c of comments) {
const num = c?.number;
const cid = c?.cid;
const addr = c?.communityAddress || c?.subplebbitAddress;
const addr = getCommentCommunityAddress(c);
if (typeof num !== 'number' || !cid || !addr) continue;
const existingCid = nextNumberToCid[addr]?.[num];
+3 -6
View File
@@ -1,12 +1,12 @@
import { ChallengeVerification, Comment, PublishCommentOptions } from '@bitsocialnet/bitsocial-react-hooks';
import { create } from 'zustand';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
type SubmitState = {
author?: any | undefined;
displayName?: string | undefined;
communityAddress: string | undefined;
subplebbitAddress: string | undefined;
title: string | undefined;
content: string | undefined;
link: string | undefined;
@@ -20,7 +20,6 @@ const usePublishPostStore = create<SubmitState>((set) => ({
author: undefined,
displayName: undefined,
communityAddress: undefined,
subplebbitAddress: undefined,
title: undefined,
content: undefined,
link: undefined,
@@ -28,8 +27,8 @@ const usePublishPostStore = create<SubmitState>((set) => ({
publishCommentOptions: {},
setPublishPostStore: (comment: Comment) =>
set(() => {
const { subplebbitAddress, author, content, link, spoiler, title } = comment;
const communityAddress = (comment as { communityAddress?: string }).communityAddress || subplebbitAddress;
const { author, content, link, spoiler, title } = comment;
const communityAddress = getCommentCommunityAddress(comment);
const displayName = 'displayName' in comment ? comment.displayName || undefined : author?.displayName;
@@ -61,7 +60,6 @@ const usePublishPostStore = create<SubmitState>((set) => ({
author: updatedAuthor,
displayName,
communityAddress,
subplebbitAddress: communityAddress,
title,
content,
link,
@@ -73,7 +71,6 @@ const usePublishPostStore = create<SubmitState>((set) => ({
set({
author: undefined,
displayName: undefined,
subplebbitAddress: undefined,
communityAddress: undefined,
title: undefined,
content: undefined,
+2 -1
View File
@@ -1,6 +1,7 @@
import { ChallengeVerification, Comment, PublishCommentOptions } from '@bitsocialnet/bitsocial-react-hooks';
import { create } from 'zustand';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
type ReplyState = {
author: { [parentCid: string]: any | undefined };
@@ -24,7 +25,7 @@ const usePublishReplyStore = create<ReplyState>((set) => ({
setPublishReplyStore: (comment: Comment) =>
set((state) => {
const { parentCid, author, content, link, spoiler } = comment;
const communityAddress = (comment as { communityAddress?: string }).communityAddress || (comment as { subplebbitAddress?: string }).subplebbitAddress;
const communityAddress = getCommentCommunityAddress(comment);
const displayName = 'displayName' in comment ? comment.displayName || undefined : author?.displayName;
+8 -8
View File
@@ -9,15 +9,15 @@ interface ReplyModalState {
parentNumber: number | null;
threadNumber: number | null;
threadCid: string | null;
subplebbitAddress: string | null;
communityAddress: string | null;
scrollY: number;
quoteInsertRequestId: number;
quoteInsertNumber: number | null;
quoteInsertSelectedText: string | null;
closeModal: () => void;
openReplyModal: (parentCid: string, parentNumber: number | undefined, postCid: string, threadNumber: number | undefined, subplebbitAddress: string) => void;
openReplyModal: (parentCid: string, parentNumber: number | undefined, postCid: string, threadNumber: number | undefined, communityAddress: string) => void;
/** Open reply modal with empty textarea, no prefilled quote. Use for "Post a Reply" footer button. */
openReplyModalEmpty: (postCid: string, threadNumber: number | undefined, subplebbitAddress: string) => void;
openReplyModalEmpty: (postCid: string, threadNumber: number | undefined, communityAddress: string) => void;
}
const getQuotedSelection = () => {
@@ -42,7 +42,7 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
parentNumber: null,
threadNumber: null,
threadCid: null,
subplebbitAddress: null,
communityAddress: null,
scrollY: 0,
quoteInsertRequestId: 0,
quoteInsertNumber: null,
@@ -62,7 +62,7 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
});
},
openReplyModal: (parentCid, parentNumber, postCid, threadNumber, subplebbitAddress) => {
openReplyModal: (parentCid, parentNumber, postCid, threadNumber, communityAddress) => {
const quotedSelection = getQuotedSelection();
// If the reply modal is already open, insert this quote in the current textarea at caret.
@@ -90,12 +90,12 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
threadNumber: threadNumber ?? null,
threadCid: postCid,
showReplyModal: true,
subplebbitAddress,
communityAddress,
scrollY,
});
},
openReplyModalEmpty: (postCid, threadNumber, subplebbitAddress) => {
openReplyModalEmpty: (postCid, threadNumber, communityAddress) => {
useSelectedTextStore.getState().resetSelectedText();
const isMobile = window.innerWidth <= 768;
const scrollY = isMobile ? window.scrollY : 0;
@@ -106,7 +106,7 @@ const useReplyModalStore = create<ReplyModalState>((set, get) => ({
threadNumber: threadNumber ?? null,
threadCid: postCid,
showReplyModal: true,
subplebbitAddress,
communityAddress,
scrollY,
quoteInsertNumber: null,
quoteInsertSelectedText: null,
@@ -1,39 +0,0 @@
import useCommunityOfflineStore from './use-community-offline-store';
type LegacySubplebbitOfflineState = {
initialLoad: boolean;
state?: string;
updatedAt?: number;
updatingState?: string;
};
type LegacySubplebbitOfflineStore = {
subplebbitOfflineState: Record<string, LegacySubplebbitOfflineState>;
setSubplebbitOfflineState: (address: string, state: Partial<LegacySubplebbitOfflineState>) => void;
initializesubplebbitOfflineState: (address: string) => void;
};
type CommunityOfflineStoreState = {
communityOfflineState: Record<string, LegacySubplebbitOfflineState>;
setCommunityOfflineState: (address: string, state: Partial<LegacySubplebbitOfflineState>) => void;
initializeCommunityOfflineState: (address: string) => void;
};
const toLegacyState = (state: CommunityOfflineStoreState) => ({
subplebbitOfflineState: state.communityOfflineState,
setSubplebbitOfflineState: state.setCommunityOfflineState,
initializesubplebbitOfflineState: state.initializeCommunityOfflineState,
});
const useSubplebbitOfflineStore = (): LegacySubplebbitOfflineStore => {
const state = useCommunityOfflineStore();
return toLegacyState(state);
};
const useSubplebbitOfflineStoreWithState = useSubplebbitOfflineStore as typeof useSubplebbitOfflineStore & {
getState: () => LegacySubplebbitOfflineStore;
};
useSubplebbitOfflineStoreWithState.getState = () => toLegacyState(useCommunityOfflineStore.getState());
export { useSubplebbitOfflineStore };
export default useSubplebbitOfflineStoreWithState;
@@ -1,8 +0,0 @@
import useSubplebbitStore from './use-communities-loading-start-timestamps-store';
const useSubplebbitLoadingStartTimestamps = (subplebbitAddresses?: string[]) => {
const addLegacyInput = subplebbitAddresses?.map((address) => address);
return useSubplebbitStore(addLegacyInput);
};
export default useSubplebbitLoadingStartTimestamps;
+7 -7
View File
@@ -23,8 +23,8 @@ const testState = vi.hoisted(() => ({
hasMore: false,
isMobile: false,
loadMoreMock: vi.fn(),
resolvedSubplebbitAddress: 'music-posting.eth' as string | undefined,
subplebbit: {
resolvedCommunityAddress: 'music-posting.eth' as string | undefined,
community: {
error: undefined as Error | undefined,
title: '/mu/ - Music',
},
@@ -53,7 +53,7 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
loadMore: testState.loadMoreMock,
reset: vi.fn(),
}),
useCommunity: () => testState.subplebbit,
useCommunity: () => testState.community,
}));
vi.mock('../../../hooks/use-directories', () => ({
@@ -62,8 +62,8 @@ vi.mock('../../../hooks/use-directories', () => ({
directories.find((entry) => entry.address === address || entry.directoryCode === address || entry.title === address),
}));
vi.mock('../../../hooks/use-resolved-subplebbit-address', () => ({
useResolvedSubplebbitAddress: () => testState.resolvedSubplebbitAddress,
vi.mock('../../../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedCommunityAddress,
}));
vi.mock('../../../hooks/use-state-string', () => ({
@@ -71,7 +71,7 @@ vi.mock('../../../hooks/use-state-string', () => ({
}));
vi.mock('../../../hooks/use-stable-community', () => ({
useCommunityField: (_address: string | undefined, selector: (value: typeof testState.subplebbit) => unknown) => selector(testState.subplebbit),
useCommunityField: (_address: string | undefined, selector: (value: typeof testState.community) => unknown) => selector(testState.community),
}));
vi.mock('../../../hooks/use-is-mobile', () => ({
@@ -126,7 +126,7 @@ describe('Archive', () => {
testState.feed = [];
testState.hasMore = false;
testState.isMobile = false;
testState.subplebbit = {
testState.community = {
error: undefined,
title: '/mu/ - Music',
};
+31 -31
View File
@@ -7,10 +7,10 @@ import { BottomButton, CatalogButton, ReturnButton, TopButton } from '../../comp
import ErrorDisplay from '../../components/error-display/error-display';
import { PageFooterDesktop, PageFooterMobile, ThreadFooterStyleRow } from '../../components/footer';
import LoadingEllipsis from '../../components/loading-ellipsis';
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { useCommunityField } from '../../hooks/use-stable-community';
import { useFeedStateString } from '../../hooks/use-state-string';
import { getSubplebbitAddress, getBoardPath } from '../../lib/utils/route-utils';
import { getCommunityAddress, getBoardPath } from '../../lib/utils/route-utils';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { removeMarkdown } from '../../lib/utils/post-utils';
import { useDirectories } from '../../hooks/use-directories';
@@ -101,13 +101,13 @@ const ArchiveFooter = ({ hasMore, loadingState, onLoadMore }: { hasMore: boolean
);
};
const ArchiveDesktopTopControls = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => (
const ArchiveDesktopTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
<div className={styles.desktopNavLinks}>
<span>
[<ReturnButton address={subplebbitAddress} />]
[<ReturnButton address={communityAddress} />]
</span>
<span>
[<CatalogButton address={subplebbitAddress} />]
[<CatalogButton address={communityAddress} />]
</span>
<span>
[<BottomButton />]
@@ -115,13 +115,13 @@ const ArchiveDesktopTopControls = ({ subplebbitAddress }: { subplebbitAddress: s
</div>
);
const ArchiveDesktopFooterControls = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => (
const ArchiveDesktopFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
<div className={styles.desktopFooterButtons}>
<span>
[<ReturnButton address={subplebbitAddress} />]
[<ReturnButton address={communityAddress} />]
</span>
<span>
[<CatalogButton address={subplebbitAddress} />]
[<CatalogButton address={communityAddress} />]
</span>
<span>
[<TopButton />]
@@ -129,18 +129,18 @@ const ArchiveDesktopFooterControls = ({ subplebbitAddress }: { subplebbitAddress
</div>
);
const ArchiveMobileTopControls = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => (
const ArchiveMobileTopControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
<div className={styles.mobileNavLinks}>
<ReturnButton address={subplebbitAddress} />
<CatalogButton address={subplebbitAddress} />
<ReturnButton address={communityAddress} />
<CatalogButton address={communityAddress} />
<BottomButton />
</div>
);
const ArchiveMobileFooterControls = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => (
const ArchiveMobileFooterControls = ({ communityAddress }: { communityAddress: string | undefined }) => (
<div className={styles.mobileFooterButtons}>
<ReturnButton address={subplebbitAddress} />
<CatalogButton address={subplebbitAddress} />
<ReturnButton address={communityAddress} />
<CatalogButton address={communityAddress} />
<TopButton />
</div>
);
@@ -151,22 +151,22 @@ const Archive = () => {
const boardIdentifier = params.boardIdentifier;
const directories = useDirectories();
const resolvedAddressFromUrl = useResolvedSubplebbitAddress();
const subplebbitAddress = useMemo(() => {
const resolvedAddressFromUrl = useResolvedCommunityAddress();
const communityAddress = useMemo(() => {
if (boardIdentifier) {
return getSubplebbitAddress(boardIdentifier, directories);
return getCommunityAddress(boardIdentifier, directories);
}
return resolvedAddressFromUrl;
}, [boardIdentifier, directories, resolvedAddressFromUrl]);
const boardPath = useMemo(() => {
if (!subplebbitAddress) {
if (!communityAddress) {
return boardIdentifier;
}
return getBoardPath(subplebbitAddress, directories);
}, [boardIdentifier, directories, subplebbitAddress]);
return getBoardPath(communityAddress, directories);
}, [boardIdentifier, directories, communityAddress]);
const boardTitle = useCommunityField(subplebbitAddress, (community) => community?.title) || `/${boardIdentifier || subplebbitAddress || t('archive')}/`;
const boardTitle = useCommunityField(communityAddress, (community) => community?.title) || `/${boardIdentifier || communityAddress || t('archive')}/`;
const archiveFilter = useMemo(
() => ({
@@ -176,9 +176,9 @@ const Archive = () => {
[],
);
const communityAddresses = useMemo(() => (subplebbitAddress ? [subplebbitAddress] : []), [subplebbitAddress]);
const communityAddresses = useMemo(() => (communityAddress ? [communityAddress] : []), [communityAddress]);
const communities = useCommunityIdentifiers(communityAddresses);
const communityIdentifier = useCommunityIdentifier(subplebbitAddress);
const communityIdentifier = useCommunityIdentifier(communityAddress);
const feedOptions = useMemo(
() => ({
@@ -212,14 +212,14 @@ const Archive = () => {
if (isLoading) {
return (
<div id='top' className={`${styles.page} ${shouldShowSnow() ? styles.garland : ''}`}>
<ArchiveMobileTopControls subplebbitAddress={subplebbitAddress} />
<ArchiveMobileTopControls communityAddress={communityAddress} />
<hr className={styles.desktopDivider} />
<ArchiveDesktopTopControls subplebbitAddress={subplebbitAddress} />
<ArchiveDesktopTopControls communityAddress={communityAddress} />
<hr className={styles.divider} />
<h4 className={styles.archiveSummary}>{t('loading_archive')}</h4>
<PageFooterDesktop firstRow={<ArchiveDesktopFooterControls subplebbitAddress={subplebbitAddress} />} styleRow={<ThreadFooterStyleRow />} />
<PageFooterDesktop firstRow={<ArchiveDesktopFooterControls communityAddress={communityAddress} />} styleRow={<ThreadFooterStyleRow />} />
<PageFooterMobile>
<ArchiveMobileFooterControls subplebbitAddress={subplebbitAddress} />
<ArchiveMobileFooterControls communityAddress={communityAddress} />
</PageFooterMobile>
</div>
);
@@ -227,9 +227,9 @@ const Archive = () => {
return (
<div id='top' className={`${styles.page} ${shouldShowSnow() ? styles.garland : ''}`}>
<ArchiveMobileTopControls subplebbitAddress={subplebbitAddress} />
<ArchiveMobileTopControls communityAddress={communityAddress} />
<hr className={styles.desktopDivider} />
<ArchiveDesktopTopControls subplebbitAddress={subplebbitAddress} />
<ArchiveDesktopTopControls communityAddress={communityAddress} />
<hr className={styles.divider} />
<h4 className={styles.archiveSummary}>{summaryText}</h4>
@@ -290,9 +290,9 @@ const Archive = () => {
<ArchiveFooter hasMore={hasMore} loadingState={loadingState} onLoadMore={loadMore} />
<PageFooterDesktop firstRow={<ArchiveDesktopFooterControls subplebbitAddress={subplebbitAddress} />} styleRow={<ThreadFooterStyleRow />} />
<PageFooterDesktop firstRow={<ArchiveDesktopFooterControls communityAddress={communityAddress} />} styleRow={<ThreadFooterStyleRow />} />
<PageFooterMobile>
<ArchiveMobileFooterControls subplebbitAddress={subplebbitAddress} />
<ArchiveMobileFooterControls communityAddress={communityAddress} />
</PageFooterMobile>
</div>
);
+1 -3
View File
@@ -75,9 +75,7 @@ const getScopedAccountComments = (options?: { commentIndices?: number[]; communi
const normalizedCommentIndices = options.commentIndices.filter((commentIndex) => Number.isInteger(commentIndex) && commentIndex >= 0);
scopedComments = normalizedCommentIndices.map((commentIndex) => testState.accountComments[commentIndex]).filter(Boolean) as TestComment[];
} else if (options?.communityAddress) {
scopedComments = scopedComments.filter(
(comment) => (comment.communityAddress || (comment as TestComment & { subplebbitAddress?: string }).subplebbitAddress) === options.communityAddress,
);
scopedComments = scopedComments.filter((comment) => comment.communityAddress === options.communityAddress);
}
if (typeof options?.newerThan === 'number') {
+4 -3
View File
@@ -19,8 +19,9 @@ import usePostNumberStore from '../../stores/use-post-number-store';
import { useBoardFeedPageSize } from '../../hooks/use-board-feed-page-size';
import useIsMobile from '../../hooks/use-is-mobile';
import { getPageSlice } from '../../lib/utils/board-feed-pagination';
import { getPageFromFeedPath, getSubplebbitAddress, isDirectoryBoard, normalizeMultiboardFeedPath, stripPageFromFeedPath } from '../../lib/utils/route-utils';
import { getPageFromFeedPath, getCommunityAddress, isDirectoryBoard, normalizeMultiboardFeedPath, stripPageFromFeedPath } from '../../lib/utils/route-utils';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getPretextItemSizeFromElement, resolveFeedVirtualizationMode } from '../../lib/utils/pretext-height-estimates';
import ErrorDisplay from '../../components/error-display/error-display';
import LoadingEllipsis from '../../components/loading-ellipsis';
@@ -114,7 +115,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, i
const resolvedAddressFromUrl = useResolvedCommunityAddress();
const communityAddress = useMemo(() => {
if (boardIdentifierProp) {
return getSubplebbitAddress(boardIdentifierProp, directories);
return getCommunityAddress(boardIdentifierProp, directories);
}
return resolvedAddressFromUrl;
}, [boardIdentifierProp, directories, resolvedAddressFromUrl]);
@@ -200,7 +201,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, i
() =>
recentAccountComments.filter((comment) => {
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
const commentCommunityAddress = comment?.communityAddress || comment?.subplebbitAddress;
const commentCommunityAddress = getCommentCommunityAddress(comment);
return (
!deleted &&
!removed &&
+2 -4
View File
@@ -77,7 +77,7 @@ function getCatalogFiltersState() {
filterItems: testState.filterItems,
incrementFilterCount: testState.incrementFilterCountMock,
searchText: testState.searchText,
setCurrentSubplebbitAddress: testState.setCurrentCommunityAddressMock,
setCurrentCommunityAddress: testState.setCurrentCommunityAddressMock,
};
}
@@ -101,9 +101,7 @@ const getScopedAccountComments = (options?: { commentIndices?: number[]; communi
const normalizedCommentIndices = options.commentIndices.filter((commentIndex) => Number.isInteger(commentIndex) && commentIndex >= 0);
scopedComments = normalizedCommentIndices.map((commentIndex) => testState.accountComments[commentIndex]).filter(Boolean) as TestComment[];
} else if (options?.communityAddress) {
scopedComments = scopedComments.filter(
(comment) => (comment.communityAddress || (comment as TestComment & { subplebbitAddress?: string }).subplebbitAddress) === options.communityAddress,
);
scopedComments = scopedComments.filter((comment) => comment.communityAddress === options.communityAddress);
}
if (typeof options?.newerThan === 'number') {
+6 -5
View File
@@ -15,7 +15,7 @@ import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { getSubplebbitAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils';
import { getCommunityAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils';
import CatalogRow from '../../components/catalog-row';
import { CatalogFooterFirstRow, PageFooterDesktop, PageFooterMobile } from '../../components/footer';
import { ReturnButton, ArchiveButton, TopButton, RefreshButton } from '../../components/board-buttons/board-buttons';
@@ -26,6 +26,7 @@ import styles from './catalog.module.css';
import { commentMatchesPattern } from '../../lib/utils/pattern-utils';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { sortCatalogFeedForDisplay } from '../../lib/utils/catalog-sort';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import {
getCatalogRowHeightEstimates,
getPretextItemSizeFromElement,
@@ -212,7 +213,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const resolvedAddressFromUrl = useResolvedCommunityAddress();
const communityAddress = useMemo(() => {
if (boardIdentifierProp) {
return getSubplebbitAddress(boardIdentifierProp, directories);
return getCommunityAddress(boardIdentifierProp, directories);
}
return resolvedAddressFromUrl;
}, [boardIdentifierProp, directories, resolvedAddressFromUrl]);
@@ -267,9 +268,9 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
// Set the current community address
useEffect(() => {
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress(communityAddress || null);
useCatalogFiltersStore.getState().setCurrentCommunityAddress(communityAddress || null);
return () => {
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress(null);
useCatalogFiltersStore.getState().setCurrentCommunityAddress(null);
};
}, [communityAddress]);
@@ -304,7 +305,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
() =>
recentAccountComments.filter((comment) => {
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
const commentCommunityAddress = comment?.communityAddress || comment?.subplebbitAddress;
const commentCommunityAddress = getCommentCommunityAddress(comment);
// Basic filtering conditions
const basicConditions =
@@ -12,6 +12,7 @@ import LoadingEllipsis from '../../../components/loading-ellipsis';
import BoxModal from '../box-modal';
import { DirectoryCommunity, findDirectoryByAddress } from '../../../hooks/use-directories';
import { useCommunityIdentifiers } from '../../../hooks/use-community-identifiers';
import { getCommentCommunityAddress } from '../../../lib/utils/comment-utils';
import { getBoardPath } from '../../../lib/utils/route-utils';
import { removeMarkdown } from '../../../lib/utils/post-utils';
@@ -59,7 +60,6 @@ const PopularThreadCard = memo(
const PopularThreadsBox = ({ directories, directoryAddresses }: { directories: DirectoryCommunity[]; directoryAddresses: string[] }) => {
const { t } = useTranslation();
const { showWorksafeContentOnly, showNsfwContentOnly } = usePopularThreadsOptionsStore();
const getCommentCommunityAddress = (post: Comment) => post.communityAddress || post.subplebbitAddress;
const directoryCommunities = useCommunityIdentifiers(directoryAddresses);
const { communities } = useCommunities({ communities: directoryCommunities });
+7 -19
View File
@@ -8,7 +8,7 @@ import useModQueueStore from '../../stores/use-mod-queue-store';
import LoadingEllipsis from '../../components/loading-ellipsis';
import ErrorDisplay from '../../components/error-display/error-display';
import { useFeedStateString } from '../../hooks/use-state-string';
import { getSubplebbitAddress, getBoardPath, extractDirectoryFromTitle, areSameBoardAddress } from '../../lib/utils/route-utils';
import { getCommunityAddress, getBoardPath, extractDirectoryFromTitle, areSameBoardAddress } from '../../lib/utils/route-utils';
import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories';
import getShortAddress from '../../lib/get-short-address';
import { BOARD_CODE_GROUPS } from '../../constants/board-codes';
@@ -23,6 +23,7 @@ import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-util
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useChallengesStore from '../../stores/use-challenges-store';
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import Tooltip from '../../components/tooltip';
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
import { useCommunityIdentifier, useCommunityIdentifiers } from '../../hooks/use-community-identifiers';
@@ -44,8 +45,6 @@ const getBoardDisplayPath = (address: string, path: string): string => {
return getShortAddress(address) || address;
};
const getCommentCommunityAddress = (comment: Comment) => comment?.communityAddress || comment?.subplebbitAddress;
interface ModQueueViewProps {
boardIdentifier?: string; // If provided, shows queue for single board
}
@@ -159,7 +158,7 @@ const ModQueueActions = ({ status, errorMessage, isPublishing, handleApprove, ha
const useModQueueActions = (comment: Comment): ModQueueActionState => {
const { t } = useTranslation();
const { cid, approved, removed, pendingApproval } = comment || {};
const communityAddress = comment?.communityAddress || comment?.subplebbitAddress;
const communityAddress = getCommentCommunityAddress(comment);
const [initiatedAction, setInitiatedAction] = useState<ModerationAction>(null);
const alreadyApproved = approved === true;
@@ -256,14 +255,7 @@ const ModQueueRow = memo(({ comment, isOdd = false, showBoard = false, boardPath
const { editedComment } = useEditedComment({ comment });
const displayComment = editedComment || comment;
const { content, title, timestamp, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, pendingApproval, number, parentCid } = displayComment;
const commentCommunityAddress = getCommentCommunityAddress(displayComment);
// Check if already moderated (from previous session or API update)
// Note: `approved` and `removed` are direct fields on the comment from CommentUpdate,
// not nested under commentModeration (which is the options object for publishing moderation actions)
const alreadyApproved = approved === true;
const alreadyRejected = isPendingApprovalRejected({ approved, removed, pendingApproval });
const { content, title, timestamp, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, number, parentCid } = displayComment;
const timeWaiting = currentTime - timestamp;
const alertThresholdSeconds = getAlertThresholdSeconds();
@@ -368,11 +360,7 @@ const ModQueueCard = memo(({ comment, showBoard = false, boardPath, boardDisplay
const { editedComment } = useEditedComment({ comment });
const displayComment = editedComment || comment;
const { content, title, timestamp, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, pendingApproval, number, parentCid } = displayComment;
const commentCommunityAddress = getCommentCommunityAddress(displayComment);
const alreadyApproved = approved === true;
const alreadyRejected = isPendingApprovalRejected({ approved, removed, pendingApproval });
const { content, title, timestamp, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, number, parentCid } = displayComment;
const timeWaiting = currentTime - timestamp;
const alertThresholdSeconds = getAlertThresholdSeconds();
@@ -725,7 +713,7 @@ export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProp
const resolvedAddress = useMemo(() => {
if (boardIdentifier) {
return getSubplebbitAddress(boardIdentifier, directories);
return getCommunityAddress(boardIdentifier, directories);
}
return undefined;
}, [boardIdentifier, directories]);
@@ -796,7 +784,7 @@ const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueViewProp
const resolvedAddress = useMemo(() => {
if (boardIdentifier) {
return getSubplebbitAddress(boardIdentifier, directories);
return getCommunityAddress(boardIdentifier, directories);
}
return undefined;
}, [boardIdentifier, directories]);
@@ -39,7 +39,7 @@ vi.mock('../../../hooks/use-directories', () => ({
}));
vi.mock('../../../lib/utils/route-utils', () => ({
getSubplebbitAddress: () => testState.communityAddress,
getCommunityAddress: () => testState.communityAddress,
}));
vi.mock('../../home', () => ({
+2 -2
View File
@@ -1,7 +1,7 @@
import { Link, useLocation } from 'react-router-dom';
import { useCommunityField } from '../../hooks/use-stable-community';
import { useDirectories } from '../../hooks/use-directories';
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
import { getCommunityAddress } from '../../lib/utils/route-utils';
import { HomeLogo } from '../home';
import NotFoundImage from '../../components/not-found-image';
import styles from './not-found.module.css';
@@ -12,7 +12,7 @@ const NotFound = () => {
const pathParts = location.pathname.split('/').filter(Boolean);
const boardIdentifier = pathParts[0] && pathParts[0] !== 'not-found' && pathParts[0] !== 'faq' ? pathParts[0] : '';
const directories = useDirectories();
const communityAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, directories) : '';
const communityAddress = boardIdentifier ? getCommunityAddress(boardIdentifier, directories) : '';
// Only subscribe to address and shortAddress to avoid rerenders from updatingState changes
const address = useCommunityField(communityAddress, (community) => community?.address);
const shortAddress = useCommunityField(communityAddress, (community) => community?.shortAddress);
+2 -1
View File
@@ -3,6 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { useAccountComments } from '@bitsocialnet/bitsocial-react-hooks';
import { useDirectories } from '../../hooks/use-directories';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getBoardPath } from '../../lib/utils/route-utils';
import { Post } from '../post';
@@ -31,7 +32,7 @@ const PendingPost = () => {
}, [isValidAccountCommentIndex, navigate]);
useEffect(() => {
const postCommunityAddress = post?.communityAddress || post?.subplebbitAddress;
const postCommunityAddress = getCommentCommunityAddress(post);
if (post?.cid && postCommunityAddress) {
const boardPath = getBoardPath(postCommunityAddress, directories);
navigate(`/${boardPath}/thread/${post.cid}`, { replace: true });
+3 -4
View File
@@ -25,7 +25,6 @@ type TestComment = {
replies?: unknown[];
state?: string;
communityAddress?: string;
subplebbitAddress?: string;
timestamp?: number;
title?: string;
};
@@ -322,7 +321,7 @@ describe('Post', () => {
},
number: 777,
replyCount: 0,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'Archived thread',
},
};
@@ -380,7 +379,7 @@ describe('Post', () => {
'legacy-cid': {
cid: 'legacy-cid',
state: 'updating',
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
},
};
testState.cachedComments = {
@@ -389,7 +388,7 @@ describe('Post', () => {
content: 'cached body',
number: 7,
replyCount: 0,
subplebbitAddress: 'music-posting.eth',
communityAddress: 'music-posting.eth',
title: 'Legacy thread',
},
};
+1 -1
View File
@@ -95,7 +95,7 @@ export const Post = memo(
replyVirtualizationModeOverride,
}: PostProps) => {
// Only subscribe to roles field to avoid rerenders from updatingState changes
const communityAddress = post?.communityAddress || post?.subplebbitAddress;
const communityAddress = getCommentCommunityAddress(post);
const roles = useCommunityField(communityAddress, (community) => community?.roles);
const isMobile = useIsMobile();
+2 -2
View File
@@ -4,7 +4,7 @@ import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
import { Footer, HomeLogo } from '../home';
import { useDirectories, DirectoryCommunity, findDirectoryByAddress } from '../../hooks/use-directories';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import { getSubplebbitAddress, getBoardPath } from '../../lib/utils/route-utils';
import { getCommunityAddress, getBoardPath } from '../../lib/utils/route-utils';
import Markdown from '../../components/markdown';
import styles from './rules.module.css';
import { useTranslation } from 'react-i18next';
@@ -171,7 +171,7 @@ const Rules = () => {
const navigate = useNavigate();
const directories = useDirectories();
const selectedAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, directories) : '';
const selectedAddress = boardIdentifier ? getCommunityAddress(boardIdentifier, directories) : '';
const handleBoardSelect = (address: string) => {
const path = getBoardPath(address, directories);