Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,13 @@ jobs:
fi

- name: Run React Doctor
if: github.event_name != 'pull_request' || steps.react-ui-changes.outputs.changed == 'true'
if: github.event_name != 'pull_request'
run: yarn doctor

- name: Run React Doctor on changed React files
if: github.event_name == 'pull_request' && steps.react-ui-changes.outputs.changed == 'true'
run: yarn doctor --diff "${{ github.event.pull_request.base.sha }}" --annotations

- name: Skip React Doctor
if: github.event_name == 'pull_request' && steps.react-ui-changes.outputs.changed != 'true'
run: echo "Skipping React Doctor because this pull request did not change React UI source."
Expand Down
3 changes: 2 additions & 1 deletion src/components/post-desktop/post-desktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { getThreadPostCountsByAuthor } from '../../lib/utils/author-post-counts'
import { withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getFeedPostHeightEstimate, getReplyHeightEstimates, reportReplyHeightAuditSample } from '../../lib/utils/pretext-height-estimates';
import { getAuthorBadge } from '../../lib/utils/author-display-utils';
import { hasCommentFlagsForDirectory } from '../../lib/comment-flag-selection';

const { addChallenge } = useChallengesStore.getState();

Expand Down Expand Up @@ -242,7 +243,7 @@ const PostInfo = ({
const directories = useDirectories();
const directoryEntry = communityAddress ? findDirectoryByAddress(directories, communityAddress) : undefined;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const showAuthorFlags = directoryEntry?.features?.hasFlags === true && !(deleted || removed || purged);
const showAuthorFlags = hasCommentFlagsForDirectory(directoryEntry) && !(deleted || removed || purged);
const postMenuProps = selectPostMenuProps(post);

const params = useParams();
Expand Down
3 changes: 2 additions & 1 deletion src/components/post-mobile/post-mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { useCurrentTime } from '../../hooks/use-current-time';
import { useBoardPseudonymityMode } from '../../hooks/use-board-pseudonymity-mode';
import CommentContent from '../comment-content';

Check warning on line 27 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/no-barrel-import

Import from barrel/index file — import directly from "../comment-content/comment-content" for better tree-shaking
import CommentMedia, { MediaLoadFailureInfo } from '../comment-media';
import FailedPublishNotice from '../failed-publish-notice';
import LoadingEllipsis from '../loading-ellipsis';
Expand Down Expand Up @@ -59,6 +59,7 @@
import { withResolvedCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getFeedPostHeightEstimate, getReplyHeightEstimates, reportReplyHeightAuditSample } from '../../lib/utils/pretext-height-estimates';
import { getAuthorBadge } from '../../lib/utils/author-display-utils';
import { hasCommentFlagsForDirectory } from '../../lib/comment-flag-selection';

const { addChallenge } = useChallengesStore.getState();

Expand All @@ -72,7 +73,7 @@
// Store scroll position for replies virtuoso across navigations
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};

const PostInfoAndMedia = ({

Check warning on line 76 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/no-giant-component

Component "PostInfoAndMedia" is 389 lines — consider breaking it into smaller focused components
onMediaLoadFailureChange,
post,
postReplyCount = 0,
Expand All @@ -89,7 +90,7 @@
const purged = resolvedPost?.commentModeration?.purged;
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
const directoryEntry = communityAddress ? findDirectoryByAddress(directories, communityAddress) : undefined;
const showAuthorFlags = directoryEntry?.features?.hasFlags === true && !(deleted || removed || purged);
const showAuthorFlags = hasCommentFlagsForDirectory(directoryEntry) && !(deleted || removed || purged);
const displayBoardPath =
boardPath && communityAddress
? boardPath !== communityAddress
Expand Down Expand Up @@ -333,7 +334,7 @@
<span
title={t('highlight_posts')}
className={styles.userAddress}
role='button'

Check warning on line 337 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/prefer-tag-over-role

Prefer the semantic `<button>` element over `role="button"` on a generic tag.
tabIndex={0}
onClick={() => handleUserAddressClick(userID, postCid)}
onKeyDown={(e) => {
Expand Down Expand Up @@ -408,7 +409,7 @@
<span
className={styles.replyToPost}
title={t('reply_to_post')}
role='button'

Check warning on line 412 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/prefer-tag-over-role

Prefer the semantic `<button>` element over `role="button"` on a generic tag.
tabIndex={0}
onMouseDown={onReplyModalClick}
onKeyDown={(e) => {
Expand Down Expand Up @@ -442,10 +443,10 @@
<LoadingEllipsis string={t('publishing')} />
) : (
<>
<button className={`button ${styles.approveButton}`} onClick={handlePendingApprove} disabled={isPublishingPending}>

Check warning on line 446 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/button-has-type

`<button>` elements must have an explicit `type` attribute.
{t('approve')}
</button>
<button className={`button ${styles.rejectButton}`} onClick={handlePendingReject} disabled={isPublishingPending}>

Check warning on line 449 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/button-has-type

`<button>` elements must have an explicit `type` attribute.
{t('reject')}
</button>
</>
Expand Down Expand Up @@ -591,7 +592,7 @@
);
};

const PostMobile = ({

Check warning on line 595 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/no-many-boolean-props

Component "PostMobile" takes 4 boolean-like props (showAllReplies, showReplies, isModQueue…) — consider compound components or explicit variants instead of stacking flags

Check warning on line 595 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/no-giant-component

Component "PostMobile" is 456 lines — consider breaking it into smaller focused components
feedVirtualizationModeOverride,
post,
roles,
Expand Down Expand Up @@ -624,13 +625,13 @@
const modQueueThreadRouteState = getQueuedCommentRouteState(resolvedPost);
const modQueueErrorMessage = formatErrorForDisplay(modQueueError);
const modQueueRemoveButton = onRemoveFromModQueue ? (
<button className='button' onClick={onRemoveFromModQueue} disabled={isPublishing}>

Check warning on line 628 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/button-has-type

`<button>` elements must have an explicit `type` attribute.
{capitalize(t('modQueue.dismiss'))}
</button>
) : null;
const linksCount = useCountLinksInReplies(resolvedPost);
const deleteFailedPostRedirectPath = isInPendingPostView ? (boardPath ? `/${boardPath}` : '/') : undefined;
const hasReplyPaginationOverride = !!replyPaginationOverride;

Check warning on line 634 in src/components/post-mobile/post-mobile.tsx

View workflow job for this annotation

GitHub Actions / Quality + Smoke

react-doctor/no-event-handler

Avoid using props and effects as an event handler. Instead, move the handler to the parent component.
const shouldFetchReplies = showReplies && !isModQueue && !hasReplyPaginationOverride;
const shouldUsePreview = shouldFetchReplies && !showAllReplies;
const cachedPreviewRepliesResult = useReplies({
Expand Down
24 changes: 24 additions & 0 deletions src/lib/__tests__/board-flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ describe('board-flags', () => {
x: 0,
y: 0,
});
expect(getBoardFlagDefinition('pol', 'MZ')).toMatchObject({
code: 'MZ',
label: 'Task Force Z',
x: 64,
y: 24,
});
expect(getBoardFlagDefinition('pol', 'NB')).toMatchObject({
code: 'NB',
label: 'National Bolshevik',
x: 0,
y: 36,
});
expect(getBoardFlagDefinition('pol', 'RE')).toMatchObject({
code: 'RE',
label: 'Republican',
x: 0,
y: 48,
});
expect(getBoardFlagDefinition('pol', 'TM')).toMatchObject({
code: 'TM',
label: 'Templar',
x: 16,
y: 48,
});
expect(getBoardFlagDefinition('pol', 'WP')).toMatchObject({ code: 'WP', x: 64, y: 48 });
});

Expand Down
14 changes: 14 additions & 0 deletions src/lib/__tests__/comment-flag-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import {
getCommentFlagPublishOptionsForDirectory,
getCommentFlagPublishOptionsFromSelection,
getCommentFlagRequestFromSelection,
hasCommentFlagsForDirectory,
} from '../comment-flag-selection';

describe('comment-flag-selection', () => {
it('does not expose options for boards without flags', () => {
expect(getCommentFlagOptionsForDirectory({ features: {}, title: '/mu/ - Music' })).toEqual([]);
});

it('detects flag-capable directories from directory metadata', () => {
expect(hasCommentFlagsForDirectory({ features: {}, title: '/mu/ - Music' })).toBe(false);
expect(hasCommentFlagsForDirectory({ features: { hasFlags: true }, title: '/pol/ - Politically Incorrect' })).toBe(true);
});

it('uses geographic location as the default for country flag boards', () => {
expect(
getCommentFlagOptionsForDirectory({
Expand Down Expand Up @@ -59,6 +65,14 @@ describe('comment-flag-selection', () => {
{ label: 'Confederate', value: 'pol:CF' },
{ label: 'Communist', value: 'pol:CM' },
]);
expect(options.slice(-6)).toEqual([
{ label: 'Republican', value: 'pol:RE' },
{ label: 'Task Force Z', value: 'pol:MZ' },
{ label: 'Templar', value: 'pol:TM' },
{ label: 'Tree Hugger', value: 'pol:TR' },
{ label: 'United Nations', value: 'pol:UN' },
{ label: 'White Supremacist', value: 'pol:WP' },
]);
});

it('matches the 4chan /mlp/ flag default', () => {
Expand Down
69 changes: 55 additions & 14 deletions src/lib/board-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const PONY_FLAG_HEIGHT = 16;
const PONY_FLAG_COLUMNS = 9;
const PONY_FLAG_SPRITE_PATH = 'assets/icons/flags-pony.png';

// Order follows 4chan's board flag selector. Coordinates follow flags/pol/flags.2.css.
const POLITICAL_FLAG_ENTRIES = [
['AC', 'Anarcho-Capitalist'],
['AN', 'Anarchist'],
Expand All @@ -42,13 +43,41 @@ const POLITICAL_FLAG_ENTRIES = [
['PC', 'Hippie'],
['PR', 'Pirate'],
['RE', 'Republican'],
['TM', 'Templar'],
['MZ', 'Task Force Z'],
['TM', 'Templar'],
['TR', 'Tree Hugger'],
['UN', 'United Nations'],
['WP', 'White Supremacist'],
] as const;

const POLITICAL_FLAG_COORDINATES = {
AC: [0, 0],
AN: [16, 0],
BL: [32, 0],
CF: [48, 0],
CM: [64, 0],
CT: [0, 12],
DM: [16, 12],
EU: [32, 12],
FC: [48, 12],
GN: [64, 12],
GY: [0, 24],
JH: [16, 24],
KN: [32, 24],
MF: [48, 24],
MZ: [64, 24],
NB: [0, 36],
NT: [16, 36],
NZ: [32, 36],
PC: [48, 36],
PR: [64, 36],
RE: [0, 48],
TM: [16, 48],
TR: [32, 48],
UN: [48, 48],
WP: [64, 48],
} as const satisfies Record<(typeof POLITICAL_FLAG_ENTRIES)[number][0], readonly [number, number]>;

const PONY_FLAG_ENTRIES = [
['4CC', '4cc /mlp/'],
['ADA', 'Adagio Dazzle'],
Expand Down Expand Up @@ -144,25 +173,37 @@ const buildFlagDefinitions = (
width: number,
height: number,
columns: number,
coordinatesByCode?: Readonly<Record<string, readonly [number, number]>>,
): ReadonlyMap<string, BoardFlagDefinition> =>
new Map(
entries.map(([code, label], index) => [
code,
{
kind,
entries.map(([code, label], index) => {
const [x, y] = coordinatesByCode?.[code] ?? [(index % columns) * width, Math.floor(index / columns) * height];
return [
code,
label,
spritePath,
width,
height,
x: (index % columns) * width,
y: Math.floor(index / columns) * height,
},
]),
{
kind,
code,
label,
spritePath,
width,
height,
x,
y,
},
] as const;
}),
);

const BOARD_FLAGS_BY_KIND: Record<BoardFlagKind, ReadonlyMap<string, BoardFlagDefinition>> = {
pol: buildFlagDefinitions(POLITICAL_FLAG_ENTRIES, 'pol', POLITICAL_FLAG_SPRITE_PATH, POLITICAL_FLAG_WIDTH, POLITICAL_FLAG_HEIGHT, POLITICAL_FLAG_COLUMNS),
pol: buildFlagDefinitions(
POLITICAL_FLAG_ENTRIES,
'pol',
POLITICAL_FLAG_SPRITE_PATH,
POLITICAL_FLAG_WIDTH,
POLITICAL_FLAG_HEIGHT,
POLITICAL_FLAG_COLUMNS,
POLITICAL_FLAG_COORDINATES,
),
pony: buildFlagDefinitions(PONY_FLAG_ENTRIES, 'pony', PONY_FLAG_SPRITE_PATH, PONY_FLAG_WIDTH, PONY_FLAG_HEIGHT, PONY_FLAG_COLUMNS),
};

Expand Down
17 changes: 10 additions & 7 deletions src/lib/comment-flag-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface CommentFlagPublishOptions {
flairs: CommentFlagRequest[] | undefined;
}

export type CommentFlagDirectory = Partial<Pick<DirectoryCommunity, 'directoryCode' | 'features' | 'title'>>;

const GEOGRAPHIC_LOCATION_OPTION: CommentFlagSelectOption = {
value: GEOGRAPHIC_LOCATION_FLAG_VALUE,
label: 'Geographic Location',
Expand All @@ -43,14 +45,18 @@ const getDirectoryCode = (directory: Pick<DirectoryCommunity, 'directoryCode' |
return directoryCode || directory?.title?.match(/^\/([^/]+)\//)?.[1]?.toLowerCase();
};

export const hasCommentFlagsForDirectory = (directory: CommentFlagDirectory | undefined): boolean => {
return directory?.features?.hasFlags === true;
};

const getBoardFlagOptions = (kind: BoardFlagKind): CommentFlagSelectOption[] =>
getBoardFlagDefinitions(kind).map((flag) => ({
value: `${flag.kind}:${flag.code}`,
label: flag.label,
}));

export const getCommentFlagOptionsForDirectory = (directory: Pick<DirectoryCommunity, 'directoryCode' | 'features' | 'title'> | undefined): CommentFlagSelectOption[] => {
if (directory?.features?.hasFlags !== true) {
export const getCommentFlagOptionsForDirectory = (directory: CommentFlagDirectory | undefined): CommentFlagSelectOption[] => {
if (!hasCommentFlagsForDirectory(directory)) {
return [];
}

Expand All @@ -70,11 +76,8 @@ export const getCommentFlagOptionsForDirectory = (directory: Pick<DirectoryCommu
return [GEOGRAPHIC_LOCATION_OPTION];
};

export const getCommentFlagPublishOptionsForDirectory = (
directory: Pick<DirectoryCommunity, 'directoryCode' | 'features' | 'title'> | undefined,
selectedValue?: string,
): CommentFlagPublishOptions => {
if (directory?.features?.hasFlags !== true) {
export const getCommentFlagPublishOptionsForDirectory = (directory: CommentFlagDirectory | undefined, selectedValue?: string): CommentFlagPublishOptions => {
if (!hasCommentFlagsForDirectory(directory)) {
return {
challengeRequest: undefined,
flairs: undefined,
Expand Down
Loading