mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor(core): remove legacy plebbit terminology
This commit is contained in:
+22
-23
@@ -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
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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 voting—communities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
|
||||
directory'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 voting—communities vote, highest-voted board wins the slot. <strong>Voting pages = discovery:</strong> Each
|
||||
directory'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 () => {
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
+4
-4
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+14
-14
@@ -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,
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+5
-5
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import useIsCommunityOffline from './use-is-community-offline';
|
||||
|
||||
export { useIsCommunityOffline as useIsSubplebbitOffline };
|
||||
export default useIsCommunityOffline;
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
@@ -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 +0,0 @@
|
||||
export { useStableSubplebbit, useSubplebbitField } from './use-stable-community';
|
||||
@@ -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 +0,0 @@
|
||||
export { SubplebbitStatsCollector, useSubplebbitsStatsStore } from './use-communities-stats';
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 [];
|
||||
};
|
||||
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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', () => ({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user