From a0910cd49ca0f455ed6616deb429b41dc55e2207 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:03:28 +0200 Subject: [PATCH 01/15] New Crowdin Translations (automated) (#30772) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ko.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index aa05887d65567e..c4c084d98ee046 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -32,7 +32,7 @@ "account.featured_tags.last_status_never": "게시물 없음", "account.featured_tags.title": "{name} 님의 추천 해시태그", "account.follow": "팔로우", - "account.follow_back": "맞팔로우", + "account.follow_back": "맞팔로우 하기", "account.followers": "팔로워", "account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.", "account.followers_counter": "{counter} 팔로워", @@ -53,7 +53,7 @@ "account.mute_notifications_short": "알림 뮤트", "account.mute_short": "뮤트", "account.muted": "뮤트됨", - "account.mutual": "상호 팔로우", + "account.mutual": "맞팔로우 중", "account.no_bio": "제공된 설명이 없습니다.", "account.open_original_page": "원본 페이지 열기", "account.posts": "게시물", From 7889e983fbe7e29f3f91784cfa2117e620514ad7 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 20 Jun 2024 13:05:25 +0200 Subject: [PATCH 02/15] Add `group_key` attribute to `NotificationSerializer` (#30776) Co-authored-by: Renaud Chaput --- app/serializers/rest/notification_serializer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 417245d19dda7a..966819585fcc7c 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::NotificationSerializer < ActiveModel::Serializer - attributes :id, :type, :created_at + attributes :id, :type, :created_at, :group_key belongs_to :from_account, key: :account, serializer: REST::AccountSerializer belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer @@ -13,6 +13,10 @@ def id object.id.to_s end + def group_key + object.group_key || "ungrouped-#{object.id}" + end + def status_type? [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type) end From 27529247b289a0e78d2deb97626ddb60baf04d86 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Thu, 20 Jun 2024 13:42:10 +0200 Subject: [PATCH 03/15] Improve `createdataLoadingThunk` (#30778) --- .../mastodon/store/typed_functions.ts | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index dae37e62255269..e5820149dbd9d7 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -82,13 +82,19 @@ export function createThunk( const discardLoadDataInPayload = Symbol('discardLoadDataInPayload'); type DiscardLoadData = typeof discardLoadDataInPayload; -type OnData = ( +type OnData = ( data: LoadDataResult, api: AppThunkApi & { + actionArg: ActionArg; discardLoadData: DiscardLoadData; }, ) => ReturnedData | DiscardLoadData | Promise; +type LoadData = ( + args: Args, + api: AppThunkApi, +) => Promise; + type ArgsType = Record | undefined; // Overload when there is no `onData` method, the payload is the `onData` result @@ -101,18 +107,18 @@ export function createDataLoadingThunk( // Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty export function createDataLoadingThunk( name: string, - loadData: (args: Args) => Promise, + loadData: LoadData, onDataOrThunkOptions?: | AppThunkOptions - | OnData, + | OnData, thunkOptions?: AppThunkOptions, ): ReturnType>; // Overload when the `onData` method returns nothing, then the mayload is the `onData` result export function createDataLoadingThunk( name: string, - loadData: (args: Args) => Promise, - onDataOrThunkOptions?: AppThunkOptions | OnData, + loadData: LoadData, + onDataOrThunkOptions?: AppThunkOptions | OnData, thunkOptions?: AppThunkOptions, ): ReturnType>; @@ -123,8 +129,10 @@ export function createDataLoadingThunk< Returned, >( name: string, - loadData: (args: Args) => Promise, - onDataOrThunkOptions?: AppThunkOptions | OnData, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, thunkOptions?: AppThunkOptions, ): ReturnType>; @@ -159,11 +167,13 @@ export function createDataLoadingThunk< Returned, >( name: string, - loadData: (args: Args) => Promise, - onDataOrThunkOptions?: AppThunkOptions | OnData, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, maybeThunkOptions?: AppThunkOptions, ) { - let onData: OnData | undefined; + let onData: OnData | undefined; let thunkOptions: AppThunkOptions | undefined; if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions; @@ -177,7 +187,10 @@ export function createDataLoadingThunk< return createThunk( name, async (arg, { getState, dispatch }) => { - const data = await loadData(arg); + const data = await loadData(arg, { + dispatch, + getState, + }); if (!onData) return data as Returned; @@ -185,6 +198,7 @@ export function createDataLoadingThunk< dispatch, getState, discardLoadData: discardLoadDataInPayload, + actionArg: arg, }); // if there is no return in `onData`, we return the `onData` result From 1c6593277696c6f4698684860f7c6d6189f9b371 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Thu, 20 Jun 2024 13:56:52 +0200 Subject: [PATCH 04/15] Convert `disconnectTimeline` and `timelineDelete` actions to Typescript (#30777) --- app/javascript/mastodon/actions/streaming.js | 2 +- app/javascript/mastodon/actions/timelines.js | 21 +++++-------------- .../mastodon/actions/timelines_typed.ts | 20 ++++++++++++++++++ .../features/account_timeline/index.jsx | 4 ++-- app/javascript/mastodon/reducers/compose.js | 9 ++++---- app/javascript/mastodon/reducers/contexts.js | 8 ++++--- app/javascript/mastodon/reducers/modal.ts | 7 ++++--- .../mastodon/reducers/notifications.js | 13 ++++++------ .../mastodon/reducers/picture_in_picture.ts | 8 +++---- app/javascript/mastodon/reducers/statuses.js | 7 ++++--- app/javascript/mastodon/reducers/timelines.js | 15 ++++++------- 11 files changed, 65 insertions(+), 49 deletions(-) create mode 100644 app/javascript/mastodon/actions/timelines_typed.ts diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 9daeb3c60fc60b..e7fe1c53ed09ed 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -77,7 +77,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti }, onDisconnect() { - dispatch(disconnectTimeline(timelineId)); + dispatch(disconnectTimeline({ timeline: timelineId })); if (options.fallback) { // @ts-expect-error diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index dc37cdf1f1f471..d1851551b12d15 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -6,9 +6,11 @@ import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; +import {timelineDelete} from './timelines_typed'; + +export { disconnectTimeline } from './timelines_typed'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; -export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; @@ -17,7 +19,6 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING'; -export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_MARK_AS_PARTIAL = 'TIMELINE_MARK_AS_PARTIAL'; @@ -62,16 +63,10 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')).toJSON(); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); - dispatch({ - type: TIMELINE_DELETE, - id, - accountId, - references, - reblogOf, - }); + dispatch(timelineDelete(id, accountId, references, reblogOf)); }; } @@ -225,12 +220,6 @@ export function connectTimeline(timeline) { }; } -export const disconnectTimeline = timeline => ({ - type: TIMELINE_DISCONNECT, - timeline, - usePendingItems: preferPendingItems, -}); - export const markAsPartial = timeline => ({ type: TIMELINE_MARK_AS_PARTIAL, timeline, diff --git a/app/javascript/mastodon/actions/timelines_typed.ts b/app/javascript/mastodon/actions/timelines_typed.ts new file mode 100644 index 00000000000000..07d82b2f015a7c --- /dev/null +++ b/app/javascript/mastodon/actions/timelines_typed.ts @@ -0,0 +1,20 @@ +import { createAction } from '@reduxjs/toolkit'; + +import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; + +export const disconnectTimeline = createAction( + 'timeline/disconnect', + ({ timeline }: { timeline: string }) => ({ + payload: { + timeline, + usePendingItems: preferPendingItems, + }, + }), +); + +export const timelineDelete = createAction<{ + statusId: string; + accountId: string; + references: string[]; + reblogOf: string | null; +}>('timelines/delete'); diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx index 0478f7a1a16b6f..dc69f83e77afaf 100644 --- a/app/javascript/mastodon/features/account_timeline/index.jsx +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -133,7 +133,7 @@ class AccountTimeline extends ImmutablePureComponent { } if (prevProps.accountId === me && accountId !== me) { - dispatch(disconnectTimeline(`account:${me}`)); + dispatch(disconnectTimeline({ timeline: `account:${me}` })); } } @@ -141,7 +141,7 @@ class AccountTimeline extends ImmutablePureComponent { const { dispatch, accountId } = this.props; if (accountId === me) { - dispatch(disconnectTimeline(`account:${me}`)); + dispatch(disconnectTimeline({ timeline: `account:${me}` })); } } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 97218e9f7505d1..9f66c09631d770 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { COMPOSE_MOUNT, COMPOSE_UNMOUNT, @@ -51,7 +53,6 @@ import { } from '../actions/compose'; import { REDRAFT } from '../actions/statuses'; import { STORE_HYDRATE } from '../actions/store'; -import { TIMELINE_DELETE } from '../actions/timelines'; import { me } from '../initial_state'; import { unescapeHTML } from '../utils/html'; import { uuid } from '../uuid'; @@ -446,10 +447,10 @@ export default function compose(state = initialState, action) { return updateSuggestionTags(state, action.token); case COMPOSE_TAG_HISTORY_UPDATE: return state.set('tagHistory', fromJS(action.tags)); - case TIMELINE_DELETE: - if (action.id === state.get('in_reply_to')) { + case timelineDelete.type: + if (action.payload.statusId === state.get('in_reply_to')) { return state.set('in_reply_to', null); - } else if (action.id === state.get('id')) { + } else if (action.payload.statusId === state.get('id')) { return state.set('id', null); } else { return state; diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index f7d7419a4e3ab9..b2c6f3f1abeae4 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,11 +1,13 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; +import { TIMELINE_UPDATE } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -97,8 +99,8 @@ export default function replies(state = initialState, action) { return filterContexts(state, action.payload.relationship, action.payload.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); - case TIMELINE_DELETE: - return deleteFromContexts(state, [action.id]); + case timelineDelete.type: + return deleteFromContexts(state, [action.payload.statusId]); case TIMELINE_UPDATE: return updateContext(state, action.status); default: diff --git a/app/javascript/mastodon/reducers/modal.ts b/app/javascript/mastodon/reducers/modal.ts index 368f26542c0159..ca85eb8c7f504a 100644 --- a/app/javascript/mastodon/reducers/modal.ts +++ b/app/javascript/mastodon/reducers/modal.ts @@ -1,10 +1,11 @@ import type { Reducer } from '@reduxjs/toolkit'; import { Record as ImmutableRecord, Stack } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; import type { ModalType } from '../actions/modal'; import { openModal, closeModal } from '../actions/modal'; -import { TIMELINE_DELETE } from '../actions/timelines'; export type ModalProps = Record; interface Modal { @@ -72,10 +73,10 @@ export const modalReducer: Reducer = (state = initialState, action) => { // TODO: type those actions else if (action.type === COMPOSE_UPLOAD_CHANGE_SUCCESS) return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); - else if (action.type === TIMELINE_DELETE) + else if (timelineDelete.match(action)) return state.update('stack', (stack) => stack.filterNot( - (modal) => modal.get('modalProps').statusId === action.id, + (modal) => modal.get('modalProps').statusId === action.payload.statusId, ), ); else return state; diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 64cddcb666864f..79aa5651ff1da6 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -1,6 +1,7 @@ import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { blockDomainSuccess } from 'mastodon/actions/domain_blocks'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; import { authorizeFollowRequestSuccess, @@ -30,7 +31,7 @@ import { NOTIFICATIONS_SET_BROWSER_SUPPORT, NOTIFICATIONS_SET_BROWSER_PERMISSION, } from '../actions/notifications'; -import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'; +import { disconnectTimeline } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -291,11 +292,11 @@ export default function notifications(state = initialState, action) { return filterNotifications(state, [action.payload.id], 'follow_request'); case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); - case TIMELINE_DELETE: - return deleteByStatus(state, action.id); - case TIMELINE_DISCONNECT: - return action.timeline === 'home' ? - state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : + case timelineDelete.type: + return deleteByStatus(state, action.payload.statusId); + case disconnectTimeline.type: + return action.payload.timeline === 'home' ? + state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : state; case NOTIFICATIONS_MARK_AS_READ: const lastNotification = state.get('items').find(item => item !== null); diff --git a/app/javascript/mastodon/reducers/picture_in_picture.ts b/app/javascript/mastodon/reducers/picture_in_picture.ts index 0feddcb706319f..10d4f1fae51233 100644 --- a/app/javascript/mastodon/reducers/picture_in_picture.ts +++ b/app/javascript/mastodon/reducers/picture_in_picture.ts @@ -4,8 +4,7 @@ import { deployPictureInPictureAction, removePictureInPicture, } from 'mastodon/actions/picture_in_picture'; - -import { TIMELINE_DELETE } from '../actions/timelines'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; export interface PIPMediaProps { src: string; @@ -49,8 +48,9 @@ export const pictureInPictureReducer: Reducer = ( ...action.payload.props, }; else if (removePictureInPicture.match(action)) return initialState; - else if (action.type === TIMELINE_DELETE) - if (state.type && state.statusId === action.id) return initialState; + else if (timelineDelete.match(action)) + if (state.type && state.statusId === action.payload.statusId) + return initialState; return state; }; diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index ca766f73a36e5d..d92174f8063bc5 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { normalizeStatusTranslation } from '../actions/importer/normalizer'; import { @@ -27,7 +29,6 @@ import { STATUS_FETCH_REQUEST, STATUS_FETCH_FAIL, } from '../actions/statuses'; -import { TIMELINE_DELETE } from '../actions/timelines'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -114,8 +115,8 @@ export default function statuses(state = initialState, action) { }); case STATUS_COLLAPSE: return state.setIn([action.id, 'collapsed'], action.isCollapsed); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references); case STATUS_TRANSLATE_SUCCESS: return statusTranslateSuccess(state, action.id, action.translation); case STATUS_TRANSLATE_UNDO: diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 4c9ab98a82ec65..786d20b3073f8d 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, @@ -7,19 +9,18 @@ import { } from '../actions/accounts'; import { TIMELINE_UPDATE, - TIMELINE_DELETE, TIMELINE_CLEAR, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, TIMELINE_SCROLL_TOP, TIMELINE_CONNECT, - TIMELINE_DISCONNECT, TIMELINE_LOAD_PENDING, TIMELINE_MARK_AS_PARTIAL, TIMELINE_INSERT, TIMELINE_GAP, TIMELINE_SUGGESTIONS, + disconnectTimeline, } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -201,8 +202,8 @@ export default function timelines(state = initialState, action) { return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references, action.reblogOf); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references, action.payload.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case blockAccountSuccess.type: @@ -214,11 +215,11 @@ export default function timelines(state = initialState, action) { return updateTop(state, action.timeline, action.top); case TIMELINE_CONNECT: return state.update(action.timeline, initialTimeline, map => reconnectTimeline(map, action.usePendingItems)); - case TIMELINE_DISCONNECT: + case disconnectTimeline.type: return state.update( - action.timeline, + action.payload.timeline, initialTimeline, - map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), + map => map.set('online', false).update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), ); case TIMELINE_MARK_AS_PARTIAL: return state.update( From f723370c6982d914695378ef70949b22a8bb235d Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 20 Jun 2024 15:44:49 +0200 Subject: [PATCH 05/15] Fix missing `account_warning` delegation in `NotificationGroup` (#30779) --- app/models/notification_group.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 43612d49bbdc13..093fdbe33ce65f 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -29,5 +29,6 @@ def self.from_notification(notification) :target_status, :report, :account_relationship_severance_event, + :account_warning, to: :notification, prefix: false end From 6ab6146c0be04271d9da1dc57b842e8998fbbfb9 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 20 Jun 2024 11:30:49 -0400 Subject: [PATCH 06/15] Remove --no-exclude-limit for RuboCop ToDo (#30427) --- .rubocop_todo.yml | 50 +++-------------------------------------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8a4e5980308023..fb3a928d343d50 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by -# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.63.5. +# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` +# using RuboCop version 1.64.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -55,7 +55,6 @@ Style/FetchEnvVar: - 'config/initializers/vapid.rb' - 'lib/mastodon/redis_config.rb' - 'lib/tasks/repo.rake' - - 'spec/system/profile_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. @@ -66,40 +65,10 @@ Style/FormatStringToken: - 'config/initializers/devise.rb' - 'lib/paperclip/color_extractor.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/GlobalStdStream: - Exclude: - - 'config/environments/development.rb' - - 'config/environments/production.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Exclude: - - 'app/lib/activitypub/activity/block.rb' - - 'app/lib/request.rb' - - 'app/lib/request_pool.rb' - - 'app/lib/webfinger.rb' - - 'app/lib/webfinger_resource.rb' - - 'app/models/concerns/account/counters.rb' - - 'app/models/concerns/user/ldap_authenticable.rb' - - 'app/models/tag.rb' - - 'app/models/user.rb' - - 'app/services/fan_out_on_write_service.rb' - - 'app/services/post_status_service.rb' - - 'app/services/process_hashtags_service.rb' - - 'app/workers/move_worker.rb' - - 'app/workers/redownload_avatar_worker.rb' - - 'app/workers/redownload_header_worker.rb' - - 'app/workers/redownload_media_worker.rb' - - 'app/workers/remote_account_refresh_worker.rb' - - 'config/initializers/devise.rb' - - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb' - - 'lib/devise/strategies/two_factor_pam_authenticatable.rb' - - 'lib/mastodon/cli/accounts.rb' - - 'lib/mastodon/cli/maintenance.rb' - - 'lib/mastodon/cli/media.rb' - - 'lib/tasks/repo.rake' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). Style/HashTransformValues: @@ -121,16 +90,10 @@ Style/MutableConstant: - 'app/services/delete_account_service.rb' - 'lib/mastodon/migration_warning.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/NilLambda: - Exclude: - - 'config/initializers/paperclip.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'app/helpers/admin/account_moderation_notes_helper.rb' - 'app/helpers/jsonld_helper.rb' - 'app/lib/admin/system_check/message.rb' - 'app/lib/request.rb' @@ -154,13 +117,6 @@ Style/RedundantConstantBase: - 'config/environments/production.rb' - 'config/initializers/sidekiq.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. -# AllowedMethods: present?, blank?, presence, try, try! -Style/SafeNavigation: - Exclude: - - 'app/models/concerns/account/finder_concern.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets From bb2d77b4a07e95e7d96df080f2d6994c1422b0c0 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 20 Jun 2024 17:54:50 +0200 Subject: [PATCH 07/15] Change `/api/v2_alpha/notifications` to only return historical data in pages (#30781) --- app/controllers/api/v2_alpha/notifications_controller.rb | 2 +- app/models/notification_group.rb | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v2_alpha/notifications_controller.rb b/app/controllers/api/v2_alpha/notifications_controller.rb index 19d3ac9018f014..edba23ab4a823c 100644 --- a/app/controllers/api/v2_alpha/notifications_controller.rb +++ b/app/controllers/api/v2_alpha/notifications_controller.rb @@ -15,7 +15,7 @@ def index @relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) end - render json: @notifications.map { |notification| NotificationGroup.from_notification(notification) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata + render json: @notifications.map { |notification| NotificationGroup.from_notification(notification, max_id: @group_metadata.dig(notification.group_key, :max_id)) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata end def show diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 093fdbe33ce65f..b1cbd7c19abf9f 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -3,13 +3,16 @@ class NotificationGroup < ActiveModelSerializers::Model attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id - def self.from_notification(notification) + def self.from_notification(notification, max_id: nil) if notification.group_key.present? # TODO: caching and preloading - most_recent_notifications = notification.account.notifications.where(group_key: notification.group_key).order(id: :desc).take(3) + scope = notification.account.notifications.where(group_key: notification.group_key) + scope = scope.where(id: ..max_id) if max_id.present? + + most_recent_notifications = scope.order(id: :desc).take(3) most_recent_id = most_recent_notifications.first.id sample_accounts = most_recent_notifications.map(&:from_account) - notifications_count = notification.account.notifications.where(group_key: notification.group_key).count + notifications_count = scope.count else most_recent_id = notification.id sample_accounts = [notification.from_account] From aa2ecd0c4c7821bf6ef4b814b5c793201409832e Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 13:52:29 +0200 Subject: [PATCH 08/15] Create .callstack.yaml --- .callstack.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .callstack.yaml diff --git a/.callstack.yaml b/.callstack.yaml new file mode 100644 index 00000000000000..d7a0512950e312 --- /dev/null +++ b/.callstack.yaml @@ -0,0 +1,31 @@ +# yaml-language-server: $schema=https://storage.googleapis.com/config-schema/schema.v2.json + +pr_review: + modules: + bug_hunter: + enabled: true + suggestions: true + + code_suggestions: + enabled: false + + code_conventions: + enabled: false + conventions: | + Exported code should be defined before internal code. + + description: + enabled: true + diagram: true + + grammar: + enabled: true + + performance: + enabled: true + + security: + enabled: true + + exclude: + - "**/go.*" From 56288e721620fbf9ea795b8792ce38a28a5e24f1 Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 13:52:52 +0200 Subject: [PATCH 09/15] Create reviewer.yaml --- .github/workflows/reviewer.yaml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/reviewer.yaml diff --git a/.github/workflows/reviewer.yaml b/.github/workflows/reviewer.yaml new file mode 100644 index 00000000000000..9fdccd74457513 --- /dev/null +++ b/.github/workflows/reviewer.yaml @@ -0,0 +1,33 @@ +name: Cal PR Review + +on: + workflow_dispatch: + inputs: + config: + type: string + description: "config for reviewer" + required: true + head: + type: string + description: "head commit sha" + required: true + base: + type: string + description: "base commit sha" + required: false +jobs: + review: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.head }} + + - name: Run reviewer + uses: addnab/docker-run-action@v3 + with: + image: callstackai/cal:latest + options: -v ${{ github.workspace }}:/code + run: | + /reviewer --dir /code --id ${{ github.run_id }} --token ${{ github.token }} --config "${{ inputs.config }}" From 3d554d568e1caf45b378475f6e3704aa2ab576c0 Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 13:53:20 +0200 Subject: [PATCH 10/15] Rename reviewer.yaml to reviewer.yml --- .github/workflows/{reviewer.yaml => reviewer.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{reviewer.yaml => reviewer.yml} (100%) diff --git a/.github/workflows/reviewer.yaml b/.github/workflows/reviewer.yml similarity index 100% rename from .github/workflows/reviewer.yaml rename to .github/workflows/reviewer.yml From c48c1b577d1ca29b7f648a2b9af0f1e4fb5bb25a Mon Sep 17 00:00:00 2001 From: hackal Date: Wed, 31 Jul 2024 14:01:36 +0200 Subject: [PATCH 11/15] introduce text parsing --- app/javascript/mastodon/actions/timelines.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index d1851551b12d15..5a5c3301a9cd2d 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -63,10 +63,10 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')).toJSON(); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')).toText(); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); - dispatch(timelineDelete(id, accountId, references, reblogOf)); + dispatch(timelineDelete(id, accountId, reference, reblogOf)); }; } From af3a1a919c4ff892fb06d9ba9222c372bed0c127 Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 14:16:56 +0200 Subject: [PATCH 12/15] Update notification_group.rb --- app/models/notification_group.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index b1cbd7c19abf9f..1dd3c507e02eca 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -9,7 +9,7 @@ def self.from_notification(notification, max_id: nil) scope = notification.account.notifications.where(group_key: notification.group_key) scope = scope.where(id: ..max_id) if max_id.present? - most_recent_notifications = scope.order(id: :desc).take(3) + most_recent_notifications = scope.order(id: :desc).take(0) most_recent_id = most_recent_notifications.first.id sample_accounts = most_recent_notifications.map(&:from_account) notifications_count = scope.count From 9a8c85226b7f71f6500e56413dd382f74353f8da Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 14:37:45 +0200 Subject: [PATCH 13/15] Update notification_group.rb --- app/models/notification_group.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 1dd3c507e02eca..7afd153edaba87 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -3,7 +3,7 @@ class NotificationGroup < ActiveModelSerializers::Model attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id - def self.from_notification(notification, max_id: nil) + def self.from_notification(notification, max_id: 0) if notification.group_key.present? # TODO: caching and preloading scope = notification.account.notifications.where(group_key: notification.group_key) From 0426f7a9c87a21d3570b385c33fd610609c0c3aa Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 14:47:08 +0200 Subject: [PATCH 14/15] Update notification_group.rb --- app/models/notification_group.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 7afd153edaba87..894c98269db289 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -3,13 +3,13 @@ class NotificationGroup < ActiveModelSerializers::Model attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id - def self.from_notification(notification, max_id: 0) + def self.from_notification(notification, max_id: nil) if notification.group_key.present? # TODO: caching and preloading scope = notification.account.notifications.where(group_key: notification.group_key) scope = scope.where(id: ..max_id) if max_id.present? - most_recent_notifications = scope.order(id: :desc).take(0) + most_recent_notifications = scope.order(id: :desc).take(-1) most_recent_id = most_recent_notifications.first.id sample_accounts = most_recent_notifications.map(&:from_account) notifications_count = scope.count @@ -21,10 +21,10 @@ def self.from_notification(notification, max_id: 0) NotificationGroup.new( notification: notification, - group_key: notification.group_key || "ungrouped-#{notification.id}", + group_key: notification || "ungrouped-#{notification.id}", sample_accounts: sample_accounts, notifications_count: notifications_count, - most_recent_notification_id: most_recent_id + most_recent_notification_id: max_id ) end From 6c2674013e0e5c609055503018a7f48dc58280b2 Mon Sep 17 00:00:00 2001 From: Adam Pavlisin Date: Wed, 31 Jul 2024 14:56:16 +0200 Subject: [PATCH 15/15] Update notification_group.rb --- app/models/notification_group.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb index 894c98269db289..1dd3c507e02eca 100644 --- a/app/models/notification_group.rb +++ b/app/models/notification_group.rb @@ -9,7 +9,7 @@ def self.from_notification(notification, max_id: nil) scope = notification.account.notifications.where(group_key: notification.group_key) scope = scope.where(id: ..max_id) if max_id.present? - most_recent_notifications = scope.order(id: :desc).take(-1) + most_recent_notifications = scope.order(id: :desc).take(0) most_recent_id = most_recent_notifications.first.id sample_accounts = most_recent_notifications.map(&:from_account) notifications_count = scope.count @@ -21,10 +21,10 @@ def self.from_notification(notification, max_id: nil) NotificationGroup.new( notification: notification, - group_key: notification || "ungrouped-#{notification.id}", + group_key: notification.group_key || "ungrouped-#{notification.id}", sample_accounts: sample_accounts, notifications_count: notifications_count, - most_recent_notification_id: max_id + most_recent_notification_id: most_recent_id ) end