merge: show specific role in moderation posting warning

This commit is contained in:
Tommaso Casaburi
2026-06-01 12:48:47 +07:00
6 changed files with 47 additions and 6 deletions
@@ -1052,6 +1052,24 @@ describe('PostForm', () => {
expect(container.textContent).not.toContain('warning: posting as moderator');
});
it('uses the 5chan dev label for known developer moderator posts', async () => {
testState.account = {
author: { address: 'plebeius.bso', displayName: 'Tom' },
subscriptions: ['music-posting.eth'],
};
testState.resolvedCommunityAddress = 'music-posting.eth';
testState.rolesByCommunity = {
'music-posting.eth': {
'plebeius.bso': { role: 'owner' },
},
};
await renderPostForm('/mu');
await clickByText(container, 'start_new_thread');
expect(container.textContent).toContain('warning: posting as 5chan dev');
});
it('shows the pasted file-link filename next to the upload button', async () => {
testState.uploadedFileName = null;
+4 -1
View File
@@ -21,6 +21,7 @@ import {
} from '../../lib/utils/post-options-utils';
import { truncateWithEllipsisInMiddle } from '../../lib/utils/string-utils';
import { getPublishURLFilename, isValidPublishURL, isValidURL } from '../../lib/utils/url-utils';
import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils';
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getBoardPath } from '../../lib/utils/route-utils';
import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
@@ -536,6 +537,8 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
const roles = useCommunityField(effectiveBoardAddress, (community) => community?.roles);
const accountRole = accountAddress ? roles?.[accountAddress]?.role : undefined;
const showBbcodeToolbar = hasModQueueAccessRole(accountRole) || (!effectiveBoardAddress && isInModView && accountCommunityAddresses.length > 0);
const moderationPostingRoleLabel = getModerationPostingRoleLabel({ address: accountAddress, role: accountRole });
const moderationPostingWarning = showBbcodeToolbar && moderationPostingRoleLabel ? `warning: posting as ${moderationPostingRoleLabel}` : undefined;
const [lengthError, setLengthError] = useState<string | null>(null);
const [formError, setFormError] = useState<string | PostOptionsValidationError | null>(null);
@@ -877,7 +880,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
/>
</tbody>
</table>
{showBbcodeToolbar ? <div className={`${styles.error} ${styles.formError}`}>warning: posting as moderator</div> : null}
{moderationPostingWarning ? <div className={`${styles.error} ${styles.formError}`}>{moderationPostingWarning}</div> : null}
{formError ? (
<div className={`${styles.error} ${styles.formError}`}>
{isPostOptionsValidationError(formError) ? <PostOptionsErrorMessage error={formError} directories={directories} /> : formError}
@@ -775,8 +775,8 @@ describe('ReplyModal', () => {
expect(container.querySelector('select[aria-label="Text color"]')).toBeNull();
expect(container.textContent).not.toContain('mods only');
expect(container.textContent).not.toContain('Mod editor');
expect(container.textContent).toContain('warning: posting as moderator');
const moderatorWarning = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'warning: posting as moderator');
expect(container.textContent).toContain('warning: posting as admin');
const moderatorWarning = Array.from(container.querySelectorAll('div')).find((element) => element.textContent === 'warning: posting as admin');
expect(moderatorWarning?.className).toContain('error');
expect(container.querySelector('button[aria-label="Quote"]')).toBeNull();
@@ -794,7 +794,7 @@ describe('ReplyModal', () => {
expect(container.querySelector('button[aria-label="Red text"]')).toBeNull();
expect(container.textContent).not.toContain('mods only');
expect(container.textContent).not.toContain('warning: posting as moderator');
expect(container.textContent).not.toContain('warning: posting as admin');
});
it('updates account state, applies upload completions, and closes once publishing succeeds', async () => {
+4 -1
View File
@@ -18,6 +18,7 @@ import {
} from '../../lib/utils/post-options-utils';
import { getPublishURLFilename, isValidPublishURL } from '../../lib/utils/url-utils';
import { hasModQueueAccessRole } from '../../lib/utils/mod-access';
import { getModerationPostingRoleLabel } from '../../lib/utils/author-display-utils';
import { isAllView, isModView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useSelectedTextStore from '../../stores/use-selected-text-store';
import useReplyModalStore from '../../stores/use-reply-modal-store';
@@ -81,6 +82,8 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
const roles = useCommunityField(communityAddress, (community) => community?.roles);
const accountRole = accountAddress ? roles?.[accountAddress]?.role : undefined;
const showBbcodeToolbar = hasModQueueAccessRole(accountRole);
const moderationPostingRoleLabel = getModerationPostingRoleLabel({ address: accountAddress, role: accountRole });
const moderationPostingWarning = showBbcodeToolbar && moderationPostingRoleLabel ? `warning: posting as ${moderationPostingRoleLabel}` : undefined;
const textRef = useRef<HTMLTextAreaElement | null>(null);
const setTextRef = useRef((element: HTMLTextAreaElement | null) => {
textRef.current = element;
@@ -609,7 +612,7 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
{t('post')}
</button>
</div>
{showBbcodeToolbar ? <div className={styles.error}>warning: posting as moderator</div> : null}
{moderationPostingWarning ? <div className={styles.error}>{moderationPostingWarning}</div> : null}
{lengthError ? (
<div className={styles.error}>{lengthError}</div>
) : error ? (
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { KNOWN_5CHAN_DEVELOPER_ENTRIES, get5chanDeveloperBadge, getAuthorBadge, isKnown5chanDeveloper } from '../author-display-utils';
import { KNOWN_5CHAN_DEVELOPER_ENTRIES, get5chanDeveloperBadge, getAuthorBadge, getModerationPostingRoleLabel, isKnown5chanDeveloper } from '../author-display-utils';
describe('author display utils', () => {
it('recognizes the hardcoded 5chan developer addresses', () => {
@@ -61,4 +61,12 @@ describe('author display utils', () => {
title: 'administrator_of_this_board',
});
});
it('returns the posting warning label for the effective moderator role', () => {
expect(getModerationPostingRoleLabel({ address: 'plebeius.bso', role: 'owner' })).toBe('5chan dev');
expect(getModerationPostingRoleLabel({ address: 'other.bso', role: 'owner' })).toBe('owner');
expect(getModerationPostingRoleLabel({ address: 'other.bso', role: 'admin' })).toBe('admin');
expect(getModerationPostingRoleLabel({ address: 'other.bso', role: 'moderator' })).toBe('moderator');
expect(getModerationPostingRoleLabel({ address: 'other.bso', role: 'viewer' })).toBeUndefined();
});
});
+9
View File
@@ -51,3 +51,12 @@ export const getAuthorBadge = ({ address, role }: { address?: string; role?: str
title: isMod ? 'moderator_of_this_board' : 'administrator_of_this_board',
};
};
export const getModerationPostingRoleLabel = ({ address, role }: { address?: string; role?: string }): string | undefined => {
if (isKnown5chanDeveloper(address)) return '5chan dev';
const normalizedRole = role?.trim().toLowerCase();
if (normalizedRole === 'owner' || normalizedRole === 'admin' || normalizedRole === 'moderator') return normalizedRole;
return undefined;
};