diff --git a/.codeclimate.yml b/.codeclimate.yml index dc8ca9a6f..2d1de877c 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -30,7 +30,7 @@ plugins: channel: eslint-7 rubocop: enabled: true - channel: rubocop-0-92 + channel: rubocop-1-70 sass-lint: enabled: true exclude_patterns: diff --git a/.rubocop.yml b/.rubocop.yml index 14728bf0e..cb7c73ec7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,17 +2,18 @@ require: - rubocop-rails AllCops: - TargetRubyVersion: 2.4 + TargetRubyVersion: 2.5 + NewCops: disable Exclude: - - 'spec/**/*' - - 'db/**/*' - - 'app/views/**/*' - - 'config/**/*' - - 'bin/*' - - 'Rakefile' - - 'node_modules/**/*' - - 'Vagrantfile' - - 'vendor/**/*' + - 'spec/**/*' + - 'db/**/*' + - 'app/views/**/*' + - 'config/**/*' + - 'bin/*' + - 'Rakefile' + - 'node_modules/**/*' + - 'Vagrantfile' + - 'vendor/**/*' - 'lib/json_ld/*' - 'lib/templates/**/*' diff --git a/Dockerfile b/Dockerfile index 95d45bab4..136b2a35a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:20.04 as build-dep # Use bash for the shell -SHELL ["bash", "-c"] +SHELL ["/usr/bin/bash", "-c"] # Install Node v12 (LTS) ENV NODE_VER="12.20.0" diff --git a/Gemfile b/Gemfile index cef134f27..75ecbda04 100644 --- a/Gemfile +++ b/Gemfile @@ -64,7 +64,7 @@ gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar' gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532' -gem 'nokogiri', '~> 1.10' +gem 'nokogiri', '~> 1.11' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.10' gem 'ox', '~> 2.14' @@ -80,7 +80,7 @@ gem 'rails-settings-cached', '~> 0.6' gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 1.2' -gem 'ruby-progressbar', '~> 1.10' +gem 'ruby-progressbar', '~> 1.11' gem 'sanitize', '~> 5.2' gem 'scenic', '~> 1.5' gem 'sidekiq', '~> 6.1' @@ -123,7 +123,7 @@ group :test do gem 'microformats', '~> 4.2' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.1' - gem 'simplecov', '~> 0.20', require: false + gem 'simplecov', '~> 0.21', require: false gem 'webmock', '~> 3.11' gem 'parallel_tests', '~> 3.4' gem 'rspec_junit_formatter', '~> 0.4' @@ -133,7 +133,7 @@ group :development do gem 'active_record_query_trace', '~> 1.8' gem 'annotate', '~> 3.1' gem 'better_errors', '~> 2.9' - gem 'binding_of_caller', '~> 0.7' + gem 'binding_of_caller', '~> 1.0' gem 'bullet', '~> 6.1' gem 'letter_opener', '~> 1.7' gem 'letter_opener_web', '~> 1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 388a8477d..ab8a5ef98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,7 +100,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) bindata (2.4.8) - binding_of_caller (0.8.0) + binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) blurhash (0.1.4) ffi (~> 1.10.0) @@ -165,7 +165,7 @@ GEM crass (1.0.6) css_parser (1.7.1) addressable - debug_inspector (0.0.3) + debug_inspector (1.0.0) devise (4.7.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -184,7 +184,7 @@ GEM diff-lcs (1.4.4) discard (1.2.0) activerecord (>= 4.2, < 7) - docile (1.3.2) + docile (1.3.4) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) doorkeeper (5.4.0) @@ -236,7 +236,7 @@ GEM fugit (1.3.9) et-orbi (~> 1.1, >= 1.1.8) raabro (~> 1.3) - fuubar (2.5.0) + fuubar (2.5.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) globalid (0.4.2) @@ -349,7 +349,7 @@ GEM mime-types-data (3.2020.0512) mimemagic (0.3.5) mini_mime (1.0.2) - mini_portile2 (2.4.0) + mini_portile2 (2.5.0) minitest (5.14.2) msgpack (1.3.3) multi_json (1.15.0) @@ -359,8 +359,9 @@ GEM net-ssh (>= 2.6.5, < 7.0.0) net-ssh (6.1.0) nio4r (2.5.4) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.11.0) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) nsa (0.2.7) @@ -433,6 +434,7 @@ GEM pundit (2.1.0) activesupport (>= 3.0.0) raabro (1.3.3) + racc (1.5.2) rack (2.2.3) rack-attack (6.3.1) rack (>= 1.0, < 3) @@ -551,7 +553,7 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 0.90.0, < 2.0) - ruby-progressbar (1.10.1) + ruby-progressbar (1.11.0) ruby-saml (1.11.0) nokogiri (>= 1.5.10) rufus-scheduler (3.6.0) @@ -589,7 +591,7 @@ GEM simple_form (5.0.3) actionpack (>= 5.0) activemodel (>= 5.0) - simplecov (0.20.0) + simplecov (0.21.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) @@ -639,7 +641,7 @@ GEM unf (~> 0.1.0) tzinfo (1.2.9) thread_safe (~> 0.1) - tzinfo-data (1.2020.5) + tzinfo-data (1.2020.6) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext @@ -688,7 +690,7 @@ DEPENDENCIES annotate (~> 3.1) aws-sdk-s3 (~> 1.87) better_errors (~> 2.9) - binding_of_caller (~> 0.7) + binding_of_caller (~> 1.0) blurhash (~> 0.1) bootsnap (~> 1.5) brakeman (~> 4.10) @@ -745,7 +747,7 @@ DEPENDENCIES mime-types (~> 3.3.1) net-ldap (~> 0.17) nilsimsa! - nokogiri (~> 1.10) + nokogiri (~> 1.11) nsa (~> 0.2) oj (~> 3.10) omniauth (~> 1.9) @@ -786,7 +788,7 @@ DEPENDENCIES rspec_junit_formatter (~> 0.4) rubocop (~> 1.7) rubocop-rails (~> 2.9) - ruby-progressbar (~> 1.10) + ruby-progressbar (~> 1.11) sanitize (~> 5.2) scenic (~> 1.5) sidekiq (~> 6.1) @@ -795,7 +797,7 @@ DEPENDENCIES sidekiq-unique-jobs (~> 6.0) simple-navigation (~> 4.1) simple_form (~> 5.0) - simplecov (~> 0.20) + simplecov (~> 0.21) sprockets (~> 3.7.2) sprockets-rails (~> 3.2) stackprof diff --git a/Procfile.dev b/Procfile.dev index e589bbf63..ba04fb661 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ -web: env PORT=3000 bundle exec puma -C config/puma.rb -sidekiq: env PORT=3000 bundle exec sidekiq +web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb +sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq stream: env PORT=4000 yarn run start webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0 diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index d3044f180..92dcb5ac7 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -5,7 +5,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController include JsonLdHelper include AccountOwnedConcern - before_action :skip_unknown_actor_delete + before_action :skip_unknown_actor_activity before_action :require_signature! skip_before_action :authenticate_user! @@ -18,13 +18,13 @@ class ActivityPub::InboxesController < ActivityPub::BaseController private - def skip_unknown_actor_delete - head 202 if unknown_deleted_account? + def skip_unknown_actor_activity + head 202 if unknown_affected_account? end - def unknown_deleted_account? + def unknown_affected_account? json = Oj.load(body, mode: :strict) - json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists? + json.is_a?(Hash) && %w(Delete Update).include?(json['type']) && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists? rescue Oj::ParseError false end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 3e66ff212..953874e1a 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -42,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController end def mute - MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration] || 0)) + MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration]&.to_i || 0)) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end diff --git a/app/controllers/api/v1/crypto/keys/claims_controller.rb b/app/controllers/api/v1/crypto/keys/claims_controller.rb index 34b21a380..f9d202d67 100644 --- a/app/controllers/api/v1/crypto/keys/claims_controller.rb +++ b/app/controllers/api/v1/crypto/keys/claims_controller.rb @@ -12,7 +12,7 @@ class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController private def set_claim_results - @claim_results = devices.map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) }.compact + @claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) } end def resource_params diff --git a/app/controllers/api/v1/crypto/keys/queries_controller.rb b/app/controllers/api/v1/crypto/keys/queries_controller.rb index 0851d797d..e6ce9f919 100644 --- a/app/controllers/api/v1/crypto/keys/queries_controller.rb +++ b/app/controllers/api/v1/crypto/keys/queries_controller.rb @@ -17,7 +17,7 @@ class Api::V1::Crypto::Keys::QueriesController < Api::BaseController end def set_query_results - @query_results = @accounts.map { |account| ::Keys::QueryService.new.call(account) }.compact + @query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) } end def account_ids diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb index 28c2ec791..867e6facf 100644 --- a/app/controllers/api/v1/markers_controller.rb +++ b/app/controllers/api/v1/markers_controller.rb @@ -7,7 +7,7 @@ class Api::V1::MarkersController < Api::BaseController before_action :require_user! def index - @markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker } + @markers = current_user.markers.where(timeline: Array(params[:timeline])).index_by(&:timeline) render json: serialize_map(@markers) end diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb index abbdb410a..3fb4b962a 100644 --- a/app/controllers/concerns/cache_concern.rb +++ b/app/controllers/concerns/cache_concern.rb @@ -38,14 +38,14 @@ module CacheConcern klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) unless uncached_ids.empty? - uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item } + uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id) uncached.each_value do |item| Rails.cache.write(item, item) end end - raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact + raw.filter_map { |item| cached_keys_with_value[item.id] || uncached[item.id] } end def cache_collection_paginated_by_id(raw, klass, limit, options) diff --git a/app/controllers/settings/pictures_controller.rb b/app/controllers/settings/pictures_controller.rb index 28df65f8f..58a432530 100644 --- a/app/controllers/settings/pictures_controller.rb +++ b/app/controllers/settings/pictures_controller.rb @@ -7,8 +7,12 @@ module Settings def destroy if valid_picture? - msg = I18n.t('generic.changes_saved_msg') if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' }) - redirect_to settings_profile_path, notice: msg, status: 303 + if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' }) + ActivityPub::UpdateDistributionWorker.perform_async(@account.id) + redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303 + else + redirect_to settings_profile_path + end else bad_request end diff --git a/app/javascript/mastodon/containers/media_container.js b/app/javascript/mastodon/containers/media_container.js index afed6868e..81f54578d 100644 --- a/app/javascript/mastodon/containers/media_container.js +++ b/app/javascript/mastodon/containers/media_container.js @@ -1,8 +1,8 @@ -import React, { PureComponent, Fragment } from 'react'; +import React, { Fragment, PureComponent } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import { List as ImmutableList, fromJS } from 'immutable'; +import { addLocaleData, IntlProvider } from 'react-intl'; +import { fromJS } from 'immutable'; import { getLocale } from 'mastodon/locales'; import { getScrollbarWidth } from 'mastodon/utils/scrollbar'; import MediaGallery from 'mastodon/components/media_gallery'; @@ -27,10 +27,11 @@ export default class MediaContainer extends PureComponent { }; state = { - media: null, - index: null, - time: null, + media : null, + index : null, + time : null, backgroundColor: null, + options : null, }; handleOpenMedia = (media, index) => { @@ -38,26 +39,29 @@ export default class MediaContainer extends PureComponent { document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; this.setState({ media, index }); - } + }; - handleOpenVideo = (video, time) => { - const media = ImmutableList([video]); + handleOpenVideo = (options) => { + const { components } = this.props; + const { media } = JSON.parse(components[options.componetIndex].getAttribute('data-props')); + const mediaList = fromJS(media); document.body.classList.add('with-modals--active'); document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; - this.setState({ media, time }); - } + this.setState({ media: mediaList, options }); + }; handleCloseMedia = () => { document.body.classList.remove('with-modals--active'); document.documentElement.style.marginRight = 0; this.setState({ - media: null, - index: null, - time: null, + media : null, + index : null, + time : null, backgroundColor: null, + options : null, }); } @@ -83,7 +87,8 @@ export default class MediaContainer extends PureComponent { ...(hashtag ? { hashtag: fromJS(hashtag) } : {}), ...(componentName === 'Video' ? { - onOpenVideo: this.handleOpenVideo, + componetIndex: i, + onOpenVideo : this.handleOpenVideo, } : { onOpenMedia: this.handleOpenMedia, }), @@ -100,7 +105,9 @@ export default class MediaContainer extends PureComponent { diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 36ed81fe2..2fcf982c2 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -92,7 +92,7 @@ class ComposeForm extends ImmutablePureComponent { const fulltext = this.getFulltextForCharacterCounting(); const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0; - return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia)); + return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > this.props.maxTootCharsLimit || (isOnlyWhitespace && !anyMedia)); } handleSubmit = () => { @@ -258,8 +258,11 @@ class ComposeForm extends ImmutablePureComponent { - -
+ +
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index 02b018247..3596bd90c 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -6,11 +6,11 @@ import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnBackButton from '../../components/column_back_button'; import ColumnHeader from '../../components/column_header'; -import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { addColumn, moveColumn, removeColumn } from '../../actions/columns'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import { connectListStream } from '../../actions/streaming'; import { expandListTimeline } from '../../actions/timelines'; -import { fetchList, deleteList, updateList } from '../../actions/lists'; +import { deleteList, fetchList, updateList } from '../../actions/lists'; import { openModal } from '../../actions/modal'; import MissingIndicator from '../../components/missing_indicator'; import LoadingIndicator from '../../components/loading_indicator'; @@ -194,7 +194,14 @@ class ListTimeline extends React.PureComponent {
{ ['none', 'list', 'followed'].map(policy => ( - + ))}
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js index 578375a7f..74ae860ca 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import classNames from 'classnames'; import { changeUploadCompose, uploadThumbnail } from '../../../actions/compose'; import { getPointerPosition } from '../../video'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import IconButton from 'mastodon/components/icon_button'; import Button from 'mastodon/components/button'; import Video from 'mastodon/features/video'; @@ -226,9 +226,9 @@ class FocalPointModal extends ImmutablePureComponent { fetchTesseract().then(({ createWorker }) => { const worker = createWorker({ workerPath: tesseractWorkerPath, - corePath: tesseractCorePath, - langPath: assetHost, - logger: ({ status, progress }) => { + corePath : tesseractCorePath, + langPath : `${assetHost}/ocr/lang-data/`, + logger : ({ status, progress }) => { if (status === 'recognizing text') { this.setState({ ocrStatus: 'detecting', progress }); } else { diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 7fe7ed094..b51179fd5 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -26,12 +26,15 @@ export default @injectIntl class MediaModal extends ImmutablePureComponent { static propTypes = { - media: ImmutablePropTypes.list.isRequired, - statusId: PropTypes.string, - index: PropTypes.number.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + media : ImmutablePropTypes.list.isRequired, + statusId : PropTypes.string, + index : PropTypes.number.isRequired, + onClose : PropTypes.func.isRequired, + intl : PropTypes.object.isRequired, onChangeBackgroundColor: PropTypes.func.isRequired, + currentTime : PropTypes.number, + autoPlay : PropTypes.bool, + volume : PropTypes.number, }; static contextTypes = { @@ -183,7 +186,7 @@ class MediaModal extends ImmutablePureComponent { /> ); } else if (image.get('type') === 'video') { - const { time } = this.props; + const { currentTime, autoPlay, volume } = this.props; return (