Compare commits
4 Commits
main
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7933543ef | ||
|
|
4be16a3e9e | ||
|
|
43c9f0b5f2 | ||
|
|
662ef2b85b |
@ -31,13 +31,15 @@ class Api::V2Alpha::NotificationsController < Api::BaseController
|
||||
'app.notification_grouping.status.unique_count' => statuses.uniq.size
|
||||
)
|
||||
|
||||
render json: @grouped_notifications, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata
|
||||
presenter = GroupedNotificationsPresenter.new(@grouped_notifications)
|
||||
render json: presenter, serializer: REST::DedupNotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:id])
|
||||
render json: NotificationGroup.from_notification(@notification), serializer: REST::NotificationGroupSerializer
|
||||
presenter = GroupedNotificationsPresenter.new([NotificationGroup.from_notification(@notification)])
|
||||
render json: presenter, serializer: REST::DedupNotificationGroupSerializer
|
||||
end
|
||||
|
||||
def clear
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type {
|
||||
ApiAccountJSON,
|
||||
ShallowApiAccountJSON,
|
||||
} from 'mastodon/api_types/accounts';
|
||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
|
||||
export const revealAccount = createAction<{
|
||||
@ -11,6 +14,10 @@ export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
|
||||
'accounts/importAccounts',
|
||||
);
|
||||
|
||||
export const importShallowAccounts = createAction<{
|
||||
accounts: ShallowApiAccountJSON[];
|
||||
}>('accounts/importShallowAccounts');
|
||||
|
||||
function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
|
||||
return {
|
||||
payload: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { importAccounts } from '../accounts_typed';
|
||||
|
||||
import { normalizeStatus, normalizePoll } from './normalizer';
|
||||
import { normalizeStatus, normalizeShallowStatus, normalizePoll } from './normalizer';
|
||||
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
@ -49,6 +49,10 @@ export function importFetchedAccounts(accounts) {
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
}
|
||||
|
||||
export function importShallowFetchedAccounts(accounts) {
|
||||
return importAccounts({ accounts });
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status) {
|
||||
return importFetchedStatuses([status]);
|
||||
}
|
||||
@ -90,6 +94,32 @@ export function importFetchedStatuses(statuses) {
|
||||
};
|
||||
}
|
||||
|
||||
export function importShallowStatuses(statuses) {
|
||||
return (dispatch, getState) => {
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
const filters = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeShallowStatus(status, getState().getIn(['statuses', status.id])));
|
||||
|
||||
if (status.filtered) {
|
||||
status.filtered.forEach(result => pushUnique(filters, result.filter));
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
|
||||
dispatch(importPolls(polls));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
dispatch(importFilters(filters));
|
||||
};
|
||||
}
|
||||
|
||||
export function importFetchedPoll(poll) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(importPolls([normalizePoll(poll, getState().getIn(['polls', poll.id]))]));
|
||||
|
||||
@ -25,13 +25,34 @@ export function normalizeFilterResult(result) {
|
||||
}
|
||||
|
||||
export function normalizeStatus(status, normalOldStatus) {
|
||||
const normalStatus = { ...status };
|
||||
normalStatus.account = status.account.id;
|
||||
const { account, reblog, ...normalStatus } = status;
|
||||
|
||||
if (status.reblog && status.reblog.id) {
|
||||
normalStatus.reblog = status.reblog.id;
|
||||
normalStatus.account_id = account.id;
|
||||
|
||||
if (reblog && reblog.id) {
|
||||
normalStatus.reblog_id = reblog.id;
|
||||
}
|
||||
|
||||
if (status.card) {
|
||||
normalStatus.card = {
|
||||
...status.card,
|
||||
authors: status.card.authors.map(author => ({
|
||||
...author,
|
||||
account_id: author.account?.id,
|
||||
account: undefined,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return normalizeShallowStatus(normalStatus, normalOldStatus);
|
||||
}
|
||||
|
||||
export function normalizeShallowStatus(status, normalOldStatus) {
|
||||
const { account_id, reblog_id, ...normalStatus } = status;
|
||||
|
||||
normalStatus.account = account_id;
|
||||
normalStatus.reblog = reblog_id;
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
normalStatus.poll = status.poll.id;
|
||||
}
|
||||
@ -41,8 +62,8 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
...status.card,
|
||||
authors: status.card.authors.map(author => ({
|
||||
...author,
|
||||
accountId: author.account?.id,
|
||||
account: undefined,
|
||||
accountId: author.account_id,
|
||||
account_id: undefined,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
apiFetchNotifications,
|
||||
} from 'mastodon/api/notifications';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { ApiGenericJSON } from 'mastodon/api_types/generic';
|
||||
import type {
|
||||
ApiNotificationGroupJSON,
|
||||
ApiNotificationJSON,
|
||||
@ -22,7 +23,12 @@ import {
|
||||
createDataLoadingThunk,
|
||||
} from 'mastodon/store/typed_functions';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||
import { importShallowAccounts } from './accounts_typed';
|
||||
import {
|
||||
importFetchedAccounts,
|
||||
importFetchedStatuses,
|
||||
importShallowStatuses,
|
||||
} from './importer';
|
||||
import { NOTIFICATIONS_FILTER_SET } from './notifications';
|
||||
import { saveSettings } from './settings';
|
||||
|
||||
@ -32,16 +38,12 @@ function excludeAllTypesExcept(filter: string) {
|
||||
|
||||
function dispatchAssociatedRecords(
|
||||
dispatch: AppDispatch,
|
||||
notifications: ApiNotificationGroupJSON[] | ApiNotificationJSON[],
|
||||
notifications: ApiNotificationJSON[],
|
||||
) {
|
||||
const fetchedAccounts: ApiAccountJSON[] = [];
|
||||
const fetchedStatuses: ApiStatusJSON[] = [];
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
if ('sample_accounts' in notification) {
|
||||
fetchedAccounts.push(...notification.sample_accounts);
|
||||
}
|
||||
|
||||
if (notification.type === 'admin.report') {
|
||||
fetchedAccounts.push(notification.report.target_account);
|
||||
}
|
||||
@ -62,6 +64,17 @@ function dispatchAssociatedRecords(
|
||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||
}
|
||||
|
||||
function dispatchGenericAssociatedRecords(
|
||||
dispatch: AppDispatch,
|
||||
apiResult: ApiGenericJSON,
|
||||
) {
|
||||
if (apiResult.accounts.length > 0)
|
||||
dispatch(importShallowAccounts({ accounts: apiResult.accounts }));
|
||||
|
||||
if (apiResult.statuses.length > 0)
|
||||
dispatch(importShallowStatuses(apiResult.statuses));
|
||||
}
|
||||
|
||||
export const fetchNotifications = createDataLoadingThunk(
|
||||
'notificationGroups/fetch',
|
||||
async (_params, { getState }) => {
|
||||
@ -75,15 +88,18 @@ export const fetchNotifications = createDataLoadingThunk(
|
||||
: excludeAllTypesExcept(activeFilter),
|
||||
});
|
||||
},
|
||||
({ notifications }, { dispatch }) => {
|
||||
dispatchAssociatedRecords(dispatch, notifications);
|
||||
({ apiResult }, { dispatch }) => {
|
||||
dispatchGenericAssociatedRecords(dispatch, apiResult);
|
||||
const payload: (ApiNotificationGroupJSON | NotificationGap)[] =
|
||||
notifications;
|
||||
apiResult.notification_groups;
|
||||
|
||||
// TODO: might be worth not using gaps for that…
|
||||
// if (nextLink) payload.push({ type: 'gap', loadUrl: nextLink.uri });
|
||||
if (notifications.length > 1)
|
||||
payload.push({ type: 'gap', maxId: notifications.at(-1)?.page_min_id });
|
||||
if (apiResult.notification_groups.length > 1)
|
||||
payload.push({
|
||||
type: 'gap',
|
||||
maxId: apiResult.notification_groups.at(-1)?.page_min_id,
|
||||
});
|
||||
|
||||
return payload;
|
||||
// dispatch(submitMarkers());
|
||||
@ -95,10 +111,10 @@ export const fetchNotificationsGap = createDataLoadingThunk(
|
||||
async (params: { gap: NotificationGap }) =>
|
||||
apiFetchNotifications({ max_id: params.gap.maxId }),
|
||||
|
||||
({ notifications }, { dispatch }) => {
|
||||
dispatchAssociatedRecords(dispatch, notifications);
|
||||
({ apiResult }, { dispatch }) => {
|
||||
dispatchGenericAssociatedRecords(dispatch, apiResult);
|
||||
|
||||
return { notifications };
|
||||
return { apiResult };
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import api, { apiRequest, getLinks } from 'mastodon/api';
|
||||
import type { ApiNotificationGroupJSON } from 'mastodon/api_types/notifications';
|
||||
import type { ApiGenericJSON } from 'mastodon/api_types/generic';
|
||||
|
||||
export const apiFetchNotifications = async (params?: {
|
||||
exclude_types?: string[];
|
||||
max_id?: string;
|
||||
}) => {
|
||||
const response = await api().request<ApiNotificationGroupJSON[]>({
|
||||
const response = await api().request<ApiGenericJSON>({
|
||||
method: 'GET',
|
||||
url: '/api/v2_alpha/notifications',
|
||||
params,
|
||||
});
|
||||
|
||||
return { notifications: response.data, links: getLinks(response) };
|
||||
return { apiResult: response.data, links: getLinks(response) };
|
||||
};
|
||||
|
||||
export const apiClearNotifications = () =>
|
||||
|
||||
@ -12,8 +12,8 @@ export interface ApiAccountRoleJSON {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// See app/serializers/rest/account_serializer.rb
|
||||
export interface ApiAccountJSON {
|
||||
// See app/serializers/rest/base_account_serializer.rb
|
||||
export interface BaseApiAccountJSON {
|
||||
acct: string;
|
||||
avatar: string;
|
||||
avatar_static: string;
|
||||
@ -34,14 +34,21 @@ export interface ApiAccountJSON {
|
||||
locked: boolean;
|
||||
noindex?: boolean;
|
||||
note: string;
|
||||
roles?: ApiAccountJSON[];
|
||||
roles?: ApiAccountRoleJSON[];
|
||||
statuses_count: number;
|
||||
uri: string;
|
||||
url: string;
|
||||
username: string;
|
||||
moved?: ApiAccountJSON;
|
||||
suspended?: boolean;
|
||||
limited?: boolean;
|
||||
memorial?: boolean;
|
||||
hide_collections: boolean;
|
||||
}
|
||||
|
||||
export interface ApiAccountJSON extends BaseApiAccountJSON {
|
||||
moved?: ApiAccountJSON;
|
||||
}
|
||||
|
||||
export interface ShallowApiAccountJSON extends BaseApiAccountJSON {
|
||||
moved_to_account_id?: string;
|
||||
}
|
||||
|
||||
9
app/javascript/mastodon/api_types/generic.ts
Normal file
9
app/javascript/mastodon/api_types/generic.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type { ShallowApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { ApiNotificationGroupJSON } from 'mastodon/api_types/notifications';
|
||||
import type { ShallowApiStatusJSON } from 'mastodon/api_types/statuses';
|
||||
|
||||
export interface ApiGenericJSON {
|
||||
statuses: ShallowApiStatusJSON[];
|
||||
accounts: ShallowApiAccountJSON[];
|
||||
notification_groups: ApiNotificationGroupJSON[];
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
import type { AccountWarningAction } from 'mastodon/models/notification_group';
|
||||
|
||||
import type { ApiAccountJSON } from './accounts';
|
||||
import type { ApiReportJSON } from './reports';
|
||||
import type { ApiReportJSON, ShallowApiReportJSON } from './reports';
|
||||
import type { ApiStatusJSON } from './statuses';
|
||||
|
||||
// See app/model/notification.rb
|
||||
@ -51,7 +51,7 @@ export interface BaseNotificationGroupJSON {
|
||||
group_key: string;
|
||||
notifications_count: number;
|
||||
type: NotificationType;
|
||||
sample_accounts: ApiAccountJSON[];
|
||||
sample_account_ids: string[];
|
||||
latest_page_notification_at: string; // FIXME: This will only be present if the notification group is returned in a paginated list, not requested directly
|
||||
most_recent_notification_id: string;
|
||||
page_min_id?: string;
|
||||
@ -60,7 +60,7 @@ export interface BaseNotificationGroupJSON {
|
||||
|
||||
interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
|
||||
type: NotificationWithStatusType;
|
||||
status: ApiStatusJSON;
|
||||
status_id: string;
|
||||
}
|
||||
|
||||
interface NotificationWithStatusJSON extends BaseNotificationJSON {
|
||||
@ -70,7 +70,7 @@ interface NotificationWithStatusJSON extends BaseNotificationJSON {
|
||||
|
||||
interface ReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'admin.report';
|
||||
report: ApiReportJSON;
|
||||
report: ShallowApiReportJSON;
|
||||
}
|
||||
|
||||
interface ReportNotificationJSON extends BaseNotificationJSON {
|
||||
@ -87,20 +87,28 @@ interface SimpleNotificationJSON extends BaseNotificationJSON {
|
||||
type: SimpleNotificationTypes;
|
||||
}
|
||||
|
||||
export interface ApiAccountWarningJSON {
|
||||
export interface BaseApiAccountWarningJSON {
|
||||
id: string;
|
||||
action: AccountWarningAction;
|
||||
text: string;
|
||||
status_ids: string[];
|
||||
created_at: string;
|
||||
target_account: ApiAccountJSON;
|
||||
appeal: unknown;
|
||||
}
|
||||
|
||||
export interface ApiAccountWarningJSON extends BaseApiAccountWarningJSON {
|
||||
target_account: ApiAccountJSON;
|
||||
}
|
||||
|
||||
export interface ShallowApiAccountWarningJSON
|
||||
extends BaseApiAccountWarningJSON {
|
||||
target_account_id: string;
|
||||
}
|
||||
|
||||
interface ModerationWarningNotificationGroupJSON
|
||||
extends BaseNotificationGroupJSON {
|
||||
type: 'moderation_warning';
|
||||
moderation_warning: ApiAccountWarningJSON;
|
||||
moderation_warning: ShallowApiAccountWarningJSON;
|
||||
}
|
||||
|
||||
interface ModerationWarningNotificationJSON extends BaseNotificationJSON {
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ApiAccountJSON } from './accounts';
|
||||
|
||||
export type ReportCategory = 'other' | 'spam' | 'legal' | 'violation';
|
||||
|
||||
export interface ApiReportJSON {
|
||||
export interface BaseApiReportJSON {
|
||||
id: string;
|
||||
action_taken: unknown;
|
||||
action_taken_at: unknown;
|
||||
@ -12,5 +12,12 @@ export interface ApiReportJSON {
|
||||
created_at: string;
|
||||
status_ids: string[];
|
||||
rule_ids: string[];
|
||||
}
|
||||
|
||||
export interface ApiReportJSON extends BaseApiReportJSON {
|
||||
target_account: ApiAccountJSON;
|
||||
}
|
||||
|
||||
export interface ShallowApiReportJSON extends BaseApiReportJSON {
|
||||
target_account_id: string;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// See app/serializers/rest/status_serializer.rb
|
||||
// See app/serializers/rest/base_status_serializer.rb
|
||||
|
||||
import type { ApiAccountJSON } from './accounts';
|
||||
import type { ApiCustomEmojiJSON } from './custom_emoji';
|
||||
@ -58,7 +58,7 @@ export interface ApiPreviewCardJSON {
|
||||
authors: ApiPreviewCardAuthorJSON[];
|
||||
}
|
||||
|
||||
export interface ApiStatusJSON {
|
||||
export interface BaseApiStatusJSON {
|
||||
id: string;
|
||||
created_at: string;
|
||||
in_reply_to_id?: string;
|
||||
@ -85,9 +85,7 @@ export interface ApiStatusJSON {
|
||||
content?: string;
|
||||
text?: string;
|
||||
|
||||
reblog?: ApiStatusJSON;
|
||||
application?: ApiStatusApplicationJSON;
|
||||
account: ApiAccountJSON;
|
||||
media_attachments: ApiMediaAttachmentJSON[];
|
||||
mentions: ApiMentionJSON[];
|
||||
|
||||
@ -97,3 +95,13 @@ export interface ApiStatusJSON {
|
||||
card?: ApiPreviewCardJSON;
|
||||
poll?: ApiPollJSON;
|
||||
}
|
||||
|
||||
export interface ApiStatusJSON extends BaseApiStatusJSON {
|
||||
account: ApiAccountJSON;
|
||||
reblog?: ApiStatusJSON;
|
||||
}
|
||||
|
||||
export interface ShallowApiStatusJSON extends BaseApiStatusJSON {
|
||||
account_id: string;
|
||||
reblog_id?: string;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import type {
|
||||
ApiAccountFieldJSON,
|
||||
ApiAccountRoleJSON,
|
||||
ApiAccountJSON,
|
||||
ShallowApiAccountJSON,
|
||||
} from 'mastodon/api_types/accounts';
|
||||
import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
@ -149,3 +150,32 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||
note_plain: unescapeHTML(accountJSON.note),
|
||||
});
|
||||
}
|
||||
|
||||
export function createAccountFromServerShallowJSON(
|
||||
serverJSON: ShallowApiAccountJSON,
|
||||
) {
|
||||
const { moved_to_account_id, ...accountJSON } = serverJSON;
|
||||
|
||||
const emojiMap = makeEmojiMap(accountJSON.emojis);
|
||||
|
||||
const displayName =
|
||||
accountJSON.display_name.trim().length === 0
|
||||
? accountJSON.username
|
||||
: accountJSON.display_name;
|
||||
|
||||
return AccountFactory({
|
||||
...accountJSON,
|
||||
moved: moved_to_account_id,
|
||||
fields: List(
|
||||
serverJSON.fields.map((field) => createAccountField(field, emojiMap)),
|
||||
),
|
||||
emojis: List(serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji))),
|
||||
roles: List(serverJSON.roles?.map((role) => AccountRoleFactory(role))),
|
||||
display_name_html: emojify(
|
||||
escapeTextContentForBrowser(displayName),
|
||||
emojiMap,
|
||||
),
|
||||
note_emojified: emojify(accountJSON.note, emojiMap),
|
||||
note_plain: unescapeHTML(accountJSON.note),
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type {
|
||||
ApiAccountRelationshipSeveranceEventJSON,
|
||||
ShallowApiAccountWarningJSON,
|
||||
ApiAccountWarningJSON,
|
||||
BaseNotificationGroupJSON,
|
||||
ApiNotificationGroupJSON,
|
||||
@ -7,14 +8,17 @@ import type {
|
||||
NotificationType,
|
||||
NotificationWithStatusType,
|
||||
} from 'mastodon/api_types/notifications';
|
||||
import type { ApiReportJSON } from 'mastodon/api_types/reports';
|
||||
import type {
|
||||
ApiReportJSON,
|
||||
ShallowApiReportJSON,
|
||||
} from 'mastodon/api_types/reports';
|
||||
|
||||
// Maximum number of avatars displayed in a notification group
|
||||
// This corresponds to the max lenght of `group.sampleAccountIds`
|
||||
export const NOTIFICATIONS_GROUP_MAX_AVATARS = 8;
|
||||
|
||||
interface BaseNotificationGroup
|
||||
extends Omit<BaseNotificationGroupJSON, 'sample_accounts'> {
|
||||
extends Omit<BaseNotificationGroupJSON, 'sample_account_ids'> {
|
||||
sampleAccountIds: string[];
|
||||
}
|
||||
|
||||
@ -96,6 +100,14 @@ function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
||||
};
|
||||
}
|
||||
|
||||
function createReportFromShallowJSON(reportJSON: ShallowApiReportJSON): Report {
|
||||
const { target_account_id, ...report } = reportJSON;
|
||||
return {
|
||||
targetAccountId: target_account_id,
|
||||
...report,
|
||||
};
|
||||
}
|
||||
|
||||
function createAccountWarningFromJSON(
|
||||
warningJSON: ApiAccountWarningJSON,
|
||||
): AccountWarning {
|
||||
@ -106,6 +118,16 @@ function createAccountWarningFromJSON(
|
||||
};
|
||||
}
|
||||
|
||||
function createAccountWarningFromShallowJSON(
|
||||
warningJSON: ShallowApiAccountWarningJSON,
|
||||
): AccountWarning {
|
||||
const { target_account_id, ...warning } = warningJSON;
|
||||
return {
|
||||
targetAccountId: target_account_id,
|
||||
...warning,
|
||||
};
|
||||
}
|
||||
|
||||
function createAccountRelationshipSeveranceEventFromJSON(
|
||||
eventJson: ApiAccountRelationshipSeveranceEventJSON,
|
||||
): AccountRelationshipSeveranceEvent {
|
||||
@ -115,8 +137,7 @@ function createAccountRelationshipSeveranceEventFromJSON(
|
||||
export function createNotificationGroupFromJSON(
|
||||
groupJson: ApiNotificationGroupJSON,
|
||||
): NotificationGroup {
|
||||
const { sample_accounts, ...group } = groupJson;
|
||||
const sampleAccountIds = sample_accounts.map((account) => account.id);
|
||||
const { sample_account_ids: sampleAccountIds, ...group } = groupJson;
|
||||
|
||||
switch (group.type) {
|
||||
case 'favourite':
|
||||
@ -125,9 +146,9 @@ export function createNotificationGroupFromJSON(
|
||||
case 'mention':
|
||||
case 'poll':
|
||||
case 'update': {
|
||||
const { status, ...groupWithoutStatus } = group;
|
||||
const { status_id, ...groupWithoutStatus } = group;
|
||||
return {
|
||||
statusId: status.id,
|
||||
statusId: status_id,
|
||||
sampleAccountIds,
|
||||
...groupWithoutStatus,
|
||||
};
|
||||
@ -135,7 +156,7 @@ export function createNotificationGroupFromJSON(
|
||||
case 'admin.report': {
|
||||
const { report, ...groupWithoutTargetAccount } = group;
|
||||
return {
|
||||
report: createReportFromJSON(report),
|
||||
report: createReportFromShallowJSON(report),
|
||||
sampleAccountIds,
|
||||
...groupWithoutTargetAccount,
|
||||
};
|
||||
@ -151,7 +172,8 @@ export function createNotificationGroupFromJSON(
|
||||
const { moderation_warning, ...groupWithoutModerationWarning } = group;
|
||||
return {
|
||||
...groupWithoutModerationWarning,
|
||||
moderationWarning: createAccountWarningFromJSON(moderation_warning),
|
||||
moderationWarning:
|
||||
createAccountWarningFromShallowJSON(moderation_warning),
|
||||
sampleAccountIds,
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,12 +5,19 @@ import {
|
||||
followAccountSuccess,
|
||||
unfollowAccountSuccess,
|
||||
importAccounts,
|
||||
importShallowAccounts,
|
||||
revealAccount,
|
||||
} from 'mastodon/actions/accounts_typed';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type {
|
||||
ApiAccountJSON,
|
||||
ShallowApiAccountJSON,
|
||||
} from 'mastodon/api_types/accounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
import { createAccountFromServerJSON } from 'mastodon/models/account';
|
||||
import {
|
||||
createAccountFromServerJSON,
|
||||
createAccountFromServerShallowJSON,
|
||||
} from 'mastodon/models/account';
|
||||
|
||||
const initialState = ImmutableMap<string, Account>();
|
||||
|
||||
@ -40,6 +47,32 @@ const normalizeAccounts = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const normalizeShallowAccount = (
|
||||
state: typeof initialState,
|
||||
account: ShallowApiAccountJSON,
|
||||
) => {
|
||||
return state.set(
|
||||
account.id,
|
||||
createAccountFromServerShallowJSON(account).set(
|
||||
'hidden',
|
||||
state.get(account.id)?.hidden === false
|
||||
? false
|
||||
: account.limited || false,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeShallowAccounts = (
|
||||
state: typeof initialState,
|
||||
accounts: ShallowApiAccountJSON[],
|
||||
) => {
|
||||
accounts.forEach((account) => {
|
||||
state = normalizeShallowAccount(state, account);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
function getCurrentUser() {
|
||||
if (!me)
|
||||
throw new Error(
|
||||
@ -57,6 +90,8 @@ export const accountsReducer: Reducer<typeof initialState> = (
|
||||
return state.setIn([action.payload.id, 'hidden'], false);
|
||||
else if (importAccounts.match(action))
|
||||
return normalizeAccounts(state, action.payload.accounts);
|
||||
else if (importShallowAccounts.match(action))
|
||||
return normalizeShallowAccounts(state, action.payload.accounts);
|
||||
else if (followAccountSuccess.match(action)) {
|
||||
return state
|
||||
.update(action.payload.relationship.id, (account) =>
|
||||
|
||||
@ -296,7 +296,9 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||
updateLastReadId(state);
|
||||
})
|
||||
.addCase(fetchNotificationsGap.fulfilled, (state, action) => {
|
||||
const { notifications } = action.payload;
|
||||
const {
|
||||
apiResult: { notification_groups: notifications },
|
||||
} = action.payload;
|
||||
|
||||
// find the gap in the existing notifications
|
||||
const gapIndex = state.groups.findIndex(
|
||||
|
||||
34
app/presenters/grouped_notifications_presenter.rb
Normal file
34
app/presenters/grouped_notifications_presenter.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class GroupedNotificationsPresenter < ActiveModelSerializers::Model
|
||||
def initialize(grouped_notifications)
|
||||
super()
|
||||
|
||||
@grouped_notifications = grouped_notifications
|
||||
end
|
||||
|
||||
def notification_groups
|
||||
@grouped_notifications
|
||||
end
|
||||
|
||||
def statuses
|
||||
@grouped_notifications.filter_map(&:target_status)
|
||||
end
|
||||
|
||||
def accounts
|
||||
@grouped_notifications.flat_map do |group|
|
||||
accounts = group.sample_accounts.dup
|
||||
|
||||
case group.type
|
||||
when :favourite, :reblog, :status, :mention, :poll, :update
|
||||
accounts << group.target_status&.account
|
||||
when :'admin.report'
|
||||
accounts << group.report&.target_account
|
||||
when :moderation_warning
|
||||
accounts << group.account_warning&.target_account
|
||||
end
|
||||
|
||||
accounts.compact
|
||||
end.uniq(&:id)
|
||||
end
|
||||
end
|
||||
@ -1,25 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::AccountSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
include FormattingHelper
|
||||
|
||||
# Please update `app/javascript/mastodon/api_types/accounts.ts` when making changes to the attributes
|
||||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :indexable, :group, :created_at,
|
||||
:note, :url, :uri, :avatar, :avatar_static, :header, :header_static,
|
||||
:followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections
|
||||
|
||||
class REST::AccountSerializer < REST::BaseAccountSerializer
|
||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
attribute :suspended, if: :suspended?
|
||||
attribute :silenced, key: :limited, if: :silenced?
|
||||
attribute :noindex, if: :local?
|
||||
|
||||
attribute :memorial, if: :memorial?
|
||||
|
||||
class AccountDecorator < SimpleDelegator
|
||||
def self.model_name
|
||||
Account.model_name
|
||||
@ -30,130 +13,10 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
||||
end
|
||||
end
|
||||
|
||||
class RoleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :color
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
has_many :roles, serializer: RoleSerializer, if: :local?
|
||||
|
||||
class FieldSerializer < ActiveModel::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
attributes :name, :value, :verified_at
|
||||
|
||||
def value
|
||||
account_field_value_format(object)
|
||||
end
|
||||
end
|
||||
|
||||
has_many :fields
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def acct
|
||||
object.pretty_acct
|
||||
end
|
||||
|
||||
def note
|
||||
object.unavailable? ? '' : account_bio_format(object)
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def uri
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def avatar
|
||||
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_original_url)
|
||||
end
|
||||
|
||||
def avatar_static
|
||||
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_static_url)
|
||||
end
|
||||
|
||||
def header
|
||||
full_asset_url(object.unavailable? ? object.header.default_url : object.header_original_url)
|
||||
end
|
||||
|
||||
def header_static
|
||||
full_asset_url(object.unavailable? ? object.header.default_url : object.header_static_url)
|
||||
end
|
||||
|
||||
def created_at
|
||||
object.created_at.midnight.as_json
|
||||
end
|
||||
|
||||
def last_status_at
|
||||
object.last_status_at&.to_date&.iso8601
|
||||
end
|
||||
|
||||
def display_name
|
||||
object.unavailable? ? '' : object.display_name
|
||||
end
|
||||
|
||||
def locked
|
||||
object.unavailable? ? false : object.locked
|
||||
end
|
||||
|
||||
def bot
|
||||
object.unavailable? ? false : object.bot
|
||||
end
|
||||
|
||||
def discoverable
|
||||
object.unavailable? ? false : object.discoverable
|
||||
end
|
||||
|
||||
def indexable
|
||||
object.unavailable? ? false : object.indexable
|
||||
end
|
||||
|
||||
def moved_to_account
|
||||
object.unavailable? ? nil : AccountDecorator.new(object.moved_to_account)
|
||||
end
|
||||
|
||||
def emojis
|
||||
object.unavailable? ? [] : object.emojis
|
||||
end
|
||||
|
||||
def fields
|
||||
object.unavailable? ? [] : object.fields
|
||||
end
|
||||
|
||||
def suspended
|
||||
object.unavailable?
|
||||
end
|
||||
|
||||
def silenced
|
||||
object.silenced?
|
||||
end
|
||||
|
||||
def memorial
|
||||
object.memorial?
|
||||
end
|
||||
|
||||
def roles
|
||||
if object.unavailable? || object.user.nil?
|
||||
[]
|
||||
else
|
||||
[object.user.role].compact.filter(&:highlighted?)
|
||||
end
|
||||
end
|
||||
|
||||
def noindex
|
||||
object.user_prefers_noindex?
|
||||
end
|
||||
|
||||
delegate :suspended?, :silenced?, :local?, :memorial?, to: :object
|
||||
|
||||
def moved_and_not_nested?
|
||||
object.moved?
|
||||
end
|
||||
|
||||
@ -1,16 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::AccountWarningSerializer < ActiveModel::Serializer
|
||||
attributes :id, :action, :text, :status_ids, :created_at
|
||||
|
||||
class REST::AccountWarningSerializer < REST::BaseAccountWarningSerializer
|
||||
has_one :target_account, serializer: REST::AccountSerializer
|
||||
has_one :appeal, serializer: REST::AppealSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def status_ids
|
||||
object&.status_ids&.map(&:to_s)
|
||||
end
|
||||
end
|
||||
|
||||
140
app/serializers/rest/base_account_serializer.rb
Normal file
140
app/serializers/rest/base_account_serializer.rb
Normal file
@ -0,0 +1,140 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BaseAccountSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
include FormattingHelper
|
||||
|
||||
# Please update `app/javascript/mastodon/api_types/accounts.ts` when making changes to the attributes
|
||||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :indexable, :group, :created_at,
|
||||
:note, :url, :uri, :avatar, :avatar_static, :header, :header_static,
|
||||
:followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections
|
||||
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
attribute :suspended, if: :suspended?
|
||||
attribute :silenced, key: :limited, if: :silenced?
|
||||
attribute :noindex, if: :local?
|
||||
|
||||
attribute :memorial, if: :memorial?
|
||||
|
||||
class RoleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :color
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
has_many :roles, serializer: RoleSerializer, if: :local?
|
||||
|
||||
class FieldSerializer < ActiveModel::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
attributes :name, :value, :verified_at
|
||||
|
||||
def value
|
||||
account_field_value_format(object)
|
||||
end
|
||||
end
|
||||
|
||||
has_many :fields
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def acct
|
||||
object.pretty_acct
|
||||
end
|
||||
|
||||
def note
|
||||
object.unavailable? ? '' : account_bio_format(object)
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def uri
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def avatar
|
||||
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_original_url)
|
||||
end
|
||||
|
||||
def avatar_static
|
||||
full_asset_url(object.unavailable? ? object.avatar.default_url : object.avatar_static_url)
|
||||
end
|
||||
|
||||
def header
|
||||
full_asset_url(object.unavailable? ? object.header.default_url : object.header_original_url)
|
||||
end
|
||||
|
||||
def header_static
|
||||
full_asset_url(object.unavailable? ? object.header.default_url : object.header_static_url)
|
||||
end
|
||||
|
||||
def created_at
|
||||
object.created_at.midnight.as_json
|
||||
end
|
||||
|
||||
def last_status_at
|
||||
object.last_status_at&.to_date&.iso8601
|
||||
end
|
||||
|
||||
def display_name
|
||||
object.unavailable? ? '' : object.display_name
|
||||
end
|
||||
|
||||
def locked
|
||||
object.unavailable? ? false : object.locked
|
||||
end
|
||||
|
||||
def bot
|
||||
object.unavailable? ? false : object.bot
|
||||
end
|
||||
|
||||
def discoverable
|
||||
object.unavailable? ? false : object.discoverable
|
||||
end
|
||||
|
||||
def indexable
|
||||
object.unavailable? ? false : object.indexable
|
||||
end
|
||||
|
||||
def emojis
|
||||
object.unavailable? ? [] : object.emojis
|
||||
end
|
||||
|
||||
def fields
|
||||
object.unavailable? ? [] : object.fields
|
||||
end
|
||||
|
||||
def suspended
|
||||
object.unavailable?
|
||||
end
|
||||
|
||||
def silenced
|
||||
object.silenced?
|
||||
end
|
||||
|
||||
def memorial
|
||||
object.memorial?
|
||||
end
|
||||
|
||||
def roles
|
||||
if object.unavailable? || object.user.nil?
|
||||
[]
|
||||
else
|
||||
[object.user.role].compact.filter(&:highlighted?)
|
||||
end
|
||||
end
|
||||
|
||||
def noindex
|
||||
object.user_prefers_noindex?
|
||||
end
|
||||
|
||||
delegate :suspended?, :silenced?, :local?, :memorial?, to: :object
|
||||
end
|
||||
15
app/serializers/rest/base_account_warning_serializer.rb
Normal file
15
app/serializers/rest/base_account_warning_serializer.rb
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BaseAccountWarningSerializer < ActiveModel::Serializer
|
||||
attributes :id, :action, :text, :status_ids, :created_at
|
||||
|
||||
has_one :appeal, serializer: REST::AppealSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def status_ids
|
||||
object&.status_ids&.map(&:to_s)
|
||||
end
|
||||
end
|
||||
22
app/serializers/rest/base_preview_card_serializer.rb
Normal file
22
app/serializers/rest/base_preview_card_serializer.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BasePreviewCardSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :url, :title, :description, :language, :type,
|
||||
:author_name, :author_url, :provider_name,
|
||||
:provider_url, :html, :width, :height,
|
||||
:image, :image_description, :embed_url, :blurhash, :published_at
|
||||
|
||||
def url
|
||||
object.original_url.presence || object.url
|
||||
end
|
||||
|
||||
def image
|
||||
object.image? ? full_asset_url(object.image.url(:original)) : nil
|
||||
end
|
||||
|
||||
def html
|
||||
Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
|
||||
end
|
||||
end
|
||||
18
app/serializers/rest/base_report_serializer.rb
Normal file
18
app/serializers/rest/base_report_serializer.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BaseReportSerializer < ActiveModel::Serializer
|
||||
attributes :id, :action_taken, :action_taken_at, :category, :comment,
|
||||
:forwarded, :created_at, :status_ids, :rule_ids
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def status_ids
|
||||
object&.status_ids&.map(&:to_s)
|
||||
end
|
||||
|
||||
def rule_ids
|
||||
object&.rule_ids&.map(&:to_s)
|
||||
end
|
||||
end
|
||||
197
app/serializers/rest/base_status_serializer.rb
Normal file
197
app/serializers/rest/base_status_serializer.rb
Normal file
@ -0,0 +1,197 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BaseStatusSerializer < ActiveModel::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
# Please update `app/javascript/mastodon/api_types/statuses.ts` when making changes to the attributes
|
||||
|
||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||
:sensitive, :spoiler_text, :visibility, :language,
|
||||
:uri, :url, :replies_count, :reblogs_count,
|
||||
:favourites_count, :edited_at
|
||||
|
||||
attribute :favourited, if: :current_user?
|
||||
attribute :reblogged, if: :current_user?
|
||||
attribute :muted, if: :current_user?
|
||||
attribute :bookmarked, if: :current_user?
|
||||
attribute :pinned, if: :pinnable?
|
||||
has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user?
|
||||
|
||||
attribute :content, unless: :source_requested?
|
||||
attribute :text, if: :source_requested?
|
||||
|
||||
belongs_to :application, if: :show_application?
|
||||
|
||||
has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer
|
||||
has_many :ordered_mentions, key: :mentions
|
||||
has_many :tags
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def in_reply_to_id
|
||||
object.in_reply_to_id&.to_s
|
||||
end
|
||||
|
||||
def in_reply_to_account_id
|
||||
object.in_reply_to_account_id&.to_s
|
||||
end
|
||||
|
||||
def current_user?
|
||||
!current_user.nil?
|
||||
end
|
||||
|
||||
def show_application?
|
||||
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
|
||||
end
|
||||
|
||||
def visibility
|
||||
# This visibility is masked behind "private"
|
||||
# to avoid API changes because there are no
|
||||
# UX differences
|
||||
if object.limited_visibility?
|
||||
'private'
|
||||
else
|
||||
object.visibility
|
||||
end
|
||||
end
|
||||
|
||||
def sensitive
|
||||
if current_user? && current_user.account_id == object.account_id
|
||||
object.sensitive
|
||||
else
|
||||
object.account.sensitized? || object.sensitive
|
||||
end
|
||||
end
|
||||
|
||||
def uri
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def content
|
||||
status_content_format(object)
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def reblogs_count
|
||||
relationships&.attributes_map&.dig(object.id, :reblogs_count) || object.reblogs_count
|
||||
end
|
||||
|
||||
def favourites_count
|
||||
relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
|
||||
end
|
||||
|
||||
def favourited
|
||||
if relationships
|
||||
relationships.favourites_map[object.id] || false
|
||||
else
|
||||
current_user.account.favourited?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def reblogged
|
||||
if relationships
|
||||
relationships.reblogs_map[object.id] || false
|
||||
else
|
||||
current_user.account.reblogged?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def muted
|
||||
if relationships
|
||||
relationships.mutes_map[object.conversation_id] || false
|
||||
else
|
||||
current_user.account.muting_conversation?(object.conversation)
|
||||
end
|
||||
end
|
||||
|
||||
def bookmarked
|
||||
if relationships
|
||||
relationships.bookmarks_map[object.id] || false
|
||||
else
|
||||
current_user.account.bookmarked?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def pinned
|
||||
if relationships
|
||||
relationships.pins_map[object.id] || false
|
||||
else
|
||||
current_user.account.pinned?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def filtered
|
||||
if relationships
|
||||
relationships.filters_map[object.id] || []
|
||||
else
|
||||
current_user.account.status_matches_filters(object)
|
||||
end
|
||||
end
|
||||
|
||||
def pinnable?
|
||||
current_user? &&
|
||||
current_user.account_id == object.account_id &&
|
||||
!object.reblog? &&
|
||||
%w(public unlisted private).include?(object.visibility)
|
||||
end
|
||||
|
||||
def source_requested?
|
||||
instance_options[:source_requested]
|
||||
end
|
||||
|
||||
def ordered_mentions
|
||||
object.active_mentions.to_a.sort_by(&:id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relationships
|
||||
instance_options && instance_options[:relationships]
|
||||
end
|
||||
|
||||
class ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :name, :website
|
||||
|
||||
def website
|
||||
object.website.presence
|
||||
end
|
||||
end
|
||||
|
||||
class MentionSerializer < ActiveModel::Serializer
|
||||
attributes :id, :username, :url, :acct
|
||||
|
||||
def id
|
||||
object.account_id.to_s
|
||||
end
|
||||
|
||||
def username
|
||||
object.account_username
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object.account)
|
||||
end
|
||||
|
||||
def acct
|
||||
object.account.pretty_acct
|
||||
end
|
||||
end
|
||||
|
||||
class TagSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :name, :url
|
||||
|
||||
def url
|
||||
tag_url(object)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::DedupNotificationGroupSerializer < ActiveModel::Serializer
|
||||
has_many :accounts, serializer: REST::Shallow::AccountSerializer
|
||||
has_many :statuses, serializer: REST::Shallow::StatusSerializer
|
||||
has_many :notification_groups, serializer: REST::Shallow::NotificationGroupSerializer
|
||||
end
|
||||
@ -1,29 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::PreviewCardSerializer < ActiveModel::Serializer
|
||||
class REST::PreviewCardSerializer < REST::BasePreviewCardSerializer
|
||||
class AuthorSerializer < ActiveModel::Serializer
|
||||
attributes :name, :url
|
||||
has_one :account, serializer: REST::AccountSerializer
|
||||
end
|
||||
|
||||
include RoutingHelper
|
||||
|
||||
attributes :url, :title, :description, :language, :type,
|
||||
:author_name, :author_url, :provider_name,
|
||||
:provider_url, :html, :width, :height,
|
||||
:image, :image_description, :embed_url, :blurhash, :published_at
|
||||
|
||||
has_many :authors, serializer: AuthorSerializer
|
||||
|
||||
def url
|
||||
object.original_url.presence || object.url
|
||||
end
|
||||
|
||||
def image
|
||||
object.image? ? full_asset_url(object.image.url(:original)) : nil
|
||||
end
|
||||
|
||||
def html
|
||||
Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,20 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::ReportSerializer < ActiveModel::Serializer
|
||||
attributes :id, :action_taken, :action_taken_at, :category, :comment,
|
||||
:forwarded, :created_at, :status_ids, :rule_ids
|
||||
|
||||
class REST::ReportSerializer < REST::BaseReportSerializer
|
||||
has_one :target_account, serializer: REST::AccountSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def status_ids
|
||||
object&.status_ids&.map(&:to_s)
|
||||
end
|
||||
|
||||
def rule_ids
|
||||
object&.rule_ids&.map(&:to_s)
|
||||
end
|
||||
end
|
||||
|
||||
11
app/serializers/rest/shallow/account_serializer.rb
Normal file
11
app/serializers/rest/shallow/account_serializer.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Shallow::AccountSerializer < REST::BaseAccountSerializer
|
||||
attribute :moved_to_account_id, if: :moved?
|
||||
|
||||
def moved_to_account_id
|
||||
object.unavailable? ? nil : object.moved_to_account_id&.to_s
|
||||
end
|
||||
|
||||
delegate :moved?, to: :object
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Shallow::AccountWarningSerializer < REST::BaseAccountWarningSerializer
|
||||
attribute :target_account_id
|
||||
|
||||
def target_account_id
|
||||
object.target_account_id&.to_s
|
||||
end
|
||||
end
|
||||
@ -1,23 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
||||
class REST::Shallow::NotificationGroupSerializer < ActiveModel::Serializer
|
||||
# Please update app/javascript/api_types/notification.ts when making changes to the attributes
|
||||
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
|
||||
|
||||
attributes :sample_account_ids
|
||||
|
||||
attribute :page_min_id, if: :paginated?
|
||||
attribute :page_max_id, if: :paginated?
|
||||
attribute :latest_page_notification_at, if: :paginated?
|
||||
|
||||
has_many :sample_accounts, serializer: REST::AccountSerializer
|
||||
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
|
||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||
attribute :status_id, if: :status_type?
|
||||
belongs_to :report, if: :report_type?, serializer: REST::Shallow::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
|
||||
belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::Shallow::AccountWarningSerializer
|
||||
|
||||
def sample_account_ids
|
||||
object.sample_accounts.pluck(:id).map(&:to_s)
|
||||
end
|
||||
|
||||
def status_type?
|
||||
[:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type)
|
||||
end
|
||||
|
||||
def status_id
|
||||
object.target_status&.id&.to_s
|
||||
end
|
||||
|
||||
def report_type?
|
||||
object.type == :'admin.report'
|
||||
end
|
||||
13
app/serializers/rest/shallow/preview_card_serializer.rb
Normal file
13
app/serializers/rest/shallow/preview_card_serializer.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Shallow::PreviewCardSerializer < REST::BasePreviewCardSerializer
|
||||
class AuthorSerializer < ActiveModel::Serializer
|
||||
attributes :name, :url, :account_id
|
||||
|
||||
def account_id
|
||||
object.account_id&.to_s
|
||||
end
|
||||
end
|
||||
|
||||
has_many :authors, serializer: AuthorSerializer
|
||||
end
|
||||
9
app/serializers/rest/shallow/report_serializer.rb
Normal file
9
app/serializers/rest/shallow/report_serializer.rb
Normal file
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Shallow::ReportSerializer < REST::BaseReportSerializer
|
||||
attribute :target_account_id
|
||||
|
||||
def target_account_id
|
||||
object.target_account_id&.to_s
|
||||
end
|
||||
end
|
||||
15
app/serializers/rest/shallow/status_serializer.rb
Normal file
15
app/serializers/rest/shallow/status_serializer.rb
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::Shallow::StatusSerializer < REST::BaseStatusSerializer
|
||||
attributes :account_id, :reblog_id
|
||||
|
||||
has_one :preview_card, key: :card, serializer: REST::Shallow::PreviewCardSerializer
|
||||
|
||||
def account_id
|
||||
object.account_id&.to_s
|
||||
end
|
||||
|
||||
def reblog_id
|
||||
object.reblog_of_id&.to_s
|
||||
end
|
||||
end
|
||||
@ -1,200 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::StatusSerializer < ActiveModel::Serializer
|
||||
include FormattingHelper
|
||||
|
||||
# Please update `app/javascript/mastodon/api_types/statuses.ts` when making changes to the attributes
|
||||
|
||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||
:sensitive, :spoiler_text, :visibility, :language,
|
||||
:uri, :url, :replies_count, :reblogs_count,
|
||||
:favourites_count, :edited_at
|
||||
|
||||
attribute :favourited, if: :current_user?
|
||||
attribute :reblogged, if: :current_user?
|
||||
attribute :muted, if: :current_user?
|
||||
attribute :bookmarked, if: :current_user?
|
||||
attribute :pinned, if: :pinnable?
|
||||
has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user?
|
||||
|
||||
attribute :content, unless: :source_requested?
|
||||
attribute :text, if: :source_requested?
|
||||
|
||||
belongs_to :reblog, serializer: REST::StatusSerializer
|
||||
belongs_to :application, if: :show_application?
|
||||
class REST::StatusSerializer < REST::BaseStatusSerializer
|
||||
belongs_to :account, serializer: REST::AccountSerializer
|
||||
|
||||
has_many :ordered_media_attachments, key: :media_attachments, serializer: REST::MediaAttachmentSerializer
|
||||
has_many :ordered_mentions, key: :mentions
|
||||
has_many :tags
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
belongs_to :reblog, serializer: REST::StatusSerializer
|
||||
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
|
||||
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
def in_reply_to_id
|
||||
object.in_reply_to_id&.to_s
|
||||
end
|
||||
|
||||
def in_reply_to_account_id
|
||||
object.in_reply_to_account_id&.to_s
|
||||
end
|
||||
|
||||
def current_user?
|
||||
!current_user.nil?
|
||||
end
|
||||
|
||||
def show_application?
|
||||
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
|
||||
end
|
||||
|
||||
def visibility
|
||||
# This visibility is masked behind "private"
|
||||
# to avoid API changes because there are no
|
||||
# UX differences
|
||||
if object.limited_visibility?
|
||||
'private'
|
||||
else
|
||||
object.visibility
|
||||
end
|
||||
end
|
||||
|
||||
def sensitive
|
||||
if current_user? && current_user.account_id == object.account_id
|
||||
object.sensitive
|
||||
else
|
||||
object.account.sensitized? || object.sensitive
|
||||
end
|
||||
end
|
||||
|
||||
def uri
|
||||
ActivityPub::TagManager.instance.uri_for(object)
|
||||
end
|
||||
|
||||
def content
|
||||
status_content_format(object)
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object)
|
||||
end
|
||||
|
||||
def reblogs_count
|
||||
relationships&.attributes_map&.dig(object.id, :reblogs_count) || object.reblogs_count
|
||||
end
|
||||
|
||||
def favourites_count
|
||||
relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
|
||||
end
|
||||
|
||||
def favourited
|
||||
if relationships
|
||||
relationships.favourites_map[object.id] || false
|
||||
else
|
||||
current_user.account.favourited?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def reblogged
|
||||
if relationships
|
||||
relationships.reblogs_map[object.id] || false
|
||||
else
|
||||
current_user.account.reblogged?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def muted
|
||||
if relationships
|
||||
relationships.mutes_map[object.conversation_id] || false
|
||||
else
|
||||
current_user.account.muting_conversation?(object.conversation)
|
||||
end
|
||||
end
|
||||
|
||||
def bookmarked
|
||||
if relationships
|
||||
relationships.bookmarks_map[object.id] || false
|
||||
else
|
||||
current_user.account.bookmarked?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def pinned
|
||||
if relationships
|
||||
relationships.pins_map[object.id] || false
|
||||
else
|
||||
current_user.account.pinned?(object)
|
||||
end
|
||||
end
|
||||
|
||||
def filtered
|
||||
if relationships
|
||||
relationships.filters_map[object.id] || []
|
||||
else
|
||||
current_user.account.status_matches_filters(object)
|
||||
end
|
||||
end
|
||||
|
||||
def pinnable?
|
||||
current_user? &&
|
||||
current_user.account_id == object.account_id &&
|
||||
!object.reblog? &&
|
||||
%w(public unlisted private).include?(object.visibility)
|
||||
end
|
||||
|
||||
def source_requested?
|
||||
instance_options[:source_requested]
|
||||
end
|
||||
|
||||
def ordered_mentions
|
||||
object.active_mentions.to_a.sort_by(&:id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relationships
|
||||
instance_options && instance_options[:relationships]
|
||||
end
|
||||
|
||||
class ApplicationSerializer < ActiveModel::Serializer
|
||||
attributes :name, :website
|
||||
|
||||
def website
|
||||
object.website.presence
|
||||
end
|
||||
end
|
||||
|
||||
class MentionSerializer < ActiveModel::Serializer
|
||||
attributes :id, :username, :url, :acct
|
||||
|
||||
def id
|
||||
object.account_id.to_s
|
||||
end
|
||||
|
||||
def username
|
||||
object.account_username
|
||||
end
|
||||
|
||||
def url
|
||||
ActivityPub::TagManager.instance.url_for(object.account)
|
||||
end
|
||||
|
||||
def acct
|
||||
object.account.pretty_acct
|
||||
end
|
||||
end
|
||||
|
||||
class TagSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :name, :url
|
||||
|
||||
def url
|
||||
tag_url(object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -58,7 +58,7 @@ RSpec.describe 'Notifications' do
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_json_types.uniq).to eq ['mention']
|
||||
expect(body_as_json[0][:page_min_id]).to_not be_nil
|
||||
expect(body_as_json.dig(:notification_groups, 0, :page_min_id)).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -84,7 +84,7 @@ RSpec.describe 'Notifications' do
|
||||
end
|
||||
|
||||
def body_json_types
|
||||
body_as_json.pluck(:type)
|
||||
body_as_json[:notification_groups].pluck(:type)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user