From 4d8c0d995981d580fa774950fa93c17b0004246e Mon Sep 17 00:00:00 2001 From: mayaeh Date: Sun, 25 Feb 2018 01:27:03 +0900 Subject: [PATCH 01/36] i18n: Update Japanese translations (#6550) * Update Japanese translations. * Add quotation marks. --- config/locales/ja.yml | 18 ++++++++++++++++++ config/locales/simple_form.ja.yml | 2 ++ 2 files changed, 20 insertions(+) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b1c22d5f9..f40e2101e 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -273,6 +273,9 @@ ja: contact_information: email: ビジネスメールアドレス username: 連絡先のユーザー名 + hero: + desc_html: フロントページに表示されます。サイズは600x100px以上推奨です。未設定の場合、インスタンスのサムネイルが使用されます。 + title: ヒーローイメージ peers_api_enabled: desc_html: 連合内でこのインスタンスが遭遇したドメインの名前 title: 接続しているインスタンスのリストを公開する @@ -289,6 +292,9 @@ ja: open: desc_html: 誰でも自由にアカウントを作成できるようにします title: 新規登録を受け付ける + show_known_fediverse_at_about_page: + desc_html: チェックを入れるとプレビュー欄に既知の連合先全てのトゥートを表示します。外すとローカルのトゥートだけ表示します。 + title: タイムラインプレビューに連合タイムラインを表示する show_staff_badge: desc_html: ユーザーページにスタッフのバッジを表示します title: スタッフバッジを表示する @@ -363,6 +369,7 @@ ja: logout: ログアウト migrate_account: 別のアカウントに引っ越す migrate_account_html: 引っ越し先を明記したい場合はこちらで設定できます。 + or_log_in_with: または次のサービスでログイン register: 登録する resend_confirmation: 確認メールを再送する reset_password: パスワードを再発行 @@ -412,6 +419,13 @@ ja: title: このページは正しくありません noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けのMastodonネイティブアプリを探すことができます。 exports: + archive_takeout: + date: 日時 + download: ダウンロード + hint_html: "トゥートとメディアのアーカイブをリクエストできます。 データはActivityPub形式で、対応しているソフトウェアで読み込むことができます。" + in_progress: 準備中... + request: アーカイブをリクエスト + size: 容量 blocks: ブロック csv: CSV follows: フォロー @@ -724,6 +738,10 @@ ja: setup: 初期設定 wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。 user_mailer: + backup_ready: + explanation: Mastodonアカウントのアーカイブを受け付けました。今すぐダウンロードできます! + subject: アーカイブの準備ができました + title: アーカイブの取り出し welcome: edit_profile_action: プロフィールを設定 edit_profile_step: アバター画像やヘッダー画像をアップロードしたり、表示名やその他プロフィールを変更しカスタマイズすることができます。新しいフォロワーからのフォローを許可する前に検討したい場合、アカウントを非公開にすることができます。 diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 2837ef1c3..5b7d3d0c6 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -41,6 +41,7 @@ ja: setting_default_privacy: 投稿の公開範囲 setting_default_sensitive: メディアを常に閲覧注意としてマークする setting_delete_modal: トゥートを削除する前に確認ダイアログを表示する + setting_display_sensitive_media: 閲覧注意としてマークされたメディアも常に表示する setting_noindex: 検索エンジンによるインデックスを拒否する setting_reduce_motion: アニメーションの動きを減らす setting_system_font_ui: システムのデフォルトフォントを使う @@ -49,6 +50,7 @@ ja: severity: 重大性 type: インポートする項目 username: ユーザー名 + username_or_email: ユーザー名またはメールアドレス interactions: must_be_follower: フォロワー以外からの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック From 7cb49eaa3aad03b60a1e1620d2f700d6ed2b3ea0 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 25 Feb 2018 03:10:57 +0900 Subject: [PATCH 02/36] Do not use function name to track components (#6542) UglifyJS2 is allowed to mangle function names, and function names can also be duplicate if they are from different scopes. Therefore function names are not reliable as identifiers. Functions as keys for Map object is a cheaper and more reliable alternative. --- .../mastodon/features/ui/components/bundle.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js index fc88e0c70..06a6c9cdd 100644 --- a/app/javascript/mastodon/features/ui/components/bundle.js +++ b/app/javascript/mastodon/features/ui/components/bundle.js @@ -26,7 +26,7 @@ class Bundle extends React.Component { onFetchFail: noop, } - static cache = {} + static cache = new Map state = { mod: undefined, @@ -51,13 +51,12 @@ class Bundle extends React.Component { load = (props) => { const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; + const cachedMod = Bundle.cache.get(fetchComponent); onFetch(); - if (Bundle.cache[fetchComponent.name]) { - const mod = Bundle.cache[fetchComponent.name]; - - this.setState({ mod: mod.default }); + if (cachedMod) { + this.setState({ mod: cachedMod.default }); onFetchSuccess(); return Promise.resolve(); } @@ -71,7 +70,7 @@ class Bundle extends React.Component { return fetchComponent() .then((mod) => { - Bundle.cache[fetchComponent.name] = mod; + Bundle.cache.set(fetchComponent, mod); this.setState({ mod: mod.default }); onFetchSuccess(); }) From 2e8a492e8843aa958c53636b24cf4d344e7ca47d Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 25 Feb 2018 03:16:11 +0900 Subject: [PATCH 03/36] Raise Mastodon::HostValidationError when host for HTTP request is private (#6410) --- Gemfile | 4 ++++ Gemfile.lock | 2 ++ app/lib/exceptions.rb | 1 + app/lib/request.rb | 19 +++++++++++++++++- app/lib/sidekiq_error_handler.rb | 11 +++++++++++ config/environments/development.rb | 6 ++++++ config/initializers/sidekiq.rb | 4 ++++ spec/lib/request_spec.rb | 31 ++++++++++++++++++++++-------- 8 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 app/lib/sidekiq_error_handler.rb diff --git a/Gemfile b/Gemfile index da5fc2f38..e2b3b1971 100644 --- a/Gemfile +++ b/Gemfile @@ -96,6 +96,10 @@ group :development, :test do gem 'rspec-rails', '~> 3.7' end +group :production, :test do + gem 'private_address_check', '~> 0.4.1' +end + group :test do gem 'capybara', '~> 2.15' gem 'climate_control', '~> 0.2' diff --git a/Gemfile.lock b/Gemfile.lock index 65a0dfabf..8293977d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -376,6 +376,7 @@ GEM premailer-rails (1.10.1) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) + private_address_check (0.4.1) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -683,6 +684,7 @@ DEPENDENCIES pghero (~> 1.7) pkg-config (~> 1.2) premailer-rails + private_address_check (~> 0.4.1) pry-rails (~> 0.3) puma (~> 3.10) pundit (~> 1.1) diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb index b2489711d..95e3365c2 100644 --- a/app/lib/exceptions.rb +++ b/app/lib/exceptions.rb @@ -4,6 +4,7 @@ module Mastodon class Error < StandardError; end class NotPermittedError < Error; end class ValidationError < Error; end + class HostValidationError < ValidationError; end class RaceConditionError < Error; end class UnexpectedResponseError < Error diff --git a/app/lib/request.rb b/app/lib/request.rb index 7671f4ffc..5776b3d78 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'ipaddr' +require 'socket' + class Request REQUEST_TARGET = '(request-target)' @@ -8,7 +11,7 @@ class Request def initialize(verb, url, **options) @verb = verb @url = Addressable::URI.parse(url).normalize - @options = options + @options = options.merge(socket_class: Socket) @headers = {} set_common_headers! @@ -87,4 +90,18 @@ class Request def http_client HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) end + + class Socket < TCPSocket + class << self + def open(host, *args) + address = IPSocket.getaddress(host) + raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address) + super address, *args + end + + alias new open + end + end + + private_constant :Socket end diff --git a/app/lib/sidekiq_error_handler.rb b/app/lib/sidekiq_error_handler.rb new file mode 100644 index 000000000..23785cf05 --- /dev/null +++ b/app/lib/sidekiq_error_handler.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SidekiqErrorHandler + def call(*) + yield + rescue Mastodon::HostValidationError => e + Rails.logger.error "#{e.class}: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + # Do not retry + end +end diff --git a/config/environments/development.rb b/config/environments/development.rb index 59bc2c3e2..2da407c32 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -85,3 +85,9 @@ Rails.application.configure do end ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false } + +module PrivateAddressCheck + def self.private_address?(*) + false + end +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index b70784d79..f875fbd95 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -9,6 +9,10 @@ end Sidekiq.configure_server do |config| config.redis = redis_params + + config.server_middleware do |chain| + chain.add SidekiqErrorHandler + end end Sidekiq.configure_client do |config| diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index 782f14b18..dc7daa52c 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -38,17 +38,32 @@ describe Request do end describe '#perform' do - before do - stub_request(:get, 'http://example.com') - subject.perform + context 'with valid host' do + before do + stub_request(:get, 'http://example.com') + subject.perform + end + + it 'executes a HTTP request' do + expect(a_request(:get, 'http://example.com')).to have_been_made.once + end + + it 'sets headers' do + expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + end end - it 'executes a HTTP request' do - expect(a_request(:get, 'http://example.com')).to have_been_made.once - end + context 'with private host' do + around do |example| + WebMock.disable! + example.run + WebMock.enable! + end - it 'sets headers' do - expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + it 'raises Mastodon::ValidationError' do + allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0') + expect{ subject.perform }.to raise_error Mastodon::ValidationError + end end end end From f0a1b1a152f8f46e2ff685b405d6361b7725f5ae Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Feb 2018 00:24:55 +0100 Subject: [PATCH 04/36] Fix #6536 (#6558) --- .env.production.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.production.sample b/.env.production.sample index 21d44a416..f6ed28fd3 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -183,7 +183,7 @@ STREAMING_CLUSTER_NUM=1 # Optional SAML authentication (cf. omniauth-saml) # SAML_ENABLED=true # SAML_ACS_URL= -# SAML_ISSUER=http://localhost:3000/auth/auth/saml/metadata +# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback # SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO # SAML_IDP_CERT= # SAML_IDP_CERT_FINGERPRINT= From 5cc716688abdf7eaafc58d804209510601190791 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Feb 2018 01:31:44 +0100 Subject: [PATCH 05/36] Ensure the app does not even start if OTP_SECRET is not set (#6557) * Ensure the app does not even start if OTP_SECRET is not set * Remove PAPERCLIP_SECRET (it's not used by anything, actually) Imports are for internal consumption and the url option isn't even used correctly, so we can remove the hash stuff from them --- .env.production.sample | 1 - app/models/import.rb | 2 +- app/models/user.rb | 2 +- lib/tasks/mastodon.rake | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.env.production.sample b/.env.production.sample index f6ed28fd3..5cd3e196e 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -33,7 +33,6 @@ LOCAL_DOMAIN=example.com # Application secrets # Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose) -PAPERCLIP_SECRET= SECRET_KEY_BASE= OTP_SECRET= diff --git a/app/models/import.rb b/app/models/import.rb index ba88435bf..fdb4c6b80 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -26,7 +26,7 @@ class Import < ApplicationRecord validates :type, presence: true - has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET'] + has_attached_file :data validates_attachment_content_type :data, content_type: FILE_TYPES validates_attachment_presence :data end diff --git a/app/models/user.rb b/app/models/user.rb index fcd574f8b..b053292da 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,7 +44,7 @@ class User < ApplicationRecord ACTIVE_DURATION = 14.days devise :two_factor_authenticatable, - otp_secret_encryption_key: ENV['OTP_SECRET'] + otp_secret_encryption_key: ENV.fetch('OTP_SECRET') devise :two_factor_backupable, otp_number_of_backup_codes: 10 diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index d2e4f38a9..bf4c53cb3 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -23,7 +23,7 @@ namespace :mastodon do prompt.say('Single user mode disables registrations and redirects the landing page to your public profile.') env['SINGLE_USER_MODE'] = prompt.yes?('Do you want to enable single user mode?', default: false) - %w(SECRET_KEY_BASE PAPERCLIP_SECRET OTP_SECRET).each do |key| + %w(SECRET_KEY_BASE OTP_SECRET).each do |key| env[key] = SecureRandom.hex(64) end From c33931b613c7da4cc2c22ff8411c38556dc579cb Mon Sep 17 00:00:00 2001 From: Ian McCowan Date: Sun, 25 Feb 2018 18:31:28 -0800 Subject: [PATCH 06/36] Fix prev/next links on public profile page (#6497) * Fix prev/next links on public profile page * Don't make pagination urls if no available statuses * Fix empty check method * Put left chevron before prev page link * Add scope for pagination "starting at" a given id * Status pagination try 2: s/prev/older and s/next/newer "older" on left, "newer" on right Use new scope for "newer" link Extract magic 20 page size to constant Remove max_id from feed pagination as it's not respected * Reinstate max_id for accounts atom stream * normalize --- app/controllers/accounts_controller.rb | 36 ++++++++++++++++---- app/javascript/styles/mastodon/accounts.scss | 16 ++++----- app/models/concerns/paginable.rb | 9 +++++ app/views/accounts/show.html.haml | 7 ++-- config/locales/en.yml | 2 ++ 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 69fd20e27..7bf35825f 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class AccountsController < ApplicationController + PAGE_SIZE = 20 + include AccountControllerConcern before_action :set_cache_headers @@ -16,13 +18,16 @@ class AccountsController < ApplicationController end @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? - @statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @statuses = filtered_status_page(params) @statuses = cache_collection(@statuses, Status) - @next_url = next_url unless @statuses.empty? + unless @statuses.empty? + @older_url = older_url if @statuses.last.id > filtered_statuses.last.id + @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id + end end format.atom do - @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id]) + @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]) render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? })) end @@ -69,13 +74,22 @@ class AccountsController < ApplicationController @account = Account.find_local!(params[:username]) end - def next_url + def older_url + ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}") + pagination_url(max_id: @statuses.last.id) + end + + def newer_url + pagination_url(min_id: @statuses.first.id) + end + + def pagination_url(max_id: nil, min_id: nil) if media_requested? - short_account_media_url(@account, max_id: @statuses.last.id) + short_account_media_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? - short_account_with_replies_url(@account, max_id: @statuses.last.id) + short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) else - short_account_url(@account, max_id: @statuses.last.id) + short_account_url(@account, max_id: max_id, min_id: min_id) end end @@ -86,4 +100,12 @@ class AccountsController < ApplicationController def replies_requested? request.path.ends_with?('/with_replies') end + + def filtered_status_page(params) + if params[:min_id].present? + filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse + else + filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a + end + end end diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 9015d04cb..c812766a1 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -233,8 +233,8 @@ a, .current, - .next, - .prev, + .newer, + .older, .page, .gap { font-size: 14px; @@ -257,13 +257,13 @@ cursor: default; } - .prev, - .next { + .older, + .newer { text-transform: uppercase; color: $ui-secondary-color; } - .prev { + .older { float: left; padding-left: 0; @@ -273,7 +273,7 @@ } } - .next { + .newer { float: right; padding-right: 0; @@ -295,8 +295,8 @@ display: none; } - .next, - .prev { + .newer, + .older { display: inline-block; } } diff --git a/app/models/concerns/paginable.rb b/app/models/concerns/paginable.rb index 6061bf9bd..66695677e 100644 --- a/app/models/concerns/paginable.rb +++ b/app/models/concerns/paginable.rb @@ -10,5 +10,14 @@ module Paginable query = query.where(arel_table[:id].gt(since_id)) if since_id.present? query } + + # Differs from :paginate_by_max_id in that it gives the results immediately following min_id, + # whereas since_id gives the items with largest id, but with since_id as a cutoff. + # Results will be in ascending order by id. + scope :paginate_by_min_id, ->(limit, min_id = nil) { + query = reorder(arel_table[:id]).limit(limit) + query = query.where(arel_table[:id].gt(min_id)) if min_id.present? + query + } end end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index accad5f78..21c585dab 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -39,6 +39,9 @@ = render partial: 'stream_entries/status', collection: @statuses, as: :status - - if @statuses.size == 20 + - if @newer_url || @older_url .pagination - = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next' + - if @older_url + = link_to safe_join([fa_icon('chevron-left'), t('pagination.older')], ' '), @older_url, class: 'older', rel: 'older' + - if @newer_url + = link_to safe_join([t('pagination.newer'), fa_icon('chevron-right')], ' '), @newer_url, class: 'newer', rel: 'newer' diff --git a/config/locales/en.yml b/config/locales/en.yml index 071c41290..026426c84 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -545,7 +545,9 @@ en: trillion: T unit: '' pagination: + newer: Newer next: Next + older: Older prev: Prev truncate: "…" preferences: From 18513a978aecd36bf61a5cd7dba08f9f20729de9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Feb 2018 16:18:41 +0100 Subject: [PATCH 07/36] Improve public account cards (#6559) - Add follow/unfollow/remote follow buttons - Format the bio properly - Always show username@domain, even for local accounts --- .../follower_accounts_controller.rb | 4 +- .../following_accounts_controller.rb | 4 +- app/javascript/styles/mastodon/accounts.scss | 67 +++++++++++-------- app/views/accounts/_follow_button.html.haml | 23 +++++++ app/views/accounts/_grid_card.html.haml | 7 +- app/views/accounts/_header.html.haml | 19 +----- 6 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 app/views/accounts/_follow_button.html.haml diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 399e79665..2d2315034 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -7,7 +7,9 @@ class FollowerAccountsController < ApplicationController @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) respond_to do |format| - format.html + format.html do + @relationships = AccountRelationshipsPresenter.new(@follows.map(&:account_id), current_user.account_id) if user_signed_in? + end format.json do render json: collection_presenter, diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 1e73d4bd4..169f9057d 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -7,7 +7,9 @@ class FollowingAccountsController < ApplicationController @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) respond_to do |format| - format.html + format.html do + @relationships = AccountRelationshipsPresenter.new(@follows.map(&:target_account_id), current_user.account_id) if user_signed_in? + end format.json do render json: collection_presenter, diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index c812766a1..873963c90 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -97,32 +97,6 @@ } } - .controls { - position: absolute; - top: 15px; - left: 15px; - z-index: 2; - - .icon-button { - color: rgba($white, 0.8); - text-decoration: none; - font-size: 13px; - line-height: 13px; - font-weight: 500; - - .fa { - font-weight: 400; - margin-right: 5px; - } - - &:hover, - &:active, - &:focus { - color: $white; - } - } - } - .roles { margin-bottom: 30px; padding: 0 15px; @@ -226,6 +200,40 @@ } } +.card, +.account-grid-card { + .controls { + position: absolute; + top: 15px; + left: 15px; + z-index: 2; + + .icon-button { + color: rgba($white, 0.8); + text-decoration: none; + font-size: 13px; + line-height: 13px; + font-weight: 500; + + .fa { + font-weight: 400; + margin-right: 5px; + } + + &:hover, + &:active, + &:focus { + color: $white; + } + } + } +} + +.account-grid-card .controls { + left: auto; + right: 15px; +} + .pagination { padding: 30px 0; text-align: center; @@ -411,13 +419,14 @@ font-weight: 400; } - .note { + .account__header__content { padding: 10px 15px; padding-top: 15px; - box-sizing: border-box; color: lighten($ui-base-color, 26%); word-wrap: break-word; - min-height: 80px; + overflow: hidden; + text-overflow: ellipsis; + height: 5.5em; } } } diff --git a/app/views/accounts/_follow_button.html.haml b/app/views/accounts/_follow_button.html.haml new file mode 100644 index 000000000..e476e0aff --- /dev/null +++ b/app/views/accounts/_follow_button.html.haml @@ -0,0 +1,23 @@ +- relationships ||= nil + +- unless account.memorial? || account.moved? + - if user_signed_in? + - requested = relationships ? relationships.requested[account.id].present? : current_account.requested?(account) + - following = relationships ? relationships.following[account.id].present? : current_account.following?(account) + + - if user_signed_in? && current_account.id != account.id && !requested + .controls + - if following + = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-times' + = t('accounts.unfollow') + - else + = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.follow') + - elsif !user_signed_in? + .controls + .remote-follow + = link_to account_remote_follow_path(account), class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.remote_follow') diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml index 305eb2c44..95acbd581 100644 --- a/app/views/accounts/_grid_card.html.haml +++ b/app/views/accounts/_grid_card.html.haml @@ -1,9 +1,12 @@ .account-grid-card .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" } + = render 'accounts/follow_button', account: account, relationships: @relationships .account-grid-card__avatar .avatar= image_tag account.avatar.url(:original) .name = link_to TagManager.instance.url_for(account) do %span.display_name.emojify= display_name(account) - %span.username @#{account.acct} - %p.note.emojify= truncate(strip_tags(account.note), length: 150) + %span.username + @#{account.local? ? account.local_username_and_domain : account.acct} + = fa_icon('lock') if account.locked? + .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account) diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index d4081af64..b3c91b869 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,23 +1,6 @@ .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } .card__illustration - - unless account.memorial? || account.moved? - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-times' - = t('accounts.unfollow') - - else - = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.follow') - - elsif !user_signed_in? - .controls - .remote-follow - = link_to account_remote_follow_path(account), class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.remote_follow') - + = render 'accounts/follow_button', account: account .avatar= image_tag account.avatar.url(:original), class: 'u-photo' .card__bio From a40167cf4d51743965a90f1f7496b5a1e9e25f18 Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 27 Feb 2018 00:19:07 +0900 Subject: [PATCH 08/36] Better grid layout for the landing page (#6543) * Use grid layout for the landing page * Fix column settings Set the ratio explicitly * Improve information board --- app/javascript/styles/mastodon/about.scss | 257 ++++++++++++++-------- app/views/about/show.html.haml | 156 ++++++++----- 2 files changed, 270 insertions(+), 143 deletions(-) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index a95b75984..9417a924b 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -15,117 +15,172 @@ $small-breakpoint: 960px; } } -.show-xs, -.show-sm { - display: none; -} -.show-m { - display: block; -} -@media screen and (max-width: $small-breakpoint) { - .hide-sm { - display: none !important; +.landing-page { + .grid { + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 2fr; + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + display: none; + } + + .column-1 { + grid-column: 1; + grid-row: 1; + } + + .column-2 { + grid-column: 2; + grid-row: 1; + } + + .column-3 { + grid-column: 3; + grid-row: 1 / 3; + } + + .column-4 { + grid-column: 1 / 3; + grid-row: 2; + } } - .show-sm { - display: block !important; - } -} + @media screen and (max-width: $small-breakpoint) { -@media screen and (max-width: $column-breakpoint) { - .hide-xs { - display: none !important; - } + .grid { + grid-template-columns: 40% 60%; - .show-xs { - display: block !important; - } -} + .column-0 { + display: none; + } -.row { - display: flex; - flex-wrap: wrap; - margin: 0 -5px; + .column-1 { + grid-column: 1; + grid-row: 1; - @for $i from 1 through 15 { - .column-#{$i} { - box-sizing: border-box; - min-height: 1px; - flex: 0 0 percentage($i / 15); - max-width: percentage($i / 15); - padding: 0 5px; - - @media screen and (max-width: $small-breakpoint) { - &-sm { - box-sizing: border-box; - min-height: 1px; - flex: 0 0 percentage($i / 15); - max-width: percentage($i / 15); - padding: 0 5px; - - @media screen and (max-width: $column-breakpoint) { - max-width: 100%; - flex: 0 0 100%; - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - } + &.non-preview .landing-page__forms { + height: 100%; } } - @media screen and (max-width: $column-breakpoint) { - max-width: 100%; - flex: 0 0 100%; - margin-bottom: 10px; + .column-2 { + grid-column: 2; + grid-row: 1 / 3; - &:last-child { - margin-bottom: 0; + &.non-preview { + grid-column: 2; + grid-row: 1; + } + } + + .column-3 { + grid-column: 1; + grid-row: 2 / 4; + } + + .column-4 { + grid-column: 2; + grid-row: 3; + + &.non-preview { + grid-column: 1 / 3; + grid-row: 2; } } } } -} -.column-flex { - display: flex; - flex-direction: column; -} + @media screen and (max-width: $column-breakpoint) { + .grid { + grid-template-columns: auto; -.separator-or { - position: relative; - margin: 40px 0; - text-align: center; + .column-0 { + display: block; + grid-column: 1; + grid-row: 1; + } - &::before { - content: ""; - display: block; - width: 100%; - height: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - position: absolute; - top: 50%; - left: 0; + .column-1 { + grid-column: 1; + grid-row: 3; + + .brand { + display: none; + } + } + + .column-2 { + grid-column: 1; + grid-row: 2; + + .landing-page__logo, + .landing-page__call-to-action { + display: none; + } + + &.non-preview { + grid-column: 1; + grid-row: 2; + } + } + + .column-3 { + grid-column: 1; + grid-row: 5; + } + + .column-4 { + grid-column: 1; + grid-row: 4; + + &.non-preview { + grid-column: 1; + grid-row: 4; + } + } + } } - span { - display: inline-block; - background: $ui-base-color; - font-size: 12px; - font-weight: 500; - color: $ui-primary-color; - text-transform: uppercase; + .column-flex { + display: flex; + flex-direction: column; + } + + .separator-or { position: relative; - z-index: 1; - padding: 0 8px; - cursor: default; - } -} + margin: 40px 0; + text-align: center; + + &::before { + content: ""; + display: block; + width: 100%; + height: 0; + border-bottom: 1px solid rgba($ui-base-lighter-color, .6); + position: absolute; + top: 50%; + left: 0; + } + + span { + display: inline-block; + background: $ui-base-color; + font-size: 12px; + font-weight: 500; + color: $ui-primary-color; + text-transform: uppercase; + position: relative; + z-index: 1; + padding: 0 8px; + cursor: default; + } + } -.landing-page { p, li { font-family: 'mastodon-font-sans-serif', sans-serif; @@ -539,6 +594,7 @@ $small-breakpoint: 960px; img { position: static; + padding: 10px 0; } @media screen and (max-width: $small-breakpoint) { @@ -558,18 +614,33 @@ $small-breakpoint: 960px; } &__call-to-action { - margin-bottom: 10px; background: darken($ui-base-color, 4%); border-radius: 4px; padding: 25px 40px; overflow: hidden; .row { + display: flex; + flex-direction: row-reverse; + flex-wrap: wrap; + justify-content: space-between; align-items: center; } - .information-board__section { - padding: 0; + .row__information-board { + display: flex; + justify-content: flex-end; + align-items: flex-end; + + .information-board__section { + flex: 1 0 80px; + padding: 0 5px; + } + } + + .row__mascot { + flex: 1; + margin: 10px -50px 0 0; } } @@ -619,6 +690,8 @@ $small-breakpoint: 960px; &__short-description { .row { + display: flex; + flex-wrap: wrap; align-items: center; margin-bottom: 40px; } @@ -668,7 +741,6 @@ $small-breakpoint: 960px; height: 100%; @media screen and (max-width: $small-breakpoint) { - margin-bottom: 10px; height: auto; } @@ -717,6 +789,7 @@ $small-breakpoint: 960px; width: 100%; flex: 1 1 auto; overflow: hidden; + height: 100%; .column-header { color: inherit; diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index fd1cda8b3..4f3cedacd 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -8,51 +8,100 @@ .landing-page.alternative .container - .row - .column-4.hide-sm.show-xs.show-m - .landing-page__forms - .brand - = link_to root_url do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + .grid + .column-0 + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - .hide-xs - = render 'forms' - - .column-7.column-9-sm - .landing-page__hero - = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title - - .landing-page__information - .landing-page__short-description - .row - .landing-page__logo.hide-xs - = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon' - - %h1 - = @instance_presenter.site_title - %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname) - - %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) - - .show-xs + - if Setting.timeline_preview + .column-1 .landing-page__forms + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + = render 'forms' - .landing-page__call-to-action.hide-xs - .row - .column-5 - .landing-page__mascot - = image_tag asset_pack_path('elephant_ui_plane.svg') - .column-5 - .information-board__section - %span= t 'about.user_count_before' - %strong= number_with_delimiter @instance_presenter.user_count - %span= t 'about.user_count_after' - .column-5 - .information-board__section - %span= t 'about.status_count_before' - %strong= number_with_delimiter @instance_presenter.status_count - %span= t 'about.status_count_after' - .landing-page__information + + - else + .column-1.non-preview + .landing-page__forms + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + + = render 'forms' + + - if Setting.timeline_preview + .column-2 + .landing-page__hero + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title + + .landing-page__information + .landing-page__short-description + .row + .landing-page__logo + = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon' + + %h1 + = @instance_presenter.site_title + %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname) + + %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + + .landing-page__call-to-action + .row + .row__information-board + .information-board__section + %span= t 'about.user_count_before' + %strong= number_with_delimiter @instance_presenter.user_count + %span= t 'about.user_count_after' + .information-board__section + %span= t 'about.status_count_before' + %strong= number_with_delimiter @instance_presenter.status_count + %span= t 'about.status_count_after' + .row__mascot + .landing-page__mascot + = image_tag asset_pack_path('elephant_ui_plane.svg') + + - else + .column-2.non-preview + .landing-page__hero + = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title + + .landing-page__information + .landing-page__short-description + .row + .landing-page__logo + = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon' + + %h1 + = @instance_presenter.site_title + %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname) + + %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + + .landing-page__call-to-action + .row + .row__information-board + .information-board__section + %span= t 'about.user_count_before' + %strong= number_with_delimiter @instance_presenter.user_count + %span= t 'about.user_count_after' + .information-board__section + %span= t 'about.status_count_before' + %strong= number_with_delimiter @instance_presenter.status_count + %span= t 'about.status_count_after' + .row__mascot + .landing-page__mascot + = image_tag asset_pack_path('elephant_ui_plane.svg') + + - if Setting.timeline_preview + .column-3 + #mastodon-timeline{ data: { props: Oj.dump(default_props) } } + + - if Setting.timeline_preview + .column-4.landing-page__information .landing-page__features %h3= t 'about.what_is_mastodon' %p= t 'about.about_mastodon_html' @@ -67,13 +116,18 @@ = link_to t('about.source_code'), @instance_presenter.source_url = " (#{@instance_presenter.version_number})" - .column-4.column-6-sm.column-flex - .show-sm.hide-xs - .landing-page__forms - .brand - = link_to root_url do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + - else + .column-4.non-preview.landing-page__information + .landing-page__features + %h3= t 'about.what_is_mastodon' + %p= t 'about.about_mastodon_html' - = render 'forms' - - if Setting.timeline_preview - #mastodon-timeline{ data: { props: Oj.dump(default_props) } } + = render 'features' + + .landing-page__features__action + = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative' + + .landing-page__footer + %p + = link_to t('about.source_code'), @instance_presenter.source_url + = " (#{@instance_presenter.version_number})" From 22a441e374b73134c5e1f122f7189b78ca34d3ff Mon Sep 17 00:00:00 2001 From: masarakki Date: Tue, 27 Feb 2018 00:19:48 +0900 Subject: [PATCH 09/36] remove-uglifier (#6561) --- Gemfile | 1 - Gemfile.lock | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index e2b3b1971..fef7758cc 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,6 @@ gem 'pkg-config', '~> 1.2' gem 'puma', '~> 3.10' gem 'rails', '~> 5.1.4' -gem 'uglifier', '~> 3.2' gem 'hamlit-rails', '~> 0.2' gem 'pg', '~> 0.20' diff --git a/Gemfile.lock b/Gemfile.lock index 8293977d8..14f713604 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,7 +178,6 @@ GEM et-orbi (1.0.8) tzinfo excon (0.59.0) - execjs (2.7.0) fabrication (2.18.0) faker (1.8.4) i18n (~> 0.5) @@ -584,8 +583,6 @@ GEM thread_safe (~> 0.1) tzinfo-data (1.2017.3) tzinfo (>= 1.0.0) - uglifier (3.2.0) - execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.4) @@ -721,7 +718,6 @@ DEPENDENCIES tty-prompt twitter-text (~> 1.14) tzinfo-data (~> 1.2017) - uglifier (~> 3.2) webmock (~> 3.0) webpacker (~> 3.0) webpush From 3a6ace487465bd82c3f9527deb85f0c739742f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Mon, 26 Feb 2018 16:20:47 +0100 Subject: [PATCH 10/36] Add Liberapay link to README.md (#6563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cf91d52c..e9fb685c4 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ Click on the screenshot below to watch a demo of the UI: **Ruby on Rails** is used for the back-end, while **React.js** and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided. -If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd` +If you would like, you can [support the development of this project on Patreon][patreon] or [Liberapay][liberapay]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd` [patreon]: https://www.patreon.com/user?u=619786 +[liberapay]: https://liberapay.com/Mastodon/ --- From 7150f2e9d3791720131dc77cd889acd0226d6acb Mon Sep 17 00:00:00 2001 From: Lynx Kotoura Date: Tue, 27 Feb 2018 01:43:45 +0900 Subject: [PATCH 11/36] Grid layout for tag pages (#6545) * Use grid layout for the landing page * Use grid layout for tag pages * Set 2 columns width as explicit percentage for tag pages --- app/javascript/styles/mastodon/about.scss | 117 ++++++++-------------- app/views/tags/_features.html.haml | 25 +++++ app/views/tags/show.html.haml | 61 ++++------- 3 files changed, 86 insertions(+), 117 deletions(-) create mode 100644 app/views/tags/_features.html.haml diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 9417a924b..c2e819f51 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -1015,93 +1015,54 @@ $small-breakpoint: 960px; } &.tag-page { - .features { - padding: 30px 0; + .grid { + @media screen and (min-width: $small-breakpoint) { + grid-template-columns: 33% 67%; + } - .container-alt { - max-width: 820px; - - #mastodon-timeline { - margin-right: 0; - border-top-right-radius: 0; - } - - .about-mastodon { - .about-hashtag { - background: darken($ui-base-color, 4%); - padding: 0 20px 20px 30px; - border-radius: 0 5px 5px 0; - - .brand { - padding-top: 20px; - margin-bottom: 20px; - - img { - height: 48px; - width: auto; - } - } - - p { - strong { - color: $ui-secondary-color; - font-weight: 700; - } - } - - .cta { - margin: 0; - - .button { - margin-right: 4px; - } - } - } - - .features-list { - margin-left: 30px; - margin-right: 10px; - } - } + .column-2 { + grid-column: 2; + grid-row: 1; } } - @media screen and (max-width: 675px) { - .features { - padding: 10px 0; + .brand { + text-align: unset; + padding: 0; - .container-alt { - display: flex; - flex-direction: column; + img { + height: 48px; + width: auto; + } + } - #mastodon-timeline { - order: 2; - flex: 0 0 auto; - height: 60vh; - margin-bottom: 20px; - border-top-right-radius: 4px; - } + .cta { + margin: 0; - .about-mastodon { - order: 1; - flex: 0 0 auto; - max-width: 100%; + .button { + margin-right: 4px; + } + } - .about-hashtag { - background: unset; - padding: 0; - border-radius: 0; - - .cta { - margin: 20px 0; - } - } - - .features-list { - display: none; - } - } + @media screen and (max-width: $column-breakpoint) { + .grid { + .column-1 { + grid-column: 1; + grid-row: 2; } + + .column-2 { + grid-column: 1; + grid-row: 1; + } + } + + .brand { + margin: 0; + } + + .landing-page__features { + display: none; } } } diff --git a/app/views/tags/_features.html.haml b/app/views/tags/_features.html.haml new file mode 100644 index 000000000..8fbc6b760 --- /dev/null +++ b/app/views/tags/_features.html.haml @@ -0,0 +1,25 @@ +.features-list + .features-list__row + .text + %h6= t 'about.features.real_conversation_title' + = t 'about.features.real_conversation_body' + .visual + = fa_icon 'fw comments' + .features-list__row + .text + %h6= t 'about.features.not_a_product_title' + = t 'about.features.not_a_product_body' + .visual + = fa_icon 'fw users' + .features-list__row + .text + %h6= t 'about.features.within_reach_title' + = t 'about.features.within_reach_body' + .visual + = fa_icon 'fw mobile' + .features-list__row + .text + %h6= t 'about.features.humane_approach_title' + = t 'about.features.humane_approach_body' + .visual + = fa_icon 'fw leaf' diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index e4c16555d..f8cdc9952 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -6,48 +6,31 @@ = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous' = render 'og' -.landing-page.tag-page +.landing-page.tag-page.alternative .features .container - #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } } + .grid + .column-1 + #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } } - .about-mastodon - .about-hashtag - .brand - = link_to root_url do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + .column-2 + .about-mastodon + .about-hashtag.landing-page__information + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - %p= t 'about.about_hashtag_html', hashtag: @tag.name + %p= t 'about.about_hashtag_html', hashtag: @tag.name - .cta - - if user_signed_in? - = link_to t('settings.back'), root_path, class: 'button button-secondary' - - else - = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' - = link_to t('about.learn_more'), about_path, class: 'button button-alternative' + .cta + - if user_signed_in? + = link_to t('settings.back'), root_path, class: 'button button-secondary' + - else + = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' + = link_to t('about.learn_more'), about_path, class: 'button button-alternative' - .features-list - .features-list__row - .text - %h6= t 'about.features.real_conversation_title' - = t 'about.features.real_conversation_body' - .visual - = fa_icon 'fw comments' - .features-list__row - .text - %h6= t 'about.features.not_a_product_title' - = t 'about.features.not_a_product_body' - .visual - = fa_icon 'fw users' - .features-list__row - .text - %h6= t 'about.features.within_reach_title' - = t 'about.features.within_reach_body' - .visual - = fa_icon 'fw mobile' - .features-list__row - .text - %h6= t 'about.features.humane_approach_title' - = t 'about.features.humane_approach_body' - .visual - = fa_icon 'fw leaf' + .landing-page__features.landing-page__information + %h3= t 'about.what_is_mastodon' + %p= t 'about.about_mastodon_html' + + = render 'features' From 76198c63b613f353f2e4a82ce89a8450ca219b88 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Mon, 26 Feb 2018 16:01:49 -0500 Subject: [PATCH 12/36] Some images can cause `convert` to fail, which crashes this whole task (#6565) * Some images can cause `convert` to fail, which crashes this whole task * Add more specific exception --- lib/tasks/mastodon.rake | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index bf4c53cb3..a06e35226 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -494,9 +494,13 @@ namespace :mastodon do accounts = accounts.where(domain: ENV['DOMAIN']) if ENV['DOMAIN'].present? accounts.find_each do |account| - account.reset_avatar! - account.reset_header! - account.save + begin + account.reset_avatar! + account.reset_header! + account.save + rescue Paperclip::Error + puts "Error resetting avatar and header for account #{username}@#{domain}" + end end end end From 6f5f434caaefda068b397d29b77c7714a9563eae Mon Sep 17 00:00:00 2001 From: TrashMacNugget <35081257+TrashMacNugget@users.noreply.github.com> Date: Mon, 26 Feb 2018 16:52:27 -0800 Subject: [PATCH 13/36] Specify AGPLv3+ (#6546) * Specify AGPLv3+ Since the documentation doesn't specify you can use Mastodon as AGPLv3 or any later version. * Use newest version of SPDX AGPLv3+ identifier --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bf7c9b66..abc13e1ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mastodon", - "license": "AGPL-3.0", + "license": "AGPL-3.0-or-later", "engines": { "node": ">=6" }, From 4072b686862048c86674bd6de16d7e20ddc52b29 Mon Sep 17 00:00:00 2001 From: beatrix Date: Mon, 26 Feb 2018 23:48:11 -0500 Subject: [PATCH 14/36] remove Uglifier call from production.rb (#6568) --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 5705ffcfe..51288bc39 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -25,7 +25,7 @@ Rails.application.configure do end # Compress JavaScripts and CSS. - config.assets.js_compressor = Uglifier.new(mangle: false) + # config.assets.js_compressor = Uglifier.new(mangle: false) # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. From 41a01bec2337e7021634f2e9c78d86a1c3002fcf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 28 Feb 2018 06:54:55 +0100 Subject: [PATCH 15/36] Federated reports (#6570) * Fix #2176: Federated reports * UI for federated reports * Add spec for ActivityPub Flag handler * Add spec for ReportService --- app/controllers/api/v1/reports_controller.rb | 12 +-- app/javascript/mastodon/actions/reports.js | 9 ++ .../features/ui/components/report_modal.js | 46 +++++++--- app/javascript/mastodon/reducers/reports.js | 4 + .../styles/mastodon/components.scss | 84 ++++++++++++++++++- app/lib/activitypub/activity.rb | 2 + app/lib/activitypub/activity/flag.rb | 25 ++++++ app/models/report.rb | 4 + .../activitypub/flag_serializer.rb | 27 ++++++ app/services/report_service.rb | 54 ++++++++++++ db/schema.rb | 1 + spec/lib/activitypub/activity/flag_spec.rb | 37 ++++++++ spec/services/report_service_spec.rb | 25 ++++++ 13 files changed, 307 insertions(+), 23 deletions(-) create mode 100644 app/lib/activitypub/activity/flag.rb create mode 100644 app/serializers/activitypub/flag_serializer.rb create mode 100644 app/services/report_service.rb create mode 100644 spec/lib/activitypub/activity/flag_spec.rb create mode 100644 spec/services/report_service_spec.rb diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 22828217d..f5095e073 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -13,14 +13,14 @@ class Api::V1::ReportsController < Api::BaseController end def create - @report = current_account.reports.create!( - target_account: reported_account, + @report = ReportService.new.call( + current_account, + reported_account, status_ids: reported_status_ids, - comment: report_params[:comment] + comment: report_params[:comment], + forward: report_params[:forward] ) - User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } - render json: @report, serializer: REST::ReportSerializer end @@ -39,6 +39,6 @@ class Api::V1::ReportsController < Api::BaseController end def report_params - params.permit(:account_id, :comment, status_ids: []) + params.permit(:account_id, :comment, :forward, status_ids: []) end end diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js index b19a07285..afa0c3412 100644 --- a/app/javascript/mastodon/actions/reports.js +++ b/app/javascript/mastodon/actions/reports.js @@ -10,6 +10,7 @@ export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; +export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE'; export function initReport(account, status) { return dispatch => { @@ -45,6 +46,7 @@ export function submitReport() { account_id: getState().getIn(['reports', 'new', 'account_id']), status_ids: getState().getIn(['reports', 'new', 'status_ids']), comment: getState().getIn(['reports', 'new', 'comment']), + forward: getState().getIn(['reports', 'new', 'forward']), }).then(response => { dispatch(closeModal()); dispatch(submitReportSuccess(response.data)); @@ -78,3 +80,10 @@ export function changeReportComment(comment) { comment, }; }; + +export function changeReportForward(forward) { + return { + type: REPORT_FORWARD_CHANGE, + forward, + }; +}; diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js index b5dfa422e..3a7e4df76 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { changeReportComment, submitReport } from '../../../actions/reports'; +import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports'; import { refreshAccountTimeline } from '../../../actions/timelines'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -10,6 +10,7 @@ import StatusCheckBox from '../../report/containers/status_check_box_container'; import { OrderedSet } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Button from '../../../components/button'; +import Toggle from 'react-toggle'; const messages = defineMessages({ placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, @@ -26,6 +27,7 @@ const makeMapStateToProps = () => { isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), account: getAccount(state, accountId), comment: state.getIn(['reports', 'new', 'comment']), + forward: state.getIn(['reports', 'new', 'forward']), statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])), }; }; @@ -42,14 +44,19 @@ export default class ReportModal extends ImmutablePureComponent { account: ImmutablePropTypes.map, statusIds: ImmutablePropTypes.orderedSet.isRequired, comment: PropTypes.string.isRequired, + forward: PropTypes.bool, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; - handleCommentChange = (e) => { + handleCommentChange = e => { this.props.dispatch(changeReportComment(e.target.value)); } + handleForwardChange = e => { + this.props.dispatch(changeReportForward(e.target.checked)); + } + handleSubmit = () => { this.props.dispatch(submitReport()); } @@ -65,12 +72,14 @@ export default class ReportModal extends ImmutablePureComponent { } render () { - const { account, comment, intl, statusIds, isSubmitting } = this.props; + const { account, comment, intl, statusIds, isSubmitting, forward } = this.props; if (!account) { return null; } + const domain = account.get('acct').split('@')[1]; + return (
@@ -78,13 +87,9 @@ export default class ReportModal extends ImmutablePureComponent {
-
-
- {statusIds.map(statusId => )} -
-
-
+

+