diff --git a/.babelrc b/.babelrc index 292d52e277..19968964ee 100644 --- a/.babelrc +++ b/.babelrc @@ -44,6 +44,7 @@ ] } ], + "transform-react-inline-elements", [ "transform-runtime", { diff --git a/.env.nanobox b/.env.nanobox index 73abefdc65..7920c47b95 100644 --- a/.env.nanobox +++ b/.env.nanobox @@ -69,7 +69,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io # PAPERCLIP_ROOT_URL=/system # Optional asset host for multi-server setups -# CDN_HOST=assets.example.com +# CDN_HOST=https://assets.example.com # S3 (optional) # S3_ENABLED=true diff --git a/.travis.yml b/.travis.yml index 4bb3326664..4d4dc08930 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ addons: - g++-6 - libprotobuf-dev - protobuf-compiler + - libicu-dev rvm: - 2.3.4 diff --git a/Aptfile b/Aptfile index 0456343ef4..3af0956e32 100644 --- a/Aptfile +++ b/Aptfile @@ -3,3 +3,4 @@ libprotobuf-dev ffmpeg libxdamage1 libxfixes3 +libicu-dev diff --git a/Dockerfile b/Dockerfile index 7033cddd40..97a6913930 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit ffmpeg \ file \ git \ + icu-dev \ imagemagick@edge \ libpq \ libxml2 \ diff --git a/Gemfile b/Gemfile index aecd82702d..b52685cba9 100644 --- a/Gemfile +++ b/Gemfile @@ -18,9 +18,11 @@ gem 'aws-sdk', '~> 2.9' gem 'paperclip', '~> 5.1' gem 'paperclip-av-transcoder', '~> 0.6' +gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.5' gem 'bootsnap' gem 'browser' +gem 'charlock_holmes', '~> 0.7.3' gem 'cld3', '~> 3.1' gem 'devise', '~> 4.2' gem 'devise-two-factor', '~> 3.0' @@ -35,6 +37,7 @@ gem 'http_accept_language', '~> 2.1' gem 'httplog', '~> 0.99' gem 'kaminari', '~> 1.0' gem 'link_header', '~> 0.0' +gem 'mime-types', '~> 3.1' gem 'nokogiri', '~> 1.7' gem 'oj', '~> 3.0' gem 'ostatus2', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index c19f31e010..ab430f4c33 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,6 +24,11 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) + active_model_serializers (0.10.6) + actionpack (>= 4.1, < 6) + activemodel (>= 4.1, < 6) + case_transform (>= 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.2) active_record_query_trace (1.5.4) activejob (5.1.2) activesupport (= 5.1.2) @@ -41,7 +46,7 @@ GEM tzinfo (~> 1.1) addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) - airbrussh (1.2.0) + airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) annotate (2.7.2) activerecord (>= 3.2, < 6.0) @@ -52,13 +57,13 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-sdk (2.9.37) - aws-sdk-resources (= 2.9.37) - aws-sdk-core (2.9.37) + aws-sdk (2.10.6) + aws-sdk-resources (= 2.10.6) + aws-sdk-core (2.10.6) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.9.37) - aws-sdk-core (= 2.9.37) + aws-sdk-resources (2.10.6) + aws-sdk-core (= 2.10.6) aws-sigv4 (1.0.0) bcrypt (3.1.11) better_errors (2.1.1) @@ -67,7 +72,7 @@ GEM rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootsnap (1.0.0) + bootsnap (1.1.1) msgpack (~> 1.0) brakeman (3.6.2) browser (2.4.0) @@ -78,7 +83,7 @@ GEM bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.8.1) + capistrano (3.8.2) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -94,15 +99,18 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (2.14.2) + capybara (2.14.4) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + case_transform (0.2) + activesupport + charlock_holmes (0.7.3) chunky_png (1.3.8) - cld3 (3.1.2) + cld3 (3.1.3) ffi (>= 1.1.0, < 1.10.0) climate_control (0.2.0) cocaine (0.5.8) @@ -142,9 +150,9 @@ GEM thread thread_safe encryptor (3.0.0) - erubi (1.6.0) + erubi (1.6.1) erubis (2.7.0) - et-orbi (1.0.4) + et-orbi (1.0.5) tzinfo execjs (2.7.0) fabrication (2.16.1) @@ -161,7 +169,7 @@ GEM addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) - hamlit (2.8.1) + hamlit (2.8.4) temple (>= 0.8.0) thor tilt @@ -182,9 +190,9 @@ GEM http-cookie (1.0.3) domain_name (~> 0.5) http-form_data (1.0.3) - http_accept_language (2.1.0) + http_accept_language (2.1.1) http_parser.rb (0.6.0) - httplog (0.99.3) + httplog (0.99.4) colorize rack i18n (0.8.4) @@ -200,6 +208,7 @@ GEM terminal-table (>= 1.5.1) jmespath (1.3.1) json (2.1.0) + jsonapi-renderer (0.1.2) kaminari (1.0.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.0.1) @@ -249,8 +258,8 @@ GEM mini_portile2 (~> 2.2.0) nokogumbo (1.4.13) nokogiri - oj (3.1.0) - openssl (2.0.3) + oj (3.2.0) + openssl (2.0.4) orm_adapter (0.5.0) ostatus2 (2.0.1) addressable (~> 2.4) @@ -272,7 +281,7 @@ GEM parallel parser (2.4.0.0) ast (~> 2.2) - pg (0.20.0) + pg (0.21.0) pghero (1.7.0) activerecord pkg-config (1.2.3) @@ -374,7 +383,7 @@ GEM rspec-expectations (~> 3.6.0) rspec-mocks (~> 3.6.0) rspec-support (~> 3.6.0) - rspec-sidekiq (3.0.1) + rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.6.0) @@ -395,10 +404,10 @@ GEM nokogiri (>= 1.4.4) nokogumbo (~> 1.4.1) sass (3.4.24) - scss_lint (0.53.0) + scss_lint (0.54.0) rake (>= 0.9, < 13) sass (~> 3.4.20) - sidekiq (5.0.2) + sidekiq (5.0.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) @@ -406,7 +415,7 @@ GEM sidekiq-bulk (0.1.1) activesupport sidekiq - sidekiq-scheduler (2.1.5) + sidekiq-scheduler (2.1.7) redis (~> 3) rufus-scheduler (~> 3.2) sidekiq (>= 3) @@ -443,7 +452,7 @@ GEM thread (0.2.2) thread_safe (0.3.6) tilt (2.0.7) - twitter-text (1.14.5) + twitter-text (1.14.6) unf (~> 0.1.0) tzinfo (1.2.3) thread_safe (~> 0.1) @@ -454,7 +463,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.4) - unicode-display_width (1.2.1) + unicode-display_width (1.3.0) uniform_notifier (1.10.0) warden (1.2.7) rack (>= 1.0) @@ -476,6 +485,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers (~> 0.10) active_record_query_trace (~> 1.5) addressable (~> 2.5) annotate (~> 2.7) @@ -492,6 +502,7 @@ DEPENDENCIES capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) capybara (~> 2.14) + charlock_holmes (~> 0.7.3) cld3 (~> 3.1) climate_control (~> 0.2) devise (~> 4.2) @@ -516,6 +527,7 @@ DEPENDENCIES link_header (~> 0.0) lograge (~> 0.5) microformats2 (~> 3.0) + mime-types (~> 3.1) nokogiri (~> 1.7) oj (~> 3.0) ostatus2 (~> 2.0) diff --git a/Vagrantfile b/Vagrantfile index 1f56fcfb3f..cbe6623b31 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -37,6 +37,7 @@ sudo apt-get install \ yarn \ libprotobuf-dev \ libreadline-dev \ + libicu-dev \ -y # Install rvm diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 04e7ddacf7..47690e81eb 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -2,9 +2,12 @@ class AboutController < ApplicationController before_action :set_body_classes - before_action :set_instance_presenter, only: [:show, :more] + before_action :set_instance_presenter, only: [:show, :more, :terms] - def show; end + def show + serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) + @initial_state_json = serializable_resource.to_json + end def more; end @@ -15,6 +18,7 @@ class AboutController < ApplicationController def new_user User.new.tap(&:build_account) end + helper_method :new_user def set_instance_presenter @@ -24,4 +28,11 @@ class AboutController < ApplicationController def set_body_classes @body_classes = 'about-body' end + + def initial_state_params + { + settings: {}, + token: current_session&.token, + } + end end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index ef2f8c4c2e..7bceee2cdc 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -22,8 +22,8 @@ module Admin end def redownload - @account.avatar = @account.avatar_remote_url - @account.header = @account.header_remote_url + @account.reset_avatar! + @account.reset_header! @account.save! redirect_to admin_account_path(@account.id) diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index fcd42c79c8..5985d62829 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -8,13 +8,21 @@ module Admin site_title site_description site_extended_description + site_terms open_registrations closed_registrations_message + open_deletion + timeline_preview + ).freeze + + BOOLEAN_SETTINGS = %w( + open_registrations + open_deletion + timeline_preview ).freeze - BOOLEAN_SETTINGS = %w(open_registrations).freeze def edit - @settings = Setting.all_as_records + @admin_settings = Form::AdminSettings.new end def update @@ -23,19 +31,19 @@ module Admin setting.update(value: value_for_update(key, value)) end - flash[:notice] = 'Success!' + flash[:notice] = I18n.t('generic.changes_saved_msg') redirect_to edit_admin_settings_path end private def settings_params - params.permit(ADMIN_SETTINGS) + params.require(:form_admin_settings).permit(ADMIN_SETTINGS) end def value_for_update(key, value) if BOOLEAN_SETTINGS.include?(key) - value == 'true' + value == '1' else value end diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index 6e3e34d964..f8c87dd16e 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -5,8 +5,7 @@ class Api::OEmbedController < Api::BaseController def show @stream_entry = find_stream_entry.stream_entry - @width = maxwidth_or_default - @height = maxheight_or_default + render json: @stream_entry, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default end private diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 1cf52ff10c..073808532a 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -6,13 +6,13 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController def show @account = current_account - render 'api/v1/accounts/show' + render json: @account, serializer: REST::CredentialAccountSerializer end def update current_account.update!(account_params) @account = current_account - render 'api/v1/accounts/show' + render json: @account, serializer: REST::CredentialAccountSerializer end private diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 81aae56d3f..80b0bef407 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController def index @accounts = load_accounts - render 'api/v1/accounts/index' + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index 63c6d54b29..55cffdf37c 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController def index @accounts = load_accounts - render 'api/v1/accounts/index' + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/accounts/relationships_controller.rb b/app/controllers/api/v1/accounts/relationships_controller.rb index cb923ab917..a88cf2021a 100644 --- a/app/controllers/api/v1/accounts/relationships_controller.rb +++ b/app/controllers/api/v1/accounts/relationships_controller.rb @@ -8,16 +8,15 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController def index @accounts = Account.where(id: account_ids).select('id') - @following = Account.following_map(account_ids, current_user.account_id) - @followed_by = Account.followed_by_map(account_ids, current_user.account_id) - @blocking = Account.blocking_map(account_ids, current_user.account_id) - @muting = Account.muting_map(account_ids, current_user.account_id) - @requested = Account.requested_map(account_ids, current_user.account_id) - @domain_blocking = Account.domain_blocking_map(account_ids, current_user.account_id) + render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships end private + def relationships + AccountRelationshipsPresenter.new(@accounts, current_user.account_id) + end + def account_ids @_account_ids ||= Array(params[:id]).map(&:to_i) end diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb index c4a8f97f24..2a5cac547b 100644 --- a/app/controllers/api/v1/accounts/search_controller.rb +++ b/app/controllers/api/v1/accounts/search_controller.rb @@ -8,8 +8,7 @@ class Api::V1::Accounts::SearchController < Api::BaseController def show @accounts = account_search - - render 'api/v1/accounts/index' + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 504ed8c07d..d9ae5c0896 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -9,6 +9,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def index @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) end private @@ -18,9 +19,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def load_statuses - cached_account_statuses.tap do |statuses| - set_maps(statuses) - end + cached_account_statuses end def cached_account_statuses diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 8fc0dd36f5..f621aa245d 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -8,49 +8,38 @@ class Api::V1::AccountsController < Api::BaseController respond_to :json - def show; end + def show + render json: @account, serializer: REST::AccountSerializer + end def follow FollowService.new.call(current_user.account, @account.acct) - set_relationship - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end def block BlockService.new.call(current_user.account, @account) - - @following = { @account.id => false } - @followed_by = { @account.id => false } - @blocking = { @account.id => true } - @requested = { @account.id => false } - @muting = { @account.id => current_account.muting?(@account.id) } - @domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) } - - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end def mute MuteService.new.call(current_user.account, @account) - set_relationship - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end def unfollow UnfollowService.new.call(current_user.account, @account) - set_relationship - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end def unblock UnblockService.new.call(current_user.account, @account) - set_relationship - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end def unmute UnmuteService.new.call(current_user.account, @account) - set_relationship - render :relationship + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships end private @@ -59,12 +48,7 @@ class Api::V1::AccountsController < Api::BaseController @account = Account.find(params[:id]) end - def set_relationship - @following = Account.following_map([@account.id], current_user.account_id) - @followed_by = Account.followed_by_map([@account.id], current_user.account_id) - @blocking = Account.blocking_map([@account.id], current_user.account_id) - @muting = Account.muting_map([@account.id], current_user.account_id) - @requested = Account.requested_map([@account.id], current_user.account_id) - @domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id) + def relationships + AccountRelationshipsPresenter.new([@account.id], current_user.account_id) end end diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb index 98e9089489..44a27b20a2 100644 --- a/app/controllers/api/v1/apps_controller.rb +++ b/app/controllers/api/v1/apps_controller.rb @@ -5,6 +5,7 @@ class Api::V1::AppsController < Api::BaseController def create @app = Doorkeeper::Application.create!(application_options) + render json: @app, serializer: REST::ApplicationSerializer end private diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 1702953cf7..a412e43414 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -9,6 +9,7 @@ class Api::V1::BlocksController < Api::BaseController def index @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index fe0819a3f5..92c0a62a98 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -9,14 +9,13 @@ class Api::V1::FavouritesController < Api::BaseController def index @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) end private def load_statuses - cached_favourites.tap do |statuses| - set_maps(statuses) - end + cached_favourites end def cached_favourites diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index eed22ef4fb..b9f50d7843 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -7,6 +7,7 @@ class Api::V1::FollowRequestsController < Api::BaseController def index @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer end def authorize diff --git a/app/controllers/api/v1/follows_controller.rb b/app/controllers/api/v1/follows_controller.rb index bcdb4e177a..e01ae5c01c 100644 --- a/app/controllers/api/v1/follows_controller.rb +++ b/app/controllers/api/v1/follows_controller.rb @@ -10,7 +10,7 @@ class Api::V1::FollowsController < Api::BaseController raise ActiveRecord::RecordNotFound if follow_params[:uri].blank? @account = FollowService.new.call(current_user.account, target_uri).try(:target_account) - render :show + render json: @account, serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index ce2181879b..1c6971c182 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -3,5 +3,7 @@ class Api::V1::InstancesController < Api::BaseController respond_to :json - def show; end + def show + render json: {}, serializer: REST::InstanceSerializer + end end diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index 25a3313195..8a1992fca4 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -11,6 +11,7 @@ class Api::V1::MediaController < Api::BaseController def create @media = current_account.media_attachments.create!(file: media_params[:file]) + render json: @media, serializer: REST::MediaAttachmentSerializer rescue Paperclip::Errors::NotIdentifiedByImageMagickError render json: file_type_error, status: 422 rescue Paperclip::Error diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 2a353df039..0c43cb9430 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -9,6 +9,7 @@ class Api::V1::MutesController < Api::BaseController def index @accounts = load_accounts + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index a28e99f2fb..8910b77e93 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -11,11 +11,12 @@ class Api::V1::NotificationsController < Api::BaseController def index @notifications = load_notifications - set_maps_for_notification_target_statuses + render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) end def show @notification = current_account.notifications.find(params[:id]) + render json: @notification, serializer: REST::NotificationSerializer end def clear @@ -46,10 +47,6 @@ class Api::V1::NotificationsController < Api::BaseController current_account.notifications.browserable(exclude_types) end - def set_maps_for_notification_target_statuses - set_maps target_statuses_from_notifications - end - def target_statuses_from_notifications @notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status) end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 8e7070d077..9592cd4bdc 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -9,6 +9,7 @@ class Api::V1::ReportsController < Api::BaseController def index @reports = current_account.reports + render json: @reports, each_serializer: REST::ReportSerializer end def create @@ -20,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } - render :show + render json: @report, serializer: REST::ReportSerializer end private diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index 8b832148c3..bc5b8e5d40 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -3,10 +3,14 @@ class Api::V1::SearchController < Api::BaseController RESULTS_LIMIT = 5 + before_action -> { doorkeeper_authorize! :read } + before_action :require_user! + respond_to :json def index - @search = OpenStruct.new(search_results) + @search = Search.new(search_results) + render json: @search, serializer: REST::SearchSerializer end private diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index e581849395..f95cf9457f 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -11,7 +11,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController def index @accounts = load_accounts - render 'api/v1/statuses/accounts' + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/statuses/favourites_controller.rb b/app/controllers/api/v1/statuses/favourites_controller.rb index b6fb13cc07..4c4b0c1604 100644 --- a/app/controllers/api/v1/statuses/favourites_controller.rb +++ b/app/controllers/api/v1/statuses/favourites_controller.rb @@ -10,7 +10,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController def create @status = favourited_status - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end def destroy @@ -19,7 +19,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController UnfavouriteWorker.perform_async(current_user.account_id, @status.id) - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end private diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb index eab88f2efd..a4bf0acdd2 100644 --- a/app/controllers/api/v1/statuses/mutes_controller.rb +++ b/app/controllers/api/v1/statuses/mutes_controller.rb @@ -14,14 +14,14 @@ class Api::V1::Statuses::MutesController < Api::BaseController current_account.mute_conversation!(@conversation) @mutes_map = { @conversation.id => true } - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end def destroy current_account.unmute_conversation!(@conversation) @mutes_map = { @conversation.id => false } - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end private diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 43593d3c5a..175217e6eb 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -11,7 +11,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController def index @accounts = load_accounts - render 'api/v1/statuses/accounts' + render json: @accounts, each_serializer: REST::AccountSerializer end private diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index ee9c5b3a62..f7f4b5a5c6 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -10,7 +10,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController def create @status = ReblogService.new.call(current_user.account, status_for_reblog) - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end def destroy @@ -20,7 +20,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController authorize status_for_destroy, :unreblog? RemovalWorker.perform_async(status_for_destroy.id) - render 'api/v1/statuses/show' + render json: @status, serializer: REST::StatusSerializer end private diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 9aa1cbc4d1..9c7124d0f0 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -13,6 +13,7 @@ class Api::V1::StatusesController < Api::BaseController def show cached = Rails.cache.read(@status.cache_key) @status = cached unless cached.nil? + render json: @status, serializer: REST::StatusSerializer end def context @@ -21,15 +22,20 @@ class Api::V1::StatusesController < Api::BaseController loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) - @context = OpenStruct.new(ancestors: loaded_ancestors, descendants: loaded_descendants) - statuses = [@status] + @context[:ancestors] + @context[:descendants] + @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) + statuses = [@status] + @context.ancestors + @context.descendants - set_maps(statuses) + render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) end def card @card = PreviewCard.find_by(status: @status) - render_empty if @card.nil? + + if @card.nil? + render_empty + else + render json: @card, serializer: REST::PreviewCardSerializer + end end def create @@ -43,7 +49,7 @@ class Api::V1::StatusesController < Api::BaseController application: doorkeeper_token.application, idempotency: request.headers['Idempotency-Key']) - render :show + render json: @status, serializer: REST::StatusSerializer end def destroy diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index 511d2f65da..3dd27710cb 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -9,15 +9,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController def show @statuses = load_statuses - render 'api/v1/timelines/show' + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) end private def load_statuses - cached_home_statuses.tap do |statuses| - set_maps(statuses) - end + cached_home_statuses end def cached_home_statuses diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index 305451cc7b..49887778e0 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -7,15 +7,13 @@ class Api::V1::Timelines::PublicController < Api::BaseController def show @statuses = load_statuses - render 'api/v1/timelines/show' + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) end private def load_statuses - cached_public_statuses.tap do |statuses| - set_maps(statuses) - end + cached_public_statuses end def cached_public_statuses diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 50afca7c72..08db04a39d 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -8,7 +8,7 @@ class Api::V1::Timelines::TagController < Api::BaseController def show @statuses = load_statuses - render 'api/v1/timelines/show' + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) end private @@ -18,9 +18,7 @@ class Api::V1::Timelines::TagController < Api::BaseController end def load_statuses - cached_tagged_statuses.tap do |statuses| - set_maps(statuses) - end + cached_tagged_statuses end def cached_tagged_statuses diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 865fcd1257..b3c2db02b3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -70,7 +70,7 @@ class ApplicationController < ActionController::Base end def current_session - @current_session ||= SessionActivation.find_by(session_id: session['auth_id']) + @current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id']) end def cache_collection(raw, klass) diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb index da4ef022a0..dccd1c2090 100644 --- a/app/controllers/authorize_follows_controller.rb +++ b/app/controllers/authorize_follows_controller.rb @@ -15,7 +15,7 @@ class AuthorizeFollowsController < ApplicationController if @account.nil? render :error else - redirect_to web_url("accounts/#{@account.id}") + render :success end rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError render :error diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 6209a3ae93..8a8b9ec76b 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,13 +2,10 @@ class HomeController < ApplicationController before_action :authenticate_user! + before_action :set_initial_state_json def index - @body_classes = 'app-body' - @token = current_session.token - @web_settings = Web::Setting.find_by(user: current_user)&.data || {} - @admin = Account.find_local(Setting.site_contact_username) - @streaming_api_base_url = Rails.configuration.x.streaming_api_base_url + @body_classes = 'app-body' end private @@ -16,4 +13,18 @@ class HomeController < ApplicationController def authenticate_user! redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in? end + + def set_initial_state_json + serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) + @initial_state_json = serializable_resource.to_json + end + + def initial_state_params + { + settings: Web::Setting.find_by(user: current_user)&.data || {}, + current_account: current_account, + token: current_session.token, + admin: Account.find_local(Setting.site_contact_username), + } + end end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 71f5a7c04b..cac5b0ba8f 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -34,9 +34,11 @@ class Settings::PreferencesController < ApplicationController def user_settings_params params.require(:user).permit( :setting_default_privacy, + :setting_default_sensitive, :setting_boost_modal, :setting_delete_modal, :setting_auto_play_gif, + :setting_system_font_ui, notification_emails: %i(follow follow_request reblog favourite mention digest), interactions: %i(must_be_follower must_be_following) ) diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 0dfa30e56d..6a57b3d636 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -6,15 +6,21 @@ module Admin::FilterHelper FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS - def filter_link_to(text, more_params) - new_url = filtered_url_for(more_params) - link_to text, new_url, class: filter_link_class(new_url) + def filter_link_to(text, link_to_params, link_class_params = link_to_params) + new_url = filtered_url_for(link_to_params) + new_class = filtered_url_for(link_class_params) + link_to text, new_url, class: filter_link_class(new_class) end def table_link_to(icon, text, path, options = {}) link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link') end + def selected?(more_params) + new_url = filtered_url_for(more_params) + filter_link_class(new_url) == 'selected' ? true : false + end + private def filter_params(more_params) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 36c37fae08..9f50d8bdb3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -31,7 +31,11 @@ module ApplicationHelper Rails.env.production? ? site_title : "#{site_title} (Dev)" end - def fa_icon(icon) - content_tag(:i, nil, class: 'fa ' + icon.split(' ').map { |cl| "fa-#{cl}" }.join(' ')) + def fa_icon(icon, attributes = {}) + class_names = attributes[:class]&.split(' ') || [] + class_names << 'fa' + class_names += icon.split(' ').map { |cl| "fa-#{cl}" } + + content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 847eff2e7f..af950aa634 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -19,6 +19,7 @@ module SettingsHelper io: 'Ido', it: 'Italiano', ja: '日本語', + ko: '한국어', nl: 'Nederlands', no: 'Norsk', oc: 'Occitan', diff --git a/app/javascript/fonts/montserrat/Montserrat-Medium.ttf b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf new file mode 100644 index 0000000000..88d70b89c3 Binary files /dev/null and b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf differ diff --git a/app/javascript/images/cloud2.png b/app/javascript/images/cloud2.png new file mode 100644 index 0000000000..f325ca6de0 Binary files /dev/null and b/app/javascript/images/cloud2.png differ diff --git a/app/javascript/images/cloud3.png b/app/javascript/images/cloud3.png new file mode 100644 index 0000000000..ab194d0b85 Binary files /dev/null and b/app/javascript/images/cloud3.png differ diff --git a/app/javascript/images/cloud4.png b/app/javascript/images/cloud4.png new file mode 100644 index 0000000000..98323f5a27 Binary files /dev/null and b/app/javascript/images/cloud4.png differ diff --git a/app/javascript/images/elephant-fren.png b/app/javascript/images/elephant-fren.png new file mode 100644 index 0000000000..3b64edf084 Binary files /dev/null and b/app/javascript/images/elephant-fren.png differ diff --git a/app/javascript/images/logo.svg b/app/javascript/images/logo.svg index c233db8428..16cb3a9440 100644 --- a/app/javascript/images/logo.svg +++ b/app/javascript/images/logo.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/javascript/mastodon/actions/bundles.js b/app/javascript/mastodon/actions/bundles.js new file mode 100644 index 0000000000..ecc9c8f7d3 --- /dev/null +++ b/app/javascript/mastodon/actions/bundles.js @@ -0,0 +1,25 @@ +export const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST'; +export const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS'; +export const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL'; + +export function fetchBundleRequest(skipLoading) { + return { + type: BUNDLE_FETCH_REQUEST, + skipLoading, + }; +} + +export function fetchBundleSuccess(skipLoading) { + return { + type: BUNDLE_FETCH_SUCCESS, + skipLoading, + }; +} + +export function fetchBundleFail(error, skipLoading) { + return { + type: BUNDLE_FETCH_FAIL, + error, + skipLoading, + }; +} diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index cda636139d..c7d2481227 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import Immutable from 'immutable'; +import { List as ImmutableList } from 'immutable'; import IntlMessageFormat from 'intl-messageformat'; import { fetchRelationships } from './accounts'; import { defineMessages } from 'react-intl'; @@ -124,7 +124,7 @@ export function refreshNotificationsFail(error, skipLoading) { export function expandNotifications() { return (dispatch, getState) => { - const items = getState().getIn(['notifications', 'items'], Immutable.List()); + const items = getState().getIn(['notifications', 'items'], ImmutableList()); if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) { return; diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 601cea0014..0597d265ec 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -1,10 +1,11 @@ -import Immutable from 'immutable'; +import { Iterable, fromJS } from 'immutable'; export const STORE_HYDRATE = 'STORE_HYDRATE'; +export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; const convertState = rawState => - Immutable.fromJS(rawState, (k, v) => - Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => + fromJS(rawState, (k, v) => + Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => Number.isNaN(x * 1) ? x : x * 1)); export function hydrateStore(rawState) { diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index cb4410eba8..dd14cb1cd0 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -1,5 +1,5 @@ import api, { getLinks } from '../api'; -import Immutable from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; @@ -66,13 +66,13 @@ export function refreshTimelineRequest(timeline, skipLoading) { export function refreshTimeline(timelineId, path, params = {}) { return function (dispatch, getState) { - const timeline = getState().getIn(['timelines', timelineId], Immutable.Map()); + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); if (timeline.get('isLoading') || timeline.get('online')) { return; } - const ids = timeline.get('items', Immutable.List()); + const ids = timeline.get('items', ImmutableList()); const newestId = ids.size > 0 ? ids.first() : null; let skipLoading = timeline.get('loaded'); @@ -111,8 +111,8 @@ export function refreshTimelineFail(timeline, error, skipLoading) { export function expandTimeline(timelineId, path, params = {}) { return (dispatch, getState) => { - const timeline = getState().getIn(['timelines', timelineId], Immutable.Map()); - const ids = timeline.get('items', Immutable.List()); + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); + const ids = timeline.get('items', ImmutableList()); if (timeline.get('isLoading') || ids.size === 0) { return; diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 027d017675..e9f041be62 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -10,7 +10,7 @@ export default class ColumnHeader extends React.PureComponent { }; static propTypes = { - title: PropTypes.string.isRequired, + title: PropTypes.node.isRequired, icon: PropTypes.string.isRequired, active: PropTypes.bool, multiColumn: PropTypes.bool, diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 12e1b44fa2..98323b0691 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -14,6 +14,7 @@ export default class DropdownMenu extends React.PureComponent { size: PropTypes.number.isRequired, direction: PropTypes.string, ariaLabel: PropTypes.string, + disabled: PropTypes.bool, }; static defaultProps = { @@ -68,9 +69,19 @@ export default class DropdownMenu extends React.PureComponent { } render () { - const { icon, items, size, direction, ariaLabel } = this.props; - const { expanded } = this.state; + const { icon, items, size, direction, ariaLabel, disabled } = this.props; + const { expanded } = this.state; const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right'; + const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }; + const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`; + + if (disabled) { + return ( +
+ +
+ ); + } const dropdownItems = expanded && (