diff --git a/app/javascript/mastodon/api_types/dummy_types.ts b/app/javascript/mastodon/api_types/dummy_types.ts new file mode 100644 index 0000000000..1f8c4db6f2 --- /dev/null +++ b/app/javascript/mastodon/api_types/dummy_types.ts @@ -0,0 +1,11 @@ +// A similar definition will eventually be added in the main house. These definitions will replace it. + +export interface ApiListJSON_KmyDummy { + id: string; + title: string; + exclusive: boolean; + notify: boolean; + + // replies_policy + // antennas +} diff --git a/app/javascript/mastodon/api_types/notifications.ts b/app/javascript/mastodon/api_types/notifications.ts index 114f20b48c..f384817b4b 100644 --- a/app/javascript/mastodon/api_types/notifications.ts +++ b/app/javascript/mastodon/api_types/notifications.ts @@ -3,6 +3,7 @@ import type { AccountWarningAction } from 'mastodon/models/notification_group'; import type { ApiAccountJSON } from './accounts'; +import type { ApiListJSON_KmyDummy } from './dummy_types'; import type { ApiReportJSON } from './reports'; import type { ApiStatusJSON } from './statuses'; @@ -17,6 +18,7 @@ export const allNotificationTypes = [ 'status_reference', 'poll', 'status', + 'list_status', 'update', 'admin.sign_up', 'admin.report', @@ -29,6 +31,7 @@ export type NotificationWithStatusType = | 'emoji_reaction' | 'reblog' | 'status' + | 'list_status' | 'mention' | 'status_reference' | 'poll' @@ -78,6 +81,7 @@ export interface BaseNotificationGroupJSON { page_min_id?: string; page_max_id?: string; emoji_reaction_groups?: NotificationEmojiReactionGroupJSON[]; + list?: ApiListJSON_KmyDummy; } interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON { diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx index a21c73e961..317ed56f42 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx @@ -13,6 +13,7 @@ import { NotificationEmojiReaction } from './notification_emoji_reaction'; import { NotificationFavourite } from './notification_favourite'; import { NotificationFollow } from './notification_follow'; import { NotificationFollowRequest } from './notification_follow_request'; +import { NotificationListStatus } from './notification_list_status'; import { NotificationMention } from './notification_mention'; import { NotificationModerationWarning } from './notification_moderation_warning'; import { NotificationPoll } from './notification_poll'; @@ -132,6 +133,14 @@ export const NotificationGroup: React.FC<{ ); break; + case 'list_status': + content = ( + + ); + break; case 'update': content = ( diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx new file mode 100644 index 0000000000..60230874f6 --- /dev/null +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_list_status.tsx @@ -0,0 +1,58 @@ +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; +import type { NotificationGroupListStatus } from 'mastodon/models/notification_group'; + +import type { LabelRenderer } from './notification_group_with_status'; +import { NotificationWithStatus } from './notification_with_status'; + +const createLabelRenderer = ( + notification: NotificationGroupListStatus, +): LabelRenderer => { + const renderer: LabelRenderer = (values) => { + const list = notification.list; + if (list) { + const listLink = ( + + + {list.title} + + + ); + values.listName = listLink; + } + + return ( + + ); + }; + return renderer; +}; + +export const NotificationListStatus: React.FC<{ + notification: NotificationGroupListStatus; + unread: boolean; +}> = ({ notification, unread }) => ( + +); diff --git a/app/javascript/mastodon/models/notification_group.ts b/app/javascript/mastodon/models/notification_group.ts index 402b1f3729..8ed43db685 100644 --- a/app/javascript/mastodon/models/notification_group.ts +++ b/app/javascript/mastodon/models/notification_group.ts @@ -54,6 +54,8 @@ export type NotificationGroupEmojiReaction = BaseNotificationWithStatus<'emoji_reaction'>; export type NotificationGroupReblog = BaseNotificationWithStatus<'reblog'>; export type NotificationGroupStatus = BaseNotificationWithStatus<'status'>; +export type NotificationGroupListStatus = + BaseNotificationWithStatus<'list_status'>; export type NotificationGroupMention = BaseNotificationWithStatus<'mention'>; export type NotificationGroupStatusReference = BaseNotificationWithStatus<'status_reference'>; @@ -103,6 +105,7 @@ export type NotificationGroup = | NotificationGroupEmojiReaction | NotificationGroupReblog | NotificationGroupStatus + | NotificationGroupListStatus | NotificationGroupMention | NotificationGroupStatusReference | NotificationGroupPoll @@ -161,6 +164,7 @@ export function createNotificationGroupFromJSON( case 'favourite': case 'reblog': case 'status': + case 'list_status': case 'mention': case 'status_reference': case 'poll': @@ -244,6 +248,7 @@ export function createNotificationGroupFromNotificationJSON( case 'favourite': case 'reblog': case 'status': + case 'list_status': case 'mention': case 'status_reference': case 'poll': diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index a66f352fd3..e6634fe3ce 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class NotificationGroup < ActiveModelSerializers::Model - attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id, :emoji_reaction_groups + attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id, :emoji_reaction_groups, :list # Try to keep this consistent with `app/javascript/mastodon/models/notification_group.ts` SAMPLE_ACCOUNTS_SIZE = 8 @@ -18,17 +18,19 @@ class NotificationGroup < ActiveModelSerializers::Model scope = scope.where(id: ..max_id) if max_id.present? # Ideally, we would not load accounts for each notification group - most_recent_notifications = scope.order(id: :desc).includes(:from_account).take(SAMPLE_ACCOUNTS_SIZE) + most_recent_notifications = scope.order(id: :desc).includes(:from_account, :list_status).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) ) + list = pick_list(most_recent_notifications) notifications_count = scope.count else most_recent_id = notification.id sample_accounts = [notification.from_account] emoji_reaction_groups = extract_emoji_reaction_pair([notification]) + list = pick_list([notification]) notifications_count = 1 end @@ -37,6 +39,7 @@ class NotificationGroup < ActiveModelSerializers::Model group_key: notification.group_key || "ungrouped-#{notification.id}", sample_accounts: sample_accounts, emoji_reaction_groups: emoji_reaction_groups, + list: list, notifications_count: notifications_count, most_recent_notification_id: most_recent_id ) @@ -50,14 +53,23 @@ class NotificationGroup < ActiveModelSerializers::Model 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 = scope.filter { |g| g.emoji_reaction.present? } + return [] if scope.empty? + 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 + + def self.pick_list(scope) + return [] unless scope.first.type == :list_status + + scope = scope.filter { |g| g.list_status.present? } + return [] if scope.empty? + + scope.first.list_status.list + end end diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb index e10dfded88..4733d73e20 100644 --- a/app/serializers/rest/notification_group_serializer.rb +++ b/app/serializers/rest/notification_group_serializer.rb @@ -13,6 +13,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer + has_one :list, if: :list_status_type?, serializer: REST::ListSerializer def sample_account_ids object.sample_accounts.pluck(:id).map(&:to_s) @@ -23,7 +24,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer end def status_type? - [:favourite, :emoji_reaction, :reblog, :status, :mention, :status_reference, :poll, :update].include?(object.type) + [:favourite, :emoji_reaction, :reblog, :status, :mention, :status_reference, :poll, :update, :list_status].include?(object.type) end def report_type? @@ -34,6 +35,10 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer object.type == :emoji_reaction end + def list_status_type? + object.type == :list_status + end + def relationship_severance_event? object.type == :severed_relationships end