From e1066cd4319a220d5be16e51ffaf5236a2f6e866 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 18 Sep 2019 16:37:27 +0200 Subject: Add password challenge to 2FA settings, e-mail notifications (#11878) Fix #3961 --- app/javascript/styles/mastodon/admin.scss | 43 +++++++++++++++++-------------- app/javascript/styles/mastodon/forms.scss | 4 +++ 2 files changed, 27 insertions(+), 20 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 5d4fe4ef8..074eee2cd 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -233,32 +233,35 @@ hr.spacer { height: 1px; } -.muted-hint { - color: $darker-text-color; +body, +.admin-wrapper .content { + .muted-hint { + color: $darker-text-color; - a { - color: $highlight-text-color; + a { + color: $highlight-text-color; + } } -} -.positive-hint { - color: $valid-value-color; - font-weight: 500; -} + .positive-hint { + color: $valid-value-color; + font-weight: 500; + } -.negative-hint { - color: $error-value-color; - font-weight: 500; -} + .negative-hint { + color: $error-value-color; + font-weight: 500; + } -.neutral-hint { - color: $dark-text-color; - font-weight: 500; -} + .neutral-hint { + color: $dark-text-color; + font-weight: 500; + } -.warning-hint { - color: $gold-star; - font-weight: 500; + .warning-hint { + color: $gold-star; + font-weight: 500; + } } .filters { diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 16352340b..80ef8797d 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -254,6 +254,10 @@ code { &-6 { max-width: 50%; } + + .actions { + margin-top: 27px; + } } .fields-group:last-child, -- cgit From d930eb88b671fa6e5573fe7342bcdda87501bdb7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2019 11:09:05 +0200 Subject: Add table of contents to about page (#11885) Move public domain blocks information to about page --- app/controllers/about_controller.rb | 43 +++----- app/javascript/styles/mastodon/about.scss | 138 +++++++++++-------------- app/javascript/styles/mastodon/containers.scss | 62 +++++++++++ app/javascript/styles/mastodon/widgets.scss | 83 ++++++++++----- app/lib/toc_generator.rb | 69 +++++++++++++ app/models/domain_block.rb | 1 + app/views/about/blocks.html.haml | 48 --------- app/views/about/more.html.haml | 59 ++++++++--- config/locales/en.yml | 27 ++--- config/routes.rb | 1 - 10 files changed, 322 insertions(+), 209 deletions(-) create mode 100644 app/lib/toc_generator.rb delete mode 100644 app/views/about/blocks.html.haml (limited to 'app/javascript/styles') diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 5e942e5c0..abd1ec0cb 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -3,9 +3,7 @@ class AboutController < ApplicationController layout 'public' - before_action :require_open_federation!, only: [:show, :more, :blocks] - before_action :check_blocklist_enabled, only: [:blocks] - before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required? + before_action :require_open_federation!, only: [:show, :more] before_action :set_body_classes, only: :show before_action :set_instance_presenter before_action :set_expires_in, only: [:show, :more, :terms] @@ -16,15 +14,20 @@ class AboutController < ApplicationController def more flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor] + + toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description) + + @contents = toc_generator.html + @table_of_contents = toc_generator.toc + @blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks? end def terms; end - def blocks - @show_rationale = Setting.show_domain_blocks_rationale == 'all' - @show_rationale |= Setting.show_domain_blocks_rationale == 'users' && !current_user.nil? && current_user.functional? - @blocks = DomainBlock.with_user_facing_limitations.order('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain').to_a - end + helper_method :display_blocks? + helper_method :display_blocks_rationale? + helper_method :public_fetch_mode? + helper_method :new_user private @@ -32,28 +35,14 @@ class AboutController < ApplicationController not_found if whitelist_mode? end - def check_blocklist_enabled - not_found if Setting.show_domain_blocks == 'disabled' - end - - def blocklist_account_required? - Setting.show_domain_blocks == 'users' + def display_blocks? + Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?) end - def block_severity_text(block) - if block.severity == 'suspend' - I18n.t('domain_blocks.suspension') - else - limitations = [] - limitations << I18n.t('domain_blocks.media_block') if block.reject_media? - limitations << I18n.t('domain_blocks.silence') if block.severity == 'silence' - limitations.join(', ') - end + def display_blocks_rationale? + Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?) end - helper_method :block_severity_text - helper_method :public_fetch_mode? - def new_user User.new.tap do |user| user.build_account @@ -61,8 +50,6 @@ class AboutController < ApplicationController end end - helper_method :new_user - def set_instance_presenter @instance_presenter = InstancePresenter.new end diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 61637ce96..c056ef85d 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -17,109 +17,102 @@ $small-breakpoint: 960px; .rich-formatting { font-family: $font-sans-serif, sans-serif; - font-size: 16px; + font-size: 14px; font-weight: 400; - font-size: 16px; - line-height: 30px; + line-height: 1.7; + word-wrap: break-word; color: $darker-text-color; - padding-right: 10px; a { color: $highlight-text-color; text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } } p, li { - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - margin-bottom: 12px; color: $darker-text-color; + } - a { - color: $highlight-text-color; - text-decoration: underline; - } + p { + margin-top: 0; + margin-bottom: .85em; &:last-child { margin-bottom: 0; } } - strong, - em { + strong { font-weight: 700; - color: lighten($darker-text-color, 10%); + color: $secondary-text-color; } - h1 { - font-family: $font-display, sans-serif; - font-size: 26px; - line-height: 30px; - font-weight: 500; - margin-bottom: 20px; + em { + font-style: italic; color: $secondary-text-color; + } - small { - font-family: $font-sans-serif, sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: lighten($darker-text-color, 10%); - } + code { + font-size: 0.85em; + background: darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.2em 0.3em; } - h2 { + h1, + h2, + h3, + h4, + h5, + h6 { font-family: $font-display, sans-serif; - font-size: 22px; - line-height: 26px; + margin-top: 1.275em; + margin-bottom: .85em; font-weight: 500; - margin-bottom: 20px; color: $secondary-text-color; } + h1 { + font-size: 2em; + } + + h2 { + font-size: 1.75em; + } + h3 { - font-family: $font-display, sans-serif; - font-size: 18px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + font-size: 1.5em; } h4 { - font-family: $font-display, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + font-size: 1.25em; } - h5 { - font-family: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + h5, + h6 { + font-size: 1em; } - h6 { - font-family: $font-display, sans-serif; - font-size: 12px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; + ul { + list-style: disc; + } + + ol { + list-style: decimal; } ul, ol { - margin-left: 20px; + margin: 0; + padding: 0; + padding-left: 2em; + margin-bottom: 0.85em; &[type='a'] { list-style-type: lower-alpha; @@ -130,31 +123,22 @@ $small-breakpoint: 960px; } } - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - li > ol, - li > ul { - margin-top: 6px; - } - hr { width: 100%; height: 0; border: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - margin: 20px 0; + border-bottom: 1px solid lighten($ui-base-color, 4%); + margin: 1.7em 0; &.spacer { height: 1px; border: 0; } } + + & > :first-child { + margin-top: 0; + } } .information-board { @@ -416,7 +400,7 @@ $small-breakpoint: 960px; } &__call-to-action { - background: darken($ui-base-color, 4%); + background: $ui-base-color; border-radius: 4px; padding: 25px 40px; overflow: hidden; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index aa45c0174..24bbf8211 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -141,6 +141,63 @@ grid-row: 3; } + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; + grid-template-columns: minmax(0, 100%); + + .column-0 { + grid-column: 1; + } + + .column-1 { + grid-column: 1; + grid-row: 3; + } + + .column-2 { + grid-column: 1; + grid-row: 2; + } + + .column-3 { + grid-column: 1; + grid-row: 4; + } + } +} + +.grid-4 { + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-auto-columns: 25%; + grid-auto-rows: max-content; + + .column-0 { + grid-column: 1 / 5; + grid-row: 1; + } + + .column-1 { + grid-column: 1 / 4; + grid-row: 2; + } + + .column-2 { + grid-column: 4; + grid-row: 2; + } + + .column-3 { + grid-column: 2 / 5; + grid-row: 3; + } + + .column-4 { + grid-column: 1; + grid-row: 3; + } + .landing-page__call-to-action { min-height: 100%; } @@ -189,6 +246,11 @@ } .column-3 { + grid-column: 1; + grid-row: 5; + } + + .column-4 { grid-column: 1; grid-row: 4; } diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 04beb869c..ca050a8d9 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -128,41 +128,43 @@ margin-bottom: 10px; } -.contact-widget, -.landing-page__information.contact-widget { - box-sizing: border-box; - padding: 20px; - min-height: 100%; - border-radius: 4px; - background: $ui-base-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); -} - .contact-widget { + min-height: 100%; font-size: 15px; color: $darker-text-color; line-height: 20px; word-wrap: break-word; font-weight: 400; + padding: 0; - strong { - font-weight: 500; + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; } - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } + .account { + border-bottom: 0; + padding: 10px 0; + padding-top: 5px; } - &__mail { - margin-top: 10px; + & > a { + display: inline-block; + padding: 10px; + padding-top: 0; + color: $darker-text-color; + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; - a { - color: $primary-text-color; - text-decoration: none; + &:hover, + &:focus, + &:active { + text-decoration: underline; } } } @@ -562,3 +564,38 @@ $fluid-breakpoint: $maximum-width + 20px; } } } + +.table-of-contents { + background: darken($ui-base-color, 4%); + min-height: 100%; + font-size: 14px; + border-radius: 4px; + + li a { + display: block; + font-weight: 500; + padding: 15px; + overflow: hidden; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-decoration: none; + color: $primary-text-color; + border-bottom: 1px solid lighten($ui-base-color, 4%); + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + li:last-child a { + border-bottom: 0; + } + + li ul { + padding-left: 20px; + border-bottom: 1px solid lighten($ui-base-color, 4%); + } +} diff --git a/app/lib/toc_generator.rb b/app/lib/toc_generator.rb new file mode 100644 index 000000000..c6e179557 --- /dev/null +++ b/app/lib/toc_generator.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class TOCGenerator + TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze + LISTED_ELEMENTS = %w(h2 h3).freeze + + class Section + attr_accessor :depth, :title, :children, :anchor + + def initialize(depth, title, anchor) + @depth = depth + @title = title + @children = [] + @anchor = anchor + end + + delegate :<<, to: :children + end + + def initialize(source_html) + @source_html = source_html + @processed = false + @target_html = '' + @headers = [] + @slugs = Hash.new { |h, k| h[k] = 0 } + end + + def html + parse_and_transform unless @processed + @target_html + end + + def toc + parse_and_transform unless @processed + @headers + end + + private + + def parse_and_transform + return if @source_html.blank? + + parsed_html = Nokogiri::HTML.fragment(@source_html) + + parsed_html.traverse do |node| + next unless TARGET_ELEMENTS.include?(node.name) + + anchor = node.text.parameterize + @slugs[anchor] += 1 + anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1 + + node['id'] = anchor + + next unless LISTED_ELEMENTS.include?(node.name) + + depth = node.name[1..-1] + latest_section = @headers.last + + if latest_section.nil? || latest_section.depth >= depth + @headers << Section.new(depth, node.text, anchor) + else + latest_section << Section.new(depth, node.text, anchor) + end + end + + @target_html = parsed_html.to_s + @processed = true + end +end diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 4383cbd05..4e865b850 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -26,6 +26,7 @@ class DomainBlock < ApplicationRecord scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) } + scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) } class << self def suspend?(domain) diff --git a/app/views/about/blocks.html.haml b/app/views/about/blocks.html.haml deleted file mode 100644 index a81a4d1eb..000000000 --- a/app/views/about/blocks.html.haml +++ /dev/null @@ -1,48 +0,0 @@ -- content_for :page_title do - = t('domain_blocks.title', instance: site_hostname) - -.grid - .column-0 - .box-widget.rich-formatting - %h2= t('domain_blocks.blocked_domains') - %p= t('domain_blocks.description', instance: site_hostname) - .table-wrapper - %table.blocks-table - %thead - %tr - %th= t('domain_blocks.domain') - %th.severity-column= t('domain_blocks.severity') - - if @show_rationale - %th.button-column - %tbody - - if @blocks.empty? - %tr - %td{ colspan: @show_rationale ? 3 : 2 }= t('domain_blocks.no_domain_blocks') - - else - - @blocks.each_with_index do |block, i| - %tr{ class: i % 2 == 0 ? 'even': nil } - %td{ title: block.domain }= block.domain - %td= block_severity_text(block) - - if @show_rationale - %td - - if block.public_comment.present? - %button.icon-button{ title: t('domain_blocks.show_rationale'), 'aria-label' => t('domain_blocks.show_rationale') } - = fa_icon 'chevron-down fw', 'aria-hidden' => true - - if @show_rationale - - if block.public_comment.present? - %tr.rationale.hidden - %td{ colspan: 3 }= block.public_comment.presence - %h2= t('domain_blocks.severity_legend.title') - - if @blocks.any? { |block| block.reject_media? } - %h3= t('domain_blocks.media_block') - %p= t('domain_blocks.severity_legend.media_block') - - if @blocks.any? { |block| block.severity == 'silence' } - %h3= t('domain_blocks.silence') - %p= t('domain_blocks.severity_legend.silence') - - if @blocks.any? { |block| block.severity == 'suspend' } - %h3= t('domain_blocks.suspension') - %p= t('domain_blocks.severity_legend.suspension') - - if public_fetch_mode? - %p= t('domain_blocks.severity_legend.suspension_disclaimer') - .column-1 - = render 'application/sidebar' diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 21431ef8e..4b3035ee8 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -5,7 +5,7 @@ = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous' = render partial: 'shared/og' -.grid-3 +.grid-4 .column-0 .public-account-header.public-account-header--no-bar .public-account-header__image @@ -28,22 +28,57 @@ = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: '' .column-2 - .landing-page__information.contact-widget - %p - %strong= t 'about.administered_by' + .contact-widget + %h4= t 'about.administered_by' = account_link_to(@instance_presenter.contact_account) - if @instance_presenter.site_contact_email.present? - %p.contact-widget__mail - %strong - = succeed ':' do - = t 'about.contact' - %br/ - = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email + %h4 + = succeed ':' do + = t 'about.contact' + + = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email .column-3 = render 'application/flashes' - .box-widget - .rich-formatting= @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html') + - if @contents.blank? && (!display_blocks? || @blocks&.empty?) + = nothing_here + - else + .box-widget + .rich-formatting + = @contents.html_safe + + - if display_blocks? && !@blocks.empty? + %h2#unavailable-content= t('about.unavailable_content') + + %p= t('about.unavailable_content_html') + + - @blocks.each do |domain_block| + %p + %strong= "#{domain_block.domain}:" + + - if domain_block.suspend? + = t('about.unavailable_content_description.suspended') + - else + = t('about.unavailable_content_description.silenced') if domain_block.silence? + = t('about.unavailable_content_description.rejecting_media') if domain_block.reject_media? + + - if display_blocks_rationale? + %strong= t('about.unavailable_content_description.reason') + = domain_block.public_comment + + .column-4 + %ul.table-of-contents + - @table_of_contents.each do |item| + %li + = link_to item.title, "##{item.anchor}" + + - unless item.children.empty? + %ul + - item.children.each do |sub_item| + %li= link_to sub_item.title, "##{sub_item.anchor}" + + - if display_blocks? && !@blocks.empty? + %li= link_to t('about.unavailable_content'), '#unavailable-content' diff --git a/config/locales/en.yml b/config/locales/en.yml index da06b0e51..dabb679e7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -17,9 +17,6 @@ en: contact_unavailable: N/A discover_users: Discover users documentation: Documentation - extended_description_html: | -

A good place for rules

-

The extended description has not been set up yet.

federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond. generic_description: "%{domain} is one server in the network" get_apps: Try a mobile app @@ -38,6 +35,13 @@ en: status_count_before: Who authored tagline: Follow friends and discover new ones terms: Terms of service + unavailable_content: Unavailable content + unavailable_content_description: + reason: 'Reason:' + rejecting_media: Media files from this server will not be processed and and no thumbnails will be displayed, requiring manual click-through to the other server. + silenced: Posts from this server will not show up anywhere except your home feed if you follow the author. + suspended: You won't be able to follow anyone from this server, and no data from it will be processed or stored, and no data exchanged. + unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server. user_count_after: one: user other: users @@ -661,23 +665,6 @@ en: directory: Profile directory explanation: Discover users based on their interests explore_mastodon: Explore %{title} - domain_blocks: - blocked_domains: List of limited and blocked domains - description: This is the list of servers that %{instance} limits or reject federation with. - domain: Domain - media_block: Media block - no_domain_blocks: "(No domain blocks)" - severity: Severity - severity_legend: - media_block: Media files coming from the server are neither fetched, stored, or displayed to the user. - silence: Accounts from silenced servers can be found, followed and interacted with, but their toots will not appear in the public timelines, and notifications from them will not reach local users who are not following them. - suspension: No content from suspended servers is stored or displayed, nor is any content sent to them. Interactions from suspended servers are ignored. - suspension_disclaimer: Suspended servers may occasionally retrieve public content from this server. - title: Severities - show_rationale: Show rationale - silence: Silence - suspension: Suspension - title: "%{instance} List of blocked instances" domain_validator: invalid_domain: is not a valid domain name errors: diff --git a/config/routes.rb b/config/routes.rb index 9ad1ea65d..dcfa079a0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -441,7 +441,6 @@ Rails.application.routes.draw do get '/about', to: 'about#show' get '/about/more', to: 'about#more' - get '/about/blocks', to: 'about#blocks' get '/terms', to: 'about#terms' match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false -- cgit From b6df9c10671cd7bf48de3dbd7a94a92fb0a148ec Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2019 19:58:40 +0200 Subject: Fix placeholder colors for inputs not being explicitly defined (#11890) Fix #11841 --- app/javascript/styles/mastodon/_mixins.scss | 18 ---------- app/javascript/styles/mastodon/components.scss | 48 ++++++++++++++++++++++++++ app/javascript/styles/mastodon/forms.scss | 4 +++ 3 files changed, 52 insertions(+), 18 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index faaffb30f..68cad0fde 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -22,24 +22,6 @@ color: $darker-text-color; font-size: 14px; margin: 0; - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - - &:focus { - background: lighten($ui-base-color, 4%); - } - - @media screen and (max-width: 600px) { - font-size: 16px; - } } @mixin search-popout { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index ef48d2438..8893848ae 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -421,6 +421,10 @@ border: 0; outline: 0; + &::placeholder { + color: $dark-text-color; + } + &:focus { outline: 0; } @@ -3533,6 +3537,28 @@ a.status-card.compact:hover { .column-select { &__control { @include search-input; + + &::placeholder { + color: lighten($darker-text-color, 4%); + } + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + + &:focus { + background: lighten($ui-base-color, 4%); + } + + @media screen and (max-width: 600px) { + font-size: 16px; + } } &__placeholder { @@ -4046,6 +4072,28 @@ a.status-card.compact:hover { padding-right: 30px; line-height: 18px; font-size: 16px; + + &::placeholder { + color: lighten($darker-text-color, 4%); + } + + &::-moz-focus-inner { + border: 0; + } + + &::-moz-focus-inner, + &:focus, + &:active { + outline: 0 !important; + } + + &:focus { + background: lighten($ui-base-color, 4%); + } + + @media screen and (max-width: 600px) { + font-size: 16px; + } } .search__icon { diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 80ef8797d..b729d912e 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -338,6 +338,10 @@ code { border-radius: 4px; padding: 10px; + &::placeholder { + color: lighten($darker-text-color, 4%); + } + &:invalid { box-shadow: none; } -- cgit From 37ccafec8fe8bf5588794257744554be61a3f22e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Sep 2019 10:51:52 +0200 Subject: Fix left side of single column layout being cropped on smaller screens (#11894) Fix #11476 --- app/javascript/styles/mastodon/components.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 8893848ae..17c94e23c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1893,6 +1893,7 @@ a.account__display-name { pointer-events: none; display: flex; justify-content: flex-end; + min-width: 285px; &--start { justify-content: flex-start; @@ -1910,6 +1911,7 @@ a.account__display-name { box-sizing: border-box; width: 100%; max-width: 600px; + flex: 0 0 auto; display: flex; flex-direction: column; -- cgit From b9a8b38844278f26b9d1d1d53256e0781ba3575a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Sep 2019 10:52:14 +0200 Subject: Fix page body not being scrollable in admin layout (#11893) Hide navigation behind hamburger icon on small screens in admin layout --- app/javascript/packs/public.js | 10 +++ app/javascript/styles/mastodon/admin.scss | 138 ++++++++++++++++++++++++----- app/javascript/styles/mastodon/basics.scss | 3 - app/views/layouts/admin.html.haml | 20 ++++- 4 files changed, 142 insertions(+), 29 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 97ee4dfa2..ed713f335 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -247,6 +247,16 @@ function main() { input.readonly = oldReadOnly; }); + + delegate(document, '.sidebar__toggle__icon', 'click', () => { + const target = document.querySelector('.sidebar ul'); + + if (target.style.display === 'block') { + target.style.display = 'none'; + } else { + target.style.display = 'block'; + } + }); } loadPolyfills().then(main).catch(error => { diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 074eee2cd..dde1d69ba 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -5,21 +5,66 @@ $content-width: 840px; .admin-wrapper { display: flex; justify-content: center; - height: 100%; + width: 100%; + min-height: 100vh; .sidebar-wrapper { - flex: 1 1 $sidebar-width; - height: 100%; - background: $ui-base-color; - display: flex; - justify-content: flex-end; + min-height: 100vh; + overflow: hidden; + pointer-events: none; + flex: 1 1 auto; + + &__inner { + display: flex; + justify-content: flex-end; + background: $ui-base-color; + height: 100%; + } } .sidebar { width: $sidebar-width; - height: 100%; padding: 0; - overflow-y: auto; + pointer-events: auto; + + &__toggle { + display: none; + background: lighten($ui-base-color, 8%); + height: 48px; + + &__logo { + flex: 1 1 auto; + + a { + display: inline-block; + padding: 15px; + } + + svg { + fill: $primary-text-color; + height: 20px; + position: relative; + bottom: -2px; + } + } + + &__icon { + display: block; + color: $darker-text-color; + text-decoration: none; + flex: 0 0 auto; + font-size: 20px; + padding: 15px; + } + + a { + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 12%); + } + } + } .logo { display: block; @@ -52,6 +97,9 @@ $content-width: 840px; transition: all 200ms linear; transition-property: color, background-color; border-radius: 4px 0 0 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; i.fa { margin-right: 5px; @@ -99,12 +147,30 @@ $content-width: 840px; } .content-wrapper { - flex: 2 1 $content-width; - overflow: auto; + box-sizing: border-box; + width: 100%; + max-width: $content-width; + flex: 1 1 auto; + } + + @media screen and (max-width: $content-width + $sidebar-width) { + .sidebar-wrapper--empty { + display: none; + } + + .sidebar-wrapper { + width: $sidebar-width; + flex: 0 0 auto; + } + } + + @media screen and (max-width: $no-columns-breakpoint) { + .sidebar-wrapper { + width: 100%; + } } .content { - max-width: $content-width; padding: 20px 15px; padding-top: 60px; padding-left: 25px; @@ -123,6 +189,12 @@ $content-width: 840px; padding-bottom: 40px; border-bottom: 1px solid lighten($ui-base-color, 8%); margin-bottom: 40px; + + @media screen and (max-width: $no-columns-breakpoint) { + border-bottom: 0; + padding-bottom: 0; + font-weight: 700; + } } h3 { @@ -147,7 +219,7 @@ $content-width: 840px; font-size: 16px; color: $secondary-text-color; line-height: 28px; - font-weight: 400; + font-weight: 500; } .fields-group h6 { @@ -176,7 +248,7 @@ $content-width: 840px; & > p { font-size: 14px; - line-height: 18px; + line-height: 21px; color: $secondary-text-color; margin-bottom: 20px; @@ -208,20 +280,42 @@ $content-width: 840px; @media screen and (max-width: $no-columns-breakpoint) { display: block; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - .sidebar-wrapper, - .content-wrapper { - flex: 0 0 auto; - height: auto; - overflow: initial; + .sidebar-wrapper { + min-height: 0; } .sidebar { width: 100%; padding: 0; height: auto; + + &__toggle { + display: flex; + } + + & > ul { + display: none; + } + + ul a, + ul ul a { + border-radius: 0; + border-bottom: 1px solid lighten($ui-base-color, 4%); + transition: none; + + &:hover { + transition: none; + } + } + + ul ul { + border-radius: 0; + } + + ul .simple-navigation-active-leaf a { + border-bottom-color: $ui-highlight-color; + } } } } @@ -270,10 +364,10 @@ body, .filter-subset { flex: 0 0 auto; - margin: 0 40px 10px 0; + margin: 0 40px 20px 0; &:last-child { - margin-bottom: 20px; + margin-bottom: 30px; } ul { diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index f9332caa3..1f3ef7da2 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -86,9 +86,6 @@ body { &.admin { background: darken($ui-base-color, 4%); - position: fixed; - width: 100%; - height: 100%; padding: 0; } diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 083f2fac7..57bda45e2 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -4,11 +4,21 @@ - content_for :content do .admin-wrapper .sidebar-wrapper - .sidebar - = link_to root_path do - = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon' + .sidebar-wrapper__inner + .sidebar + = link_to root_path do + = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon' + + .sidebar__toggle + .sidebar__toggle__logo + = link_to root_path do + = svg_logo_full + + = link_to '#', class: 'sidebar__toggle__icon' do + = fa_icon 'bars' + + = render_navigation - = render_navigation .content-wrapper .content %h2= yield :page_title @@ -17,4 +27,6 @@ = yield + .sidebar-wrapper.sidebar-wrapper--empty + = render template: 'layouts/application' -- cgit From bc5678d0151dd96e0ec5f3d4084ac6356c1d02f5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 21 Sep 2019 20:01:16 +0200 Subject: Change conversations UI (#11896) Fix #11414, fix #9860, fix #10434 --- app/javascript/mastodon/actions/conversations.js | 28 +++++ .../mastodon/components/avatar_composite.js | 28 +++-- .../mastodon/containers/status_container.js | 1 + .../direct_timeline/components/conversation.js | 128 ++++++++++++++++++--- .../containers/conversation_container.js | 75 ++++++++++-- app/javascript/styles/mastodon/components.scss | 89 ++++++++------ 6 files changed, 280 insertions(+), 69 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js index c6e062ef7..4ef654b1f 100644 --- a/app/javascript/mastodon/actions/conversations.js +++ b/app/javascript/mastodon/actions/conversations.js @@ -15,6 +15,10 @@ export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; export const CONVERSATIONS_READ = 'CONVERSATIONS_READ'; +export const CONVERSATIONS_DELETE_REQUEST = 'CONVERSATIONS_DELETE_REQUEST'; +export const CONVERSATIONS_DELETE_SUCCESS = 'CONVERSATIONS_DELETE_SUCCESS'; +export const CONVERSATIONS_DELETE_FAIL = 'CONVERSATIONS_DELETE_FAIL'; + export const mountConversations = () => ({ type: CONVERSATIONS_MOUNT, }); @@ -82,3 +86,27 @@ export const updateConversations = conversation => dispatch => { conversation, }); }; + +export const deleteConversation = conversationId => (dispatch, getState) => { + dispatch(deleteConversationRequest(conversationId)); + + api(getState).delete(`/api/v1/conversations/${conversationId}`) + .then(() => dispatch(deleteConversationSuccess(conversationId))) + .catch(error => dispatch(deleteConversationFail(conversationId, error))); +}; + +export const deleteConversationRequest = id => ({ + type: CONVERSATIONS_DELETE_REQUEST, + id, +}); + +export const deleteConversationSuccess = id => ({ + type: CONVERSATIONS_DELETE_SUCCESS, + id, +}); + +export const deleteConversationFail = (id, error) => ({ + type: CONVERSATIONS_DELETE_FAIL, + id, + error, +}); diff --git a/app/javascript/mastodon/components/avatar_composite.js b/app/javascript/mastodon/components/avatar_composite.js index 4a9a73c51..5d5b89749 100644 --- a/app/javascript/mastodon/components/avatar_composite.js +++ b/app/javascript/mastodon/components/avatar_composite.js @@ -35,35 +35,35 @@ export default class AvatarComposite extends React.PureComponent { if (size === 2) { if (index === 0) { - right = '2px'; + right = '1px'; } else { - left = '2px'; + left = '1px'; } } else if (size === 3) { if (index === 0) { - right = '2px'; + right = '1px'; } else if (index > 0) { - left = '2px'; + left = '1px'; } if (index === 1) { - bottom = '2px'; + bottom = '1px'; } else if (index > 1) { - top = '2px'; + top = '1px'; } } else if (size === 4) { if (index === 0 || index === 2) { - right = '2px'; + right = '1px'; } if (index === 1 || index === 3) { - left = '2px'; + left = '1px'; } if (index < 2) { - bottom = '2px'; + bottom = '1px'; } else { - top = '2px'; + top = '1px'; } } @@ -88,7 +88,13 @@ export default class AvatarComposite extends React.PureComponent { return (
- {accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))} + {accounts.take(4).map((account, i) => this.renderItem(account, Math.min(accounts.size, 4), i))} + + {accounts.size > 4 && ( + + +{accounts.size - 4} + + )}
); } diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index fa58589a6..7b0906b39 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -56,6 +56,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onReply (status, router) { dispatch((_, getState) => { let state = getState(); + if (state.getIn(['compose', 'text']).trim().length !== 0) { dispatch(openModal('CONFIRM', { message: intl.formatMessage(messages.replyMessage), diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js index ffcd6d281..cc3faf0de 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js @@ -2,9 +2,28 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import StatusContainer from '../../../containers/status_container'; +import StatusContent from 'mastodon/components/status_content'; +import AttachmentList from 'mastodon/components/attachment_list'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import AvatarComposite from 'mastodon/components/avatar_composite'; +import Permalink from 'mastodon/components/permalink'; +import IconButton from 'mastodon/components/icon_button'; +import RelativeTimestamp from 'mastodon/components/relative_timestamp'; +import { HotKeys } from 'react-hotkeys'; -export default class Conversation extends ImmutablePureComponent { +const messages = defineMessages({ + more: { id: 'status.more', defaultMessage: 'More' }, + open: { id: 'conversation.open', defaultMessage: 'View conversation' }, + reply: { id: 'status.reply', defaultMessage: 'Reply' }, + markAsRead: { id: 'conversation.mark_as_read', defaultMessage: 'Mark as read' }, + delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' }, + muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, + unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, +}); + +export default @injectIntl +class Conversation extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -13,11 +32,12 @@ export default class Conversation extends ImmutablePureComponent { static propTypes = { conversationId: PropTypes.string.isRequired, accounts: ImmutablePropTypes.list.isRequired, - lastStatusId: PropTypes.string, + lastStatus: ImmutablePropTypes.map, unread:PropTypes.bool.isRequired, onMoveUp: PropTypes.func, onMoveDown: PropTypes.func, markRead: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, }; handleClick = () => { @@ -25,13 +45,25 @@ export default class Conversation extends ImmutablePureComponent { return; } - const { lastStatusId, unread, markRead } = this.props; + const { lastStatus, unread, markRead } = this.props; if (unread) { markRead(); } - this.context.router.history.push(`/statuses/${lastStatusId}`); + this.context.router.history.push(`/statuses/${lastStatus.get('id')}`); + } + + handleMarkAsRead = () => { + this.props.markRead(); + } + + handleReply = () => { + this.props.reply(this.props.lastStatus, this.context.router.history); + } + + handleDelete = () => { + this.props.delete(); } handleHotkeyMoveUp = () => { @@ -42,22 +74,88 @@ export default class Conversation extends ImmutablePureComponent { this.props.onMoveDown(this.props.conversationId); } + handleConversationMute = () => { + this.props.onMute(this.props.lastStatus); + } + + handleShowMore = () => { + this.props.onToggleHidden(this.props.lastStatus); + } + render () { - const { accounts, lastStatusId, unread } = this.props; + const { accounts, lastStatus, unread, intl } = this.props; - if (lastStatusId === null) { + if (lastStatus === null) { return null; } + const menu = [ + { text: intl.formatMessage(messages.open), action: this.handleClick }, + null, + ]; + + menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute }); + + if (unread) { + menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead }); + menu.push(null); + } + + menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete }); + + const names = accounts.map(a => ).reduce((prev, cur) => [prev, ', ', cur]); + + const handlers = { + reply: this.handleReply, + open: this.handleClick, + moveUp: this.handleHotkeyMoveUp, + moveDown: this.handleHotkeyMoveDown, + toggleHidden: this.handleShowMore, + }; + return ( - + +
+
+ +
+ +
+
+
+ +
+ +
+ {names} }} /> +
+
+ + + + {lastStatus.get('media_attachments').size > 0 && ( + + )} + +
+ + +
+ +
+
+
+
+
); } diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js index bd6f6bfb0..94cef81a7 100644 --- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js +++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js @@ -1,19 +1,74 @@ import { connect } from 'react-redux'; import Conversation from '../components/conversation'; -import { markConversationRead } from '../../../actions/conversations'; +import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations'; +import { makeGetStatus } from 'mastodon/selectors'; +import { replyCompose } from 'mastodon/actions/compose'; +import { openModal } from 'mastodon/actions/modal'; +import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'mastodon/actions/statuses'; +import { defineMessages, injectIntl } from 'react-intl'; -const mapStateToProps = (state, { conversationId }) => { - const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); +const messages = defineMessages({ + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, +}); + +const mapStateToProps = () => { + const getStatus = makeGetStatus(); + + return (state, { conversationId }) => { + const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId); + const lastStatusId = conversation.get('last_status', null); - return { - accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), - unread: conversation.get('unread'), - lastStatusId: conversation.get('last_status', null), + return { + accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)), + unread: conversation.get('unread'), + lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }), + }; }; }; -const mapDispatchToProps = (dispatch, { conversationId }) => ({ - markRead: () => dispatch(markConversationRead(conversationId)), +const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({ + + markRead () { + dispatch(markConversationRead(conversationId)); + }, + + reply (status, router) { + dispatch((_, getState) => { + let state = getState(); + + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)), + })); + } else { + dispatch(replyCompose(status, router)); + } + }); + }, + + delete () { + dispatch(deleteConversation(conversationId)); + }, + + onMute (status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, + + onToggleHidden (status) { + if (status.get('hidden')) { + dispatch(revealStatus(status.get('id'))); + } else { + dispatch(hideStatus(status.get('id'))); + } + }, + }); -export default connect(mapStateToProps, mapDispatchToProps)(Conversation); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Conversation)); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 17c94e23c..f4f26203e 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1276,14 +1276,28 @@ &-composite { @include avatar-radius; + border-radius: 50%; overflow: hidden; + position: relative; + cursor: default; & > div { - @include avatar-radius; float: left; position: relative; box-sizing: border-box; } + + &__label { + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: $primary-text-color; + text-shadow: 1px 1px 2px $base-shadow-color; + font-weight: 700; + font-size: 15px; + } } } @@ -6383,48 +6397,57 @@ noscript { } } -.layout-toggle { +.conversation { display: flex; + border-bottom: 1px solid lighten($ui-base-color, 8%); padding: 5px; + padding-bottom: 0; - button { - box-sizing: border-box; - flex: 0 0 50%; - background: transparent; - padding: 5px; - border: 0; - position: relative; + &:focus { + background: lighten($ui-base-color, 2%); + outline: 0; + } - &:hover, - &:focus, - &:active { - svg path:first-child { - fill: lighten($ui-base-color, 16%); - } - } + &__avatar { + flex: 0 0 auto; + padding: 10px; + padding-top: 12px; } - svg { - width: 100%; - height: auto; + &__content { + flex: 1 1 auto; + padding: 10px 5px; + padding-right: 15px; - path:first-child { - fill: lighten($ui-base-color, 12%); + &__info { + overflow: hidden; } - path:last-child { - fill: darken($ui-base-color, 14%); + &__relative-time { + float: right; + font-size: 15px; + color: $darker-text-color; + padding-left: 15px; } - } - &__active { - color: $ui-highlight-color; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: lighten($ui-base-color, 12%); - border-radius: 50%; - padding: 0.35rem; + &__names { + color: $darker-text-color; + font-size: 15px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; + + a { + color: $primary-text-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } } } -- cgit From 26b810561a5b7cfd1766699358d998b5882a5876 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Sun, 22 Sep 2019 19:58:29 +0900 Subject: Fix ugly TOC when title is too long (#11916) * Fix ugly TOC when title is too long * Fix TOC using grid, minmax --- app/javascript/styles/mastodon/containers.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 24bbf8211..2d1bf1abd 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -169,7 +169,7 @@ .grid-4 { display: grid; grid-gap: 10px; - grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-columns: 25%; grid-auto-rows: max-content; -- cgit From b359974d9b356bb723fe046466b178328cf9bbaf Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 22 Sep 2019 14:15:18 +0200 Subject: Show user what options they have voted (#11195) * Add own_votes field to poll results in REST API Fixes #10679 * Display user votes in WebUI * Update styling * Add vote checkmark to public pages --- app/javascript/mastodon/actions/importer/normalizer.js | 3 ++- app/javascript/mastodon/components/poll.js | 7 ++++++- app/javascript/styles/mastodon/polls.scss | 10 ++++++++-- app/models/poll.rb | 4 ++++ app/serializers/rest/poll_serializer.rb | 5 +++++ app/views/statuses/_poll.html.haml | 8 ++++++-- 6 files changed, 31 insertions(+), 6 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 5e7e78e69..f7108fdb9 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -73,8 +73,9 @@ export function normalizePoll(poll) { const emojiMap = makeEmojiMap(normalPoll); - normalPoll.options = poll.options.map(option => ({ + normalPoll.options = poll.options.map((option, index) => ({ ...option, + voted: poll.own_votes && poll.own_votes.includes(index), title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap), })); diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js index 373f710d3..4c9b23b77 100644 --- a/app/javascript/mastodon/components/poll.js +++ b/app/javascript/mastodon/components/poll.js @@ -10,6 +10,7 @@ import spring from 'react-motion/lib/spring'; import escapeTextContentForBrowser from 'escape-html'; import emojify from 'mastodon/features/emoji/emoji'; import RelativeTimestamp from './relative_timestamp'; +import Icon from 'mastodon/components/icon'; const messages = defineMessages({ closed: { id: 'poll.closed', defaultMessage: 'Closed' }, @@ -103,6 +104,7 @@ class Poll extends ImmutablePureComponent { const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); const active = !!this.state.selected[`${optionIndex}`]; + const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex)); let titleEmojified = option.get('title_emojified'); if (!titleEmojified) { @@ -131,7 +133,10 @@ class Poll extends ImmutablePureComponent { /> {!showResults && } - {showResults && {Math.round(percent)}%} + {showResults && + {!!voted && } + {Math.round(percent)}% + } diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index e80220f27..85ba138b4 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -95,13 +95,19 @@ &__number { display: inline-block; - width: 36px; + width: 48px; font-weight: 700; padding: 0 10px; text-align: right; margin-top: auto; margin-bottom: auto; - flex: 0 0 36px; + flex: 0 0 48px; + } + + &__vote__mark { + float: left; + color: $valid-value-color; + line-height: 18px; } &__footer { diff --git a/app/models/poll.rb b/app/models/poll.rb index 8f72c7b11..55a8f13a6 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -54,6 +54,10 @@ class Poll < ApplicationRecord account.id == account_id || votes.where(account: account).exists? end + def own_votes(account) + votes.where(account: account).pluck(:choice) + end + delegate :local?, to: :account def remote? diff --git a/app/serializers/rest/poll_serializer.rb b/app/serializers/rest/poll_serializer.rb index 356c45b83..eb98bb2d2 100644 --- a/app/serializers/rest/poll_serializer.rb +++ b/app/serializers/rest/poll_serializer.rb @@ -8,6 +8,7 @@ class REST::PollSerializer < ActiveModel::Serializer has_many :emojis, serializer: REST::CustomEmojiSerializer attribute :voted, if: :current_user? + attribute :own_votes, if: :current_user? def id object.id.to_s @@ -21,6 +22,10 @@ class REST::PollSerializer < ActiveModel::Serializer object.voted?(current_user.account) end + def own_votes + object.own_votes(current_user.account) + end + def current_user? !current_user.nil? end diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml index ba34890df..d6b36a5d1 100644 --- a/app/views/statuses/_poll.html.haml +++ b/app/views/statuses/_poll.html.haml @@ -1,15 +1,19 @@ - show_results = (user_signed_in? && poll.voted?(current_account)) || poll.expired? +- own_votes = user_signed_in? ? poll.own_votes(current_account) : [] .poll %ul - - poll.loaded_options.each do |option| + - poll.loaded_options.each_with_index do |option, index| %li - if show_results - percent = poll.votes_count > 0 ? 100 * option.votes_count / poll.votes_count : 0 %span.poll__chart{ style: "width: #{percent}%" } %label.poll__text>< - %span.poll__number= percent.round + %span.poll__number>< + - if own_votes.include?(index) + %i.poll__vote__mark.fa.fa-check + = percent.round = Formatter.instance.format_poll_option(status, option, autoplay: autoplay) - else %label.poll__text>< -- cgit From f31530b74d0f2ab77845db26babc25f5de337bd4 Mon Sep 17 00:00:00 2001 From: Cutls Date: Fri, 27 Sep 2019 09:14:49 +0900 Subject: Fix overflow on conversations (#11965) * Fix: overflow on conversations * Fix: overflow on conversations --- app/javascript/styles/mastodon/components.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f4f26203e..7562cc709 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6418,13 +6418,17 @@ noscript { flex: 1 1 auto; padding: 10px 5px; padding-right: 15px; + word-break: break-all; + overflow: hidden; &__info { overflow: hidden; + display: flex; + flex-direction: row-reverse; + justify-content: space-between; } &__relative-time { - float: right; font-size: 15px; color: $darker-text-color; padding-left: 15px; @@ -6437,6 +6441,8 @@ noscript { overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; + flex-basis: 170px; + flex-shrink: 1000; a { color: $primary-text-color; -- cgit From b0cda7a504655f6ced33802af67cabb6f3e46e19 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 28 Sep 2019 19:41:36 +0200 Subject: Fix vote checkmark in poll results (#11990) --- app/javascript/mastodon/components/poll.js | 13 +++++++------ app/javascript/styles/mastodon/polls.scss | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'app/javascript/styles') diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js index 4c9b23b77..dd7e2fcd3 100644 --- a/app/javascript/mastodon/components/poll.js +++ b/app/javascript/mastodon/components/poll.js @@ -14,6 +14,7 @@ import Icon from 'mastodon/components/icon'; const messages = defineMessages({ closed: { id: 'poll.closed', defaultMessage: 'Closed' }, + voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer', description: 'Tooltip of the "voted" checkmark in polls' }, }); const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { @@ -100,11 +101,11 @@ class Poll extends ImmutablePureComponent { }; renderOption (option, optionIndex, showResults) { - const { poll, disabled } = this.props; - const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; - const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); - const active = !!this.state.selected[`${optionIndex}`]; - const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex)); + const { poll, disabled, intl } = this.props; + const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; + const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); + const active = !!this.state.selected[`${optionIndex}`]; + const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex)); let titleEmojified = option.get('title_emojified'); if (!titleEmojified) { @@ -134,7 +135,7 @@ class Poll extends ImmutablePureComponent { {!showResults && } {showResults && - {!!voted && } + {!!voted && } {Math.round(percent)}% } diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 85ba138b4..f59a9d693 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -95,18 +95,18 @@ &__number { display: inline-block; - width: 48px; + width: 52px; font-weight: 700; padding: 0 10px; + padding-left: 8px; text-align: right; margin-top: auto; margin-bottom: auto; - flex: 0 0 48px; + flex: 0 0 52px; } &__vote__mark { float: left; - color: $valid-value-color; line-height: 18px; } -- cgit From bd9685f7980838ecc675af20cf52ef1e686d98d6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 29 Sep 2019 16:23:01 +0200 Subject: Fix public list of domain blocks being too verbose on about page (#11967) --- app/javascript/styles/mastodon/about.scss | 41 ++++++++++++++++++ app/javascript/styles/mastodon/tables.scss | 67 ------------------------------ app/views/about/_domain_blocks.html.haml | 10 +++++ app/views/about/more.html.haml | 22 ++++------ config/locales/en.yml | 9 ++-- 5 files changed, 65 insertions(+), 84 deletions(-) create mode 100644 app/views/about/_domain_blocks.html.haml (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index c056ef85d..1dd8b7954 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -136,6 +136,47 @@ $small-breakpoint: 960px; } } + table { + width: 100%; + border-collapse: collapse; + break-inside: auto; + margin-top: 24px; + margin-bottom: 32px; + + thead tr, + tbody tr { + break-after: auto; + break-inside: avoid; + border-bottom: 1px solid lighten($ui-base-color, 4%); + font-size: 1em; + line-height: 1.625; + font-weight: 400; + text-align: left; + color: $darker-text-color; + } + + thead tr { + border-bottom-width: 2px; + line-height: 1.5; + font-weight: 500; + color: $dark-text-color; + } + + th, + td { + padding: 8px; + align-self: start; + align-items: start; + + &.nowrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 25%; + } + } + } + & > :first-child { margin-top: 0; } diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index d6403986f..5a6e10aa4 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -292,70 +292,3 @@ a.table-action-link { } } } - -.blocks-table { - width: 100%; - max-width: 100%; - border-spacing: 0; - border-collapse: collapse; - table-layout: fixed; - border: 1px solid darken($ui-base-color, 8%); - - thead { - border: 1px solid darken($ui-base-color, 8%); - background: darken($ui-base-color, 4%); - font-weight: 500; - - th.severity-column { - width: 120px; - } - - th.button-column { - width: 23px; - } - } - - tbody > tr { - border: 1px solid darken($ui-base-color, 8%); - border-bottom: 0; - background: darken($ui-base-color, 4%); - - &:hover { - background: darken($ui-base-color, 2%); - } - - &.even { - background: $ui-base-color; - - &:hover { - background: lighten($ui-base-color, 2%); - } - } - - &.rationale { - background: lighten($ui-base-color, 4%); - border-top: 0; - - &:hover { - background: lighten($ui-base-color, 6%); - } - - &.hidden { - display: none; - } - } - - td:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - } - - th, - td { - padding: 8px; - line-height: 18px; - vertical-align: top; - text-align: left; - } -} diff --git a/app/views/about/_domain_blocks.html.haml b/app/views/about/_domain_blocks.html.haml new file mode 100644 index 000000000..940bcb934 --- /dev/null +++ b/app/views/about/_domain_blocks.html.haml @@ -0,0 +1,10 @@ +%table + %thead + %tr + %th= t('about.unavailable_content_description.domain') + %th= t('about.unavailable_content_description.reason') + %tbody + - domain_blocks.each do |domain_block| + %tr + %td.nowrap= domain_block.domain + %td= domain_block.public_comment if display_blocks_rationale? diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index cba2fe657..7e156db61 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -55,19 +55,15 @@ %p= t('about.unavailable_content_html') - - @blocks.each do |domain_block| - %p - %strong= "#{domain_block.domain}:" - - - if domain_block.suspend? - = t('about.unavailable_content_description.suspended') - - else - = t('about.unavailable_content_description.silenced') if domain_block.silence? - = t('about.unavailable_content_description.rejecting_media') if domain_block.reject_media? - - - if display_blocks_rationale? && domain_block.public_comment.present? - %strong= t('about.unavailable_content_description.reason') - = domain_block.public_comment + - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty? + %p= t('about.unavailable_content_description.rejecting_media') + = render partial: 'domain_blocks', locals: { domain_blocks: blocks } + - if (blocks = @blocks.select(&:silence?)) && !blocks.empty? + %p= t('about.unavailable_content_description.silenced') + = render partial: 'domain_blocks', locals: { domain_blocks: blocks } + - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty? + %p= t('about.unavailable_content_description.suspended') + = render partial: 'domain_blocks', locals: { domain_blocks: blocks } .column-4 %ul.table-of-contents diff --git a/config/locales/en.yml b/config/locales/en.yml index 1e7d0701b..dbdfe0ca0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -37,10 +37,11 @@ en: terms: Terms of service unavailable_content: Unavailable content unavailable_content_description: - reason: 'Reason:' - rejecting_media: Media files from this server will not be processed and and no thumbnails will be displayed, requiring manual click-through to the other server. - silenced: Posts from this server will not show up anywhere except your home feed if you follow the author. - suspended: You won't be able to follow anyone from this server, and no data from it will be processed or stored, and no data exchanged. + domain: Server + reason: Reason + rejecting_media: 'Media files from these servers will not be processed or stored, and and no thumbnails will be displayed, requiring manual click-through to the original file:' + silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users'' interactions, unless you are following them:' + suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:' unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server. user_count_after: one: user -- cgit From d51201a75ac47ac90615ee8987534c5772274779 Mon Sep 17 00:00:00 2001 From: trwnh Date: Sun, 29 Sep 2019 09:54:24 -0500 Subject: Fix muted text color not applying to all text (#11996) Pleroma generates polls without p tag --- app/javascript/styles/mastodon/components.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'app/javascript/styles') diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 7562cc709..398522afb 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1529,6 +1529,7 @@ a.account__display-name { } .muted { + .status__content, .status__content p, .status__content a { color: $dark-text-color; -- cgit From 9027bfff0c25a6da1bcef7ce880e5d8211062d1d Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 29 Sep 2019 21:46:05 +0200 Subject: Add explanation to mute dialog, refactor and clean up mute/block UI (#11992) * Add some explanation to the mute modal dialog * Remove `isSubmitting` from mute modal code, this wasn't used * Refactor block modal Signed-off-by: Thibaut Girka * Refactor SCSS a bit * Put mute modal toggle to the same side as in the report dialog for consistency * Reword mute explanation * Fix mute explanation styling * Left-align all text in mute confirmation modal --- app/javascript/mastodon/actions/blocks.js | 14 +++ .../mastodon/containers/status_container.js | 18 +--- .../containers/header_container.js | 15 +-- .../status/containers/detailed_status_container.js | 18 +--- app/javascript/mastodon/features/status/index.js | 20 +--- .../mastodon/features/ui/components/block_modal.js | 103 +++++++++++++++++++++ .../mastodon/features/ui/components/modal_root.js | 2 + .../mastodon/features/ui/components/mute_modal.js | 15 +-- .../mastodon/features/ui/util/async-components.js | 4 + app/javascript/mastodon/reducers/blocks.js | 22 +++++ app/javascript/mastodon/reducers/index.js | 2 + app/javascript/mastodon/reducers/mutes.js | 2 - app/javascript/styles/mastodon-light/diff.scss | 2 + app/javascript/styles/mastodon/components.scss | 73 ++++++++++----- 14 files changed, 222 insertions(+), 88 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/components/block_modal.js create mode 100644 app/javascript/mastodon/reducers/blocks.js (limited to 'app/javascript/styles') diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js index 7000f5a71..fd9881302 100644 --- a/app/javascript/mastodon/actions/blocks.js +++ b/app/javascript/mastodon/actions/blocks.js @@ -1,6 +1,7 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; +import { openModal } from './modal'; export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; @@ -10,6 +11,8 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; +export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL'; + export function fetchBlocks() { return (dispatch, getState) => { dispatch(fetchBlocksRequest()); @@ -83,3 +86,14 @@ export function expandBlocksFail(error) { error, }; }; + +export function initBlockModal(account) { + return dispatch => { + dispatch({ + type: BLOCKS_INIT_MODAL, + account, + }); + + dispatch(openModal('BLOCK')); + }; +} diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 7b0906b39..fb22676e0 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -1,4 +1,3 @@ -import React from 'react'; import { connect } from 'react-redux'; import Status from '../components/status'; import { makeGetStatus } from '../selectors'; @@ -15,7 +14,6 @@ import { pin, unpin, } from '../actions/interactions'; -import { blockAccount } from '../actions/accounts'; import { muteStatus, unmuteStatus, @@ -24,9 +22,10 @@ import { revealStatus, } from '../actions/statuses'; import { initMuteModal } from '../actions/mutes'; +import { initBlockModal } from '../actions/blocks'; import { initReport } from '../actions/reports'; import { openModal } from '../actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { boostModal, deleteModal } from '../initial_state'; import { showAlertForError } from '../actions/alerts'; @@ -35,10 +34,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); const makeMapStateToProps = () => { @@ -138,16 +135,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onBlock (status) { const account = status.get('account'); - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')}
}} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - secondary: intl.formatMessage(messages.blockAndReport), - onSecondary: () => { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account, status)); - }, - })); + dispatch(initBlockModal(account)); }, onReport (status) { diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index 4d4ae6e82..8728b4806 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -5,7 +5,6 @@ import Header from '../components/header'; import { followAccount, unfollowAccount, - blockAccount, unblockAccount, unmuteAccount, pinAccount, @@ -16,6 +15,7 @@ import { directCompose, } from '../../../actions/compose'; import { initMuteModal } from '../../../actions/mutes'; +import { initBlockModal } from '../../../actions/blocks'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; @@ -25,9 +25,7 @@ import { List as ImmutableList } from 'immutable'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, - blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); const makeMapStateToProps = () => { @@ -64,16 +62,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (account.getIn(['relationship', 'blocking'])) { dispatch(unblockAccount(account.get('id'))); } else { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - secondary: intl.formatMessage(messages.blockAndReport), - onSecondary: () => { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account)); - }, - })); + dispatch(initBlockModal(account)); } }, diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js index 61e0c428a..333c295dc 100644 --- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js +++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js @@ -1,4 +1,3 @@ -import React from 'react'; import { connect } from 'react-redux'; import DetailedStatus from '../components/detailed_status'; import { makeGetStatus } from '../../../selectors'; @@ -15,7 +14,6 @@ import { pin, unpin, } from '../../../actions/interactions'; -import { blockAccount } from '../../../actions/accounts'; import { muteStatus, unmuteStatus, @@ -24,9 +22,10 @@ import { revealStatus, } from '../../../actions/statuses'; import { initMuteModal } from '../../../actions/mutes'; +import { initBlockModal } from '../../../actions/blocks'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { boostModal, deleteModal } from '../../../initial_state'; import { showAlertForError } from '../../../actions/alerts'; @@ -35,10 +34,8 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); const makeMapStateToProps = () => { @@ -138,16 +135,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onBlock (status) { const account = status.get('account'); - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - secondary: intl.formatMessage(messages.blockAndReport), - onSecondary: () => { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account, status)); - }, - })); + dispatch(initBlockModal(account)); }, onReport (status) { diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index f78a9489a..029057d40 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -23,7 +23,6 @@ import { mentionCompose, directCompose, } from '../../actions/compose'; -import { blockAccount } from '../../actions/accounts'; import { muteStatus, unmuteStatus, @@ -32,6 +31,7 @@ import { revealStatus, } from '../../actions/statuses'; import { initMuteModal } from '../../actions/mutes'; +import { initBlockModal } from '../../actions/blocks'; import { initReport } from '../../actions/reports'; import { makeGetStatus } from '../../selectors'; import { ScrollContainer } from 'react-router-scroll-4'; @@ -39,7 +39,7 @@ import ColumnBackButton from '../../components/column_back_button'; import ColumnHeader from '../../components/column_header'; import StatusContainer from '../../containers/status_container'; import { openModal } from '../../actions/modal'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; import { boostModal, deleteModal } from '../../initial_state'; @@ -52,13 +52,11 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, - blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' }, }); const makeMapStateToProps = () => { @@ -296,19 +294,9 @@ class Status extends ImmutablePureComponent { } handleBlockClick = (status) => { - const { dispatch, intl } = this.props; + const { dispatch } = this.props; const account = status.get('account'); - - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - secondary: intl.formatMessage(messages.blockAndReport), - onSecondary: () => { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account, status)); - }, - })); + dispatch(initBlockModal(account)); } handleReport = (status) => { diff --git a/app/javascript/mastodon/features/ui/components/block_modal.js b/app/javascript/mastodon/features/ui/components/block_modal.js new file mode 100644 index 000000000..a07baeaa6 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/block_modal.js @@ -0,0 +1,103 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import { makeGetAccount } from '../../../selectors'; +import Button from '../../../components/button'; +import { closeModal } from '../../../actions/modal'; +import { blockAccount } from '../../../actions/accounts'; +import { initReport } from '../../../actions/reports'; + + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = state => ({ + account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => { + return { + onConfirm(account) { + dispatch(blockAccount(account.get('id'))); + }, + + onBlockAndReport(account) { + dispatch(blockAccount(account.get('id'))); + dispatch(initReport(account)); + }, + + onClose() { + dispatch(closeModal()); + }, + }; +}; + +export default @connect(makeMapStateToProps, mapDispatchToProps) +@injectIntl +class BlockModal extends React.PureComponent { + + static propTypes = { + account: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired, + onBlockAndReport: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.button.focus(); + } + + handleClick = () => { + this.props.onClose(); + this.props.onConfirm(this.props.account); + } + + handleSecondary = () => { + this.props.onClose(); + this.props.onBlockAndReport(this.props.account); + } + + handleCancel = () => { + this.props.onClose(); + } + + setRef = (c) => { + this.button = c; + } + + render () { + const { account } = this.props; + + return ( +
+
+

+ @{account.get('acct')} }} + /> +

+
+ +
+ + + +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 5fc37368b..58d3ba8db 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -13,6 +13,7 @@ import ConfirmationModal from './confirmation_modal'; import FocalPointModal from './focal_point_modal'; import { MuteModal, + BlockModal, ReportModal, EmbedModal, ListEditor, @@ -25,6 +26,7 @@ const MODAL_COMPONENTS = { 'BOOST': () => Promise.resolve({ default: BoostModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'MUTE': MuteModal, + 'BLOCK': BlockModal, 'REPORT': ReportModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'EMBED': EmbedModal, diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js index ac356b42a..c364c5ba2 100644 --- a/app/javascript/mastodon/features/ui/components/mute_modal.js +++ b/app/javascript/mastodon/features/ui/components/mute_modal.js @@ -11,7 +11,6 @@ import { toggleHideNotifications } from '../../../actions/mutes'; const mapStateToProps = state => { return { - isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), account: state.getIn(['mutes', 'new', 'account']), notifications: state.getIn(['mutes', 'new', 'notifications']), }; @@ -38,7 +37,6 @@ export default @connect(mapStateToProps, mapDispatchToProps) class MuteModal extends React.PureComponent { static propTypes = { - isSubmitting: PropTypes.bool.isRequired, account: PropTypes.object.isRequired, notifications: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, @@ -81,11 +79,16 @@ class MuteModal extends React.PureComponent { values={{ name: @{account.get('acct')} }} />

-
-
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 0084c1510..bb0fcb859 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -106,6 +106,10 @@ export function MuteModal () { return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal'); } +export function BlockModal () { + return import(/* webpackChunkName: "modals/block_modal" */'../components/block_modal'); +} + export function ReportModal () { return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); } diff --git a/app/javascript/mastodon/reducers/blocks.js b/app/javascript/mastodon/reducers/blocks.js new file mode 100644 index 000000000..1b6507163 --- /dev/null +++ b/app/javascript/mastodon/reducers/blocks.js @@ -0,0 +1,22 @@ +import Immutable from 'immutable'; + +import { + BLOCKS_INIT_MODAL, +} from '../actions/blocks'; + +const initialState = Immutable.Map({ + new: Immutable.Map({ + account_id: null, + }), +}); + +export default function mutes(state = initialState, action) { + switch (action.type) { + case BLOCKS_INIT_MODAL: + return state.withMutations((state) => { + state.setIn(['new', 'account_id'], action.account.get('id')); + }); + default: + return state; + } +} diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 0f4b209d4..b8d608888 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -15,6 +15,7 @@ import settings from './settings'; import push_notifications from './push_notifications'; import status_lists from './status_lists'; import mutes from './mutes'; +import blocks from './blocks'; import reports from './reports'; import contexts from './contexts'; import compose from './compose'; @@ -51,6 +52,7 @@ const reducers = { settings, push_notifications, mutes, + blocks, reports, contexts, compose, diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js index a96232dbd..4672e5097 100644 --- a/app/javascript/mastodon/reducers/mutes.js +++ b/app/javascript/mastodon/reducers/mutes.js @@ -7,7 +7,6 @@ import { const initialState = Immutable.Map({ new: Immutable.Map({ - isSubmitting: false, account: null, notifications: true, }), @@ -17,7 +16,6 @@ export default function mutes(state = initialState, action) { switch (action.type) { case MUTES_INIT_MODAL: return state.withMutations((state) => { - state.setIn(['new', 'isSubmitting'], false); state.setIn(['new', 'account'], action.account); state.setIn(['new', 'notifications'], true); }); diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index e7114ed07..45305d696 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -310,6 +310,7 @@ html { .boost-modal, .confirmation-modal, .mute-modal, +.block-modal, .report-modal, .embed-modal, .error-modal, @@ -326,6 +327,7 @@ html { .boost-modal__action-bar, .confirmation-modal__action-bar, .mute-modal__action-bar, +.block-modal__action-bar, .onboarding-modal__paginator, .error-modal__footer { background: darken($ui-base-color, 6%); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 398522afb..64d40f824 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4533,7 +4533,8 @@ a.status-card.compact:hover { .confirmation-modal, .report-modal, .actions-modal, -.mute-modal { +.mute-modal, +.block-modal { background: lighten($ui-secondary-color, 8%); color: $inverted-text-color; border-radius: 8px; @@ -4587,7 +4588,8 @@ a.status-card.compact:hover { .boost-modal__action-bar, .confirmation-modal__action-bar, -.mute-modal__action-bar { +.mute-modal__action-bar, +.block-modal__action-bar { display: flex; justify-content: space-between; background: $ui-secondary-color; @@ -4615,11 +4617,13 @@ a.status-card.compact:hover { font-size: 14px; } -.mute-modal { +.mute-modal, +.block-modal { line-height: 24px; } -.mute-modal .react-toggle { +.mute-modal .react-toggle, +.block-modal .react-toggle { vertical-align: middle; } @@ -4830,33 +4834,35 @@ a.status-card.compact:hover { } .confirmation-modal__action-bar, -.mute-modal__action-bar { - .confirmation-modal__secondary-button, - .confirmation-modal__cancel-button, - .mute-modal__cancel-button { - background-color: transparent; - color: $lighter-text-color; - font-size: 14px; - font-weight: 500; - - &:hover, - &:focus, - &:active { - color: darken($lighter-text-color, 4%); - } - } - +.mute-modal__action-bar, +.block-modal__action-bar { .confirmation-modal__secondary-button { flex-shrink: 1; } } +.confirmation-modal__secondary-button, +.confirmation-modal__cancel-button, +.mute-modal__cancel-button, +.block-modal__cancel-button { + background-color: transparent; + color: $lighter-text-color; + font-size: 14px; + font-weight: 500; + + &:hover, + &:focus, + &:active { + color: darken($lighter-text-color, 4%); + } +} + .confirmation-modal__container, .mute-modal__container, +.block-modal__container, .report-modal__target { padding: 30px; font-size: 16px; - text-align: center; strong { font-weight: 500; @@ -4869,6 +4875,31 @@ a.status-card.compact:hover { } } +.confirmation-modal__container, +.report-modal__target { + text-align: center; +} + +.block-modal, +.mute-modal { + &__explanation { + margin-top: 20px; + } + + .setting-toggle { + margin-top: 20px; + margin-bottom: 24px; + display: flex; + align-items: center; + + &__label { + color: $inverted-text-color; + margin: 0; + margin-left: 8px; + } + } +} + .report-modal__target { padding: 15px; -- cgit