From 8995e9238a56fbf29331ec9f3415cae5e3ae7d7a 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 b1c22d5f92..f40e2101e9 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 2837ef1c3c..5b7d3d0c68 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 2187a87f3a7de09603e9ab903f10083d11200dde 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 fc88e0c70f..06a6c9cddf 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 a5a434a8f622e26d8b7b3f7bce570ef903cf91c6 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 da5fc2f38b..e2b3b1971c 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 65a0dfabfd..8293977d8d 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 b2489711da..95e3365c2b 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 7671f4ffc7..5776b3d78b 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 0000000000..23785cf055 --- /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 59bc2c3e25..2da407c323 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 b70784d79a..f875fbd951 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 782f14b180..dc7daa52cb 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 - end + 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 'executes a HTTP request' do - expect(a_request(:get, 'http://example.com')).to have_been_made.once + it 'sets headers' do + expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + end end - it 'sets headers' do - expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + context 'with private host' do + around do |example| + WebMock.disable! + example.run + WebMock.enable! + end + + 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 b5105e689811e5823f6938ad985d5be12bcc283e 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 21d44a416d..f6ed28fd3e 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 f35356609bab94791557136f07535484ffd75b3b 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 f6ed28fd3e..5cd3e196e2 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 ba88435bfd..fdb4c6b80f 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 fcd574f8bd..b053292da5 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 d2e4f38a9a..bf4c53cb34 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 88461c1abee639e82bd5a9ba92088b75e210ec29 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 69fd20e27f..7bf35825f9 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 9015d04cb3..c812766a10 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 6061bf9bdf..66695677e9 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 accad5f781..21c585daba 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 071c412909..026426c84d 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 058b96ddf40a17ded4ea8c1950426556a9f7720b 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 399e79665e..2d2315034c 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 1e73d4bd40..169f9057d8 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 c812766a10..873963c904 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 0000000000..e476e0aff1 --- /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 305eb2c44c..95acbd581e 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 d4081af645..b3c91b8696 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 8050d02e869b94644cef46efa2433717aaea4a23 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 | 249 ++++++++++++++-------- app/views/about/show.html.haml | 148 +++++++++---- 2 files changed, 262 insertions(+), 135 deletions(-) diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index a95b759840..9417a924b1 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; + } - .show-sm { - display: block !important; - } -} + .column-1 { + grid-column: 1; + grid-row: 1; + } -@media screen and (max-width: $column-breakpoint) { - .hide-xs { - display: none !important; - } + .column-2 { + grid-column: 2; + grid-row: 1; + } + + .column-3 { + grid-column: 3; + grid-row: 1 / 3; + } - .show-xs { - display: block !important; + .column-4 { + grid-column: 1 / 3; + grid-row: 2; + } } -} -.row { - display: flex; - flex-wrap: wrap; - margin: 0 -5px; + @media screen and (max-width: $small-breakpoint) { - @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; + .grid { + grid-template-columns: 40% 60%; - @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; + .column-0 { + display: none; + } - @media screen and (max-width: $column-breakpoint) { - max-width: 100%; - flex: 0 0 100%; - margin-bottom: 10px; + .column-1 { + grid-column: 1; + grid-row: 1; - &: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 fd1cda8b39..4f3cedacd6 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' - - .hide-xs + .grid + .column-0 + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' + + - 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' - .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 + - else + .column-1.non-preview + .landing-page__forms + .brand + = link_to root_url do + = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - .landing-page__information - .landing-page__short-description + = 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 - .landing-page__logo.hide-xs - = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon' + .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') - %h1 - = @instance_presenter.site_title - %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname) + - 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 - %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) + .landing-page__information + .landing-page__short-description + .row + .landing-page__logo + = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon' - .show-xs - .landing-page__forms - = 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 + %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 eed7c16ab08674b55350d6c913f810de269e300a 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 e2b3b1971c..fef7758cc6 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 8293977d8d..14f7136045 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 cfa4c40e04a63f7f6cf974d9007651403ee6211a 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 5cf91d52ca..e9fb685c41 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 527210fd7b43ed2508d2e1aa670b9981bee48007 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 | 113 +++++++--------------- app/views/tags/_features.html.haml | 25 +++++ app/views/tags/show.html.haml | 61 +++++------- 3 files changed, 84 insertions(+), 115 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 9417a924b1..c2e819f517 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; - - .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; - } - } + .grid { + @media screen and (min-width: $small-breakpoint) { + grid-template-columns: 33% 67%; + } - .cta { - margin: 0; + .column-2 { + grid-column: 2; + grid-row: 1; + } + } - .button { - margin-right: 4px; - } - } - } + .brand { + text-align: unset; + padding: 0; - .features-list { - margin-left: 30px; - margin-right: 10px; - } - } + img { + height: 48px; + width: auto; } } - @media screen and (max-width: 675px) { - .features { - padding: 10px 0; + .cta { + margin: 0; - .container-alt { - display: flex; - flex-direction: column; - - #mastodon-timeline { - order: 2; - flex: 0 0 auto; - height: 60vh; - margin-bottom: 20px; - border-top-right-radius: 4px; - } + .button { + margin-right: 4px; + } + } - .about-mastodon { - order: 1; - flex: 0 0 auto; - max-width: 100%; + @media screen and (max-width: $column-breakpoint) { + .grid { + .column-1 { + grid-column: 1; + grid-row: 2; + } - .about-hashtag { - background: unset; - padding: 0; - border-radius: 0; + .column-2 { + grid-column: 1; + grid-row: 1; + } + } - .cta { - margin: 20px 0; - } - } + .brand { + margin: 0; + } - .features-list { - display: none; - } - } - } + .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 0000000000..8fbc6b7607 --- /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 e4c16555da..f8cdc99523 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 fee184c0fd5cdcc8b8ad7946c62604af59959fba 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 bf4c53cb34..a06e35226b 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 d73bbd5830b761aac7b17cb9d83893fb0069c925 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 3bf7c9b66d..abc13e1eab 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 caa0e2e2f85d0f5040c7f95f8391465199c38954 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 5705ffcfe9..51288bc395 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 0ebd2a171882c633426bff4e747505e0008b2513 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 | 44 +++++++--- 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, 306 insertions(+), 22 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 22828217d2..f5095e0730 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 b19a07285b..afa0c34121 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 b5dfa422e4..3a7e4df762 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 => )} -
-
-
+

+