Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from
Aug 16, 2024
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
19 changes: 19 additions & 0 deletions app/javascript/mastodon/api_types/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -70,6 +88,7 @@ interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
interface NotificationWithStatusJSON extends BaseNotificationJSON {
type: NotificationWithStatusType;
status: ApiStatusJSON;
emoji_reaction?: NotifyEmojiReactionJSON;
}

interface ReportNotificationGroupJSON extends BaseNotificationGroupJSON {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,6 +28,7 @@ export const NotificationGroupWithStatus: React.FC<{
actions?: JSX.Element;
count: number;
accountIds: string[];
emojiReactionGroups?: EmojiReactionGroup[];
timestamp: string;
labelRenderer: LabelRenderer;
labelSeeMoreHref?: string;
Expand All @@ -36,6 +39,7 @@ export const NotificationGroupWithStatus: React.FC<{
iconId,
timestamp,
accountIds,
emojiReactionGroups,
actions,
count,
statusId,
Expand Down Expand Up @@ -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}
Expand Down
67 changes: 65 additions & 2 deletions app/javascript/mastodon/models/notification_group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
ApiNotificationJSON,
NotificationType,
NotificationWithStatusType,
NotificationEmojiReactionGroupJSON,
NotifyEmojiReactionJSON,
} from 'mastodon/api_types/notifications';
import type { ApiReportJSON } from 'mastodon/api_types/reports';

Expand All @@ -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>
Expand Down Expand Up @@ -119,14 +138,27 @@ 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 {
const { sample_account_ids: sampleAccountIds, ...group } = groupJson;

switch (group.type) {
case 'favourite':
case 'emoji_reaction':
case 'reblog':
case 'status':
case 'mention':
Expand All @@ -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 {
Expand Down Expand Up @@ -187,14 +242,22 @@ export function createNotificationGroupFromNotificationJSON(

switch (notification.type) {
case 'favourite':
case 'emoji_reaction':
case 'reblog':
case 'status':
case 'mention':
case 'status_reference':
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':
Expand Down
35 changes: 35 additions & 0 deletions app/javascript/mastodon/reducers/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' &&
Expand Down
19 changes: 19 additions & 0 deletions app/javascript/styles/mastodon/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10907,6 +10907,25 @@ noscript {
&__wrapper {
display: flex;
justify-content: space-between;

&__for_emoji_reaction {
display: flex;
justify-content: start;

.emoji {
display: inline-block;
width: 40px;
align-self: center;

img {
height: 20px;
}
}

.notification-group__avatar-group {
margin-left: 8px;
}
}
}

&__label {
Expand Down
24 changes: 23 additions & 1 deletion app/models/notification_group.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# frozen_string_literal: true

class NotificationGroup < ActiveModelSerializers::Model
attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id
attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id, :emoji_reaction_groups

# Try to keep this consistent with `app/javascript/mastodon/models/notification_group.ts`
SAMPLE_ACCOUNTS_SIZE = 8
SAMPLE_ACCOUNTS_SIZE_FOR_EMOJI_REACTION = 40

class NotificationEmojiReactionGroup < ActiveModelSerializers::Model
attributes :emoji_reaction, :sample_accounts
end

def self.from_notification(notification, max_id: nil)
if notification.group_key.present?
Expand All @@ -16,17 +21,22 @@ def self.from_notification(notification, max_id: nil)
most_recent_notifications = scope.order(id: :desc).includes(:from_account).take(SAMPLE_ACCOUNTS_SIZE)
most_recent_id = most_recent_notifications.first.id
sample_accounts = most_recent_notifications.map(&:from_account)
emoji_reaction_groups = extract_emoji_reaction_pair(
scope.order(id: :desc).includes(emoji_reaction: :account).take(SAMPLE_ACCOUNTS_SIZE_FOR_EMOJI_REACTION)
)
notifications_count = scope.count
else
most_recent_id = notification.id
sample_accounts = [notification.from_account]
emoji_reaction_groups = extract_emoji_reaction_pair([notification])
notifications_count = 1
end

NotificationGroup.new(
notification: notification,
group_key: notification.group_key || "ungrouped-#{notification.id}",
sample_accounts: sample_accounts,
emoji_reaction_groups: emoji_reaction_groups,
notifications_count: notifications_count,
most_recent_notification_id: most_recent_id
)
Expand All @@ -38,4 +48,16 @@ def self.from_notification(notification, max_id: nil)
:account_relationship_severance_event,
:account_warning,
to: :notification, prefix: false

def self.extract_emoji_reaction_pair(scope)
scope = scope.filter { |g| g.emoji_reaction.present? }

return [] if scope.empty?
return [] unless scope.first.type == :emoji_reaction

scope
.each_with_object({}) { |e, h| h[e.emoji_reaction.name] = (h[e.emoji_reaction.name] || []).push(e.emoji_reaction) }
.to_a
.map { |pair| NotificationEmojiReactionGroup.new(emoji_reaction: pair[1].first, sample_accounts: pair[1].take(SAMPLE_ACCOUNTS_SIZE).map(&:account)) }
end
end
15 changes: 15 additions & 0 deletions app/serializers/rest/notification_group_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def report_type?
object.type == :'admin.report'
end

def emoji_reaction_type?
object.type == :emoji_reaction
end

def relationship_severance_event?
object.type == :severed_relationships
end
Expand All @@ -56,4 +60,15 @@ def latest_page_notification_at
def paginated?
!instance_options[:group_metadata].nil?
end

class NotificationEmojiReactionGroupSerializer < ActiveModel::Serializer
has_one :emoji_reaction, serializer: REST::NotifyEmojiReactionSerializer
attribute :sample_account_ids

def sample_account_ids
object.sample_accounts.pluck(:id).map(&:to_s)
end
end

has_many :emoji_reaction_groups, each_serializer: NotificationEmojiReactionGroupSerializer, if: :emoji_reaction_type?
end
1 change: 1 addition & 0 deletions app/serializers/rest/notify_emoji_reaction_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

class REST::NotifyEmojiReactionSerializer < ActiveModel::Serializer
# Please update app/javascript/api_types/notification.ts when making changes to the attributes
include RoutingHelper

attributes :name
Expand Down
Loading
Loading