Change: 絵文字リアクションの通知のグループ化で、アカウントを絵文字の種類ごとに表示 (#796)

* Change: 絵文字リアクションの通知のグループ化で、アカウントを絵文字の種類ごとに表示

* Fix lint

* アカウントの一括取得数を制限

* ストリーミング対応

* Fix

* Fix

* Fix

* Fix some problems

* Fix
This commit is contained in:
KMY(雪あすか) 2024-08-17 08:16:27 +09:00 committed by GitHub
parent 5dec110dec
commit f14c2d3ada
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 258 additions and 7 deletions

View file

@ -43,12 +43,29 @@ export type NotificationType =
| 'admin.sign_up'
| 'admin.report';
export interface NotifyEmojiReactionJSON {
name: string;
count: number;
me: boolean;
url?: string;
static_url?: string;
domain?: string;
width?: number;
height?: number;
}
export interface NotificationEmojiReactionGroupJSON {
emoji_reaction: NotifyEmojiReactionJSON;
sample_account_ids: string[];
}
export interface BaseNotificationJSON {
id: string;
type: NotificationType;
created_at: string;
group_key: string;
account: ApiAccountJSON;
emoji_reaction?: NotifyEmojiReactionJSON;
}
export interface BaseNotificationGroupJSON {
@ -60,6 +77,7 @@ export interface BaseNotificationGroupJSON {
most_recent_notification_id: string;
page_min_id?: string;
page_max_id?: string;
emoji_reaction_groups?: NotificationEmojiReactionGroupJSON[];
}
interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
@ -70,6 +88,7 @@ interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
interface NotificationWithStatusJSON extends BaseNotificationJSON {
type: NotificationWithStatusType;
status: ApiStatusJSON;
emoji_reaction?: NotifyEmojiReactionJSON;
}
interface ReportNotificationGroupJSON extends BaseNotificationGroupJSON {

View file

@ -32,6 +32,7 @@ export const NotificationEmojiReaction: React.FC<{
icon={EmojiReactionIcon}
iconId='star'
accountIds={notification.sampleAccountIds}
emojiReactionGroups={notification.emojiReactionGroups}
statusId={notification.statusId}
timestamp={notification.latest_page_notification_at}
count={notification.notifications_count}

View file

@ -6,9 +6,11 @@ import { HotKeys } from 'react-hotkeys';
import { replyComposeById } from 'mastodon/actions/compose';
import { navigateToStatus } from 'mastodon/actions/statuses';
import EmojiView from 'mastodon/components/emoji_view';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import type { EmojiReactionGroup } from 'mastodon/models/notification_group';
import { useAppDispatch } from 'mastodon/store';
import { AvatarGroup } from './avatar_group';
@ -26,6 +28,7 @@ export const NotificationGroupWithStatus: React.FC<{
actions?: JSX.Element;
count: number;
accountIds: string[];
emojiReactionGroups?: EmojiReactionGroup[];
timestamp: string;
labelRenderer: LabelRenderer;
labelSeeMoreHref?: string;
@ -36,6 +39,7 @@ export const NotificationGroupWithStatus: React.FC<{
iconId,
timestamp,
accountIds,
emojiReactionGroups,
actions,
count,
statusId,
@ -89,11 +93,28 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main'>
<div className='notification-group__main__header'>
<div className='notification-group__main__header__wrapper'>
<AvatarGroup accountIds={accountIds} />
{emojiReactionGroups?.map((group) => (
<div key={group.emoji.name}>
<div className='notification-group__main__header__wrapper__for_emoji_reaction'>
<EmojiView
name={group.emoji.name}
url={group.emoji.url}
staticUrl={group.emoji.static_url}
/>
<AvatarGroup accountIds={group.sampleAccountIds} />
{actions}
</div>
{actions}
</div>
</div>
))}
{!emojiReactionGroups && (
<div className='notification-group__main__header__wrapper'>
<AvatarGroup accountIds={accountIds} />
{actions}
</div>
)}
<div className='notification-group__main__header__label'>
{label}

View file

@ -6,6 +6,8 @@ import type {
ApiNotificationJSON,
NotificationType,
NotificationWithStatusType,
NotificationEmojiReactionGroupJSON,
NotifyEmojiReactionJSON,
} from 'mastodon/api_types/notifications';
import type { ApiReportJSON } from 'mastodon/api_types/reports';
@ -22,6 +24,23 @@ interface BaseNotificationWithStatus<Type extends NotificationWithStatusType>
extends BaseNotificationGroup {
type: Type;
statusId: string;
emojiReactionGroups?: EmojiReactionGroup[];
}
interface EmojiInfo {
name: string;
count: number;
me: boolean;
url?: string;
static_url?: string;
domain?: string;
width?: number;
height?: number;
}
export interface EmojiReactionGroup {
emoji: EmojiInfo;
sampleAccountIds: string[];
}
interface BaseNotification<Type extends NotificationType>
@ -119,6 +138,20 @@ function createAccountRelationshipSeveranceEventFromJSON(
return eventJson;
}
function createEmojiReactionGroupsFromJSON(
json: NotifyEmojiReactionJSON | undefined,
sampleAccountIds: string[],
): EmojiReactionGroup[] {
if (typeof json === 'undefined') return [];
return [
{
emoji: json,
sampleAccountIds,
},
];
}
export function createNotificationGroupFromJSON(
groupJson: ApiNotificationGroupJSON,
): NotificationGroup {
@ -126,7 +159,6 @@ export function createNotificationGroupFromJSON(
switch (group.type) {
case 'favourite':
case 'emoji_reaction':
case 'reblog':
case 'status':
case 'mention':
@ -140,6 +172,29 @@ export function createNotificationGroupFromJSON(
...groupWithoutStatus,
};
}
case 'emoji_reaction': {
const {
status_id: statusId,
emoji_reaction_groups: emojiReactionGroups,
...groupWithoutStatus
} = group;
const groups = (
typeof emojiReactionGroups === 'undefined'
? ([] as NotificationEmojiReactionGroupJSON[])
: emojiReactionGroups
).map((g) => {
return {
sampleAccountIds: g.sample_account_ids,
emoji: g.emoji_reaction,
} as EmojiReactionGroup;
});
return {
statusId,
sampleAccountIds,
emojiReactionGroups: groups,
...groupWithoutStatus,
};
}
case 'admin.report': {
const { report, ...groupWithoutTargetAccount } = group;
return {
@ -187,7 +242,6 @@ export function createNotificationGroupFromNotificationJSON(
switch (notification.type) {
case 'favourite':
case 'emoji_reaction':
case 'reblog':
case 'status':
case 'mention':
@ -195,6 +249,15 @@ export function createNotificationGroupFromNotificationJSON(
case 'poll':
case 'update':
return { ...group, statusId: notification.status.id };
case 'emoji_reaction':
return {
...group,
statusId: notification.status.id,
emojiReactionGroups: createEmojiReactionGroupsFromJSON(
notification.emoji_reaction,
group.sampleAccountIds,
),
};
case 'admin.report':
return { ...group, report: createReportFromJSON(notification.report) };
case 'severed_relationships':

View file

@ -211,6 +211,41 @@ function processNewNotification(
if (existingGroupIndex > -1) {
const existingGroup = groups[existingGroupIndex];
if (existingGroup && existingGroup.type !== 'gap') {
// Update emoji reaction emoji groups
if (existingGroup.type === 'emoji_reaction') {
const emojiReactionGroups = existingGroup.emojiReactionGroups;
const emojiReactionData = notification.emoji_reaction;
if (emojiReactionGroups && emojiReactionData) {
const sameEmojiIndex = emojiReactionGroups.findIndex(
(g) => g.emoji.name === emojiReactionData.name,
);
if (sameEmojiIndex > -1) {
const sameEmoji = emojiReactionGroups[sameEmojiIndex];
if (sameEmoji) {
if (
!sameEmoji.sampleAccountIds.includes(notification.account.id) &&
sameEmoji.sampleAccountIds.unshift(notification.account.id) >
NOTIFICATIONS_GROUP_MAX_AVATARS
)
sameEmoji.sampleAccountIds.pop();
emojiReactionGroups.splice(sameEmojiIndex, 1);
emojiReactionGroups.unshift(sameEmoji);
}
} else {
emojiReactionGroups.unshift({
emoji: emojiReactionData,
sampleAccountIds: [notification.account.id],
});
}
}
}
}
if (
existingGroup &&
existingGroup.type !== 'gap' &&