fix(flags): move reply flag below link and auto-geo on /bant/

Hide the flag selector on /bant/ while still publishing geographic location, and place the reply modal flag field after the link field.
This commit is contained in:
Tommaso Casaburi
2026-05-24 23:38:15 +07:00
parent 2030b8c5bb
commit 633542a1f7
5 changed files with 105 additions and 19 deletions
+3 -3
View File
@@ -24,7 +24,7 @@ import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/
import { hasModQueueAccessRole } from '../../lib/utils/mod-access'; import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getBoardPath } from '../../lib/utils/route-utils'; import { getBoardPath } from '../../lib/utils/route-utils';
import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsFromSelection, type CommentFlagSelectOption } from '../../lib/comment-flag-selection'; import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory, type CommentFlagSelectOption } from '../../lib/comment-flag-selection';
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses'; import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories'; import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
import { useCommunityField } from '../../hooks/use-stable-community'; import { useCommunityField } from '../../hooks/use-stable-community';
@@ -576,7 +576,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
return; return;
} }
const flagPublishOptions = getCommentFlagPublishOptionsFromSelection(flagRef.current?.value); const flagPublishOptions = getCommentFlagPublishOptionsForDirectory(directoryEntry, flagRef.current?.value);
nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? getBoardIndexPath() : null; nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? getBoardIndexPath() : null;
publishPost({ content: publishContent, ...flagPublishOptions }); publishPost({ content: publishContent, ...flagPublishOptions });
@@ -688,7 +688,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
return; return;
} }
const flagPublishOptions = getCommentFlagPublishOptionsFromSelection(flagRef.current?.value); const flagPublishOptions = getCommentFlagPublishOptionsForDirectory(directoryEntry, flagRef.current?.value);
nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? getBoardIndexPath() : null; nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? getBoardIndexPath() : null;
publishReply({ content: publishContent, ...flagPublishOptions }); publishReply({ content: publishContent, ...flagPublishOptions });
@@ -329,6 +329,12 @@ describe('ReplyModal', () => {
features: { hasFlags: true }, features: { hasFlags: true },
title: '/pol/ - Politically Incorrect', title: '/pol/ - Politically Incorrect',
}, },
'international-nsfw.bso': {
address: 'international-nsfw.bso',
directoryCode: 'bant',
features: { hasFlags: true },
title: '/bant/ - International/Random',
},
'random-nsfw.bso': { 'random-nsfw.bso': {
address: 'random-nsfw.bso', address: 'random-nsfw.bso',
features: {}, features: {},
@@ -443,6 +449,24 @@ describe('ReplyModal', () => {
.slice(0, 3) .slice(0, 3)
.map((option) => option.textContent), .map((option) => option.textContent),
).toEqual(['Geographic Location', 'Anarcho-Capitalist', 'Anarchist']); ).toEqual(['Geographic Location', 'Anarcho-Capitalist', 'Anarchist']);
const linkInput = container.querySelectorAll<HTMLInputElement>('input[type="text"]')[2];
expect(Boolean(linkInput!.compareDocumentPosition(flagSelect!) & Node.DOCUMENT_POSITION_FOLLOWING)).toBe(true);
await clickButtonByText('post');
expect(testState.publishReplyMock).toHaveBeenCalledWith({
content: '>>42\nselected text',
challengeRequest: {
challengeAnswers: ['bitsocial-flags:5chan:flag:country:auto'],
},
flairs: [{ type: 'country', code: 'auto', text: 'flag:country:auto' }],
});
});
it('publishes geographic location on /bant/ without showing a flag selector', async () => {
await renderReplyModal('/bant/thread/post-1', 'international-nsfw.bso');
expect(container.querySelector<HTMLSelectElement>('select[aria-label="flag"]')).toBeNull();
await clickButtonByText('post'); await clickButtonByText('post');
+15 -15
View File
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { setAccount, useAccount } from '@bitsocial/bitsocial-react-hooks'; import { setAccount, useAccount } from '@bitsocial/bitsocial-react-hooks';
import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation-utils'; import { getExpiringMediaLinkAlert } from '../../lib/utils/media-link-validation-utils';
import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsFromSelection } from '../../lib/comment-flag-selection'; import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsForDirectory } from '../../lib/comment-flag-selection';
import { import {
type DiceRoll, type DiceRoll,
type FortuneEntry, type FortuneEntry,
@@ -169,7 +169,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
return; return;
} }
const flagPublishOptions = getCommentFlagPublishOptionsFromSelection(flagRef.current?.value); const flagPublishOptions = getCommentFlagPublishOptionsForDirectory(directoryEntry, flagRef.current?.value);
setError(null); setError(null);
nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? `/${postOptionsDirectoryCode || params.boardIdentifier || communityAddress}` : null; nonokoRedirectPathRef.current = hasNonokoOption(currentOptions) ? `/${postOptionsDirectoryCode || params.boardIdentifier || communityAddress}` : null;
@@ -520,6 +520,19 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
}} }}
/> />
</div> </div>
<div className={styles.link}>
<input
type='text'
ref={urlRef}
aria-label={requirePostLinkIsMedia ? t('link_to_file') : t('link')}
placeholder={requirePostLinkIsMedia ? FILE_LINK_PLACEHOLDER : capitalize(t('link'))}
disabled={isUploading}
onChange={(e) => {
setUrl(e.target.value);
setPublishReplyOptions({ link: e.target.value });
}}
/>
</div>
{flagOptions.length > 0 && ( {flagOptions.length > 0 && (
<div> <div>
<select <select
@@ -538,19 +551,6 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
</select> </select>
</div> </div>
)} )}
<div className={styles.link}>
<input
type='text'
ref={urlRef}
aria-label={requirePostLinkIsMedia ? t('link_to_file') : t('link')}
placeholder={requirePostLinkIsMedia ? FILE_LINK_PLACEHOLDER : capitalize(t('link'))}
disabled={isUploading}
onChange={(e) => {
setUrl(e.target.value);
setPublishReplyOptions({ link: e.target.value });
}}
/>
</div>
<div className={styles.footer}> <div className={styles.footer}>
{showUploadControls && ( {showUploadControls && (
<span className={styles.uploadContainer}> <span className={styles.uploadContainer}>
@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { getCommentFlagOptionsForDirectory, getCommentFlagPublishOptionsFromSelection, getCommentFlagRequestFromSelection } from '../comment-flag-selection'; import {
getCommentFlagOptionsForDirectory,
getCommentFlagPublishOptionsForDirectory,
getCommentFlagPublishOptionsFromSelection,
getCommentFlagRequestFromSelection,
} from '../comment-flag-selection';
describe('comment-flag-selection', () => { describe('comment-flag-selection', () => {
it('does not expose options for boards without flags', () => { it('does not expose options for boards without flags', () => {
@@ -16,6 +21,29 @@ describe('comment-flag-selection', () => {
).toEqual([{ label: 'Geographic Location', value: 'country:auto' }]); ).toEqual([{ label: 'Geographic Location', value: 'country:auto' }]);
}); });
it('does not expose a flag selector on /bant/ but still publishes geographic location', () => {
expect(
getCommentFlagOptionsForDirectory({
directoryCode: 'bant',
features: { hasFlags: true },
title: '/bant/ - International/Random',
}),
).toEqual([]);
expect(
getCommentFlagPublishOptionsForDirectory({
directoryCode: 'bant',
features: { hasFlags: true },
title: '/bant/ - International/Random',
}),
).toEqual({
challengeRequest: {
challengeAnswers: ['bitsocial-flags:5chan:flag:country:auto'],
},
flairs: [{ type: 'country', code: 'auto', text: 'flag:country:auto' }],
});
});
it('matches the 4chan /pol/ flag order', () => { it('matches the 4chan /pol/ flag order', () => {
const options = getCommentFlagOptionsForDirectory({ const options = getCommentFlagOptionsForDirectory({
directoryCode: 'pol', directoryCode: 'pol',
+34
View File
@@ -35,6 +35,9 @@ const NO_FLAG_OPTION: CommentFlagSelectOption = {
label: 'None', label: 'None',
}; };
/** Boards that always publish geographic location without showing a flag selector. */
const AUTO_GEOGRAPHIC_FLAG_DIRECTORY_CODES = new Set(['bant']);
const getDirectoryCode = (directory: Pick<DirectoryCommunity, 'directoryCode' | 'title'> | undefined): string | undefined => { const getDirectoryCode = (directory: Pick<DirectoryCommunity, 'directoryCode' | 'title'> | undefined): string | undefined => {
const directoryCode = directory?.directoryCode?.trim().toLowerCase(); const directoryCode = directory?.directoryCode?.trim().toLowerCase();
return directoryCode || directory?.title?.match(/^\/([^/]+)\//)?.[1]?.toLowerCase(); return directoryCode || directory?.title?.match(/^\/([^/]+)\//)?.[1]?.toLowerCase();
@@ -52,6 +55,10 @@ export const getCommentFlagOptionsForDirectory = (directory: Pick<DirectoryCommu
} }
const directoryCode = getDirectoryCode(directory); const directoryCode = getDirectoryCode(directory);
if (directoryCode && AUTO_GEOGRAPHIC_FLAG_DIRECTORY_CODES.has(directoryCode)) {
return [];
}
if (directoryCode === 'mlp') { if (directoryCode === 'mlp') {
return [NO_FLAG_OPTION, ...getBoardFlagOptions('pony')]; return [NO_FLAG_OPTION, ...getBoardFlagOptions('pony')];
} }
@@ -63,6 +70,33 @@ export const getCommentFlagOptionsForDirectory = (directory: Pick<DirectoryCommu
return [GEOGRAPHIC_LOCATION_OPTION]; return [GEOGRAPHIC_LOCATION_OPTION];
}; };
export const getCommentFlagPublishOptionsForDirectory = (
directory: Pick<DirectoryCommunity, 'directoryCode' | 'features' | 'title'> | undefined,
selectedValue?: string,
): CommentFlagPublishOptions => {
if (directory?.features?.hasFlags !== true) {
return {
challengeRequest: undefined,
flairs: undefined,
};
}
const directoryCode = getDirectoryCode(directory);
if (directoryCode && AUTO_GEOGRAPHIC_FLAG_DIRECTORY_CODES.has(directoryCode)) {
return getCommentFlagPublishOptionsFromSelection(GEOGRAPHIC_LOCATION_FLAG_VALUE);
}
const flagOptions = getCommentFlagOptionsForDirectory(directory);
if (flagOptions.length === 0) {
return {
challengeRequest: undefined,
flairs: undefined,
};
}
return getCommentFlagPublishOptionsFromSelection(selectedValue ?? flagOptions[0]?.value);
};
export const getCommentFlagRequestFromSelection = (value: string | undefined): CommentFlagRequest | undefined => { export const getCommentFlagRequestFromSelection = (value: string | undefined): CommentFlagRequest | undefined => {
if (!value || value === NO_FLAG_VALUE) { if (!value || value === NO_FLAG_VALUE) {
return undefined; return undefined;