mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor: migrate 5chan to the community hooks API (#1073)
* refactor(community-api): migrate 5chan to community hooks * fix(review): address PR feedback * fix(review): preserve legacy board context fallbacks * fix(review): address latest bot feedback * fix(review): use communityAddress in edit menu privileges
This commit is contained in:
@@ -2,13 +2,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
consoleErrorMock: vi.fn(),
|
||||
subplebbits: {} as Record<string, { roles?: Record<string, { role?: string }> }>,
|
||||
communities: {} as Record<string, { roles?: Record<string, { role?: string }> }>,
|
||||
}));
|
||||
|
||||
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/stores/subplebbits', () => ({
|
||||
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/stores/communities', () => ({
|
||||
default: {
|
||||
getState: () => ({
|
||||
subplebbits: testState.subplebbits,
|
||||
communities: testState.communities,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
@@ -20,7 +20,7 @@ describe('pattern-utils', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
testState.subplebbits = {
|
||||
testState.communities = {
|
||||
'music-posting.eth': {
|
||||
roles: {
|
||||
'author-1': { role: 'moderator' },
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
type CommentWithLegacyCommunityAddress = {
|
||||
communityAddress?: string;
|
||||
replies?: {
|
||||
pages?: Record<
|
||||
string,
|
||||
| {
|
||||
comments?: Array<CommentWithLegacyCommunityAddress | undefined>;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
};
|
||||
subplebbitAddress?: string;
|
||||
};
|
||||
|
||||
export const getCommentCommunityAddress = (comment?: unknown) => {
|
||||
if (!comment || typeof comment !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const record = comment as { communityAddress?: unknown; subplebbitAddress?: 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']) => {
|
||||
if (!replies?.pages) {
|
||||
return replies;
|
||||
}
|
||||
|
||||
let nextPages = replies.pages;
|
||||
let pagesChanged = false;
|
||||
|
||||
for (const [sortType, page] of Object.entries(replies.pages)) {
|
||||
if (!page?.comments?.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let nextComments = page.comments;
|
||||
let commentsChanged = false;
|
||||
|
||||
page.comments.forEach((reply, index) => {
|
||||
const normalizedReply = withResolvedCommentCommunityAddress(reply);
|
||||
if (normalizedReply === reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commentsChanged) {
|
||||
nextComments = [...(page.comments ?? [])];
|
||||
commentsChanged = true;
|
||||
}
|
||||
nextComments[index] = normalizedReply;
|
||||
});
|
||||
|
||||
if (!commentsChanged) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pagesChanged) {
|
||||
nextPages = { ...replies.pages };
|
||||
pagesChanged = true;
|
||||
}
|
||||
|
||||
nextPages[sortType] = {
|
||||
...page,
|
||||
comments: nextComments,
|
||||
};
|
||||
}
|
||||
|
||||
if (!pagesChanged) {
|
||||
return replies;
|
||||
}
|
||||
|
||||
return {
|
||||
...replies,
|
||||
pages: nextPages,
|
||||
};
|
||||
};
|
||||
|
||||
export const withResolvedCommentCommunityAddress = <T extends CommentWithLegacyCommunityAddress | undefined | null>(comment: T): T => {
|
||||
if (!comment) {
|
||||
return comment;
|
||||
}
|
||||
|
||||
const communityAddress = getCommentCommunityAddress(comment);
|
||||
const replies = withResolvedReplyPages(comment.replies);
|
||||
const needsResolvedCommunityAddress = !!communityAddress && comment.communityAddress !== communityAddress;
|
||||
|
||||
if (!needsResolvedCommunityAddress && replies === comment.replies) {
|
||||
return comment;
|
||||
}
|
||||
|
||||
return {
|
||||
...comment,
|
||||
...(needsResolvedCommunityAddress ? { communityAddress } : {}),
|
||||
...(replies !== comment.replies ? { replies } : {}),
|
||||
} as T;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
|
||||
import feedsStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/feeds';
|
||||
import repliesStore, { feedOptionsToFeedName } from '@bitsocialnet/bitsocial-react-hooks/dist/stores/replies';
|
||||
import subplebbitsPagesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/subplebbits-pages';
|
||||
import communitiesPagesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import type { DirectoryCommunity } from '../../hooks/use-directories';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import type { ExternalQuoteReference, ExternalQuoteSearchStatus } from './external-quote-utils';
|
||||
@@ -26,7 +26,7 @@ type ResolvedExternalQuoteTarget = {
|
||||
comment?: Comment;
|
||||
isUnavailable: boolean;
|
||||
route: string;
|
||||
subplebbitAddress: string;
|
||||
communityAddress: string;
|
||||
};
|
||||
|
||||
const waitFor = async <T>(callback: () => T | undefined | false, timeoutMs = WAIT_FOR_STORE_TIMEOUT_MS) => {
|
||||
@@ -54,36 +54,39 @@ const isUnavailableComment = (
|
||||
} | null,
|
||||
) => Boolean(comment?.deleted || comment?.removed || comment?.commentModeration?.purged);
|
||||
|
||||
const getBoardFeedName = (accountId: string, subplebbitAddress: string) =>
|
||||
`external-quote-board-${accountId}-${subplebbitAddress}-${BOARD_FEED_SORT_TYPE}-${BOARD_SEARCH_POSTS_PER_PAGE}`;
|
||||
const getBoardFeedName = (accountId: string, communityAddress: string) =>
|
||||
`external-quote-board-${accountId}-${communityAddress}-${BOARD_FEED_SORT_TYPE}-${BOARD_SEARCH_POSTS_PER_PAGE}`;
|
||||
|
||||
const getCachedComment = (cid?: string) => (cid ? subplebbitsPagesStore.getState().comments[cid] : undefined);
|
||||
const getCachedComment = (cid?: string) => (cid ? communitiesPagesStore.getState().comments[cid] : undefined);
|
||||
|
||||
const findLoadedCommentByNumber = ({ number, subplebbitAddress }: { number: number; subplebbitAddress: string }) => {
|
||||
const comments = Object.values(subplebbitsPagesStore.getState().comments) as Array<Comment | undefined>;
|
||||
const findLoadedCommentByNumber = ({ number, communityAddress }: { number: number; communityAddress: string }) => {
|
||||
const comments = Object.values(communitiesPagesStore.getState().comments) as Array<Comment | undefined>;
|
||||
|
||||
return comments.find((comment) => comment?.subplebbitAddress === subplebbitAddress && comment?.number === number && comment?.cid);
|
||||
return comments.find((comment) => {
|
||||
const address = (comment as { communityAddress?: string }).communityAddress || comment?.subplebbitAddress;
|
||||
return address === communityAddress && comment?.number === number && comment?.cid;
|
||||
});
|
||||
};
|
||||
|
||||
const buildResolvedTarget = ({
|
||||
cid,
|
||||
comment,
|
||||
directories,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
}: {
|
||||
cid: string;
|
||||
comment?: Comment;
|
||||
directories: DirectoryCommunity[];
|
||||
subplebbitAddress: string;
|
||||
communityAddress: string;
|
||||
}): ResolvedExternalQuoteTarget => {
|
||||
const boardPath = getBoardPath(subplebbitAddress, directories);
|
||||
const boardPath = getBoardPath(communityAddress, directories);
|
||||
return {
|
||||
boardPath,
|
||||
cid,
|
||||
comment,
|
||||
isUnavailable: isUnavailableComment(comment),
|
||||
route: `/${boardPath}/thread/${cid}`,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +121,7 @@ const loadBoardThreads = async ({
|
||||
number,
|
||||
onStatus,
|
||||
quoteDisplay,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
directories,
|
||||
}: {
|
||||
account: ResolverAccount;
|
||||
@@ -126,7 +129,7 @@ const loadBoardThreads = async ({
|
||||
number: number;
|
||||
onStatus?: (status: ExternalQuoteSearchStatus) => void;
|
||||
quoteDisplay: string;
|
||||
subplebbitAddress: string;
|
||||
communityAddress: string;
|
||||
}) => {
|
||||
const accountId = account.id;
|
||||
if (!accountId) {
|
||||
@@ -138,7 +141,7 @@ const loadBoardThreads = async ({
|
||||
kind: 'same-board',
|
||||
number,
|
||||
raw: quoteDisplay,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
},
|
||||
directories,
|
||||
);
|
||||
@@ -149,10 +152,10 @@ const loadBoardThreads = async ({
|
||||
quoteDisplay,
|
||||
});
|
||||
|
||||
const feedName = getBoardFeedName(accountId, subplebbitAddress);
|
||||
const feedName = getBoardFeedName(accountId, communityAddress);
|
||||
const feedState = feedsStore.getState();
|
||||
if (!feedState.feedsOptions[feedName]) {
|
||||
await feedState.addFeedToStore(feedName, [subplebbitAddress], BOARD_FEED_SORT_TYPE, account, false, BOARD_SEARCH_POSTS_PER_PAGE);
|
||||
await feedState.addFeedToStore(feedName, [communityAddress], BOARD_FEED_SORT_TYPE, account, false, BOARD_SEARCH_POSTS_PER_PAGE);
|
||||
}
|
||||
|
||||
await waitForBoardFeedPage(feedName, 0, 1);
|
||||
@@ -210,7 +213,7 @@ const searchThreadReplies = async ({
|
||||
number,
|
||||
onStatus,
|
||||
quoteDisplay,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
threads,
|
||||
}: {
|
||||
account: ResolverAccount;
|
||||
@@ -218,7 +221,7 @@ const searchThreadReplies = async ({
|
||||
number: number;
|
||||
onStatus?: (status: ExternalQuoteSearchStatus) => void;
|
||||
quoteDisplay: string;
|
||||
subplebbitAddress: string;
|
||||
communityAddress: string;
|
||||
threads: Comment[];
|
||||
}) => {
|
||||
const accountId = account.id;
|
||||
@@ -231,7 +234,7 @@ const searchThreadReplies = async ({
|
||||
kind: 'same-board',
|
||||
number,
|
||||
raw: quoteDisplay,
|
||||
subplebbitAddress,
|
||||
communityAddress,
|
||||
},
|
||||
directories,
|
||||
);
|
||||
@@ -302,21 +305,21 @@ export const resolveExternalQuoteTarget = async ({
|
||||
throw new Error('Missing active account while resolving external quote');
|
||||
}
|
||||
|
||||
const targetSubplebbitAddress = getExternalQuoteBoardAddress(reference, directories);
|
||||
const targetCommunityAddress = getExternalQuoteBoardAddress(reference, directories);
|
||||
const quoteDisplay = reference.raw;
|
||||
const cachedCid = usePostNumberStore.getState().numberToCid[targetSubplebbitAddress]?.[reference.number];
|
||||
const cachedCid = usePostNumberStore.getState().numberToCid[targetCommunityAddress]?.[reference.number];
|
||||
if (cachedCid) {
|
||||
return buildResolvedTarget({
|
||||
cid: cachedCid,
|
||||
comment: getCachedComment(cachedCid),
|
||||
directories,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
}
|
||||
|
||||
const loadedComment = findLoadedCommentByNumber({
|
||||
number: reference.number,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
if (loadedComment?.cid) {
|
||||
registerComments([loadedComment]);
|
||||
@@ -324,7 +327,7 @@ export const resolveExternalQuoteTarget = async ({
|
||||
cid: loadedComment.cid,
|
||||
comment: loadedComment,
|
||||
directories,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -334,7 +337,7 @@ export const resolveExternalQuoteTarget = async ({
|
||||
number: reference.number,
|
||||
onStatus,
|
||||
quoteDisplay,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
|
||||
if (matchingThread?.cid) {
|
||||
@@ -343,7 +346,7 @@ export const resolveExternalQuoteTarget = async ({
|
||||
cid: matchingThread.cid,
|
||||
comment: matchingThread,
|
||||
directories,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,7 +356,7 @@ export const resolveExternalQuoteTarget = async ({
|
||||
number: reference.number,
|
||||
onStatus,
|
||||
quoteDisplay,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
threads,
|
||||
});
|
||||
|
||||
@@ -366,6 +369,6 @@ export const resolveExternalQuoteTarget = async ({
|
||||
cid: matchingReply.cid,
|
||||
comment: matchingReply,
|
||||
directories,
|
||||
subplebbitAddress: targetSubplebbitAddress,
|
||||
communityAddress: targetCommunityAddress,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DirectoryCommunity } from '../../hooks/use-directories';
|
||||
import { getBoardPath, getSubplebbitAddress } from './route-utils';
|
||||
import { getBoardPath, getCommunityAddress, getSubplebbitAddress } from './route-utils';
|
||||
import { QUOTE_NUMBER_REGEX } from './url-utils';
|
||||
|
||||
const CROSSBOARD_NUMBER_BOARD_PART = '(?:[a-zA-Z0-9]{1,10}|12D3KooW[a-zA-Z0-9]{44}|[a-zA-Z0-9\\-.]+)';
|
||||
@@ -11,7 +11,9 @@ export type SameBoardExternalQuoteReference = {
|
||||
kind: 'same-board';
|
||||
number: number;
|
||||
raw: string;
|
||||
subplebbitAddress: string;
|
||||
communityAddress?: string;
|
||||
// legacy compatibility alias
|
||||
subplebbitAddress?: string;
|
||||
};
|
||||
|
||||
export type CrossBoardExternalQuoteReference = {
|
||||
@@ -23,35 +25,59 @@ export type CrossBoardExternalQuoteReference = {
|
||||
|
||||
export type ExternalQuoteReference = SameBoardExternalQuoteReference | CrossBoardExternalQuoteReference;
|
||||
|
||||
const getAddressForCanonicalReference = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]): string => {
|
||||
if (reference.kind === 'cross-board') {
|
||||
return resolveLegacyCommunityAddress(reference.boardIdentifier, directories);
|
||||
}
|
||||
|
||||
return resolveLegacyCommunityAddress(reference.communityAddress || reference.subplebbitAddress || '', directories);
|
||||
};
|
||||
|
||||
const getExternalQuoteKey = (reference: ExternalQuoteReference) =>
|
||||
reference.kind === 'cross-board'
|
||||
? `${reference.kind}:${reference.boardIdentifier}:${reference.number}`
|
||||
: `${reference.kind}:${reference.subplebbitAddress}:${reference.number}`;
|
||||
: `${reference.kind}:${reference.communityAddress || reference.subplebbitAddress}:${reference.number}`;
|
||||
|
||||
const resolveLegacyCommunityAddress = (boardIdentifier: string, communities: DirectoryCommunity[]) => {
|
||||
// Canonical resolver in route utils handles directory or address mapping.
|
||||
const address = getCommunityAddress(boardIdentifier, communities);
|
||||
if (address) {
|
||||
return address;
|
||||
}
|
||||
|
||||
// Backward-compat helper alias if needed by callers with older util behavior.
|
||||
return getSubplebbitAddress(boardIdentifier, communities);
|
||||
};
|
||||
|
||||
export const getExternalQuoteBoardAddress = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]) =>
|
||||
reference.kind === 'cross-board' ? getSubplebbitAddress(reference.boardIdentifier, directories) : reference.subplebbitAddress;
|
||||
getAddressForCanonicalReference(reference, directories);
|
||||
|
||||
export const getExternalQuoteBoardLabel = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]) => {
|
||||
const address = getExternalQuoteBoardAddress(reference, directories);
|
||||
const address = getAddressForCanonicalReference(reference, directories);
|
||||
return getBoardPath(address, directories);
|
||||
};
|
||||
|
||||
export const extractUnresolvedExternalQuoteReferences = ({
|
||||
content,
|
||||
scopedNumberToCid,
|
||||
communityAddress,
|
||||
subplebbitAddress,
|
||||
}: {
|
||||
content?: string;
|
||||
scopedNumberToCid?: Record<number, string>;
|
||||
// canonical input
|
||||
communityAddress?: string;
|
||||
// backward-compatible input name
|
||||
subplebbitAddress?: string;
|
||||
}) => {
|
||||
const effectiveCommunityAddress = communityAddress || subplebbitAddress;
|
||||
if (!content) {
|
||||
return [] as ExternalQuoteReference[];
|
||||
}
|
||||
|
||||
const references = new Map<string, ExternalQuoteReference>();
|
||||
|
||||
if (subplebbitAddress) {
|
||||
if (effectiveCommunityAddress) {
|
||||
for (const match of content.matchAll(new RegExp(QUOTE_NUMBER_REGEX.source, 'g'))) {
|
||||
const number = Number.parseInt(match[1], 10);
|
||||
if (Number.isNaN(number) || scopedNumberToCid?.[number]) {
|
||||
@@ -62,7 +88,8 @@ export const extractUnresolvedExternalQuoteReferences = ({
|
||||
kind: 'same-board',
|
||||
number,
|
||||
raw: `>>${number}`,
|
||||
subplebbitAddress,
|
||||
communityAddress: effectiveCommunityAddress,
|
||||
subplebbitAddress: effectiveCommunityAddress,
|
||||
};
|
||||
references.set(getExternalQuoteKey(reference), reference);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
|
||||
import useSubplebbitsStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/subplebbits';
|
||||
import communitiesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities';
|
||||
|
||||
type CommunityLike = {
|
||||
roles?: Record<string, { role?: string }>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a text matches a pattern according to various pattern matching rules:
|
||||
@@ -138,12 +142,14 @@ 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 => {
|
||||
if (!role || !comment?.author?.address || !comment?.subplebbitAddress) {
|
||||
const communityAddress = (comment as { communityAddress?: string }).communityAddress ?? comment?.subplebbitAddress;
|
||||
|
||||
if (!role || !comment?.author?.address || !communityAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const subplebbits = useSubplebbitsStore.getState().subplebbits;
|
||||
const subplebbit = subplebbits[comment.subplebbitAddress];
|
||||
const communities = communitiesStore.getState().communities;
|
||||
const subplebbit = communities[communityAddress] as CommunityLike | undefined;
|
||||
|
||||
if (!subplebbit?.roles) {
|
||||
return false;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Comment } from '@bitsocialnet/bitsocial-react-hooks';
|
||||
import { getCommentCommunityAddress } from './comment-utils';
|
||||
|
||||
export type PostMenuProps = {
|
||||
cid?: string;
|
||||
postCid?: string;
|
||||
parentCid?: string;
|
||||
communityAddress?: string;
|
||||
subplebbitAddress?: string;
|
||||
authorAddress?: string;
|
||||
link?: string;
|
||||
@@ -14,16 +16,21 @@ export type PostMenuProps = {
|
||||
removed?: boolean;
|
||||
};
|
||||
|
||||
export const selectPostMenuProps = (post?: Comment): PostMenuProps => ({
|
||||
cid: post?.cid,
|
||||
postCid: post?.postCid,
|
||||
parentCid: post?.parentCid,
|
||||
subplebbitAddress: post?.subplebbitAddress,
|
||||
authorAddress: post?.author?.address,
|
||||
link: post?.link,
|
||||
linkWidth: post?.linkWidth,
|
||||
linkHeight: post?.linkHeight,
|
||||
thumbnailUrl: post?.thumbnailUrl,
|
||||
deleted: post?.deleted,
|
||||
removed: post?.removed,
|
||||
});
|
||||
export const selectPostMenuProps = (post?: Comment): PostMenuProps => {
|
||||
const communityAddress = getCommentCommunityAddress(post);
|
||||
|
||||
return {
|
||||
cid: post?.cid,
|
||||
postCid: post?.postCid,
|
||||
parentCid: post?.parentCid,
|
||||
communityAddress,
|
||||
subplebbitAddress: post?.subplebbitAddress,
|
||||
authorAddress: post?.author?.address,
|
||||
link: post?.link,
|
||||
linkWidth: post?.linkWidth,
|
||||
linkHeight: post?.linkHeight,
|
||||
thumbnailUrl: post?.thumbnailUrl,
|
||||
deleted: post?.deleted,
|
||||
removed: post?.removed,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,8 +10,18 @@ export interface CommentWithCid {
|
||||
}
|
||||
|
||||
/** Minimal FeedOptions shape for board-feed filtering */
|
||||
type LegacyFeedOptionsLike = {
|
||||
subplebbitAddresses?: string[];
|
||||
sortType: string;
|
||||
postsPerPage?: number;
|
||||
filter?: unknown;
|
||||
newerThan?: number;
|
||||
modQueue?: unknown;
|
||||
accountComments?: unknown;
|
||||
};
|
||||
|
||||
export interface FeedOptionsLike {
|
||||
subplebbitAddresses: string[];
|
||||
communityAddresses?: string[];
|
||||
sortType: string;
|
||||
postsPerPage?: number;
|
||||
filter?: unknown;
|
||||
@@ -21,7 +31,7 @@ export interface FeedOptionsLike {
|
||||
}
|
||||
|
||||
/** FeedsOptions-like map */
|
||||
export type FeedsOptionsLike = Record<string, FeedOptionsLike>;
|
||||
export type FeedsOptionsLike = Record<string, FeedOptionsLike | LegacyFeedOptionsLike>;
|
||||
|
||||
/** Loaded feeds map: feedName -> Comment[] */
|
||||
export type LoadedFeedsLike = Record<string, CommentWithCid[]>;
|
||||
@@ -46,14 +56,29 @@ export function findPostPageInFeed(feed: CommentWithCid[], postCid: string, guiP
|
||||
* Strict board-feed filter criteria.
|
||||
* A feed is a "board feed" iff:
|
||||
* - sortType === 'active'
|
||||
* - single-sub feed (subplebbitAddresses.length === 1)
|
||||
* - single-board feed (one community)
|
||||
* - no filter, no newerThan, no modQueue, no accountComments
|
||||
*/
|
||||
export function isBoardFeedOptions(opts: FeedOptionsLike, subplebbitAddress: string): boolean {
|
||||
const getCommunityAddresses = (opts: FeedOptionsLike | LegacyFeedOptionsLike): string[] => {
|
||||
if ('communityAddresses' in opts && Array.isArray(opts.communityAddresses)) {
|
||||
return opts.communityAddresses;
|
||||
}
|
||||
if ('subplebbitAddresses' in opts && Array.isArray(opts.subplebbitAddresses)) {
|
||||
return opts.subplebbitAddresses;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Supports both canonical `communityAddresses` and legacy `subplebbitAddresses`.
|
||||
*/
|
||||
export function isBoardFeedOptions(opts: FeedOptionsLike | LegacyFeedOptionsLike, communityAddress: string): boolean {
|
||||
const communityAddresses = getCommunityAddresses(opts);
|
||||
|
||||
return (
|
||||
opts.sortType === 'active' &&
|
||||
opts.subplebbitAddresses?.length === 1 &&
|
||||
opts.subplebbitAddresses[0] === subplebbitAddress &&
|
||||
communityAddresses.length === 1 &&
|
||||
communityAddresses[0] === communityAddress &&
|
||||
!opts.filter &&
|
||||
opts.newerThan == null &&
|
||||
!opts.modQueue &&
|
||||
@@ -67,7 +92,7 @@ export function isBoardFeedOptions(opts: FeedOptionsLike, subplebbitAddress: str
|
||||
*
|
||||
* @param feedsOptions - Feeds store feedsOptions
|
||||
* @param loadedFeeds - Feeds store loadedFeeds
|
||||
* @param subplebbitAddress - Board subplebbit address
|
||||
* @param communityAddress - Board community address
|
||||
* @param postCid - CID of the post (OP) to locate
|
||||
* @param guiPostsPerPage - Posts per GUI page
|
||||
* @returns 1-based page number, or undefined if not found in any matching feed
|
||||
@@ -75,15 +100,15 @@ export function isBoardFeedOptions(opts: FeedOptionsLike, subplebbitAddress: str
|
||||
export function findPostPageInLoadedBoardFeeds(
|
||||
feedsOptions: FeedsOptionsLike,
|
||||
loadedFeeds: LoadedFeedsLike,
|
||||
subplebbitAddress: string,
|
||||
communityAddress: string,
|
||||
postCid: string,
|
||||
guiPostsPerPage: number,
|
||||
): number | undefined {
|
||||
if (!subplebbitAddress || !postCid || guiPostsPerPage <= 0) return undefined;
|
||||
if (!communityAddress || !postCid || guiPostsPerPage <= 0) return undefined;
|
||||
|
||||
for (const feedName of Object.keys(feedsOptions)) {
|
||||
const opts = feedsOptions[feedName];
|
||||
if (!opts || !isBoardFeedOptions(opts, subplebbitAddress)) continue;
|
||||
if (!opts || !isBoardFeedOptions(opts as FeedOptionsLike, communityAddress)) continue;
|
||||
|
||||
const feed = loadedFeeds[feedName];
|
||||
if (!feed || !Array.isArray(feed)) continue;
|
||||
|
||||
@@ -85,7 +85,7 @@ export const getBoardPath = (communityAddress: string, communities: DirectoryCom
|
||||
/**
|
||||
* Convert URL path (directory code or address) to community address
|
||||
*/
|
||||
export const getSubplebbitAddress = (boardIdentifier: string, communities: DirectoryCommunity[]): string => {
|
||||
export const getCommunityAddress = (boardIdentifier: string, communities: DirectoryCommunity[]): string => {
|
||||
const directoryToAddress = getDirectoryToAddressMap(communities);
|
||||
|
||||
// Check if it's a directory code
|
||||
@@ -98,6 +98,11 @@ export const getSubplebbitAddress = (boardIdentifier: string, communities: Direc
|
||||
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).
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user