diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index e850a83a85..06c8ed5186 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -102,12 +102,17 @@ export const ensureComposeIsVisible = (getState) => { }; export function setComposeToStatus(status, text, spoiler_text, content_type) { - return{ - type: COMPOSE_SET_STATUS, - status, - text, - spoiler_text, - content_type, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: COMPOSE_SET_STATUS, + status, + text, + spoiler_text, + content_type, + maxOptions + }); }; } diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index 2b6b589b37..e852b54855 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -90,11 +90,16 @@ export function fetchStatusFail(id, error, skipLoading) { } export function redraft(status, raw_text, content_type) { - return { - type: REDRAFT, - status, - raw_text, - content_type, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: REDRAFT, + status, + raw_text, + content_type, + maxOptions, + }); }; } diff --git a/app/javascript/flavours/glitch/api/interactions.ts b/app/javascript/flavours/glitch/api/interactions.ts index 172f97a256..75092f4e72 100644 --- a/app/javascript/flavours/glitch/api/interactions.ts +++ b/app/javascript/flavours/glitch/api/interactions.ts @@ -1,10 +1,11 @@ import { apiRequestPost } from 'flavours/glitch/api'; -import type { Status, StatusVisibility } from 'flavours/glitch/models/status'; +import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses'; +import type { StatusVisibility } from 'flavours/glitch/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => - apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, { visibility, }); export const apiUnreblog = (statusId: string) => - apiRequestPost(`v1/statuses/${statusId}/unreblog`); + apiRequestPost(`v1/statuses/${statusId}/unreblog`); diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 04a2857836..6e2b6674e4 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -623,8 +623,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); @@ -653,8 +658,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 3e43090788..45a3e7e1aa 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -96,11 +96,16 @@ export const ensureComposeIsVisible = (getState) => { }; export function setComposeToStatus(status, text, spoiler_text) { - return{ - type: COMPOSE_SET_STATUS, - status, - text, - spoiler_text, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: COMPOSE_SET_STATUS, + status, + text, + spoiler_text, + maxOptions, + }); }; } diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 1e4e545d8c..cc50a6b622 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -90,10 +90,15 @@ export function fetchStatusFail(id, error, skipLoading) { } export function redraft(status, raw_text) { - return { - type: REDRAFT, - status, - raw_text, + return (dispatch, getState) => { + const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']); + + dispatch({ + type: REDRAFT, + status, + raw_text, + maxOptions, + }); }; } diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts index 118b5f06d2..62808dcddc 100644 --- a/app/javascript/mastodon/api/interactions.ts +++ b/app/javascript/mastodon/api/interactions.ts @@ -1,10 +1,11 @@ import { apiRequestPost } from 'mastodon/api'; -import type { Status, StatusVisibility } from 'mastodon/models/status'; +import type { ApiStatusJSON } from 'mastodon/api_types/statuses'; +import type { StatusVisibility } from 'mastodon/models/status'; export const apiReblog = (statusId: string, visibility: StatusVisibility) => - apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, { visibility, }); export const apiUnreblog = (statusId: string) => - apiRequestPost(`v1/statuses/${statusId}/unreblog`); + apiRequestPost(`v1/statuses/${statusId}/unreblog`); diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index bfa2ec6a06..3275e16049 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -504,8 +504,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); @@ -533,8 +538,13 @@ export default function compose(state = initialState, action) { } if (action.status.get('poll')) { + let options = action.status.getIn(['poll', 'options']).map(x => x.get('title')); + if (options.size < action.maxOptions) { + options = options.push(''); + } + map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), + options: options, multiple: action.status.getIn(['poll', 'multiple']), expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), })); diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 80d5ed68d3..8792b94550 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -100,6 +100,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @status.ordered_media_attachment_ids = @next_media_attachments.map(&:id) @media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids + + @status.media_attachments.reload if @media_attachments_changed end def download_media_files! diff --git a/app/workers/scheduler/self_destruct_scheduler.rb b/app/workers/scheduler/self_destruct_scheduler.rb index d0b6ce8a07..12cb60135b 100644 --- a/app/workers/scheduler/self_destruct_scheduler.rb +++ b/app/workers/scheduler/self_destruct_scheduler.rb @@ -21,8 +21,9 @@ class Scheduler::SelfDestructScheduler def sidekiq_overwhelmed? redis_mem_info = Sidekiq.redis_info + maxmemory = [redis_mem_info['maxmemory'].to_f, redis_mem_info['total_system_memory'].to_f].filter(&:positive?).min - Sidekiq::Stats.new.enqueued > MAX_ENQUEUED || redis_mem_info['used_memory'].to_f > redis_mem_info['total_system_memory'].to_f * MAX_REDIS_MEM_USAGE + Sidekiq::Stats.new.enqueued > MAX_ENQUEUED || redis_mem_info['used_memory'].to_f > maxmemory * MAX_REDIS_MEM_USAGE end def delete_accounts! diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index abf9c1dd93..2c880365ce 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -343,6 +343,42 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end end + context 'when originally without media attachments and text is removed' do + before do + stub_request(:get, 'https://example.com/foo.png').to_return(body: attachment_fixture('emojo.png')) + end + + let(:payload) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Note', + content: '', + updated: '2021-09-08T22:39:25Z', + attachment: [ + { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png' }, + ], + } + end + + it 'updates media attachments, fetches attachment, records media and text removal in edit' do + subject.call(status, json, json) + + expect(status.reload.ordered_media_attachments.first) + .to be_present + .and(have_attributes(remote_url: 'https://example.com/foo.png')) + + expect(a_request(:get, 'https://example.com/foo.png')) + .to have_been_made + + expect(status.edits.reload.last.ordered_media_attachment_ids) + .to_not be_empty + + expect(status.edits.reload.last.text) + .to_not be_present + end + end + context 'when originally with media attachments' do let(:media_attachments) { [Fabricate(:media_attachment, remote_url: 'https://example.com/foo.png'), Fabricate(:media_attachment, remote_url: 'https://example.com/unused.png')] }