feat(flags): add comment flags (#1140)

* feat(flags): add comment flags

* fix(flags): address review feedback

* fix(flags): clear stale flag publish data
This commit is contained in:
Tommaso Casaburi
2026-05-24 23:14:36 +07:00
committed by GitHub
parent 804c14fc35
commit f74b33f43b
66 changed files with 1293 additions and 74 deletions
@@ -189,6 +189,7 @@ describe('use-directories', () => {
expect(findDirectoryByAddress(communities, 'music-posting.bso')?.address).toBe('music-posting.bso');
expect(findDirectoryByAddress(communities, 'music-posting.eth')?.address).toBe('music-posting.bso');
expect(findDirectoryByAddress(communities, '12D3KooWQdQ6TkVA1Xe9zzaFP6vXBgsLeMAewpLpLwbsAYKivnQy')?.address).toBe('music-posting.bso');
expect(findDirectoryByAddress([], '12D3KooWNFgjQWX2EUEs7pixdjkWSLh21EZ9NeYnV8iMaCyYhLGJ')).toBeUndefined();
expect(findDirectoryByAddress(communities, undefined)).toBeUndefined();
});
@@ -155,4 +155,30 @@ describe('usePublishPost', () => {
expect(testState.lastPublishOptions?.content).toBe('Fresh body');
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
it('clears stale flag data from one-shot publish options', async () => {
await act(async () => {
latestValue.setPublishPostOptions({
content: 'Flag body',
challengeRequest: {
challengeAnswers: ['bitsocial-flags:5chan:flag:country:auto'],
},
flairs: [{ text: 'flag:country:auto', type: 'country', code: 'auto' }],
} as never);
});
await act(async () => {
latestValue.publishPost({
content: 'Plain body',
challengeRequest: undefined,
flairs: undefined,
} as never);
await Promise.resolve();
});
expect(testState.lastPublishOptions?.content).toBe('Plain body');
expect(testState.lastPublishOptions?.challengeRequest).toBeUndefined();
expect(testState.lastPublishOptions?.flairs).toBeUndefined();
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
});
@@ -83,8 +83,10 @@ describe('usePublishReply', () => {
usePostNumberStore.setState({ cidToNumber: {}, numberToCid: { 'music.eth': { 12: 'quoted-cid' } } });
usePublishReplyStore.setState({
author: {},
challengeRequest: {},
content: {},
displayName: {},
flairs: {},
link: {},
publishCommentOptions: {},
spoiler: {},
@@ -186,6 +188,33 @@ describe('usePublishReply', () => {
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
it('clears stale flag data from one-shot reply options', async () => {
await act(async () => {
latestValue.setPublishReplyOptions({
content: 'Flag reply',
challengeRequest: {
challengeAnswers: ['bitsocial-flags:5chan:flag:pol:AC'],
},
flairs: [{ text: 'flag:pol:AC', type: 'pol', code: 'AC' }],
} as never);
});
await act(async () => {
await latestValue.publishReply({
content: 'Plain reply',
challengeRequest: undefined,
flairs: undefined,
} as never);
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 0));
});
expect(testState.lastPublishOptions?.content).toBe('Plain reply');
expect(testState.lastPublishOptions?.challengeRequest).toBeUndefined();
expect(testState.lastPublishOptions?.flairs).toBeUndefined();
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
it('blocks publish when a same-board external quote cannot be resolved', async () => {
testState.resolveExternalQuoteTargetMock.mockResolvedValue(null);
+8 -1
View File
@@ -136,7 +136,14 @@ export const findDirectoryByAddress = (directories: DirectoryCommunity[], addres
}
const normalizedAddress = normalizeBoardAddress(address);
return directories.find((community) => getDirectoryIdentifiers(community).some((identifier) => normalizeBoardAddress(identifier) === normalizedAddress));
const normalizedMatch = directories.find((community) =>
getDirectoryIdentifiers(community).some((identifier) => normalizeBoardAddress(identifier) === normalizedAddress),
);
if (normalizedMatch) {
return normalizedMatch;
}
return undefined;
};
const adaptV1Communities = (value: Record<string, unknown>): DirectoryCommunity[] => {
+6 -3
View File
@@ -10,12 +10,13 @@ type UsePublishPostOptions = {
};
const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
const { author, title, content, link, spoiler, publishCommentOptions } = usePublishPostStore(
const { author, title, content, link, flairs, spoiler, publishCommentOptions } = usePublishPostStore(
useShallow((state) => ({
author: state.author,
title: state.title || undefined,
content: state.content || undefined,
link: state.link || undefined,
flairs: state.flairs,
spoiler: state.spoiler || false,
publishCommentOptions: state.publishCommentOptions,
})),
@@ -39,7 +40,9 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
title,
content,
link,
flairs,
spoiler,
...(publishCommentOptions.challengeRequest ? { challengeRequest: publishCommentOptions.challengeRequest } : {}),
};
const displayName = author?.displayName;
@@ -48,7 +51,7 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
}
return baseOptions;
}, [author, content, link, spoiler, communityAddress, title]);
}, [author, content, flairs, link, spoiler, communityAddress, title, publishCommentOptions.challengeRequest]);
const setPublishPostOptions = useCallback(
(options: Partial<Comment>) => {
@@ -90,7 +93,7 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
useEffect(() => {
setPublishPostError(null);
}, [author?.displayName, blockedReason, communityAddress, content, link, spoiler, title]);
}, [author?.displayName, blockedReason, communityAddress, content, flairs, link, spoiler, title]);
const startPublishPost = useCallback(() => {
if (blockedReason) {
+6 -3
View File
@@ -23,11 +23,12 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
const account = useAccount();
const directories = useDirectories();
const { author, content, link, spoiler, publishCommentOptions } = usePublishReplyStore(
const { author, content, link, flairs, spoiler, publishCommentOptions } = usePublishReplyStore(
useShallow((state) => ({
author: state.author[parentCid],
content: state.content[parentCid] || undefined,
link: state.link[parentCid] || undefined,
flairs: state.flairs[parentCid],
spoiler: state.spoiler[parentCid] || false,
publishCommentOptions: state.publishCommentOptions[parentCid],
})),
@@ -57,7 +58,9 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
postCid: postCid ?? parentCid,
content,
link,
flairs,
spoiler,
...(publishCommentOptions?.challengeRequest ? { challengeRequest: publishCommentOptions.challengeRequest } : {}),
};
const displayName = author?.displayName;
@@ -66,7 +69,7 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
}
return baseOptions;
}, [author, content, link, parentCid, postCid, spoiler, communityAddress]);
}, [author, content, flairs, link, parentCid, postCid, spoiler, communityAddress, publishCommentOptions?.challengeRequest]);
const setPublishReplyOptions = useCallback(
(options: Partial<Comment>) => {
@@ -142,7 +145,7 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
setPublishReplyError(null);
setPublishReplyStateMessage(null);
setIsResolvingExternalQuotes(false);
}, [blockedReason, content, communityAddress]);
}, [blockedReason, content, communityAddress, flairs]);
useEffect(() => {
if (pendingPublishRequestId === 0 || pendingPublishRequestId === startedPublishRequestIdRef.current) {