Merge pull request #1310 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
rebase/4.0.0rc2
ThibG 2020-04-02 22:17:29 +02:00 committed by GitHub
commit 5c5317041f
75 changed files with 605 additions and 556 deletions

14
Gemfile
View File

@ -69,7 +69,7 @@ gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b
gem 'nokogiri', '~> 1.10' gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2' gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.10' gem 'oj', '~> 3.10'
gem 'ox', '~> 2.12' gem 'ox', '~> 2.13'
gem 'parslet' gem 'parslet'
gem 'parallel', '~> 1.19' gem 'parallel', '~> 1.19'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
@ -94,7 +94,7 @@ gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.2.0' gem 'stoplight', '~> 2.2.0'
gem 'strong_migrations', '~> 0.6' gem 'strong_migrations', '~> 0.6'
gem 'tty-command', '~> 0.9', require: false gem 'tty-command', '~> 0.9', require: false
gem 'tty-prompt', '~> 0.20', require: false gem 'tty-prompt', '~> 0.21', require: false
gem 'twitter-text', '~> 1.14' gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2019' gem 'tzinfo-data', '~> 1.2019'
gem 'webpacker', '~> 4.2' gem 'webpacker', '~> 4.2'
@ -112,7 +112,7 @@ group :development, :test do
gem 'i18n-tasks', '~> 0.9', require: false gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.8' gem 'pry-byebug', '~> 3.8'
gem 'pry-rails', '~> 0.3' gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 3.9' gem 'rspec-rails', '~> 4.0'
end end
group :production, :test do group :production, :test do
@ -122,19 +122,19 @@ end
group :test do group :test do
gem 'capybara', '~> 3.31' gem 'capybara', '~> 3.31'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.10' gem 'faker', '~> 2.11'
gem 'microformats', '~> 4.2' gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.18', require: false gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.8' gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 2.30' gem 'parallel_tests', '~> 2.32'
end end
group :development do group :development do
gem 'active_record_query_trace', '~> 1.7' gem 'active_record_query_trace', '~> 1.7'
gem 'annotate', '~> 3.0' gem 'annotate', '~> 3.0'
gem 'better_errors', '~> 2.5' gem 'better_errors', '~> 2.6'
gem 'binding_of_caller', '~> 0.7' gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1' gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7' gem 'letter_opener', '~> 1.7'
@ -142,7 +142,7 @@ group :development do
gem 'memory_profiler' gem 'memory_profiler'
gem 'rubocop', '~> 0.79', require: false gem 'rubocop', '~> 0.79', require: false
gem 'rubocop-rails', '~> 2.4', require: false gem 'rubocop-rails', '~> 2.4', require: false
gem 'brakeman', '~> 4.7', require: false gem 'brakeman', '~> 4.8', require: false
gem 'bundler-audit', '~> 0.6', require: false gem 'bundler-audit', '~> 0.6', require: false
gem 'capistrano', '~> 3.12' gem 'capistrano', '~> 3.12'

View File

@ -108,7 +108,7 @@ GEM
aws-sigv4 (1.1.1) aws-sigv4 (1.1.1)
aws-eventstream (~> 1.0, >= 1.0.2) aws-eventstream (~> 1.0, >= 1.0.2)
bcrypt (3.1.12) bcrypt (3.1.12)
better_errors (2.5.1) better_errors (2.6.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
@ -116,9 +116,9 @@ GEM
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blurhash (0.1.4) blurhash (0.1.4)
ffi (~> 1.10.0) ffi (~> 1.10.0)
bootsnap (1.4.5) bootsnap (1.4.6)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (4.7.2) brakeman (4.8.0)
browser (4.0.0) browser (4.0.0)
builder (3.2.4) builder (3.2.4)
bullet (6.1.0) bullet (6.1.0)
@ -166,7 +166,7 @@ GEM
cocaine (0.5.8) cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
coderay (1.1.2) coderay (1.1.2)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.6)
connection_pool (2.2.2) connection_pool (2.2.2)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
@ -218,7 +218,7 @@ GEM
tzinfo tzinfo
excon (0.71.0) excon (0.71.0)
fabrication (2.21.0) fabrication (2.21.0)
faker (2.10.1) faker (2.11.0)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
faraday (0.17.3) faraday (0.17.3)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
@ -299,19 +299,19 @@ GEM
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.0) idn-ruby (0.1.0)
ipaddress (0.8.3) ipaddress (0.8.3)
iso-639 (0.2.8) iso-639 (0.3.5)
jaro_winkler (1.5.4) jaro_winkler (1.5.4)
jmespath (1.4.0) jmespath (1.4.0)
json (2.3.0) json (2.3.0)
json-canonicalization (0.2.0) json-canonicalization (0.2.0)
json-ld (3.1.1) json-ld (3.1.2)
htmlentities (~> 4.3) htmlentities (~> 4.3)
json-canonicalization (~> 0.2) json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.14) multi_json (~> 1.14)
rack (~> 2.0) rack (~> 2.0)
rdf (~> 3.1) rdf (~> 3.1)
json-ld-preloaded (3.1.1) json-ld-preloaded (3.1.2)
json-ld (~> 3.1) json-ld (~> 3.1)
rdf (~> 3.1) rdf (~> 3.1)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
@ -365,7 +365,7 @@ GEM
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.14.0) minitest (5.14.0)
msgpack (1.3.1) msgpack (1.3.3)
multi_json (1.14.1) multi_json (1.14.1)
multipart-post (2.1.1) multipart-post (2.1.1)
necromancer (0.5.1) necromancer (0.5.1)
@ -383,7 +383,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.3) oj (3.10.5)
omniauth (1.9.1) omniauth (1.9.1)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -395,7 +395,7 @@ GEM
omniauth (~> 1.3, >= 1.3.2) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7) ruby-saml (~> 1.7)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.12.1) ox (2.13.2)
paperclip (6.0.0) paperclip (6.0.0)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -406,7 +406,7 @@ GEM
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.19.1) parallel (1.19.1)
parallel_tests (2.30.1) parallel_tests (2.32.0)
parallel parallel
parser (2.7.0.5) parser (2.7.0.5)
ast (~> 2.4.0) ast (~> 2.4.0)
@ -414,7 +414,7 @@ GEM
pastel (0.7.3) pastel (0.7.3)
equatable (~> 0.6) equatable (~> 0.6)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.2.2) pg (1.2.3)
pghero (2.4.1) pghero (2.4.1)
activerecord (>= 5) activerecord (>= 5)
pkg-config (1.4.1) pkg-config (1.4.1)
@ -531,14 +531,14 @@ GEM
rspec-mocks (3.9.1) rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0) rspec-support (~> 3.9.0)
rspec-rails (3.9.1) rspec-rails (4.0.0)
actionpack (>= 3.0) actionpack (>= 4.2)
activesupport (>= 3.0) activesupport (>= 4.2)
railties (>= 3.0) railties (>= 4.2)
rspec-core (~> 3.9.0) rspec-core (~> 3.9)
rspec-expectations (~> 3.9.0) rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9.0) rspec-mocks (~> 3.9)
rspec-support (~> 3.9.0) rspec-support (~> 3.9)
rspec-sidekiq (3.0.3) rspec-sidekiq (3.0.3)
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
@ -577,7 +577,7 @@ GEM
sidekiq (>= 3) sidekiq (>= 3)
thwait thwait
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (6.0.20) sidekiq-unique-jobs (6.0.21)
concurrent-ruby (~> 1.0, >= 1.0.5) concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0) sidekiq (>= 4.0, < 7.0)
thor (~> 0) thor (~> 0)
@ -616,11 +616,11 @@ GEM
thread_safe (0.3.6) thread_safe (0.3.6)
thwait (0.1.0) thwait (0.1.0)
tilt (2.0.10) tilt (2.0.10)
tty-color (0.5.0) tty-color (0.5.1)
tty-command (0.9.0) tty-command (0.9.0)
pastel (~> 0.7.0) pastel (~> 0.7.0)
tty-cursor (0.7.0) tty-cursor (0.7.1)
tty-prompt (0.20.0) tty-prompt (0.21.0)
necromancer (~> 0.5.0) necromancer (~> 0.5.0)
pastel (~> 0.7.0) pastel (~> 0.7.0)
tty-reader (~> 0.7.0) tty-reader (~> 0.7.0)
@ -628,7 +628,7 @@ GEM
tty-cursor (~> 0.7) tty-cursor (~> 0.7)
tty-screen (~> 0.7) tty-screen (~> 0.7)
wisper (~> 2.0.0) wisper (~> 2.0.0)
tty-screen (0.7.0) tty-screen (0.7.1)
twitter-text (1.14.7) twitter-text (1.14.7)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.6) tzinfo (1.2.6)
@ -669,11 +669,11 @@ DEPENDENCIES
addressable (~> 2.7) addressable (~> 2.7)
annotate (~> 3.0) annotate (~> 3.0)
aws-sdk-s3 (~> 1.61) aws-sdk-s3 (~> 1.61)
better_errors (~> 2.5) better_errors (~> 2.6)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
blurhash (~> 0.1) blurhash (~> 0.1)
bootsnap (~> 1.4) bootsnap (~> 1.4)
brakeman (~> 4.7) brakeman (~> 4.8)
browser browser
bullet (~> 6.1) bullet (~> 6.1)
bundler-audit (~> 0.6) bundler-audit (~> 0.6)
@ -696,7 +696,7 @@ DEPENDENCIES
dotenv-rails (~> 2.7) dotenv-rails (~> 2.7)
e2mmap (~> 0.1.0) e2mmap (~> 0.1.0)
fabrication (~> 2.21) fabrication (~> 2.21)
faker (~> 2.10) faker (~> 2.11)
fast_blank (~> 1.0) fast_blank (~> 1.0)
fastimage fastimage
fog-core (<= 2.1.0) fog-core (<= 2.1.0)
@ -734,11 +734,11 @@ DEPENDENCIES
omniauth (~> 1.9) omniauth (~> 1.9)
omniauth-cas (~> 1.1) omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
ox (~> 2.12) ox (~> 2.13)
paperclip (~> 6.0) paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6) paperclip-av-transcoder (~> 0.6)
parallel (~> 1.19) parallel (~> 1.19)
parallel_tests (~> 2.30) parallel_tests (~> 2.32)
parslet parslet
pg (~> 1.2) pg (~> 1.2)
pghero (~> 2.4) pghero (~> 2.4)
@ -763,7 +763,7 @@ DEPENDENCIES
redis-namespace (~> 1.7) redis-namespace (~> 1.7)
redis-rails (~> 5.0) redis-rails (~> 5.0)
rqrcode (~> 1.1) rqrcode (~> 1.1)
rspec-rails (~> 3.9) rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.0) rspec-sidekiq (~> 3.0)
rubocop (~> 0.79) rubocop (~> 0.79)
rubocop-rails (~> 2.4) rubocop-rails (~> 2.4)
@ -785,7 +785,7 @@ DEPENDENCIES
thor (~> 0.20) thor (~> 0.20)
thwait (~> 0.1.0) thwait (~> 0.1.0)
tty-command (~> 0.9) tty-command (~> 0.9)
tty-prompt (~> 0.20) tty-prompt (~> 0.21)
twitter-text (~> 1.14) twitter-text (~> 1.14)
tzinfo-data (~> 1.2019) tzinfo-data (~> 1.2019)
webmock (~> 3.8) webmock (~> 3.8)

View File

@ -7,6 +7,7 @@ class Api::V1::StatusesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
before_action :require_user!, except: [:show, :context] before_action :require_user!, except: [:show, :context]
before_action :set_status, only: [:show, :context] before_action :set_status, only: [:show, :context]
before_action :set_thread, only: [:create]
override_rate_limit_headers :create, family: :statuses override_rate_limit_headers :create, family: :statuses
@ -36,7 +37,7 @@ class Api::V1::StatusesController < Api::BaseController
def create def create
@status = PostStatusService.new.call(current_user.account, @status = PostStatusService.new.call(current_user.account,
text: status_params[:status], text: status_params[:status],
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), thread: @thread,
media_ids: status_params[:media_ids], media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive], sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text], spoiler_text: status_params[:spoiler_text],
@ -70,6 +71,12 @@ class Api::V1::StatusesController < Api::BaseController
raise ActiveRecord::RecordNotFound raise ActiveRecord::RecordNotFound
end end
def set_thread
@thread = status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id])
rescue ActiveRecord::RecordNotFound
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
end
def status_params def status_params
params.permit( params.permit(
:status, :status,

View File

@ -29,6 +29,6 @@ class Settings::ImportsController < Settings::BaseController
end end
def import_params def import_params
params.require(:import).permit(:data, :type) params.require(:import).permit(:data, :type, :mode)
end end
end end

View File

@ -370,6 +370,7 @@ export function fetchFollowersFail(id, error) {
type: FOLLOWERS_FETCH_FAIL, type: FOLLOWERS_FETCH_FAIL,
id, id,
error, error,
skipNotFound: true,
}; };
}; };
@ -456,6 +457,7 @@ export function fetchFollowingFail(id, error) {
type: FOLLOWING_FETCH_FAIL, type: FOLLOWING_FETCH_FAIL,
id, id,
error, error,
skipNotFound: true,
}; };
}; };
@ -545,6 +547,7 @@ export function fetchRelationshipsFail(error) {
type: RELATIONSHIPS_FETCH_FAIL, type: RELATIONSHIPS_FETCH_FAIL,
error, error,
skipLoading: true, skipLoading: true,
skipNotFound: true,
}; };
}; };

View File

@ -34,11 +34,11 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u
}; };
}; };
export function showAlertForError(error) { export function showAlertForError(error, skipNotFound = false) {
if (error.response) { if (error.response) {
const { data, status, statusText, headers } = error.response; const { data, status, statusText, headers } = error.response;
if (status === 404 || status === 410) { if (skipNotFound && (status === 404 || status === 410)) {
// Skip these errors as they are reflected in the UI // Skip these errors as they are reflected in the UI
return { type: ALERT_NOOP }; return { type: ALERT_NOOP };
} }

View File

@ -27,4 +27,5 @@ export const fetchAccountIdentityProofsFail = (accountId, err) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL, type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
accountId, accountId,
err, err,
skipNotFound: true,
}); });

View File

@ -165,6 +165,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) {
timeline, timeline,
error, error,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
skipNotFound: timeline.startsWith('account:'),
}; };
}; };

View File

@ -127,15 +127,7 @@ class Poll extends ImmutablePureComponent {
return ( return (
<li key={option.get('title')}> <li key={option.get('title')}>
{showResults && ( <label className={classNames('poll__option', { selectable: !showResults })}>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
<label className={classNames('poll__text', { selectable: !showResults })}>
<input <input
name='vote-options' name='vote-options'
type={poll.get('multiple') ? 'checkbox' : 'radio'} type={poll.get('multiple') ? 'checkbox' : 'radio'}
@ -157,12 +149,26 @@ class Poll extends ImmutablePureComponent {
/> />
)} )}
{showResults && <span className='poll__number'> {showResults && <span className='poll__number'>
{!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />}
{Math.round(percent)}% {Math.round(percent)}%
</span>} </span>}
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} /> <span
className='poll__option__text'
dangerouslySetInnerHTML={{ __html: titleEmojified }}
/>
{!!voted && <span className='poll__voted'>
<Icon id='check' className='poll__voted__mark' title={intl.formatMessage(messages.voted)} />
</span>}
</label> </label>
{showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
</li> </li>
); );
} }

View File

@ -62,7 +62,7 @@ class Option extends React.PureComponent {
return ( return (
<li> <li>
<label className='poll__text editable'> <label className='poll__option editable'>
<span className={classNames('poll__input', { checkbox: isPollMultiple })} /> <span className={classNames('poll__input', { checkbox: isPollMultiple })} />
<AutosuggestInput <AutosuggestInput

View File

@ -195,7 +195,7 @@ class Conversation extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'> <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
<div className='conversation__avatar'> <div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
<AvatarComposite accounts={accounts} size={48} /> <AvatarComposite accounts={accounts} size={48} />
</div> </div>

View File

@ -8,7 +8,7 @@ export default function errorsMiddleware() {
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
if (action.type.match(isFail)) { if (action.type.match(isFail)) {
dispatch(showAlertForError(action.error)); dispatch(showAlertForError(action.error, action.skipNotFound));
} }
} }

View File

@ -344,7 +344,6 @@ export default function compose(state = initialState, action) {
}); });
case COMPOSE_SPOILERNESS_CHANGE: case COMPOSE_SPOILERNESS_CHANGE:
return state.withMutations(map => { return state.withMutations(map => {
map.set('spoiler_text', '');
map.set('spoiler', !state.get('spoiler')); map.set('spoiler', !state.get('spoiler'));
map.set('idempotencyKey', uuid()); map.set('idempotencyKey', uuid());

View File

@ -51,7 +51,6 @@
@include avatar-radius; @include avatar-radius;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
cursor: default;
& div { & div {
@include avatar-radius; @include avatar-radius;

View File

@ -1515,6 +1515,7 @@
padding: 10px; padding: 10px;
padding-top: 12px; padding-top: 12px;
position: relative; position: relative;
cursor: pointer;
} }
&__unread { &__unread {

View File

@ -331,13 +331,11 @@
} }
.display-name { .display-name {
color: $light-text-color;
strong { strong {
color: $inverted-text-color; color: $inverted-text-color;
} }
span {
color: $lighter-text-color;
}
} }
.status__content { .status__content {

View File

@ -14,20 +14,18 @@
} }
&__chart { &__chart {
position: absolute;
top: 0;
left: 0;
height: 100%;
display: inline-block;
border-radius: 4px; border-radius: 4px;
background: darken($ui-primary-color, 14%); display: block;
background: darken($ui-primary-color, 5%);
height: 5px;
min-width: 1%;
&.leading { &.leading {
background: $ui-highlight-color; background: $ui-highlight-color;
} }
} }
&__text { &__option {
position: relative; position: relative;
display: flex; display: flex;
padding: 6px 0; padding: 6px 0;
@ -35,6 +33,13 @@
cursor: default; cursor: default;
overflow: hidden; overflow: hidden;
&__text {
display: inline-block;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: calc(100% - 45px - 25px);
}
input[type=radio], input[type=radio],
input[type=checkbox] { input[type=checkbox] {
display: none; display: none;
@ -119,19 +124,18 @@
&__number { &__number {
display: inline-block; display: inline-block;
width: 52px; width: 45px;
font-weight: 700; font-weight: 700;
padding: 0 10px; flex: 0 0 45px;
padding-left: 8px;
text-align: right;
margin-top: auto;
margin-bottom: auto;
flex: 0 0 52px;
} }
&__vote__mark { &__voted {
float: left; padding: 0 5px;
line-height: 18px; display: inline-block;
&__mark {
font-size: 18px;
}
} }
&__footer { &__footer {
@ -208,7 +212,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
.poll__text { .poll__option {
flex: 0 0 auto; flex: 0 0 auto;
width: calc(100% - (23px + 6px)); width: calc(100% - (23px + 6px));
margin-right: 6px; margin-right: 6px;

View File

@ -396,6 +396,7 @@ export function fetchFollowersFail(id, error) {
type: FOLLOWERS_FETCH_FAIL, type: FOLLOWERS_FETCH_FAIL,
id, id,
error, error,
skipNotFound: true,
}; };
}; };
@ -482,6 +483,7 @@ export function fetchFollowingFail(id, error) {
type: FOLLOWING_FETCH_FAIL, type: FOLLOWING_FETCH_FAIL,
id, id,
error, error,
skipNotFound: true,
}; };
}; };
@ -571,6 +573,7 @@ export function fetchRelationshipsFail(error) {
type: RELATIONSHIPS_FETCH_FAIL, type: RELATIONSHIPS_FETCH_FAIL,
error, error,
skipLoading: true, skipLoading: true,
skipNotFound: true,
}; };
}; };

View File

@ -34,11 +34,11 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u
}; };
}; };
export function showAlertForError(error) { export function showAlertForError(error, skipNotFound = false) {
if (error.response) { if (error.response) {
const { data, status, statusText, headers } = error.response; const { data, status, statusText, headers } = error.response;
if (status === 404 || status === 410) { if (skipNotFound && (status === 404 || status === 410)) {
// Skip these errors as they are reflected in the UI // Skip these errors as they are reflected in the UI
return { type: ALERT_NOOP }; return { type: ALERT_NOOP };
} }

View File

@ -27,4 +27,5 @@ export const fetchAccountIdentityProofsFail = (accountId, err) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL, type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
accountId, accountId,
err, err,
skipNotFound: true,
}); });

View File

@ -149,6 +149,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) {
timeline, timeline,
error, error,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
skipNotFound: timeline.startsWith('account:'),
}; };
}; };

View File

@ -76,8 +76,9 @@ class ColumnHeader extends React.PureComponent {
handlePin = () => { handlePin = () => {
if (!this.props.pinned) { if (!this.props.pinned) {
this.historyBack(); this.context.router.history.replace('/');
} }
this.props.onPin(); this.props.onPin();
} }

View File

@ -127,15 +127,7 @@ class Poll extends ImmutablePureComponent {
return ( return (
<li key={option.get('title')}> <li key={option.get('title')}>
{showResults && ( <label className={classNames('poll__option', { selectable: !showResults })}>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
<label className={classNames('poll__text', { selectable: !showResults })}>
<input <input
name='vote-options' name='vote-options'
type={poll.get('multiple') ? 'checkbox' : 'radio'} type={poll.get('multiple') ? 'checkbox' : 'radio'}
@ -157,12 +149,26 @@ class Poll extends ImmutablePureComponent {
/> />
)} )}
{showResults && <span className='poll__number'> {showResults && <span className='poll__number'>
{!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />}
{Math.round(percent)}% {Math.round(percent)}%
</span>} </span>}
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} /> <span
className='poll__option__text'
dangerouslySetInnerHTML={{ __html: titleEmojified }}
/>
{!!voted && <span className='poll__voted'>
<Icon id='check' className='poll__voted__mark' title={intl.formatMessage(messages.voted)} />
</span>}
</label> </label>
{showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
</li> </li>
); );
} }

View File

@ -432,16 +432,10 @@ class Status extends ImmutablePureComponent {
</a> </a>
</div> </div>
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} /> <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} showThread={showThread} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
{media} {media}
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
<button className='status__content__read-more-button' onClick={this.handleClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
</button>
)}
<StatusActionBar status={status} account={account} {...other} /> <StatusActionBar status={status} account={account} {...other} />
</div> </div>
</div> </div>

View File

@ -20,6 +20,7 @@ export default class StatusContent extends React.PureComponent {
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
expanded: PropTypes.bool, expanded: PropTypes.bool,
showThread: PropTypes.bool,
onExpandedToggle: PropTypes.func, onExpandedToggle: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
collapsable: PropTypes.bool, collapsable: PropTypes.bool,
@ -181,6 +182,7 @@ export default class StatusContent extends React.PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderReadMore = this.props.onClick && status.get('collapsed'); const renderReadMore = this.props.onClick && status.get('collapsed');
const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
const content = { __html: status.get('contentHtml') }; const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') }; const spoilerContent = { __html: status.get('spoilerHtml') };
@ -195,6 +197,12 @@ export default class StatusContent extends React.PureComponent {
directionStyle.direction = 'rtl'; directionStyle.direction = 'rtl';
} }
const showThreadButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
</button>
);
const readMoreButton = ( const readMoreButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'> <button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth /> <FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth />
@ -229,6 +237,8 @@ export default class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} /> <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />} {!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div> </div>
); );
} else if (this.props.onClick) { } else if (this.props.onClick) {
@ -237,6 +247,8 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} /> <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />} {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div>, </div>,
]; ];
@ -251,6 +263,8 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} /> <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />} {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div> </div>
); );
} }

View File

@ -75,7 +75,7 @@ class Option extends React.PureComponent {
return ( return (
<li> <li>
<label className='poll__text editable'> <label className='poll__option editable'>
<span <span
className={classNames('poll__input', { checkbox: isPollMultiple })} className={classNames('poll__input', { checkbox: isPollMultiple })}
onClick={this.handleToggleMultiple} onClick={this.handleToggleMultiple}

View File

@ -160,7 +160,7 @@ class Conversation extends ImmutablePureComponent {
return ( return (
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'> <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
<div className='conversation__avatar'> <div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
<AvatarComposite accounts={accounts} size={48} /> <AvatarComposite accounts={accounts} size={48} />
</div> </div>

View File

@ -8,7 +8,7 @@ export default function errorsMiddleware() {
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
if (action.type.match(isFail)) { if (action.type.match(isFail)) {
dispatch(showAlertForError(action.error)); dispatch(showAlertForError(action.error, action.skipNotFound));
} }
} }

View File

@ -261,7 +261,6 @@ export default function compose(state = initialState, action) {
}); });
case COMPOSE_SPOILERNESS_CHANGE: case COMPOSE_SPOILERNESS_CHANGE:
return state.withMutations(map => { return state.withMutations(map => {
map.set('spoiler_text', '');
map.set('spoiler', !state.get('spoiler')); map.set('spoiler', !state.get('spoiler'));
map.set('idempotencyKey', uuid()); map.set('idempotencyKey', uuid());

View File

@ -142,7 +142,7 @@ html {
} }
.compose-form__autosuggest-wrapper, .compose-form__autosuggest-wrapper,
.poll__text input[type="text"], .poll__option input[type="text"],
.compose-form .spoiler-input__input, .compose-form .spoiler-input__input,
.compose-form__poll-wrapper select, .compose-form__poll-wrapper select,
.search__input, .search__input,

View File

@ -1028,13 +1028,11 @@
} }
.display-name { .display-name {
color: $light-text-color;
strong { strong {
color: $inverted-text-color; color: $inverted-text-color;
} }
span {
color: $light-text-color;
}
} }
.status__content { .status__content {
@ -1333,7 +1331,6 @@
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
cursor: default;
& > div { & > div {
float: left; float: left;
@ -6570,6 +6567,7 @@ noscript {
padding: 10px; padding: 10px;
padding-top: 12px; padding-top: 12px;
position: relative; position: relative;
cursor: pointer;
} }
&__unread { &__unread {

View File

@ -8,20 +8,18 @@
} }
&__chart { &__chart {
position: absolute;
top: 0;
left: 0;
height: 100%;
display: inline-block;
border-radius: 4px; border-radius: 4px;
background: darken($ui-primary-color, 14%); display: block;
background: darken($ui-primary-color, 5%);
height: 5px;
min-width: 1%;
&.leading { &.leading {
background: $ui-highlight-color; background: $ui-highlight-color;
} }
} }
&__text { &__option {
position: relative; position: relative;
display: flex; display: flex;
padding: 6px 0; padding: 6px 0;
@ -29,6 +27,13 @@
cursor: default; cursor: default;
overflow: hidden; overflow: hidden;
&__text {
display: inline-block;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: calc(100% - 45px - 25px);
}
input[type=radio], input[type=radio],
input[type=checkbox] { input[type=checkbox] {
display: none; display: none;
@ -112,19 +117,18 @@
&__number { &__number {
display: inline-block; display: inline-block;
width: 52px; width: 45px;
font-weight: 700; font-weight: 700;
padding: 0 10px; flex: 0 0 45px;
padding-left: 8px;
text-align: right;
margin-top: auto;
margin-bottom: auto;
flex: 0 0 52px;
} }
&__vote__mark { &__voted {
float: left; padding: 0 5px;
line-height: 18px; display: inline-block;
&__mark {
font-size: 18px;
}
} }
&__footer { &__footer {
@ -199,7 +203,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
.poll__text { .poll__option {
flex: 0 0 auto; flex: 0 0 auto;
width: calc(100% - (23px + 6px)); width: calc(100% - (23px + 6px));
margin-right: 6px; margin-right: 6px;

View File

@ -64,7 +64,8 @@ class ImportService < BaseService
end end
def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {}) def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {})
items = @data.take(limit).map { |row| [row['Account address']&.strip, Hash[extra_fields.map { |key, header| [key, row[header]&.strip] }]] }.reject { |(id, _)| id.blank? } local_domain_suffix = "@#{Rails.configuration.x.local_domain}"
items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), Hash[extra_fields.map { |key, header| [key, row[header]&.strip] }]] }.reject { |(id, _)| id.blank? }
if @import.overwrite? if @import.overwrite?
presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] } presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] }

View File

@ -10,10 +10,10 @@ class SearchService < BaseService
@resolve = options[:resolve] || false @resolve = options[:resolve] || false
default_results.tap do |results| default_results.tap do |results|
next if @query.blank? next if @query.blank? || @limit.zero?
if url_query? if url_query?
results.merge!(url_resource_results) unless url_resource.nil? || (@options[:type].present? && url_resource_symbol != @options[:type].to_sym) results.merge!(url_resource_results) unless url_resource.nil? || @offset.positive? || (@options[:type].present? && url_resource_symbol != @options[:type].to_sym)
elsif @query.present? elsif @query.present?
results[:accounts] = perform_accounts_search! if account_searchable? results[:accounts] = perform_accounts_search! if account_searchable?
results[:statuses] = perform_statuses_search! if full_text_searchable? results[:statuses] = perform_statuses_search! if full_text_searchable?

View File

@ -8,16 +8,16 @@
%li %li
- if show_results - if show_results
- percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0 - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0
%span.poll__chart{ style: "width: #{percent}%" } %label.poll__option><
%label.poll__text><
%span.poll__number>< %span.poll__number><
- if own_votes.include?(index) - if own_votes.include?(index)
%i.poll__vote__mark.fa.fa-check %i.poll__voted__mark.fa.fa-check
= percent.round = percent.round
= Formatter.instance.format_poll_option(status, option, autoplay: autoplay) = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
%span.poll__chart{ style: "width: #{percent}%" }
- else - else
%label.poll__text>< %label.poll__option><
%span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
= Formatter.instance.format_poll_option(status, option, autoplay: autoplay) = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
.poll__footer .poll__footer

View File

@ -4,7 +4,7 @@ class ActivityPub::DistributePollUpdateWorker
include Sidekiq::Worker include Sidekiq::Worker
include Payloadable include Payloadable
sidekiq_options queue: 'push', unique: :until_executed, retry: 0 sidekiq_options queue: 'push', lock: :until_executed, retry: 0
def perform(status_id) def perform(status_id)
@status = Status.find(status_id) @status = Status.find(status_id)

View File

@ -3,7 +3,7 @@
class ActivityPub::SynchronizeFeaturedCollectionWorker class ActivityPub::SynchronizeFeaturedCollectionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', unique: :until_executed sidekiq_options queue: 'pull', lock: :until_executed
def perform(account_id) def perform(account_id)
ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id))

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class AfterRemoteFollowRequestWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull', retry: 5
def perform(follow_request_id); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class AfterRemoteFollowWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull', retry: 5
def perform(follow_id); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class NotificationWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: 5
def perform(xml, source_account_id, target_account_id); end
end

View File

@ -3,7 +3,7 @@
class PollExpirationNotifyWorker class PollExpirationNotifyWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed sidekiq_options lock: :until_executed
def perform(poll_id) def perform(poll_id)
poll = Poll.find(poll_id) poll = Poll.find(poll_id)

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class ProcessingWorker
include Sidekiq::Worker
sidekiq_options backtrace: true
def perform(account_id, body); end
end

View File

@ -3,7 +3,7 @@
class PublishScheduledStatusWorker class PublishScheduledStatusWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed sidekiq_options lock: :until_executed
def perform(scheduled_status_id) def perform(scheduled_status_id)
scheduled_status = ScheduledStatus.find(scheduled_status_id) scheduled_status = ScheduledStatus.find(scheduled_status_id)

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::ConfirmationWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: false
def perform(subscription_id, mode, secret = nil, lease_seconds = nil); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::DeliveryWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: 3, dead: false
def perform(subscription_id, payload); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::DistributionWorker
include Sidekiq::Worker
sidekiq_options queue: 'push'
def perform(stream_entry_ids); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::RawDistributionWorker
include Sidekiq::Worker
sidekiq_options queue: 'push'
def perform(xml, source_account_id); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::SubscribeWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: 10, unique: :until_executed, dead: false
def perform(account_id); end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Pubsubhubbub::UnsubscribeWorker
include Sidekiq::Worker
sidekiq_options queue: 'push', retry: false, unique: :until_executed, dead: false
def perform(account_id); end
end

View File

@ -3,7 +3,7 @@
class RegenerationWorker class RegenerationWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed sidekiq_options lock: :until_executed
def perform(account_id, _ = :home) def perform(account_id, _ = :home)
account = Account.find(account_id) account = Account.find(account_id)

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class RemoteProfileUpdateWorker
include Sidekiq::Worker
sidekiq_options queue: 'pull'
def perform(account_id, body, resubscribe); end
end

View File

@ -3,7 +3,7 @@
class ResolveAccountWorker class ResolveAccountWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', unique: :until_executed sidekiq_options queue: 'pull', lock: :until_executed
def perform(uri) def perform(uri)
ResolveAccountService.new.call(uri) ResolveAccountService.new.call(uri)

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class SalmonWorker
include Sidekiq::Worker
sidekiq_options backtrace: true
def perform(account_id, body); end
end

View File

@ -3,7 +3,7 @@
class Scheduler::BackupCleanupScheduler class Scheduler::BackupCleanupScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
old_backups.reorder(nil).find_each(&:destroy!) old_backups.reorder(nil).find_each(&:destroy!)

View File

@ -3,7 +3,7 @@
class Scheduler::DoorkeeperCleanupScheduler class Scheduler::DoorkeeperCleanupScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all

View File

@ -3,7 +3,7 @@
class Scheduler::EmailScheduler class Scheduler::EmailScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
FREQUENCY = 7.days.freeze FREQUENCY = 7.days.freeze
SIGN_IN_OFFSET = 1.day.freeze SIGN_IN_OFFSET = 1.day.freeze

View File

@ -4,7 +4,7 @@ class Scheduler::FeedCleanupScheduler
include Sidekiq::Worker include Sidekiq::Worker
include Redisable include Redisable
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
clean_home_feeds! clean_home_feeds!

View File

@ -5,7 +5,7 @@ class Scheduler::IpCleanupScheduler
RETENTION_PERIOD = 1.year RETENTION_PERIOD = 1.year
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
time_ago = RETENTION_PERIOD.ago time_ago = RETENTION_PERIOD.ago

View File

@ -3,7 +3,7 @@
class Scheduler::MediaCleanupScheduler class Scheduler::MediaCleanupScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
unattached_media.find_each(&:destroy) unattached_media.find_each(&:destroy)

View File

@ -3,7 +3,7 @@
class Scheduler::PgheroScheduler class Scheduler::PgheroScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
PgHero.capture_space_stats PgHero.capture_space_stats

View File

@ -3,7 +3,7 @@
class Scheduler::ScheduledStatusesScheduler class Scheduler::ScheduledStatusesScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
publish_scheduled_statuses! publish_scheduled_statuses!

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Scheduler::SubscriptionsCleanupScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0
def perform; end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Scheduler::SubscriptionsScheduler
include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0
def perform; end
end

View File

@ -3,7 +3,7 @@
class Scheduler::TrendingTagsScheduler class Scheduler::TrendingTagsScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
TrendingTags.update! if Setting.trends TrendingTags.update! if Setting.trends

View File

@ -3,7 +3,7 @@
class Scheduler::UserCleanupScheduler class Scheduler::UserCleanupScheduler
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options unique: :until_executed, retry: 0 sidekiq_options lock: :until_executed, retry: 0
def perform def perform
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch| User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|

View File

@ -3,7 +3,7 @@
class VerifyAccountLinksWorker class VerifyAccountLinksWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull', retry: false, unique: :until_executed sidekiq_options queue: 'pull', retry: false, lock: :until_executed
def perform(account_id) def perform(account_id)
account = Account.find(account_id) account = Account.find(account_id)

View File

@ -37,6 +37,7 @@ if Rails.env.production?
p.style_src :self, :unsafe_inline, assets_host p.style_src :self, :unsafe_inline, assets_host
p.media_src :self, :data, *data_hosts p.media_src :self, :data, *data_hosts
p.frame_src :self, :https p.frame_src :self, :https
p.child_src :self, :blob, assets_host
p.worker_src :self, :blob, assets_host p.worker_src :self, :blob, assets_host
p.connect_src :self, :blob, :data, Rails.configuration.x.streaming_api_base_url, *data_hosts p.connect_src :self, :blob, :data, Rails.configuration.x.streaming_api_base_url, *data_hosts
p.manifest_src :self, assets_host p.manifest_src :self, assets_host

View File

@ -42,6 +42,7 @@ class Rack::Attack
/auth/sign_in /auth/sign_in
/auth /auth
/auth/password /auth/password
/auth/confirmation
).freeze ).freeze
PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ }) PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ })

View File

@ -13,6 +13,11 @@ Sidekiq.configure_server do |config|
config.server_middleware do |chain| config.server_middleware do |chain|
chain.add SidekiqErrorHandler chain.add SidekiqErrorHandler
end end
config.death_handlers << lambda do |job, _ex|
digest = job['lock_digest']
SidekiqUniqueJobs::Digests.delete_by_digest(digest) if digest
end
end end
Sidekiq.configure_client do |config| Sidekiq.configure_client do |config|

View File

@ -1093,6 +1093,8 @@ en:
disallowed_hashtags: disallowed_hashtags:
one: 'contained a disallowed hashtag: %{tags}' one: 'contained a disallowed hashtag: %{tags}'
other: 'contained the disallowed hashtags: %{tags}' other: 'contained the disallowed hashtags: %{tags}'
errors:
in_reply_not_found: The status you are trying to reply to does not appear to exist.
language_detection: Automatically detect language language_detection: Automatically detect language
open_in_web: Open in web open_in_web: Open in web
over_character_limit: character limit of %{max} exceeded over_character_limit: character limit of %{max} exceeded

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'sidekiq/web' require 'sidekiq_unique_jobs/web'
require 'sidekiq-scheduler/web' require 'sidekiq-scheduler/web'
Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base] Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]

View File

@ -115,7 +115,7 @@ module Mastodon
when :filesystem when :filesystem
require 'find' require 'find'
root_path = ENV.fetch('RAILS_ROOT_PATH', File.join(':rails_root', 'public', 'system')).gsub(':rails_root', Rails.root.to_s) root_path = ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')).gsub(':rails_root', Rails.root.to_s)
Find.find(File.join(*[root_path, prefix].compact)) do |path| Find.find(File.join(*[root_path, prefix].compact)) do |path|
next if File.directory?(path) next if File.directory?(path)

View File

@ -65,17 +65,17 @@
"@babel/plugin-proposal-decorators": "^7.8.3", "@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-transform-react-inline-elements": "^7.9.0", "@babel/plugin-transform-react-inline-elements": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0", "@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.8.3", "@babel/preset-env": "^7.9.0",
"@babel/preset-react": "^7.8.3", "@babel/preset-react": "^7.9.4",
"@babel/runtime": "^7.8.4", "@babel/runtime": "^7.8.4",
"@clusterws/cws": "^0.17.3", "@clusterws/cws": "^0.17.3",
"@gamestdio/websocket": "^0.3.2", "@gamestdio/websocket": "^0.3.2",
"array-includes": "^3.1.1", "array-includes": "^3.1.1",
"arrow-key-navigation": "^1.1.0", "arrow-key-navigation": "^1.1.0",
"atrament": "0.2.4", "atrament": "0.2.4",
"autoprefixer": "^9.7.4", "autoprefixer": "^9.7.5",
"axios": "^0.19.2", "axios": "^0.19.2",
"babel-loader": "^8.0.6", "babel-loader": "^8.1.0",
"babel-plugin-lodash": "^3.3.4", "babel-plugin-lodash": "^3.3.4",
"babel-plugin-preval": "^5.0.0", "babel-plugin-preval": "^5.0.0",
"babel-plugin-react-intl": "^3.4.1", "babel-plugin-react-intl": "^3.4.1",
@ -127,7 +127,7 @@
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"punycode": "^2.1.0", "punycode": "^2.1.0",
"@rails/ujs": "^6.0.2", "@rails/ujs": "^6.0.2",
"react": "^16.12.0", "react": "^16.13.1",
"react-dom": "^16.13.0", "react-dom": "^16.13.0",
"react-hotkeys": "^1.1.4", "react-hotkeys": "^1.1.4",
"react-immutable-proptypes": "^2.2.0", "react-immutable-proptypes": "^2.2.0",
@ -157,13 +157,13 @@
"sass": "^1.26.3", "sass": "^1.26.3",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"stacktrace-js": "^2.0.2", "stacktrace-js": "^2.0.2",
"stringz": "^2.0.0", "stringz": "^2.1.0",
"substring-trie": "^1.0.2", "substring-trie": "^1.0.2",
"terser-webpack-plugin": "^2.3.5", "terser-webpack-plugin": "^2.3.5",
"tesseract.js": "^2.0.0-alpha.16", "tesseract.js": "^2.0.0-alpha.16",
"throng": "^4.0.0", "throng": "^4.0.0",
"tiny-queue": "^0.2.1", "tiny-queue": "^0.2.1",
"uuid": "^3.4.0", "uuid": "^7.0.2",
"wavesurfer.js": "^3.3.1", "wavesurfer.js": "^3.3.1",
"webpack": "^4.42.1", "webpack": "^4.42.1",
"webpack-assets-manifest": "^3.1.1", "webpack-assets-manifest": "^3.1.1",
@ -174,7 +174,7 @@
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"babel-jest": "^25.1.0", "babel-jest": "^25.2.4",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2", "enzyme-adapter-react-16": "^1.15.2",
"eslint": "^6.8.0", "eslint": "^6.8.0",
@ -188,6 +188,6 @@
"react-test-renderer": "^16.13.0", "react-test-renderer": "^16.13.0",
"sass-lint": "^1.13.1", "sass-lint": "^1.13.1",
"webpack-dev-server": "^3.10.3", "webpack-dev-server": "^3.10.3",
"yargs": "^15.1.0" "yargs": "^15.3.1"
} }
} }

View File

@ -91,10 +91,6 @@ RSpec.describe ImportService, type: :service do
let(:csv) { attachment_fixture('mute-imports.txt') } let(:csv) { attachment_fixture('mute-imports.txt') }
before do
allow(NotificationWorker).to receive(:perform_async)
end
describe 'when no accounts are followed' do describe 'when no accounts are followed' do
let(:import) { Import.create(account: account, type: 'following', data: csv) } let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, including boosts' do it 'follows the listed accounts, including boosts' do
@ -135,10 +131,6 @@ RSpec.describe ImportService, type: :service do
let(:csv) { attachment_fixture('new-following-imports.txt') } let(:csv) { attachment_fixture('new-following-imports.txt') }
before do
allow(NotificationWorker).to receive(:perform_async)
end
describe 'when no accounts are followed' do describe 'when no accounts are followed' do
let(:import) { Import.create(account: account, type: 'following', data: csv) } let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, respecting boosts' do it 'follows the listed accounts, respecting boosts' do

643
yarn.lock

File diff suppressed because it is too large Load Diff