Merge pull request #619 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
		
						commit
						e77e055739
					
				
					 203 changed files with 4596 additions and 2975 deletions
				
			
		|  | @ -31,7 +31,7 @@ class AboutController < ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'about-body' | ||||
|     @body_classes = 'with-modals' | ||||
|   end | ||||
| 
 | ||||
|   def initial_state_params | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ class AccountsController < ApplicationController | |||
|     respond_to do |format| | ||||
|       format.html do | ||||
|         use_pack 'public' | ||||
|         @body_classes    = 'with-modals' | ||||
|         @pinned_statuses = [] | ||||
| 
 | ||||
|         if current_account && @account.blocking?(current_account) | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| class Auth::ConfirmationsController < Devise::ConfirmationsController | ||||
|   layout 'auth' | ||||
| 
 | ||||
|   before_action :set_body_classes | ||||
|   before_action :set_user, only: [:finish_signup] | ||||
|   before_action :set_pack | ||||
| 
 | ||||
|  | @ -28,6 +29,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController | |||
|     @user = current_user | ||||
|   end | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'lighter' | ||||
|   end | ||||
| 
 | ||||
|   def user_params | ||||
|     params.require(:user).permit(:email) | ||||
|   end | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| class Auth::PasswordsController < Devise::PasswordsController | ||||
|   before_action :check_validity_of_reset_password_token, only: :edit | ||||
|   before_action :set_pack | ||||
|   before_action :set_body_classes | ||||
| 
 | ||||
|   layout 'auth' | ||||
| 
 | ||||
|  | @ -15,6 +16,10 @@ class Auth::PasswordsController < Devise::PasswordsController | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'lighter' | ||||
|   end | ||||
| 
 | ||||
|   def reset_password_token_is_valid? | ||||
|     resource_class.with_reset_password_token(params[:reset_password_token]).present? | ||||
|   end | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController | |||
|   before_action :set_pack | ||||
|   before_action :set_sessions, only: [:edit, :update] | ||||
|   before_action :set_instance_presenter, only: [:new, :create, :update] | ||||
|   before_action :set_body_classes, only: [:new, :create] | ||||
| 
 | ||||
|   def destroy | ||||
|     not_found | ||||
|  | @ -84,6 +85,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController | |||
|     @instance_presenter = InstancePresenter.new | ||||
|   end | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'lighter' | ||||
|   end | ||||
| 
 | ||||
|   def set_invite | ||||
|     @invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil | ||||
|   end | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ class Auth::SessionsController < Devise::SessionsController | |||
|   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] | ||||
|   prepend_before_action :set_pack | ||||
|   before_action :set_instance_presenter, only: [:new] | ||||
|   before_action :set_body_classes | ||||
| 
 | ||||
|   def new | ||||
|     Devise.omniauth_configs.each do |provider, config| | ||||
|  | @ -114,6 +115,10 @@ class Auth::SessionsController < Devise::SessionsController | |||
|     @instance_presenter = InstancePresenter.new | ||||
|   end | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'lighter' | ||||
|   end | ||||
| 
 | ||||
|   def home_paths(resource) | ||||
|     paths = [about_path] | ||||
|     if single_user_mode? && resource.is_a?(User) | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ module AccountControllerConcern | |||
|   included do | ||||
|     layout 'public' | ||||
|     before_action :set_account | ||||
|     before_action :set_instance_presenter | ||||
|     before_action :set_link_headers | ||||
|     before_action :check_account_suspension | ||||
|   end | ||||
|  | @ -18,6 +19,10 @@ module AccountControllerConcern | |||
|     @account = Account.find_local!(params[:account_username]) | ||||
|   end | ||||
| 
 | ||||
|   def set_instance_presenter | ||||
|     @instance_presenter = InstancePresenter.new | ||||
|   end | ||||
| 
 | ||||
|   def set_link_headers | ||||
|     response.headers['Link'] = LinkHeader.new( | ||||
|       [ | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ class InvitesController < ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def invites | ||||
|     Invite.where(user: current_user) | ||||
|     Invite.where(user: current_user).order(id: :desc) | ||||
|   end | ||||
| 
 | ||||
|   def resource_params | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ class StatusesController < ApplicationController | |||
| 
 | ||||
|   before_action :set_account | ||||
|   before_action :set_status | ||||
|   before_action :set_instance_presenter | ||||
|   before_action :set_link_headers | ||||
|   before_action :check_account_suspension | ||||
|   before_action :redirect_to_original, only: [:show] | ||||
|  | @ -22,6 +23,8 @@ class StatusesController < ApplicationController | |||
|     respond_to do |format| | ||||
|       format.html do | ||||
|         use_pack 'public' | ||||
|         @body_classes = 'with-modals' | ||||
| 
 | ||||
|         set_ancestors | ||||
|         set_descendants | ||||
| 
 | ||||
|  | @ -150,6 +153,10 @@ class StatusesController < ApplicationController | |||
|     raise ActiveRecord::RecordNotFound | ||||
|   end | ||||
| 
 | ||||
|   def set_instance_presenter | ||||
|     @instance_presenter = InstancePresenter.new | ||||
|   end | ||||
| 
 | ||||
|   def check_account_suspension | ||||
|     gone if @account.suspended? | ||||
|   end | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ class TagsController < ApplicationController | |||
|   private | ||||
| 
 | ||||
|   def set_body_classes | ||||
|     @body_classes = 'tag-body' | ||||
|     @body_classes = 'with-modals' | ||||
|   end | ||||
| 
 | ||||
|   def set_instance_presenter | ||||
|  |  | |||
|  | @ -12,6 +12,52 @@ module StreamEntriesHelper | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def account_action_button(account) | ||||
|     if user_signed_in? | ||||
|       if account.id == current_user.account_id | ||||
|         link_to settings_profile_url, class: 'button logo-button' do | ||||
|           safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')]) | ||||
|         end | ||||
|       elsif current_account.following?(account) || current_account.requested?(account) | ||||
|         link_to account_unfollow_path(account), class: 'button logo-button', data: { method: :post } do | ||||
|           safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')]) | ||||
|         end | ||||
|       else | ||||
|         link_to account_follow_path(account), class: 'button logo-button', data: { method: :post } do | ||||
|           safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')]) | ||||
|         end | ||||
|       end | ||||
|     else | ||||
|       link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do | ||||
|         safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')]) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def account_badge(account) | ||||
|     if account.bot? | ||||
|       content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') | ||||
|     elsif Setting.show_staff_badge && account.user_staff? | ||||
|       content_tag(:div, class: 'roles') do | ||||
|         if account.user_admin? | ||||
|           content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') | ||||
|         elsif account.user_moderator? | ||||
|           content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def link_to_more(url) | ||||
|     link_to t('statuses.show_more'), url, class: 'load-more load-gap' | ||||
|   end | ||||
| 
 | ||||
|   def nothing_here(extra_classes = '') | ||||
|     content_tag(:div, class: "nothing-here #{extra_classes}") do | ||||
|       t('accounts.nothing_here') | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def account_description(account) | ||||
|     prepend_str = [ | ||||
|       [ | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| //  This file will be loaded on public pages, regardless of theme.
 | ||||
| 
 | ||||
| const { delegate } = require('rails-ujs'); | ||||
| const { length } = require('stringz'); | ||||
| 
 | ||||
| delegate(document, '.webapp-btn', 'click', ({ target, button }) => { | ||||
|   if (button !== 0) { | ||||
|  |  | |||
|  | @ -3,24 +3,29 @@ | |||
| const { length } = require('stringz'); | ||||
| const { delegate } = require('rails-ujs'); | ||||
| 
 | ||||
| delegate(document, '.account_display_name', 'input', ({ target }) => { | ||||
| delegate(document, '#account_display_name', 'input', ({ target }) => { | ||||
|   const nameCounter = document.querySelector('.name-counter'); | ||||
|   const name        = document.querySelector('.card .display-name strong'); | ||||
| 
 | ||||
|   if (nameCounter) { | ||||
|     nameCounter.textContent = 30 - length(target.value); | ||||
|   } | ||||
| 
 | ||||
|   if (name) { | ||||
|     name.innerHTML = emojify(target.value); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| delegate(document, '.account_note', 'input', ({ target }) => { | ||||
| delegate(document, '#account_note', 'input', ({ target }) => { | ||||
|   const noteCounter = document.querySelector('.note-counter'); | ||||
| 
 | ||||
|   if (noteCounter) { | ||||
|     noteCounter.textContent = 500 - length(target.value); | ||||
|     noteCounter.textContent = 160 - length(target.value); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| delegate(document, '#account_avatar', 'change', ({ target }) => { | ||||
|   const avatar = document.querySelector('.card.compact .avatar img'); | ||||
|   const avatar = document.querySelector('.card .avatar img'); | ||||
|   const [file] = target.files || []; | ||||
|   const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; | ||||
| 
 | ||||
|  | @ -28,9 +33,19 @@ delegate(document, '#account_avatar', 'change', ({ target }) => { | |||
| }); | ||||
| 
 | ||||
| delegate(document, '#account_header', 'change', ({ target }) => { | ||||
|   const header = document.querySelector('.card.compact'); | ||||
|   const header = document.querySelector('.card .card__img img'); | ||||
|   const [file] = target.files || []; | ||||
|   const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; | ||||
| 
 | ||||
|   header.style.backgroundImage = `url(${url})`; | ||||
|   header.src = url; | ||||
| }); | ||||
| 
 | ||||
| delegate(document, '#account_locked', 'change', ({ target }) => { | ||||
|   const lock = document.querySelector('.card .display-name i'); | ||||
| 
 | ||||
|   if (target.checked) { | ||||
|     lock.style.display = 'inline'; | ||||
|   } else { | ||||
|     lock.style.display = 'none'; | ||||
|   } | ||||
| }); | ||||
|  |  | |||
|  | @ -60,6 +60,32 @@ const getUnitDelay = units => { | |||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const timeAgoString = (intl, date, now, year) => { | ||||
|   const delta = now - date.getTime(); | ||||
| 
 | ||||
|   let relativeTime; | ||||
| 
 | ||||
|   if (delta < 10 * SECOND) { | ||||
|     relativeTime = intl.formatMessage(messages.just_now); | ||||
|   } else if (delta < 7 * DAY) { | ||||
|     if (delta < MINUTE) { | ||||
|       relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); | ||||
|     } else if (delta < HOUR) { | ||||
|       relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); | ||||
|     } else if (delta < DAY) { | ||||
|       relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); | ||||
|     } else { | ||||
|       relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); | ||||
|     } | ||||
|   } else if (date.getFullYear() === year) { | ||||
|     relativeTime = intl.formatDate(date, shortDateFormatOptions); | ||||
|   } else { | ||||
|     relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); | ||||
|   } | ||||
| 
 | ||||
|   return relativeTime; | ||||
| }; | ||||
| 
 | ||||
| @injectIntl | ||||
| export default class RelativeTimestamp extends React.Component { | ||||
| 
 | ||||
|  | @ -121,28 +147,8 @@ export default class RelativeTimestamp extends React.Component { | |||
|   render () { | ||||
|     const { timestamp, intl, year } = this.props; | ||||
| 
 | ||||
|     const date  = new Date(timestamp); | ||||
|     const delta = this.state.now - date.getTime(); | ||||
| 
 | ||||
|     let relativeTime; | ||||
| 
 | ||||
|     if (delta < 10 * SECOND) { | ||||
|       relativeTime = intl.formatMessage(messages.just_now); | ||||
|     } else if (delta < 7 * DAY) { | ||||
|       if (delta < MINUTE) { | ||||
|         relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); | ||||
|       } else if (delta < HOUR) { | ||||
|         relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); | ||||
|       } else if (delta < DAY) { | ||||
|         relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); | ||||
|       } else { | ||||
|         relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); | ||||
|       } | ||||
|     } else if (date.getFullYear() === year) { | ||||
|       relativeTime = intl.formatDate(date, shortDateFormatOptions); | ||||
|     } else { | ||||
|       relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); | ||||
|     } | ||||
|     const date         = new Date(timestamp); | ||||
|     const relativeTime = timeAgoString(intl, date, this.state.now, year); | ||||
| 
 | ||||
|     return ( | ||||
|       <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}> | ||||
|  |  | |||
|  | @ -423,7 +423,7 @@ export default class Status extends ImmutablePureComponent { | |||
|         mediaIcon = 'video-camera'; | ||||
|       } else {  //  Media type is 'image' or 'gifv'
 | ||||
|         media = ( | ||||
|           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} > | ||||
|           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> | ||||
|             {Component => ( | ||||
|               <Component | ||||
|                 media={attachments} | ||||
|  |  | |||
|  | @ -29,19 +29,19 @@ export default class MediaContainer extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   handleOpenMedia = (media, index) => { | ||||
|     document.body.classList.add('media-standalone__body'); | ||||
|     document.body.classList.add('with-modals--active'); | ||||
|     this.setState({ media, index }); | ||||
|   } | ||||
| 
 | ||||
|   handleOpenVideo = (video, time) => { | ||||
|     const media = ImmutableList([video]); | ||||
| 
 | ||||
|     document.body.classList.add('media-standalone__body'); | ||||
|     document.body.classList.add('with-modals--active'); | ||||
|     this.setState({ media, time }); | ||||
|   } | ||||
| 
 | ||||
|   handleCloseMedia = () => { | ||||
|     document.body.classList.remove('media-standalone__body'); | ||||
|     document.body.classList.remove('with-modals--active'); | ||||
|     this.setState({ media: null, index: null, time: null }); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React from 'react'; | ||||
| import React, { Fragment } from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import { Provider } from 'react-redux'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import configureStore from 'flavours/glitch/store/configureStore'; | ||||
|  | @ -8,6 +9,7 @@ import { getLocale } from 'mastodon/locales'; | |||
| import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline'; | ||||
| import CommunityTimeline from 'flavours/glitch/features/standalone/community_timeline'; | ||||
| import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline'; | ||||
| import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container'; | ||||
| import initialState from 'flavours/glitch/util/initial_state'; | ||||
| 
 | ||||
| const { localeData, messages } = getLocale(); | ||||
|  | @ -47,7 +49,13 @@ export default class TimelineContainer extends React.PureComponent { | |||
|     return ( | ||||
|       <IntlProvider locale={locale} messages={messages}> | ||||
|         <Provider store={store}> | ||||
|           {timeline} | ||||
|           <Fragment> | ||||
|             {timeline} | ||||
|             {ReactDOM.createPortal( | ||||
|               <ModalContainer />, | ||||
|               document.getElementById('modal-container'), | ||||
|             )} | ||||
|           </Fragment> | ||||
|         </Provider> | ||||
|       </IntlProvider> | ||||
|     ); | ||||
|  |  | |||
|  | @ -44,6 +44,18 @@ export default class ModalRoot extends React.PureComponent { | |||
|     onClose: PropTypes.func.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   getSnapshotBeforeUpdate () { | ||||
|     return { visible: !!this.props.type }; | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate (prevProps, prevState, { visible }) { | ||||
|     if (visible) { | ||||
|       document.body.classList.add('with-modals--active'); | ||||
|     } else { | ||||
|       document.body.classList.remove('with-modals--active'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   renderLoading = modalId => () => { | ||||
|     return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null; | ||||
|   } | ||||
|  |  | |||
|  | @ -574,6 +574,7 @@ $small-breakpoint: 960px; | |||
|       .avatar { | ||||
|         width: 80px; | ||||
|         height: 80px; | ||||
|         @include avatar-size(80px); | ||||
|         margin: 0 auto; | ||||
|         margin-bottom: 15px; | ||||
| 
 | ||||
|  | @ -582,6 +583,7 @@ $small-breakpoint: 960px; | |||
|           width: 80px; | ||||
|           height: 80px; | ||||
|           border-radius: 48px; | ||||
|           @include avatar-radius(); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|  | @ -716,6 +718,7 @@ $small-breakpoint: 960px; | |||
|       &__avatar { | ||||
|         width: 44px; | ||||
|         height: 44px; | ||||
|         @include avatar-size(48px); | ||||
|         background-size: 44px 44px; | ||||
|       } | ||||
| 
 | ||||
|  | @ -1094,6 +1097,21 @@ $small-breakpoint: 960px; | |||
|   } | ||||
| 
 | ||||
|   &.tag-page { | ||||
|     @media screen and (max-width: $column-breakpoint) { | ||||
|       padding: 0; | ||||
| 
 | ||||
|       .container { | ||||
|         padding: 0; | ||||
|       } | ||||
| 
 | ||||
|       #mastodon-timeline { | ||||
|         display: block; | ||||
|         width: 100vw; | ||||
|         height: 100vh; | ||||
|         border-radius: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .grid { | ||||
|       @media screen and (min-width: $small-breakpoint) { | ||||
|         grid-template-columns: 33% 67%; | ||||
|  | @ -1125,23 +1143,16 @@ $small-breakpoint: 960px; | |||
| 
 | ||||
|     @media screen and (max-width: $column-breakpoint) { | ||||
|       .grid { | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|           grid-row: 2; | ||||
|         } | ||||
|         grid-gap: 0; | ||||
| 
 | ||||
|         .column-2 { | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|           grid-row: 1; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .brand { | ||||
|         margin: 0; | ||||
|       } | ||||
| 
 | ||||
|       .landing-page__features { | ||||
|         display: none; | ||||
|         .column-2 { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -1,243 +1,102 @@ | |||
| .card { | ||||
|   background-color: lighten($ui-base-color, 4%); | ||||
|   background-size: cover; | ||||
|   background-position: center; | ||||
|   border-radius: 4px 4px 0 0; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
|   display: flex; | ||||
| 
 | ||||
|   &::after { | ||||
|     background: rgba(darken($ui-base-color, 8%), 0.5); | ||||
|   & > a { | ||||
|     display: block; | ||||
|     content: ""; | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     z-index: 1; | ||||
|   } | ||||
|     text-decoration: none; | ||||
|     color: inherit; | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       box-shadow: none; | ||||
|     } | ||||
| 
 | ||||
|   .card__illustration { | ||||
|     padding: 60px 0; | ||||
|     position: relative; | ||||
|     flex: 1 1 auto; | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   } | ||||
| 
 | ||||
|   .card__bio { | ||||
|     max-width: 260px; | ||||
|     flex: 1 1 auto; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: space-between; | ||||
|     background: rgba(darken($ui-base-color, 8%), 0.8); | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|   } | ||||
| 
 | ||||
|   &.compact { | ||||
|     padding: 30px 0; | ||||
|     border-radius: 4px; | ||||
| 
 | ||||
|     .avatar { | ||||
|       margin-bottom: 0; | ||||
| 
 | ||||
|       img { | ||||
|         object-fit: cover; | ||||
|     &:hover, | ||||
|     &:active, | ||||
|     &:focus { | ||||
|       .card__bar { | ||||
|         background: lighten($ui-base-color, 8%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .name { | ||||
|     display: block; | ||||
|     font-size: 20px; | ||||
|     line-height: 18px * 1.5; | ||||
|     color: $primary-text-color; | ||||
|     padding: 10px 15px; | ||||
|     padding-bottom: 0; | ||||
|     font-weight: 500; | ||||
|   &__img { | ||||
|     height: 130px; | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|     margin-bottom: 15px; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| 
 | ||||
|     small { | ||||
|       display: block; | ||||
|       font-size: 14px; | ||||
|       color: $highlight-text-color; | ||||
|       font-weight: 400; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .avatar { | ||||
|     width: 120px; | ||||
|     margin: 0 auto; | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|     @include avatar-size(120px); | ||||
|     background: darken($ui-base-color, 12%); | ||||
|     border-radius: 4px 4px 0 0; | ||||
| 
 | ||||
|     img { | ||||
|       width: 120px; | ||||
|       height: 120px; | ||||
|       display: block; | ||||
|       border-radius: 120px; | ||||
|       box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|       @include avatar-radius(); | ||||
|       @include avatar-size(120px); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .roles { | ||||
|     margin-bottom: 15px; | ||||
|     padding: 0 15px; | ||||
|   } | ||||
| 
 | ||||
|   .details-counters { | ||||
|     margin-top: 30px; | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     width: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .counter { | ||||
|     width: 33.3%; | ||||
|     box-sizing: border-box; | ||||
|     flex: 0 0 auto; | ||||
|     color: $darker-text-color; | ||||
|     padding: 5px 10px 0; | ||||
|     margin-bottom: 10px; | ||||
|     border-right: 1px solid lighten($ui-base-color, 4%); | ||||
|     cursor: default; | ||||
|     text-align: center; | ||||
|     position: relative; | ||||
| 
 | ||||
|     a { | ||||
|       display: block; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       border-right: 0; | ||||
|     } | ||||
| 
 | ||||
|     &::after { | ||||
|       display: block; | ||||
|       content: ""; | ||||
|       position: absolute; | ||||
|       bottom: -10px; | ||||
|       left: 0; | ||||
|       width: 100%; | ||||
|       border-bottom: 4px solid $ui-primary-color; | ||||
|       opacity: 0.5; | ||||
|       transition: all 400ms ease; | ||||
|       height: 100%; | ||||
|       margin: 0; | ||||
|       object-fit: cover; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
| 
 | ||||
|     &.active { | ||||
|       &::after { | ||||
|         border-bottom: 4px solid $highlight-text-color; | ||||
|         opacity: 1; | ||||
|     @media screen and (max-width: 600px) { | ||||
|       height: 200px; | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__bar { | ||||
|     position: relative; | ||||
|     padding: 15px; | ||||
|     display: flex; | ||||
|     justify-content: flex-start; | ||||
|     align-items: center; | ||||
|     background: lighten($ui-base-color, 4%); | ||||
|     border-radius: 0 0 4px 4px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       border-radius: 0; | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       flex: 0 0 auto; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       @include avatar-size(48px); | ||||
|       padding-top: 2px; | ||||
| 
 | ||||
|       img { | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         display: block; | ||||
|         margin: 0; | ||||
|         border-radius: 4px; | ||||
|         @include avatar-radius(); | ||||
|         background: darken($ui-base-color, 8%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       &::after { | ||||
|         opacity: 1; | ||||
|         transition-duration: 100ms; | ||||
|     .display-name { | ||||
|       margin-left: 15px; | ||||
|       text-align: left; | ||||
| 
 | ||||
|       strong { | ||||
|         font-size: 15px; | ||||
|         color: $primary-text-color; | ||||
|         font-weight: 500; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     a { | ||||
|       text-decoration: none; | ||||
|       color: inherit; | ||||
|     } | ||||
| 
 | ||||
|     .counter-label { | ||||
|       font-size: 12px; | ||||
|       display: block; | ||||
|       margin-bottom: 5px; | ||||
|     } | ||||
| 
 | ||||
|     .counter-number { | ||||
|       font-weight: 500; | ||||
|       font-size: 18px; | ||||
|       color: $primary-text-color; | ||||
|       font-family: 'mastodon-font-display', sans-serif; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .bio { | ||||
|     font-size: 14px; | ||||
|     line-height: 18px; | ||||
|     padding: 0 15px; | ||||
|     text-align: center; | ||||
|     color: $secondary-text-color; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 480px) { | ||||
|     display: block; | ||||
| 
 | ||||
|     .card__bio { | ||||
|       max-width: none; | ||||
|     } | ||||
| 
 | ||||
|     .name, | ||||
|     .roles { | ||||
|       text-align: center; | ||||
|       margin-bottom: 5px; | ||||
|     } | ||||
| 
 | ||||
|     .bio { | ||||
|       margin-bottom: 15px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .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 { | ||||
|       span { | ||||
|         display: block; | ||||
|         font-size: 14px; | ||||
|         color: $darker-text-color; | ||||
|         font-weight: 400; | ||||
|         margin-right: 5px; | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:active, | ||||
|       &:focus { | ||||
|         color: $white; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .account-grid-card .controls { | ||||
|   left: auto; | ||||
|   right: 15px; | ||||
| } | ||||
| 
 | ||||
| .pagination { | ||||
|   padding: 30px 0; | ||||
|   text-align: center; | ||||
|  | @ -314,260 +173,23 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .accounts-grid { | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   background: darken($simple-background-color, 8%); | ||||
|   border-radius: 0 0 4px 4px; | ||||
|   padding: 20px 5px; | ||||
|   padding-bottom: 10px; | ||||
|   overflow: hidden; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   z-index: 2; | ||||
|   position: relative; | ||||
| 
 | ||||
|   &.empty img { | ||||
|     position: absolute; | ||||
|     opacity: 0.2; | ||||
|     height: 200px; | ||||
|     left: 0; | ||||
|     bottom: 0; | ||||
|     pointer-events: none; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
| 
 | ||||
|   .account-grid-card { | ||||
|     box-sizing: border-box; | ||||
|     width: 335px; | ||||
|     background: $simple-background-color; | ||||
|     border-radius: 4px; | ||||
|     color: $inverted-text-color; | ||||
|     margin: 0 5px 10px; | ||||
|     position: relative; | ||||
| 
 | ||||
|     @media screen and (max-width: 740px) { | ||||
|       width: calc(100% - 10px); | ||||
|     } | ||||
| 
 | ||||
|     .account-grid-card__header { | ||||
|       overflow: hidden; | ||||
|       height: 100px; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|       background-color: lighten($inverted-text-color, 4%); | ||||
|       background-size: cover; | ||||
|       background-position: center; | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::after { | ||||
|         background: rgba(darken($ui-base-color, 8%), 0.5); | ||||
|         display: block; | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         left: 0; | ||||
|         top: 0; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         z-index: 1; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .account-grid-card__avatar { | ||||
|       box-sizing: border-box; | ||||
|       padding: 15px; | ||||
|       position: absolute; | ||||
|       z-index: 2; | ||||
|       top: 100px - (40px + 2px); | ||||
|       left: -2px; | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       width: 80px; | ||||
|       height: 80px; | ||||
|       @include avatar-size(80px); | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         width: 80px; | ||||
|         height: 80px; | ||||
|         border-radius: 80px; | ||||
|         border: 2px solid $simple-background-color; | ||||
|         background: $simple-background-color; | ||||
|         @include avatar-radius(); | ||||
|         @include avatar-size(80px); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .name { | ||||
|       padding: 15px; | ||||
|       padding-top: 10px; | ||||
|       padding-left: 15px + 80px + 15px; | ||||
| 
 | ||||
|       a { | ||||
|         display: block; | ||||
|         color: $inverted-text-color; | ||||
|         text-decoration: none; | ||||
|         text-overflow: ellipsis; | ||||
|         overflow: hidden; | ||||
|         font-weight: 500; | ||||
| 
 | ||||
|         &:hover { | ||||
|           .display_name { | ||||
|             text-decoration: underline; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .display_name { | ||||
|       font-size: 16px; | ||||
|       display: block; | ||||
|       text-overflow: ellipsis; | ||||
|       overflow: hidden; | ||||
|     } | ||||
| 
 | ||||
|     .username { | ||||
|       color: $lighter-text-color; | ||||
|       font-size: 14px; | ||||
|       font-weight: 400; | ||||
|     } | ||||
| 
 | ||||
|     .account__header__content { | ||||
|       padding: 10px 15px; | ||||
|       padding-top: 15px; | ||||
|       color: $lighter-text-color; | ||||
|       word-wrap: break-word; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       height: 5.5em; | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::after { | ||||
|         display: block; | ||||
|         content: ""; | ||||
|         width: 100%; | ||||
|         height: 100px; | ||||
|         position: absolute; | ||||
|         bottom: 0; | ||||
|         background: linear-gradient(to bottom, rgba($simple-background-color, 0.01) 0%, rgba($simple-background-color, 1) 100%); | ||||
|         left: 0; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         pointer-events: none; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .nothing-here { | ||||
|   width: 100%; | ||||
|   display: block; | ||||
|   background: $ui-base-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   color: $light-text-color; | ||||
|   font-size: 14px; | ||||
|   font-weight: 500; | ||||
|   text-align: center; | ||||
|   padding: 130px 0; | ||||
|   padding-top: 125px; | ||||
|   margin: 0 auto; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   cursor: default; | ||||
| } | ||||
| 
 | ||||
| .account-card { | ||||
|   padding: 14px 10px; | ||||
|   background: $simple-background-color; | ||||
|   border-radius: 4px; | ||||
|   text-align: left; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   padding: 20px; | ||||
|   min-height: 30vh; | ||||
| 
 | ||||
|   .detailed-status__display-name { | ||||
|     display: block; | ||||
|     overflow: hidden; | ||||
|     margin-bottom: 15px; | ||||
| 
 | ||||
|     &:last-child { | ||||
|       margin-bottom: 0; | ||||
|     } | ||||
| 
 | ||||
|     & > div { | ||||
|       float: left; | ||||
|       margin-right: 10px; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       @include avatar-size(48px); | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       display: block; | ||||
|       border-radius: 4px; | ||||
|       @include avatar-radius(); | ||||
|     } | ||||
| 
 | ||||
|     .display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       overflow: hidden; | ||||
|       white-space: nowrap; | ||||
|       text-overflow: ellipsis; | ||||
|       cursor: default; | ||||
| 
 | ||||
|       strong { | ||||
|         font-weight: 500; | ||||
|         color: $ui-base-color; | ||||
| 
 | ||||
|         @each $lang in $cjk-langs { | ||||
|           &:lang(#{$lang}) { | ||||
|             font-weight: 700; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       span { | ||||
|         font-size: 14px; | ||||
|         color: $light-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       .display-name { | ||||
|         strong { | ||||
|           text-decoration: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .account__header__content { | ||||
|     font-size: 14px; | ||||
|     color: $inverted-text-color; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .activity-stream-tabs { | ||||
|   background: $simple-background-color; | ||||
|   border-bottom: 1px solid $ui-secondary-color; | ||||
|   position: relative; | ||||
|   z-index: 2; | ||||
| 
 | ||||
|   a { | ||||
|     display: inline-block; | ||||
|     padding: 15px; | ||||
|     text-decoration: none; | ||||
|     color: $highlight-text-color; | ||||
|     text-transform: uppercase; | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:active, | ||||
|     &:focus { | ||||
|       color: lighten($highlight-text-color, 8%); | ||||
|     } | ||||
| 
 | ||||
|     &.active { | ||||
|       color: $inverted-text-color; | ||||
|       cursor: default; | ||||
|     } | ||||
|   &--under-tabs { | ||||
|     border-radius: 0 0 4px 4px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -596,4 +218,56 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| @import 'metadata'; | ||||
| .account__header__fields { | ||||
|   padding: 0; | ||||
|   margin: 15px -15px -15px; | ||||
|   border: 0 none; | ||||
|   border-top: 1px solid lighten($ui-base-color, 12%); | ||||
|   border-bottom: 1px solid lighten($ui-base-color, 12%); | ||||
|   font-size: 14px; | ||||
|   line-height: 20px; | ||||
| 
 | ||||
|   dl { | ||||
|     display: flex; | ||||
|     border-bottom: 1px solid lighten($ui-base-color, 12%); | ||||
|   } | ||||
| 
 | ||||
|   dt, | ||||
|   dd { | ||||
|     box-sizing: border-box; | ||||
|     padding: 14px; | ||||
|     text-align: center; | ||||
|     max-height: 48px; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|     text-overflow: ellipsis; | ||||
|   } | ||||
| 
 | ||||
|   dt { | ||||
|     font-weight: 500; | ||||
|     width: 120px; | ||||
|     flex: 0 0 auto; | ||||
|     color: $secondary-text-color; | ||||
|     background: rgba(darken($ui-base-color, 8%), 0.5); | ||||
|   } | ||||
| 
 | ||||
|   dd { | ||||
|     flex: 1 1 auto; | ||||
|     color: $darker-text-color; | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: $highlight-text-color; | ||||
|     text-decoration: none; | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:focus, | ||||
|     &:active { | ||||
|       text-decoration: underline; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   dl:last-child { | ||||
|     border-bottom: 0; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,10 @@ | |||
| body { | ||||
|   font-family: 'mastodon-font-sans-serif', sans-serif; | ||||
|   background: $ui-base-color; | ||||
|   background-size: cover; | ||||
|   background-attachment: fixed; | ||||
|   background: darken($ui-base-color, 8%); | ||||
|   font-size: 13px; | ||||
|   line-height: 18px; | ||||
|   font-weight: 400; | ||||
|   color: $primary-text-color; | ||||
|   padding-bottom: 20px; | ||||
|   text-rendering: optimizelegibility; | ||||
|   font-feature-settings: "kern"; | ||||
|   text-size-adjust: none; | ||||
|  | @ -35,20 +32,28 @@ body { | |||
|     height: 100%; | ||||
|     padding: 0; | ||||
|     background: $ui-base-color; | ||||
| 
 | ||||
|     &.with-modals--active { | ||||
|       overflow-y: hidden; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.about-body { | ||||
|     background: darken($ui-base-color, 8%); | ||||
|     padding-bottom: 0; | ||||
|   &.lighter { | ||||
|     background: $ui-base-color; | ||||
|   } | ||||
| 
 | ||||
|   &.tag-body { | ||||
|     background: darken($ui-base-color, 8%); | ||||
|     padding-bottom: 0; | ||||
|   &.with-modals { | ||||
|     overflow-x: hidden; | ||||
|     overflow-y: scroll; | ||||
| 
 | ||||
|     &--active { | ||||
|       overflow-y: hidden; | ||||
|       margin-right: 13px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.embed { | ||||
|     background: transparent; | ||||
|     background: lighten($ui-base-color, 4%); | ||||
|     margin: 0; | ||||
|     padding-bottom: 0; | ||||
| 
 | ||||
|  |  | |||
|  | @ -442,6 +442,18 @@ | |||
|   background: lighten($ui-base-color, 4%); | ||||
|   padding: 14px 10px; | ||||
| 
 | ||||
|   &--flex { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     justify-content: space-between; | ||||
|     align-items: flex-start; | ||||
| 
 | ||||
|     .status__content, | ||||
|     .detailed-status__meta { | ||||
|       flex: 100%; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .status__content { | ||||
|     font-size: 19px; | ||||
|     line-height: 24px; | ||||
|  |  | |||
|  | @ -60,10 +60,6 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .media-standalone__body { | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .account-header { | ||||
|   width: 400px; | ||||
|   margin: 0 auto; | ||||
|  | @ -87,6 +83,7 @@ | |||
|   .avatar { | ||||
|     width: 40px; | ||||
|     height: 40px; | ||||
|     @include avatar-size(40px); | ||||
|     margin-right: 8px; | ||||
| 
 | ||||
|     img { | ||||
|  | @ -95,6 +92,7 @@ | |||
|       display: block; | ||||
|       margin: 0; | ||||
|       border-radius: 4px; | ||||
|       @include avatar-radius(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -118,3 +116,580 @@ | |||
|     margin-left: 8px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .public-layout { | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     padding-top: 48px; | ||||
|   } | ||||
| 
 | ||||
|   .container { | ||||
|     max-width: 960px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       padding: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .header { | ||||
|     background: lighten($ui-base-color, 8%); | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|     border-radius: 4px; | ||||
|     height: 48px; | ||||
|     margin: 10px 0; | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     justify-content: center; | ||||
|     flex-wrap: nowrap; | ||||
|     overflow: hidden; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       position: fixed; | ||||
|       width: 100%; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       margin: 0; | ||||
|       border-radius: 0; | ||||
|       box-shadow: none; | ||||
|       z-index: 110; | ||||
|     } | ||||
| 
 | ||||
|     & > div { | ||||
|       flex: 1 1 33.3%; | ||||
|       min-height: 1px; | ||||
|     } | ||||
| 
 | ||||
|     .nav-left { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: flex-start; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .nav-center { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: center; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .nav-right { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: flex-end; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .brand { | ||||
|       display: block; | ||||
|       padding: 15px; | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         height: 18px; | ||||
|         width: auto; | ||||
|         position: relative; | ||||
|         bottom: -2px; | ||||
| 
 | ||||
|         @media screen and (max-width: $no-gap-breakpoint) { | ||||
|           height: 20px; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         background: lighten($ui-base-color, 12%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .nav-link { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       padding: 0 1rem; | ||||
|       font-size: 12px; | ||||
|       font-weight: 500; | ||||
|       text-decoration: none; | ||||
|       color: $darker-text-color; | ||||
|       white-space: nowrap; | ||||
|       text-align: center; | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         text-decoration: underline; | ||||
|         color: $primary-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .nav-button { | ||||
|       background: lighten($ui-base-color, 16%); | ||||
|       margin: 8px; | ||||
|       margin-left: 0; | ||||
|       border-radius: 4px; | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
|         background: lighten($ui-base-color, 20%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   $no-columns-breakpoint: 600px; | ||||
| 
 | ||||
|   .grid { | ||||
|     display: grid; | ||||
|     grid-gap: 10px; | ||||
|     grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); | ||||
|     grid-auto-columns: 25%; | ||||
|     grid-auto-rows: max-content; | ||||
| 
 | ||||
|     .column-0 { | ||||
|       grid-row: 1; | ||||
|       grid-column: 1; | ||||
|     } | ||||
| 
 | ||||
|     .column-1 { | ||||
|       grid-row: 1; | ||||
|       grid-column: 2; | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-columns-breakpoint) { | ||||
|       grid-template-columns: 100%; | ||||
|       grid-gap: 0; | ||||
| 
 | ||||
|       .column-1 { | ||||
|         display: none; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .public-account-header { | ||||
|     overflow: hidden; | ||||
|     margin-bottom: 10px; | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|     &__image { | ||||
|       border-radius: 4px 4px 0 0; | ||||
|       overflow: hidden; | ||||
|       height: 300px; | ||||
|       position: relative; | ||||
|       background: darken($ui-base-color, 12%); | ||||
| 
 | ||||
|       &::after { | ||||
|         content: ""; | ||||
|         display: block; | ||||
|         position: absolute; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|       } | ||||
| 
 | ||||
|       img { | ||||
|         object-fit: cover; | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         margin: 0; | ||||
|         border-radius: 4px 4px 0 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         height: 200px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       margin-bottom: 0; | ||||
|       box-shadow: none; | ||||
| 
 | ||||
|       &__image::after { | ||||
|         display: none; | ||||
|       } | ||||
| 
 | ||||
|       &__image, | ||||
|       &__image img { | ||||
|         border-radius: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__bar { | ||||
|       position: relative; | ||||
|       margin-top: -80px; | ||||
|       display: flex; | ||||
|       justify-content: flex-start; | ||||
| 
 | ||||
|       &::before { | ||||
|         content: ""; | ||||
|         display: block; | ||||
|         background: lighten($ui-base-color, 4%); | ||||
|         position: absolute; | ||||
|         bottom: 0; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         height: 60px; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         z-index: -1; | ||||
|       } | ||||
| 
 | ||||
|       .avatar { | ||||
|         display: block; | ||||
|         width: 120px; | ||||
|         height: 120px; | ||||
|         @include avatar-size(120px); | ||||
|         padding-left: 20px - 4px; | ||||
|         flex: 0 0 auto; | ||||
| 
 | ||||
|         img { | ||||
|           display: block; | ||||
|           width: 100%; | ||||
|           height: 100%; | ||||
|           margin: 0; | ||||
|           border-radius: 50%; | ||||
|           border: 4px solid lighten($ui-base-color, 4%); | ||||
|           background: darken($ui-base-color, 8%); | ||||
|           @include avatar-radius(); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         margin-top: 0; | ||||
|         background: lighten($ui-base-color, 4%); | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         padding: 5px; | ||||
| 
 | ||||
|         &::before { | ||||
|           display: none; | ||||
|         } | ||||
| 
 | ||||
|         .avatar { | ||||
|           width: 48px; | ||||
|           height: 48px; | ||||
|           @include avatar-size(48px); | ||||
|           padding: 7px 0; | ||||
|           padding-left: 10px; | ||||
| 
 | ||||
|           img { | ||||
|             border: 0; | ||||
|             border-radius: 4px; | ||||
|             @include avatar-radius(); | ||||
|           } | ||||
| 
 | ||||
|           @media screen and (max-width: 360px) { | ||||
|             display: none; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-gap-breakpoint) { | ||||
|         border-radius: 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-columns-breakpoint) { | ||||
|         flex-wrap: wrap; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__tabs { | ||||
|       flex: 1 1 auto; | ||||
|       margin-left: 20px; | ||||
| 
 | ||||
|       &__name { | ||||
|         padding-top: 20px; | ||||
|         padding-bottom: 8px; | ||||
| 
 | ||||
|         h1 { | ||||
|           font-size: 20px; | ||||
|           line-height: 18px * 1.5; | ||||
|           color: $primary-text-color; | ||||
|           font-weight: 500; | ||||
|           overflow: hidden; | ||||
|           white-space: nowrap; | ||||
|           text-overflow: ellipsis; | ||||
|           text-shadow: 1px 1px 1px $base-shadow-color; | ||||
| 
 | ||||
|           small { | ||||
|             display: block; | ||||
|             font-size: 14px; | ||||
|             color: $primary-text-color; | ||||
|             font-weight: 400; | ||||
|             overflow: hidden; | ||||
|             text-overflow: ellipsis; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         margin-left: 15px; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
| 
 | ||||
|         &__name { | ||||
|           padding-top: 0; | ||||
|           padding-bottom: 0; | ||||
| 
 | ||||
|           h1 { | ||||
|             font-size: 16px; | ||||
|             line-height: 24px; | ||||
|             text-shadow: none; | ||||
| 
 | ||||
|             small { | ||||
|               color: $darker-text-color; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &__tabs { | ||||
|         display: flex; | ||||
|         justify-content: flex-start; | ||||
|         align-items: stretch; | ||||
|         height: 58px; | ||||
| 
 | ||||
|         .details-counters { | ||||
|           display: flex; | ||||
|           flex-direction: row; | ||||
|           min-width: 300px; | ||||
|         } | ||||
| 
 | ||||
|         @media screen and (max-width: $no-columns-breakpoint) { | ||||
|           .details-counters { | ||||
|             display: none; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .counter { | ||||
|           width: 33.3%; | ||||
|           box-sizing: border-box; | ||||
|           flex: 0 0 auto; | ||||
|           color: $darker-text-color; | ||||
|           padding: 10px; | ||||
|           border-right: 1px solid lighten($ui-base-color, 4%); | ||||
|           cursor: default; | ||||
|           text-align: center; | ||||
|           position: relative; | ||||
| 
 | ||||
|           a { | ||||
|             display: block; | ||||
|           } | ||||
| 
 | ||||
|           &:last-child { | ||||
|             border-right: 0; | ||||
|           } | ||||
| 
 | ||||
|           &::after { | ||||
|             display: block; | ||||
|             content: ""; | ||||
|             position: absolute; | ||||
|             bottom: 0; | ||||
|             left: 0; | ||||
|             width: 100%; | ||||
|             border-bottom: 4px solid $ui-primary-color; | ||||
|             opacity: 0.5; | ||||
|             transition: all 400ms ease; | ||||
|           } | ||||
| 
 | ||||
|           &.active { | ||||
|             &::after { | ||||
|               border-bottom: 4px solid $highlight-text-color; | ||||
|               opacity: 1; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           &:hover { | ||||
|             &::after { | ||||
|               opacity: 1; | ||||
|               transition-duration: 100ms; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           a { | ||||
|             text-decoration: none; | ||||
|             color: inherit; | ||||
|           } | ||||
| 
 | ||||
|           .counter-label { | ||||
|             font-size: 12px; | ||||
|             display: block; | ||||
|           } | ||||
| 
 | ||||
|           .counter-number { | ||||
|             font-weight: 500; | ||||
|             font-size: 18px; | ||||
|             margin-bottom: 5px; | ||||
|             color: $primary-text-color; | ||||
|             font-family: 'mastodon-font-display', sans-serif; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .spacer { | ||||
|           flex: 1 1 auto; | ||||
|           height: 1px; | ||||
|         } | ||||
| 
 | ||||
|         &__buttons { | ||||
|           padding: 7px 8px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__extra { | ||||
|       display: none; | ||||
|       margin-top: 4px; | ||||
| 
 | ||||
|       .public-account-bio { | ||||
|         border-radius: 0; | ||||
|         box-shadow: none; | ||||
|         background: transparent; | ||||
|         margin: 0 -5px; | ||||
| 
 | ||||
|         .account__header__fields { | ||||
|           border-top: 1px solid lighten($ui-base-color, 12%); | ||||
|         } | ||||
| 
 | ||||
|         .roles { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &__links { | ||||
|         margin-top: -15px; | ||||
|         font-size: 14px; | ||||
|         color: $darker-text-color; | ||||
| 
 | ||||
|         a { | ||||
|           display: inline-block; | ||||
|           color: $darker-text-color; | ||||
|           text-decoration: none; | ||||
|           padding: 15px; | ||||
| 
 | ||||
|           strong { | ||||
|             font-weight: 700; | ||||
|             color: $primary-text-color; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-columns-breakpoint) { | ||||
|         display: block; | ||||
|         flex: 100%; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .account__section-headline { | ||||
|     border-radius: 4px 4px 0 0; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       border-radius: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .detailed-status__meta { | ||||
|     margin-top: 25px; | ||||
|   } | ||||
| 
 | ||||
|   .public-account-bio { | ||||
|     background: lighten($ui-base-color, 8%); | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|     border-radius: 4px; | ||||
|     overflow: hidden; | ||||
|     margin-bottom: 10px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       box-shadow: none; | ||||
|       margin-bottom: 0; | ||||
|       border-radius: 0; | ||||
|     } | ||||
| 
 | ||||
|     .account__header__fields { | ||||
|       margin: 0; | ||||
|       border-top: 0; | ||||
| 
 | ||||
|       a { | ||||
|         color: lighten($ui-highlight-color, 8%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .account__header__content { | ||||
|       padding: 20px; | ||||
|       padding-bottom: 0; | ||||
|       color: $primary-text-color; | ||||
|     } | ||||
| 
 | ||||
|     &__extra, | ||||
|     .roles { | ||||
|       padding: 20px; | ||||
|       font-size: 14px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .roles { | ||||
|       padding-bottom: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .static-icon-button { | ||||
|     color: $action-button-color; | ||||
|     font-size: 18px; | ||||
| 
 | ||||
|     & > span { | ||||
|       font-size: 14px; | ||||
|       font-weight: 500; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .card-grid { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     min-width: 100%; | ||||
|     margin: 0 -5px; | ||||
| 
 | ||||
|     & > div { | ||||
|       box-sizing: border-box; | ||||
|       flex: 1 0 auto; | ||||
|       width: 300px; | ||||
|       padding: 0 5px; | ||||
|       margin-bottom: 10px; | ||||
|       max-width: 33.333%; | ||||
| 
 | ||||
|       @media screen and (max-width: 900px) { | ||||
|         max-width: 50%; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         max-width: 100%; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       margin: 0; | ||||
|       border-top: 1px solid lighten($ui-base-color, 8%); | ||||
| 
 | ||||
|       & > div { | ||||
|         width: 100%; | ||||
|         padding: 0; | ||||
|         margin-bottom: 0; | ||||
|         border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||
| 
 | ||||
|         &:last-child { | ||||
|           border-bottom: 0; | ||||
|         } | ||||
| 
 | ||||
|         .card__bar { | ||||
|           background: $ui-base-color; | ||||
| 
 | ||||
|           &:hover, | ||||
|           &:active, | ||||
|           &:focus { | ||||
|             background: lighten($ui-base-color, 4%); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,38 +1,140 @@ | |||
| .footer { | ||||
|   text-align: center; | ||||
|   margin-top: 30px; | ||||
|   font-size: 12px; | ||||
|   color: $darker-text-color; | ||||
| .public-layout { | ||||
|   .footer { | ||||
|     text-align: left; | ||||
|     padding-top: 20px; | ||||
|     padding-bottom: 60px; | ||||
|     font-size: 12px; | ||||
|     color: lighten($ui-base-color, 34%); | ||||
| 
 | ||||
|   .footer__domain { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     a { | ||||
|       color: inherit; | ||||
|       text-decoration: none; | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       padding-left: 20px; | ||||
|       padding-right: 20px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .powered-by, | ||||
|   .single-user-login { | ||||
|     font-weight: 400; | ||||
|     .grid { | ||||
|       display: grid; | ||||
|       grid-gap: 10px; | ||||
|       grid-template-columns: 1fr 1fr 2fr 1fr 1fr; | ||||
| 
 | ||||
|     a { | ||||
|       color: inherit; | ||||
|       text-decoration: underline; | ||||
|       font-weight: 500; | ||||
|       .column-0 { | ||||
|         grid-column: 1; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       &:hover { | ||||
|       .column-1 { | ||||
|         grid-column: 2; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       .column-2 { | ||||
|         grid-column: 3; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|         text-align: center; | ||||
| 
 | ||||
|         h4 a { | ||||
|           color: lighten($ui-base-color, 34%); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .column-3 { | ||||
|         grid-column: 4; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       .column-4 { | ||||
|         grid-column: 5; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 690px) { | ||||
|         grid-template-columns: 1fr 2fr 1fr; | ||||
| 
 | ||||
|         .column-0, | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|         } | ||||
| 
 | ||||
|         .column-1 { | ||||
|           grid-row: 2; | ||||
|         } | ||||
| 
 | ||||
|         .column-2 { | ||||
|           grid-column: 2; | ||||
|         } | ||||
| 
 | ||||
|         .column-3, | ||||
|         .column-4 { | ||||
|           grid-column: 3; | ||||
|         } | ||||
| 
 | ||||
|         .column-4 { | ||||
|           grid-row: 2; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         .column-1 { | ||||
|           display: block; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-gap-breakpoint) { | ||||
|         .column-0, | ||||
|         .column-1, | ||||
|         .column-3, | ||||
|         .column-4 { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     h4 { | ||||
|       text-transform: uppercase; | ||||
|       font-weight: 700; | ||||
|       margin-bottom: 8px; | ||||
|       color: $darker-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: inherit; | ||||
|         text-decoration: none; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     img { | ||||
|       margin: 0 4px; | ||||
|       position: relative; | ||||
|       bottom: -1px; | ||||
|       height: 18px; | ||||
|       vertical-align: top; | ||||
|     ul a { | ||||
|       text-decoration: none; | ||||
|       color: lighten($ui-base-color, 34%); | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:active, | ||||
|       &:focus { | ||||
|         text-decoration: underline; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .brand { | ||||
|       svg { | ||||
|         display: block; | ||||
|         height: 36px; | ||||
|         width: auto; | ||||
|         margin: 0 auto; | ||||
| 
 | ||||
|         path { | ||||
|           fill: lighten($ui-base-color, 34%); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         svg path { | ||||
|           fill: lighten($ui-base-color, 38%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
| @import 'modal'; | ||||
| @import 'footer'; | ||||
| @import 'compact_header'; | ||||
| @import 'landing_strip'; | ||||
| @import 'widgets'; | ||||
| @import 'forms'; | ||||
| @import 'accounts'; | ||||
| @import 'stream_entries'; | ||||
|  |  | |||
|  | @ -1,111 +0,0 @@ | |||
| .landing-strip, | ||||
| .memoriam-strip { | ||||
|   background: rgba(darken($ui-base-color, 7%), 0.8); | ||||
|   color: $darker-text-color; | ||||
|   font-weight: 400; | ||||
|   padding: 14px; | ||||
|   border-radius: 4px; | ||||
|   margin-bottom: 20px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
|   } | ||||
| 
 | ||||
|   .logo { | ||||
|     width: 30px; | ||||
|     height: 30px; | ||||
|     flex: 0 0 auto; | ||||
|     margin-right: 15px; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .memoriam-strip { | ||||
|   background: rgba($base-shadow-color, 0.7); | ||||
| } | ||||
| 
 | ||||
| .moved-strip { | ||||
|   padding: 14px; | ||||
|   border-radius: 4px; | ||||
|   background: rgba(darken($ui-base-color, 7%), 0.8); | ||||
|   color: $secondary-text-color; | ||||
|   font-weight: 400; | ||||
|   margin-bottom: 20px; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
| 
 | ||||
|     &.mention { | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         text-decoration: none; | ||||
|       } | ||||
| 
 | ||||
|       &:focus, | ||||
|       &:hover, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
| 
 | ||||
|         span { | ||||
|           text-decoration: underline; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__message { | ||||
|     margin-bottom: 15px; | ||||
| 
 | ||||
|     .fa { | ||||
|       margin-right: 5px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__card { | ||||
|     .detailed-status__display-avatar { | ||||
|       position: relative; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       margin-bottom: 0; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         color: $highlight-text-color; | ||||
|         font-weight: 400; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,368 +1,185 @@ | |||
| .activity-stream { | ||||
|   clear: both; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   border-radius: 4px; | ||||
|   overflow: hidden; | ||||
|   margin-bottom: 10px; | ||||
| 
 | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     margin-bottom: 0; | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
| 
 | ||||
|   &--headless { | ||||
|     border-radius: 0; | ||||
|     margin: 0; | ||||
|     box-shadow: none; | ||||
| 
 | ||||
|     .detailed-status, | ||||
|     .status { | ||||
|       border-radius: 0 !important; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   div[data-component] { | ||||
|     width: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .entry { | ||||
|     background: $simple-background-color; | ||||
|     background: $ui-base-color; | ||||
| 
 | ||||
|     .detailed-status.light, | ||||
|     .status.light, | ||||
|     .more.light { | ||||
|       border-bottom: 1px solid $ui-secondary-color; | ||||
|     .detailed-status, | ||||
|     .status, | ||||
|     .load-more { | ||||
|       animation: none; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-bottom: 0; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:first-child { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-radius: 4px 4px 0 0; | ||||
|       } | ||||
| 
 | ||||
|       &:last-child { | ||||
|         &, | ||||
|         .detailed-status.light, | ||||
|         .status.light { | ||||
|         .detailed-status, | ||||
|         .status { | ||||
|           border-radius: 4px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: 740px) { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-radius: 0 !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   &.with-header { | ||||
|     .entry { | ||||
|       &:first-child { | ||||
|         &, | ||||
|         .detailed-status.light, | ||||
|         .status.light { | ||||
|           border-radius: 0; | ||||
|         } | ||||
| .button.logo-button { | ||||
|   flex: 0 auto; | ||||
|   font-size: 14px; | ||||
|   background: $ui-highlight-color; | ||||
|   color: $primary-text-color; | ||||
|   text-transform: none; | ||||
|   line-height: 36px; | ||||
|   height: auto; | ||||
|   padding: 3px 15px; | ||||
|   border: 0; | ||||
| 
 | ||||
|         &:last-child { | ||||
|           &, | ||||
|           .detailed-status.light, | ||||
|           .status.light { | ||||
|             border-radius: 0 0 4px 4px; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|   svg { | ||||
|     width: 20px; | ||||
|     height: auto; | ||||
|     vertical-align: middle; | ||||
|     margin-right: 5px; | ||||
| 
 | ||||
|     path:first-child { | ||||
|       fill: $primary-text-color; | ||||
|     } | ||||
| 
 | ||||
|     path:last-child { | ||||
|       fill: $ui-highlight-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .media-gallery__gifv__label { | ||||
|     bottom: 9px; | ||||
|   &:active, | ||||
|   &:focus, | ||||
|   &:hover { | ||||
|     background: lighten($ui-highlight-color, 10%); | ||||
| 
 | ||||
|     svg path:last-child { | ||||
|       fill: lighten($ui-highlight-color, 10%); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .status.light { | ||||
|     padding: 14px 14px 14px (48px + 14px * 2); | ||||
|     position: relative; | ||||
|     min-height: 48px; | ||||
|     cursor: default; | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     svg { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|     .status__header { | ||||
|       font-size: 15px; | ||||
| .embed, | ||||
| .public-layout { | ||||
|   .detailed-status { | ||||
|     padding: 15px; | ||||
|   } | ||||
| 
 | ||||
|       .status__meta { | ||||
|         float: right; | ||||
|         font-size: 14px; | ||||
|   .status { | ||||
|     padding: 15px 15px 15px (48px + 15px * 2); | ||||
|     min-height: 48px + 2px; | ||||
| 
 | ||||
|         .status__relative-time { | ||||
|           color: $lighter-text-color; | ||||
|         } | ||||
|       } | ||||
|     &__avatar { | ||||
|       left: 15px; | ||||
|       top: 17px; | ||||
|     } | ||||
| 
 | ||||
|     .status__display-name { | ||||
|     &__content { | ||||
|       padding-top: 5px; | ||||
|     } | ||||
| 
 | ||||
|     &__prepend { | ||||
|       margin-left: 48px + 15px * 2; | ||||
|       padding-top: 15px; | ||||
|     } | ||||
| 
 | ||||
|     &__prepend-icon-wrapper { | ||||
|       left: -32px; | ||||
|     } | ||||
| 
 | ||||
|     .media-gallery, | ||||
|     &__action-bar, | ||||
|     .video-player { | ||||
|       margin-top: 10px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Styling from upstream's WebUI, as public pages use the same layout | ||||
| .embed, | ||||
| .public-layout { | ||||
|   .status { | ||||
|     .status__info .status__display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       padding-right: 25px; | ||||
|       color: $inverted-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .status__info { | ||||
|       font-size: 15px; | ||||
|       display: initial; | ||||
|     } | ||||
| 
 | ||||
|     .status__relative-time { | ||||
|       color: $dark-text-color; | ||||
|       float: right; | ||||
|       font-size: 14px; | ||||
|       width: auto; | ||||
|       margin: initial; | ||||
|       padding: initial; | ||||
|     } | ||||
| 
 | ||||
|     .status__info .status__display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       padding-right: 25px; | ||||
|       margin: initial; | ||||
|     } | ||||
| 
 | ||||
|     .status__avatar { | ||||
|       position: absolute; | ||||
|       left: 14px; | ||||
|       top: 14px; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       @include avatar-size(48px); | ||||
| 
 | ||||
|       & > div { | ||||
|         width: 48px; | ||||
|         height: 48px; | ||||
|         @include avatar-size(48px); | ||||
|       } | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         border-radius: 4px; | ||||
|         @include avatar-radius(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       //overflow: hidden; | ||||
|       //white-space: nowrap; | ||||
|       //text-overflow: ellipsis; | ||||
| 
 | ||||
|       strong { | ||||
|         font-weight: 500; | ||||
|         color: $inverted-text-color; | ||||
| 
 | ||||
|         @each $lang in $cjk-langs { | ||||
|           &:lang(#{$lang}) { | ||||
|             font-weight: 700; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       span { | ||||
|         font-size: 14px; | ||||
|         color: $light-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__content { | ||||
|       color: $inverted-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: $highlight-text-color; | ||||
|       } | ||||
| 
 | ||||
|       a.status__content__spoiler-link { | ||||
|         color: $primary-text-color; | ||||
|         background: $ui-base-color; | ||||
| 
 | ||||
|         &:hover { | ||||
|           background: lighten($ui-base-color, 8%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .detailed-status.light { | ||||
|     padding: 14px; | ||||
|     background: $simple-background-color; | ||||
|     cursor: default; | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       display: block; | ||||
|       overflow: hidden; | ||||
|       margin-bottom: 15px; | ||||
| 
 | ||||
|       & > div { | ||||
|         float: left; | ||||
|         margin-right: 10px; | ||||
|       } | ||||
| 
 | ||||
|       .display-name { | ||||
|         display: block; | ||||
|         max-width: 100%; | ||||
|         overflow: hidden; | ||||
|         white-space: nowrap; | ||||
|         text-overflow: ellipsis; | ||||
| 
 | ||||
|         strong { | ||||
|           font-weight: 500; | ||||
|           color: $inverted-text-color; | ||||
| 
 | ||||
|           @each $lang in $cjk-langs { | ||||
|             &:lang(#{$lang}) { | ||||
|               font-weight: 700; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         span { | ||||
|           font-size: 14px; | ||||
|           color: $light-text-color; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       @include avatar-size(48px); | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         border-radius: 4px; | ||||
|         @include avatar-radius(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__content { | ||||
|       color: $inverted-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: $highlight-text-color; | ||||
|       } | ||||
| 
 | ||||
|       a.status__content__spoiler-link { | ||||
|         color: $primary-text-color; | ||||
|         background: $ui-base-color; | ||||
| 
 | ||||
|         &:hover { | ||||
|           background: lighten($ui-base-color, 8%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__meta { | ||||
|       margin-top: 15px; | ||||
|       color: $light-text-color; | ||||
|       font-size: 14px; | ||||
|       line-height: 18px; | ||||
| 
 | ||||
|       a { | ||||
|         color: inherit; | ||||
|       } | ||||
| 
 | ||||
|       span > span { | ||||
|         font-weight: 500; | ||||
|         font-size: 12px; | ||||
|         margin-left: 6px; | ||||
|         display: inline-block; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status-card { | ||||
|       border-color: lighten($ui-secondary-color, 4%); | ||||
|       color: $lighter-text-color; | ||||
| 
 | ||||
|       &:hover { | ||||
|         background: lighten($ui-secondary-color, 4%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status-card__title, | ||||
|     .status-card__description { | ||||
|       color: $inverted-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .status-card__image { | ||||
|       background: $ui-secondary-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .media-spoiler { | ||||
|     background: $ui-base-color; | ||||
|     color: $darker-text-color; | ||||
|   } | ||||
| 
 | ||||
|   .pre-header { | ||||
|     padding: 14px 0; | ||||
|     padding-left: (48px + 14px * 2); | ||||
|     padding-bottom: 0; | ||||
|     margin-bottom: -4px; | ||||
|     color: $light-text-color; | ||||
|     font-size: 14px; | ||||
|     position: relative; | ||||
| 
 | ||||
|     .pre-header__icon { | ||||
|       position: absolute; | ||||
|       left: (48px + 14px * 2 - 30px); | ||||
|     } | ||||
| 
 | ||||
|     .status__display-name.muted strong { | ||||
|       color: $light-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .open-in-web-link { | ||||
|     text-decoration: none; | ||||
| 
 | ||||
|     &:hover { | ||||
|       text-decoration: underline; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .more { | ||||
|     color: $darker-text-color; | ||||
|     display: block; | ||||
|     padding: 14px; | ||||
|     text-align: center; | ||||
| 
 | ||||
|     &:not(:hover) { | ||||
|       text-decoration: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .embed { | ||||
|   .activity-stream { | ||||
|     box-shadow: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .entry { | ||||
| 
 | ||||
|   .detailed-status.light { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     justify-content: space-between; | ||||
|     align-items: flex-start; | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       flex: 1; | ||||
|       margin: 0 5px 15px 0; | ||||
|     } | ||||
| 
 | ||||
|     .button.button-secondary.logo-button { | ||||
|       flex: 0 auto; | ||||
|       font-size: 14px; | ||||
| 
 | ||||
|       svg { | ||||
|         width: 20px; | ||||
|         height: auto; | ||||
|         vertical-align: middle; | ||||
|         margin-right: 5px; | ||||
| 
 | ||||
|         path:first-child { | ||||
|           fill: $ui-primary-color; | ||||
|         } | ||||
| 
 | ||||
|         path:last-child { | ||||
|           fill: $simple-background-color; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:active, | ||||
|       &:focus, | ||||
|       &:hover { | ||||
|         svg path:first-child { | ||||
|           fill: lighten($ui-primary-color, 4%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__content, | ||||
|     .detailed-status__meta { | ||||
|       flex: 100%; | ||||
|       width: 48px; | ||||
|       margin: initial; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -49,6 +49,8 @@ $media-modal-media-max-width: 100%; | |||
| // put margins on top and bottom of image to avoid the screen covered by image. | ||||
| $media-modal-media-max-height: 80%; | ||||
| 
 | ||||
| $no-gap-breakpoint: 415px; | ||||
| 
 | ||||
| // Avatar border size (8% default, 100% for rounded avatars) | ||||
| $ui-avatar-border-size: 8%; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										161
									
								
								app/javascript/flavours/glitch/styles/widgets.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								app/javascript/flavours/glitch/styles/widgets.scss
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | |||
| .hero-widget { | ||||
|   margin-bottom: 10px; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|   &__img { | ||||
|     width: 100%; | ||||
|     height: 167px; | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     border-radius: 4px 4px 0 0; | ||||
|     background: $base-shadow-color; | ||||
| 
 | ||||
|     img { | ||||
|       object-fit: cover; | ||||
|       display: block; | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       margin: 0; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__text { | ||||
|     background: $ui-base-color; | ||||
|     padding: 20px; | ||||
|     border-radius: 0 0 4px 4px; | ||||
|     font-size: 15px; | ||||
|     color: $darker-text-color; | ||||
|     line-height: 20px; | ||||
|     word-wrap: break-word; | ||||
|     font-weight: 400; | ||||
| 
 | ||||
|     .emojione { | ||||
|       width: 20px; | ||||
|       height: 20px; | ||||
|       margin: -3px 0 0; | ||||
|     } | ||||
| 
 | ||||
|     p { | ||||
|       margin-bottom: 20px; | ||||
| 
 | ||||
|       &:last-child { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     em { | ||||
|       display: inline; | ||||
|       margin: 0; | ||||
|       padding: 0; | ||||
|       font-weight: 700; | ||||
|       background: transparent; | ||||
|       font-family: inherit; | ||||
|       font-size: inherit; | ||||
|       line-height: inherit; | ||||
|       color: lighten($darker-text-color, 10%); | ||||
|     } | ||||
| 
 | ||||
|     a { | ||||
|       color: $secondary-text-color; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       &:hover { | ||||
|         text-decoration: underline; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .moved-account-widget { | ||||
|   padding: 15px; | ||||
|   padding-bottom: 20px; | ||||
|   border-radius: 4px; | ||||
|   background: $ui-base-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   color: $secondary-text-color; | ||||
|   font-weight: 400; | ||||
|   margin-bottom: 10px; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
| 
 | ||||
|     &.mention { | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         text-decoration: none; | ||||
|       } | ||||
| 
 | ||||
|       &:focus, | ||||
|       &:hover, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
| 
 | ||||
|         span { | ||||
|           text-decoration: underline; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__message { | ||||
|     margin-bottom: 15px; | ||||
| 
 | ||||
|     .fa { | ||||
|       margin-right: 5px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__card { | ||||
|     .detailed-status__display-avatar { | ||||
|       position: relative; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       margin-bottom: 0; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         font-weight: 400; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .memoriam-widget { | ||||
|   padding: 20px; | ||||
|   border-radius: 4px; | ||||
|   background: $base-shadow-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   font-size: 14px; | ||||
|   color: $darker-text-color; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| .moved-account-widget, | ||||
| .memoriam-widget { | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     margin-bottom: 0; | ||||
|     box-shadow: none; | ||||
|     border-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | @ -60,6 +60,32 @@ const getUnitDelay = units => { | |||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const timeAgoString = (intl, date, now, year) => { | ||||
|   const delta = now - date.getTime(); | ||||
| 
 | ||||
|   let relativeTime; | ||||
| 
 | ||||
|   if (delta < 10 * SECOND) { | ||||
|     relativeTime = intl.formatMessage(messages.just_now); | ||||
|   } else if (delta < 7 * DAY) { | ||||
|     if (delta < MINUTE) { | ||||
|       relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); | ||||
|     } else if (delta < HOUR) { | ||||
|       relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); | ||||
|     } else if (delta < DAY) { | ||||
|       relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); | ||||
|     } else { | ||||
|       relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); | ||||
|     } | ||||
|   } else if (date.getFullYear() === year) { | ||||
|     relativeTime = intl.formatDate(date, shortDateFormatOptions); | ||||
|   } else { | ||||
|     relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); | ||||
|   } | ||||
| 
 | ||||
|   return relativeTime; | ||||
| }; | ||||
| 
 | ||||
| @injectIntl | ||||
| export default class RelativeTimestamp extends React.Component { | ||||
| 
 | ||||
|  | @ -121,28 +147,8 @@ export default class RelativeTimestamp extends React.Component { | |||
|   render () { | ||||
|     const { timestamp, intl, year } = this.props; | ||||
| 
 | ||||
|     const date  = new Date(timestamp); | ||||
|     const delta = this.state.now - date.getTime(); | ||||
| 
 | ||||
|     let relativeTime; | ||||
| 
 | ||||
|     if (delta < 10 * SECOND) { | ||||
|       relativeTime = intl.formatMessage(messages.just_now); | ||||
|     } else if (delta < 7 * DAY) { | ||||
|       if (delta < MINUTE) { | ||||
|         relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); | ||||
|       } else if (delta < HOUR) { | ||||
|         relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) }); | ||||
|       } else if (delta < DAY) { | ||||
|         relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) }); | ||||
|       } else { | ||||
|         relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); | ||||
|       } | ||||
|     } else if (date.getFullYear() === year) { | ||||
|       relativeTime = intl.formatDate(date, shortDateFormatOptions); | ||||
|     } else { | ||||
|       relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); | ||||
|     } | ||||
|     const date         = new Date(timestamp); | ||||
|     const relativeTime = timeAgoString(intl, date, this.state.now, year); | ||||
| 
 | ||||
|     return ( | ||||
|       <time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}> | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| import { debounce } from 'lodash'; | ||||
| import React from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import StatusContainer from '../containers/status_container'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import LoadGap from './load_gap'; | ||||
| import ScrollableList from './scrollable_list'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| export default class StatusList extends ImmutablePureComponent { | ||||
| 
 | ||||
|  | @ -71,7 +71,7 @@ export default class StatusList extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other }  = this.props; | ||||
|     const { statusIds, featuredStatusIds, shouldUpdateScroll, onLoadMore, timelineId, ...other }  = this.props; | ||||
|     const { isLoading, isPartial } = other; | ||||
| 
 | ||||
|     if (isPartial) { | ||||
|  | @ -122,7 +122,7 @@ export default class StatusList extends ImmutablePureComponent { | |||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}> | ||||
|       <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} shouldUpdateScroll={shouldUpdateScroll} ref={this.setRef}> | ||||
|         {scrollableContent} | ||||
|       </ScrollableList> | ||||
|     ); | ||||
|  |  | |||
|  | @ -29,19 +29,19 @@ export default class MediaContainer extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   handleOpenMedia = (media, index) => { | ||||
|     document.body.classList.add('media-standalone__body'); | ||||
|     document.body.classList.add('with-modals--active'); | ||||
|     this.setState({ media, index }); | ||||
|   } | ||||
| 
 | ||||
|   handleOpenVideo = (video, time) => { | ||||
|     const media = ImmutableList([video]); | ||||
| 
 | ||||
|     document.body.classList.add('media-standalone__body'); | ||||
|     document.body.classList.add('with-modals--active'); | ||||
|     this.setState({ media, time }); | ||||
|   } | ||||
| 
 | ||||
|   handleCloseMedia = () => { | ||||
|     document.body.classList.remove('media-standalone__body'); | ||||
|     document.body.classList.remove('with-modals--active'); | ||||
|     this.setState({ media: null, index: null, time: null }); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -142,17 +142,17 @@ export default class ActionBar extends React.PureComponent { | |||
|         <div className='account__action-bar'> | ||||
|           <div className='account__action-bar-links'> | ||||
|             <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}> | ||||
|               <span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span> | ||||
|               <FormattedMessage id='account.posts' defaultMessage='Toots' /> | ||||
|               <strong>{shortNumberFormat(account.get('statuses_count'))}</strong> | ||||
|             </Link> | ||||
| 
 | ||||
|             <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}> | ||||
|               <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span> | ||||
|               <FormattedMessage id='account.follows' defaultMessage='Follows' /> | ||||
|               <strong>{shortNumberFormat(account.get('following_count'))}</strong> | ||||
|             </Link> | ||||
| 
 | ||||
|             <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}> | ||||
|               <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span> | ||||
|               <FormattedMessage id='account.followers' defaultMessage='Followers' /> | ||||
|               <strong>{shortNumberFormat(account.get('followers_count'))}</strong> | ||||
|             </Link> | ||||
|           </div> | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ const mapStateToProps = (state, props) => ({ | |||
| class LoadMoreMedia extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     maxId: PropTypes.string, | ||||
|     onLoadMore: PropTypes.func.isRequired, | ||||
|   }; | ||||
|  | @ -90,7 +91,7 @@ export default class AccountGallery extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { medias, isLoading, hasMore } = this.props; | ||||
|     const { medias, shouldUpdateScroll, isLoading, hasMore } = this.props; | ||||
| 
 | ||||
|     let loadOlder = null; | ||||
| 
 | ||||
|  | @ -110,7 +111,7 @@ export default class AccountGallery extends ImmutablePureComponent { | |||
|       <Column> | ||||
|         <ColumnBackButton /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='account_gallery'> | ||||
|         <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             <HeaderContainer accountId={this.props.params.accountId} /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ export default class AccountTimeline extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     statusIds: ImmutablePropTypes.list, | ||||
|     featuredStatusIds: ImmutablePropTypes.list, | ||||
|     isLoading: PropTypes.bool, | ||||
|  | @ -61,7 +62,7 @@ export default class AccountTimeline extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props; | ||||
|     const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props; | ||||
| 
 | ||||
|     if (!statusIds && isLoading) { | ||||
|       return ( | ||||
|  | @ -83,6 +84,7 @@ export default class AccountTimeline extends ImmutablePureComponent { | |||
|           isLoading={isLoading} | ||||
|           hasMore={hasMore} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -8,8 +10,6 @@ import Column from '../ui/components/column'; | |||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import AccountContainer from '../../containers/account_container'; | ||||
| import { fetchBlocks, expandBlocks } from '../../actions/blocks'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }, | ||||
|  | @ -26,6 +26,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -43,7 +44,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, accountIds } = this.props; | ||||
|     const { intl, accountIds, shouldUpdateScroll } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|  | @ -56,7 +57,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|     return ( | ||||
|       <Column icon='ban' heading={intl.formatMessage(messages.heading)}> | ||||
|         <ColumnBackButtonSlim /> | ||||
|         <ScrollContainer scrollKey='blocks'> | ||||
|         <ScrollContainer scrollKey='blocks' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             {accountIds.map(id => | ||||
|               <AccountContainer key={id} id={id} /> | ||||
|  |  | |||
|  | @ -39,6 +39,7 @@ export default class CommunityTimeline extends React.PureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     columnId: PropTypes.string, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     hasUnread: PropTypes.bool, | ||||
|  | @ -100,7 +101,7 @@ export default class CommunityTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; | ||||
|     const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|     return ( | ||||
|  | @ -124,6 +125,7 @@ export default class CommunityTimeline extends React.PureComponent { | |||
|           timelineId={`community${onlyMedia ? ':media' : ''}`} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ export default class DirectTimeline extends React.PureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     columnId: PropTypes.string, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     hasUnread: PropTypes.bool, | ||||
|  | @ -71,7 +72,7 @@ export default class DirectTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, hasUnread, columnId, multiColumn } = this.props; | ||||
|     const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|     return ( | ||||
|  | @ -93,6 +94,7 @@ export default class DirectTimeline extends React.PureComponent { | |||
|           timelineId='direct' | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { debounce } from 'lodash'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import DomainContainer from '../../containers/domain_container'; | ||||
| import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { debounce } from 'lodash'; | ||||
| import ScrollableList from '../../components/scrollable_list'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|  | @ -28,6 +28,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     domains: ImmutablePropTypes.orderedSet, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -41,7 +42,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|   }, 300, { leading: true }); | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, domains } = this.props; | ||||
|     const { intl, domains, shouldUpdateScroll } = this.props; | ||||
| 
 | ||||
|     if (!domains) { | ||||
|       return ( | ||||
|  | @ -54,7 +55,7 @@ export default class Blocks extends ImmutablePureComponent { | |||
|     return ( | ||||
|       <Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}> | ||||
|         <ColumnBackButtonSlim /> | ||||
|         <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}> | ||||
|         <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           {domains.map(domain => | ||||
|             <DomainContainer key={domain} domain={domain} /> | ||||
|           )} | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     statusIds: ImmutablePropTypes.list.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     columnId: PropTypes.string, | ||||
|  | @ -67,7 +68,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
|   }, 300, { leading: true }) | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; | ||||
|     const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|     return ( | ||||
|  | @ -90,6 +91,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
|           hasMore={hasMore} | ||||
|           isLoading={isLoading} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4'; | |||
| import AccountContainer from '../../containers/account_container'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnBackButton from '../../components/column_back_button'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), | ||||
|  | @ -20,6 +20,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -34,7 +35,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accountIds } = this.props; | ||||
|     const { shouldUpdateScroll, accountIds } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|  | @ -48,7 +49,7 @@ export default class Favourites extends ImmutablePureComponent { | |||
|       <Column> | ||||
|         <ColumnBackButton /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='favourites'> | ||||
|         <ScrollContainer scrollKey='favourites' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable'> | ||||
|             {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} | ||||
|           </div> | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -8,8 +10,6 @@ import Column from '../ui/components/column'; | |||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import AccountAuthorizeContainer from './containers/account_authorize_container'; | ||||
| import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, | ||||
|  | @ -26,6 +26,7 @@ export default class FollowRequests extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -43,7 +44,7 @@ export default class FollowRequests extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, accountIds } = this.props; | ||||
|     const { intl, shouldUpdateScroll, accountIds } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|  | @ -57,7 +58,7 @@ export default class FollowRequests extends ImmutablePureComponent { | |||
|       <Column icon='users' heading={intl.formatMessage(messages.heading)}> | ||||
|         <ColumnBackButtonSlim /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='follow_requests'> | ||||
|         <ScrollContainer scrollKey='follow_requests' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             {accountIds.map(id => | ||||
|               <AccountAuthorizeContainer key={id} id={id} /> | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -14,7 +15,6 @@ import Column from '../ui/components/column'; | |||
| import HeaderContainer from '../account_timeline/containers/header_container'; | ||||
| import LoadMore from '../../components/load_more'; | ||||
| import ColumnBackButton from '../../components/column_back_button'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), | ||||
|  | @ -27,6 +27,7 @@ export default class Followers extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     hasMore: PropTypes.bool, | ||||
|   }; | ||||
|  | @ -57,7 +58,7 @@ export default class Followers extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accountIds, hasMore } = this.props; | ||||
|     const { shouldUpdateScroll, accountIds, hasMore } = this.props; | ||||
| 
 | ||||
|     let loadMore = null; | ||||
| 
 | ||||
|  | @ -77,7 +78,7 @@ export default class Followers extends ImmutablePureComponent { | |||
|       <Column> | ||||
|         <ColumnBackButton /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='followers'> | ||||
|         <ScrollContainer scrollKey='followers' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             <div className='followers'> | ||||
|               <HeaderContainer accountId={this.props.params.accountId} hideTabs /> | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -14,7 +15,6 @@ import Column from '../ui/components/column'; | |||
| import HeaderContainer from '../account_timeline/containers/header_container'; | ||||
| import LoadMore from '../../components/load_more'; | ||||
| import ColumnBackButton from '../../components/column_back_button'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), | ||||
|  | @ -27,6 +27,7 @@ export default class Following extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     hasMore: PropTypes.bool, | ||||
|   }; | ||||
|  | @ -57,7 +58,7 @@ export default class Following extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accountIds, hasMore } = this.props; | ||||
|     const { shouldUpdateScroll, accountIds, hasMore } = this.props; | ||||
| 
 | ||||
|     let loadMore = null; | ||||
| 
 | ||||
|  | @ -77,7 +78,7 @@ export default class Following extends ImmutablePureComponent { | |||
|       <Column> | ||||
|         <ColumnBackButton /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='following'> | ||||
|         <ScrollContainer scrollKey='following' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             <div className='following'> | ||||
|               <HeaderContainer accountId={this.props.params.accountId} hideTabs /> | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ export default class HashtagTimeline extends React.PureComponent { | |||
|     params: PropTypes.object.isRequired, | ||||
|     columnId: PropTypes.string, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     hasUnread: PropTypes.bool, | ||||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
|  | @ -83,7 +84,7 @@ export default class HashtagTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { hasUnread, columnId, multiColumn } = this.props; | ||||
|     const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; | ||||
|     const { id } = this.props.params; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|  | @ -107,6 +108,7 @@ export default class HashtagTimeline extends React.PureComponent { | |||
|           timelineId={`hashtag:${id}`} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ export default class HomeTimeline extends React.PureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     hasUnread: PropTypes.bool, | ||||
|     isPartial: PropTypes.bool, | ||||
|  | @ -93,7 +94,7 @@ export default class HomeTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, hasUnread, columnId, multiColumn } = this.props; | ||||
|     const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|     return ( | ||||
|  | @ -117,6 +118,7 @@ export default class HomeTimeline extends React.PureComponent { | |||
|           onLoadMore={this.handleLoadMore} | ||||
|           timelineId='home' | ||||
|           emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ export default class ListTimeline extends React.PureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     columnId: PropTypes.string, | ||||
|     hasUnread: PropTypes.bool, | ||||
|     multiColumn: PropTypes.bool, | ||||
|  | @ -112,7 +113,7 @@ export default class ListTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { hasUnread, columnId, multiColumn, list } = this.props; | ||||
|     const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list } = this.props; | ||||
|     const { id } = this.props.params; | ||||
|     const pinned = !!columnId; | ||||
|     const title  = list ? list.get('title') : id; | ||||
|  | @ -166,6 +167,7 @@ export default class ListTimeline extends React.PureComponent { | |||
|           timelineId={`list:${id}`} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -8,8 +10,6 @@ import Column from '../ui/components/column'; | |||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import AccountContainer from '../../containers/account_container'; | ||||
| import { fetchMutes, expandMutes } from '../../actions/mutes'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, | ||||
|  | @ -26,6 +26,7 @@ export default class Mutes extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -43,7 +44,7 @@ export default class Mutes extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, accountIds } = this.props; | ||||
|     const { intl, shouldUpdateScroll, accountIds } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|  | @ -56,7 +57,7 @@ export default class Mutes extends ImmutablePureComponent { | |||
|     return ( | ||||
|       <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}> | ||||
|         <ColumnBackButtonSlim /> | ||||
|         <ScrollContainer scrollKey='mutes'> | ||||
|         <ScrollContainer scrollKey='mutes' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable mutes' onScroll={this.handleScroll}> | ||||
|             {accountIds.map(id => | ||||
|               <AccountContainer key={id} id={id} /> | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     statusIds: ImmutablePropTypes.list.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     hasMore: PropTypes.bool.isRequired, | ||||
|  | @ -42,7 +43,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, statusIds, hasMore } = this.props; | ||||
|     const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}> | ||||
|  | @ -51,6 +52,7 @@ export default class PinnedStatuses extends ImmutablePureComponent { | |||
|           statusIds={statusIds} | ||||
|           scrollKey='pinned_statuses' | ||||
|           hasMore={hasMore} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -39,6 +39,7 @@ export default class PublicTimeline extends React.PureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     columnId: PropTypes.string, | ||||
|     multiColumn: PropTypes.bool, | ||||
|  | @ -107,7 +108,7 @@ export default class PublicTimeline extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; | ||||
|     const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props; | ||||
|     const pinned = !!columnId; | ||||
| 
 | ||||
|     return ( | ||||
|  | @ -131,6 +132,7 @@ export default class PublicTimeline extends React.PureComponent { | |||
|           trackScroll={!pinned} | ||||
|           scrollKey={`public_timeline-${columnId}`} | ||||
|           emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} | ||||
|           shouldUpdateScroll={shouldUpdateScroll} | ||||
|         /> | ||||
|       </Column> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
|  | @ -8,7 +9,6 @@ import { ScrollContainer } from 'react-router-scroll-4'; | |||
| import AccountContainer from '../../containers/account_container'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnBackButton from '../../components/column_back_button'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), | ||||
|  | @ -20,6 +20,7 @@ export default class Reblogs extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     params: PropTypes.object.isRequired, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     shouldUpdateScroll: PropTypes.func, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -34,7 +35,7 @@ export default class Reblogs extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accountIds } = this.props; | ||||
|     const { shouldUpdateScroll, accountIds } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|  | @ -48,7 +49,7 @@ export default class Reblogs extends ImmutablePureComponent { | |||
|       <Column> | ||||
|         <ColumnBackButton /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='reblogs'> | ||||
|         <ScrollContainer scrollKey='reblogs' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className='scrollable reblogs'> | ||||
|             {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} | ||||
|           </div> | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { HotKeys } from 'react-hotkeys'; | ||||
| import { boostModal, deleteModal } from '../../initial_state'; | ||||
| import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen'; | ||||
| import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, | ||||
|  | @ -370,7 +370,7 @@ export default class Status extends ImmutablePureComponent { | |||
| 
 | ||||
|   render () { | ||||
|     let ancestors, descendants; | ||||
|     const { status, ancestorsIds, descendantsIds, intl } = this.props; | ||||
|     const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props; | ||||
|     const { fullscreen } = this.state; | ||||
| 
 | ||||
|     if (status === null) { | ||||
|  | @ -410,7 +410,7 @@ export default class Status extends ImmutablePureComponent { | |||
|           )} | ||||
|         /> | ||||
| 
 | ||||
|         <ScrollContainer scrollKey='thread'> | ||||
|         <ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}> | ||||
|           <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}> | ||||
|             {ancestors} | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ const messages = defineMessages({ | |||
|   next: { id: 'lightbox.next', defaultMessage: 'Next' }, | ||||
| }); | ||||
| 
 | ||||
| const previewState = 'previewMediaModal'; | ||||
| export const previewState = 'previewMediaModal'; | ||||
| 
 | ||||
| @injectIntl | ||||
| export default class MediaModal extends ImmutablePureComponent { | ||||
|  |  | |||
|  | @ -41,14 +41,15 @@ export default class ModalRoot extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   getSnapshotBeforeUpdate () { | ||||
|     const visible = !!this.props.type; | ||||
|     return { | ||||
|       overflowY: visible ? 'hidden' : null, | ||||
|     }; | ||||
|     return { visible: !!this.props.type }; | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate (prevProps, prevState, { overflowY }) { | ||||
|     document.body.style.overflowY = overflowY; | ||||
|   componentDidUpdate (prevProps, prevState, { visible }) { | ||||
|     if (visible) { | ||||
|       document.body.classList.add('with-modals--active'); | ||||
|     } else { | ||||
|       document.body.classList.remove('with-modals--active'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   renderLoading = modalId => () => { | ||||
|  |  | |||
|  | @ -1,12 +1,14 @@ | |||
| import classNames from 'classnames'; | ||||
| import React from 'react'; | ||||
| import NotificationsContainer from './containers/notifications_container'; | ||||
| import { HotKeys } from 'react-hotkeys'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Redirect, withRouter } from 'react-router-dom'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import NotificationsContainer from './containers/notifications_container'; | ||||
| import LoadingBarContainer from './containers/loading_bar_container'; | ||||
| import TabsBar from './components/tabs_bar'; | ||||
| import ModalContainer from './containers/modal_container'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Redirect, withRouter } from 'react-router-dom'; | ||||
| import { isMobile } from '../../is_mobile'; | ||||
| import { debounce } from 'lodash'; | ||||
| import { uploadCompose, resetCompose } from '../../actions/compose'; | ||||
|  | @ -44,9 +46,8 @@ import { | |||
|   PinnedStatuses, | ||||
|   Lists, | ||||
| } from './util/async-components'; | ||||
| import { HotKeys } from 'react-hotkeys'; | ||||
| import { me } from '../../initial_state'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { previewState } from './components/media_modal'; | ||||
| 
 | ||||
| // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | ||||
| // Without this it ends up in ~8 very commonly used bundles.
 | ||||
|  | @ -117,6 +118,10 @@ class SwitchingColumnsArea extends React.PureComponent { | |||
|     window.removeEventListener('resize', this.handleResize); | ||||
|   } | ||||
| 
 | ||||
|   shouldUpdateScroll (_, { location }) { | ||||
|     return location.state !== previewState; | ||||
|   } | ||||
| 
 | ||||
|   handleResize = debounce(() => { | ||||
|     // The cached heights are no longer accurate, invalidate
 | ||||
|     this.props.onLayoutChange(); | ||||
|  | @ -141,36 +146,36 @@ class SwitchingColumnsArea extends React.PureComponent { | |||
|           {redirect} | ||||
|           <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> | ||||
|           <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> | ||||
|           <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ onlyMedia: true }} /> | ||||
|           <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ onlyMedia: true }} /> | ||||
|           <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} /> | ||||
|           <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/timelines/public/media' component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} /> | ||||
|           <WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, onlyMedia: true }} /> | ||||
|           <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
| 
 | ||||
|           <WrappedRoute path='/notifications' component={Notifications} content={children} /> | ||||
|           <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} /> | ||||
|           <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> | ||||
|           <WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
| 
 | ||||
|           <WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} /> | ||||
| 
 | ||||
|           <WrappedRoute path='/statuses/new' component={Compose} content={children} /> | ||||
|           <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} /> | ||||
|           <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} /> | ||||
|           <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> | ||||
|           <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
| 
 | ||||
|           <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> | ||||
|           <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
| 
 | ||||
|           <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} /> | ||||
|           <WrappedRoute path='/blocks' component={Blocks} content={children} /> | ||||
|           <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} /> | ||||
|           <WrappedRoute path='/mutes' component={Mutes} content={children} /> | ||||
|           <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} /> | ||||
|           <WrappedRoute path='/lists' component={Lists} content={children} /> | ||||
| 
 | ||||
|           <WrappedRoute component={GenericNotFound} content={children} /> | ||||
|  |  | |||
|  | @ -1,17 +1,17 @@ | |||
| { | ||||
|   "account.badges.bot": "Robot", | ||||
|   "account.block": "Blokovat @{name}", | ||||
|   "account.block": "Zablokovat uživatele @{name}", | ||||
|   "account.block_domain": "Skrýt vše z {domain}", | ||||
|   "account.blocked": "Blokován/a", | ||||
|   "account.direct": "Přímá zpráva pro uživatele @{name}", | ||||
|   "account.disclaimer_full": "Níže uvedené informace nemusejí zcela odrážet profil uživatele.", | ||||
|   "account.domain_blocked": "Doména skryta", | ||||
|   "account.edit_profile": "Uprav profil", | ||||
|   "account.edit_profile": "Upravit profil", | ||||
|   "account.follow": "Sleduj", | ||||
|   "account.followers": "Sledovatelé", | ||||
|   "account.follows": "Sleduje", | ||||
|   "account.follows_you": "Sleduje vás", | ||||
|   "account.hide_reblogs": "Skrýt povýšení od uživatele @{name}", | ||||
|   "account.hide_reblogs": "Skrýt boosty od uživatele @{name}", | ||||
|   "account.media": "Média", | ||||
|   "account.mention": "Zmínit uživatele @{name}", | ||||
|   "account.moved_to": "{name} se přesunul/a na:", | ||||
|  | @ -23,7 +23,7 @@ | |||
|   "account.report": "Nahlásit uživatele @{name}", | ||||
|   "account.requested": "Požadavek čeká na schválení. Kliknutím zrušíte požadavek o sledování", | ||||
|   "account.share": "Sdílet profil uživatele @{name}", | ||||
|   "account.show_reblogs": "Zobrazit povýšení od uživatele @{name}", | ||||
|   "account.show_reblogs": "Zobrazit boosty od uživatele @{name}", | ||||
|   "account.unblock": "Odblokovat uživatele @{name}", | ||||
|   "account.unblock_domain": "Odkrýt doménu {domain}", | ||||
|   "account.unfollow": "Přestat sledovat", | ||||
|  | @ -64,7 +64,7 @@ | |||
|   "compose_form.direct_message_warning_learn_more": "Zjistit více", | ||||
|   "compose_form.hashtag_warning": "Tento toot nebude zobrazen pod žádným hashtagem, neboť je neuvedený. Pouze veřejné tooty mohou být vyhledány podle hashtagu.", | ||||
|   "compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledovatele.", | ||||
|   "compose_form.lock_disclaimer.lock": "zamknutý", | ||||
|   "compose_form.lock_disclaimer.lock": "zamčený", | ||||
|   "compose_form.placeholder": "Co máte na mysli?", | ||||
|   "compose_form.publish": "Tootnout", | ||||
|   "compose_form.publish_loud": "{publish}!", | ||||
|  | @ -85,225 +85,225 @@ | |||
|   "confirmations.mute.confirm": "Ignorovat", | ||||
|   "confirmations.mute.message": "Jste si jistý/á, že chcete ignorovat uživatele {name}?", | ||||
|   "confirmations.redraft.confirm": "Vymazat a přepsat", | ||||
|   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.", | ||||
|   "confirmations.unfollow.confirm": "Unfollow", | ||||
|   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", | ||||
|   "embed.instructions": "Embed this status on your website by copying the code below.", | ||||
|   "embed.preview": "Here is what it will look like:", | ||||
|   "emoji_button.activity": "Activity", | ||||
|   "emoji_button.custom": "Custom", | ||||
|   "emoji_button.flags": "Flags", | ||||
|   "emoji_button.food": "Food & Drink", | ||||
|   "emoji_button.label": "Insert emoji", | ||||
|   "emoji_button.nature": "Nature", | ||||
|   "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", | ||||
|   "emoji_button.objects": "Objects", | ||||
|   "emoji_button.people": "People", | ||||
|   "emoji_button.recent": "Frequently used", | ||||
|   "emoji_button.search": "Search...", | ||||
|   "emoji_button.search_results": "Search results", | ||||
|   "emoji_button.symbols": "Symbols", | ||||
|   "emoji_button.travel": "Travel & Places", | ||||
|   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", | ||||
|   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", | ||||
|   "empty_column.hashtag": "There is nothing in this hashtag yet.", | ||||
|   "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", | ||||
|   "empty_column.home.public_timeline": "the public timeline", | ||||
|   "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", | ||||
|   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", | ||||
|   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", | ||||
|   "follow_request.authorize": "Authorize", | ||||
|   "follow_request.reject": "Reject", | ||||
|   "getting_started.developers": "Developers", | ||||
|   "confirmations.redraft.message": "Jste si jistý/á, že chcete vymazat a přepsat tento status? Ztratíte všechny jeho odpovědi, boosty a oblíbení.", | ||||
|   "confirmations.unfollow.confirm": "Přestat sledovat", | ||||
|   "confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?", | ||||
|   "embed.instructions": "Pro přidání statusu na vaši webovou stránku zkopírujte níže uvedený kód.", | ||||
|   "embed.preview": "Takhle to bude vypadat:", | ||||
|   "emoji_button.activity": "Aktivita", | ||||
|   "emoji_button.custom": "Vlastní", | ||||
|   "emoji_button.flags": "Vlajky", | ||||
|   "emoji_button.food": "Jídla a nápoje", | ||||
|   "emoji_button.label": "Vložit emoji", | ||||
|   "emoji_button.nature": "Příroda", | ||||
|   "emoji_button.not_found": "Žádné emoji!! (╯°□°)╯︵ ┻━┻", | ||||
|   "emoji_button.objects": "Předměty", | ||||
|   "emoji_button.people": "Lidé", | ||||
|   "emoji_button.recent": "Často používané", | ||||
|   "emoji_button.search": "Hledat...", | ||||
|   "emoji_button.search_results": "Výsledky hledání", | ||||
|   "emoji_button.symbols": "Symboly", | ||||
|   "emoji_button.travel": "Cestování a místa", | ||||
|   "empty_column.community": "Místní časová osa je prázdná. Napište něco veřejně a rozhýbejte to tu!", | ||||
|   "empty_column.direct": "Ještě nemáte žádné přímé zprávy. Pokud nějakou pošlete nebo dostanete, zobrazí se zde.", | ||||
|   "empty_column.hashtag": "Pod tímto hashtagem ještě nic není.", | ||||
|   "empty_column.home": "Vaše domovská časová osa je prázdná! Začněte navštívením {public} nebo použijte hledání a seznamte se s dalšími uživateli.", | ||||
|   "empty_column.home.public_timeline": "veřejné časové osy", | ||||
|   "empty_column.list": "V tomto seznamu ještě nic není. Pokud budou členové tohoto seznamu psát nové statusy, objeví se zde.", | ||||
|   "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.", | ||||
|   "empty_column.public": "Tady nic není! Napište něco veřejně, nebo manuálně začněte sledovat uživatele z jiných instancí, aby tu něco přibylo", | ||||
|   "follow_request.authorize": "Autorizovat", | ||||
|   "follow_request.reject": "Odmítnout", | ||||
|   "getting_started.developers": "Vývojáři", | ||||
|   "getting_started.documentation": "Documentation", | ||||
|   "getting_started.find_friends": "Find friends from Twitter", | ||||
|   "getting_started.heading": "Getting started", | ||||
|   "getting_started.invite": "Invite people", | ||||
|   "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", | ||||
|   "getting_started.security": "Security", | ||||
|   "getting_started.terms": "Terms of service", | ||||
|   "home.column_settings.basic": "Basic", | ||||
|   "home.column_settings.show_reblogs": "Show boosts", | ||||
|   "home.column_settings.show_replies": "Show replies", | ||||
|   "keyboard_shortcuts.back": "to navigate back", | ||||
|   "keyboard_shortcuts.boost": "to boost", | ||||
|   "keyboard_shortcuts.column": "to focus a status in one of the columns", | ||||
|   "keyboard_shortcuts.compose": "to focus the compose textarea", | ||||
|   "keyboard_shortcuts.description": "Description", | ||||
|   "keyboard_shortcuts.down": "to move down in the list", | ||||
|   "keyboard_shortcuts.enter": "to open status", | ||||
|   "keyboard_shortcuts.favourite": "to favourite", | ||||
|   "keyboard_shortcuts.heading": "Keyboard Shortcuts", | ||||
|   "keyboard_shortcuts.hotkey": "Hotkey", | ||||
|   "keyboard_shortcuts.legend": "to display this legend", | ||||
|   "keyboard_shortcuts.mention": "to mention author", | ||||
|   "getting_started.find_friends": "Najděte si přátele z Twitteru", | ||||
|   "getting_started.heading": "Začínáme", | ||||
|   "getting_started.invite": "Pozvat lidi", | ||||
|   "getting_started.open_source_notice": "Mastodon je otevřený software. Na GitHubu k němu můžete přispět nebo nahlásit chyby: {github}.", | ||||
|   "getting_started.security": "Zabezpečení", | ||||
|   "getting_started.terms": "Podmínky používání", | ||||
|   "home.column_settings.basic": "Základní", | ||||
|   "home.column_settings.show_reblogs": "Zobrazit boosty", | ||||
|   "home.column_settings.show_replies": "Zobrazit odpovědi", | ||||
|   "keyboard_shortcuts.back": "k návratu zpět", | ||||
|   "keyboard_shortcuts.boost": "k boostnutí", | ||||
|   "keyboard_shortcuts.column": "k zaměření na status v jednom ze sloupců", | ||||
|   "keyboard_shortcuts.compose": "k zaměření na psací prostor", | ||||
|   "keyboard_shortcuts.description": "Popis", | ||||
|   "keyboard_shortcuts.down": "k přesunutí dolů v seznamu", | ||||
|   "keyboard_shortcuts.enter": "k otevření statusu", | ||||
|   "keyboard_shortcuts.favourite": "k oblíbení", | ||||
|   "keyboard_shortcuts.heading": "Klávesové zkratky", | ||||
|   "keyboard_shortcuts.hotkey": "Horká klávesa", | ||||
|   "keyboard_shortcuts.legend": "k zobrazení této legendy", | ||||
|   "keyboard_shortcuts.mention": "ke zmínění autora", | ||||
|   "keyboard_shortcuts.profile": "to open author's profile", | ||||
|   "keyboard_shortcuts.reply": "to reply", | ||||
|   "keyboard_shortcuts.search": "to focus search", | ||||
|   "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", | ||||
|   "keyboard_shortcuts.toot": "to start a brand new toot", | ||||
|   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", | ||||
|   "keyboard_shortcuts.up": "to move up in the list", | ||||
|   "lightbox.close": "Close", | ||||
|   "lightbox.next": "Next", | ||||
|   "lightbox.previous": "Previous", | ||||
|   "lists.account.add": "Add to list", | ||||
|   "lists.account.remove": "Remove from list", | ||||
|   "lists.delete": "Delete list", | ||||
|   "lists.edit": "Edit list", | ||||
|   "lists.new.create": "Add list", | ||||
|   "lists.new.title_placeholder": "New list title", | ||||
|   "lists.search": "Search among people you follow", | ||||
|   "lists.subheading": "Your lists", | ||||
|   "loading_indicator.label": "Loading...", | ||||
|   "media_gallery.toggle_visible": "Toggle visibility", | ||||
|   "missing_indicator.label": "Not found", | ||||
|   "missing_indicator.sublabel": "This resource could not be found", | ||||
|   "mute_modal.hide_notifications": "Hide notifications from this user?", | ||||
|   "navigation_bar.blocks": "Blocked users", | ||||
|   "navigation_bar.community_timeline": "Local timeline", | ||||
|   "navigation_bar.direct": "Direct messages", | ||||
|   "navigation_bar.discover": "Discover", | ||||
|   "navigation_bar.domain_blocks": "Hidden domains", | ||||
|   "navigation_bar.edit_profile": "Edit profile", | ||||
|   "navigation_bar.favourites": "Favourites", | ||||
|   "navigation_bar.filters": "Muted words", | ||||
|   "navigation_bar.follow_requests": "Follow requests", | ||||
|   "navigation_bar.info": "About this instance", | ||||
|   "navigation_bar.keyboard_shortcuts": "Hotkeys", | ||||
|   "navigation_bar.lists": "Lists", | ||||
|   "navigation_bar.logout": "Logout", | ||||
|   "navigation_bar.mutes": "Muted users", | ||||
|   "navigation_bar.personal": "Personal", | ||||
|   "navigation_bar.pins": "Pinned toots", | ||||
|   "navigation_bar.preferences": "Preferences", | ||||
|   "navigation_bar.public_timeline": "Federated timeline", | ||||
|   "navigation_bar.security": "Security", | ||||
|   "notification.favourite": "{name} favourited your status", | ||||
|   "notification.follow": "{name} followed you", | ||||
|   "notification.mention": "{name} mentioned you", | ||||
|   "notification.reblog": "{name} boosted your status", | ||||
|   "notifications.clear": "Clear notifications", | ||||
|   "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", | ||||
|   "notifications.column_settings.alert": "Desktop notifications", | ||||
|   "notifications.column_settings.favourite": "Favourites:", | ||||
|   "notifications.column_settings.follow": "New followers:", | ||||
|   "notifications.column_settings.mention": "Mentions:", | ||||
|   "notifications.column_settings.push": "Push notifications", | ||||
|   "notifications.column_settings.push_meta": "This device", | ||||
|   "notifications.column_settings.reblog": "Boosts:", | ||||
|   "notifications.column_settings.show": "Show in column", | ||||
|   "notifications.column_settings.sound": "Play sound", | ||||
|   "notifications.group": "{count} notifications", | ||||
|   "onboarding.done": "Done", | ||||
|   "onboarding.next": "Next", | ||||
|   "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", | ||||
|   "onboarding.page_four.home": "The home timeline shows posts from people you follow.", | ||||
|   "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", | ||||
|   "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", | ||||
|   "onboarding.page_one.full_handle": "Your full handle", | ||||
|   "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", | ||||
|   "onboarding.page_one.welcome": "Welcome to Mastodon!", | ||||
|   "onboarding.page_six.admin": "Your instance's admin is {admin}.", | ||||
|   "onboarding.page_six.almost_done": "Almost done...", | ||||
|   "onboarding.page_six.appetoot": "Bon Appetoot!", | ||||
|   "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", | ||||
|   "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", | ||||
|   "onboarding.page_six.guidelines": "community guidelines", | ||||
|   "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", | ||||
|   "onboarding.page_six.various_app": "mobile apps", | ||||
|   "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", | ||||
|   "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", | ||||
|   "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", | ||||
|   "onboarding.skip": "Skip", | ||||
|   "privacy.change": "Adjust status privacy", | ||||
|   "privacy.direct.long": "Post to mentioned users only", | ||||
|   "privacy.direct.short": "Direct", | ||||
|   "privacy.private.long": "Post to followers only", | ||||
|   "privacy.private.short": "Followers-only", | ||||
|   "privacy.public.long": "Post to public timelines", | ||||
|   "privacy.public.short": "Public", | ||||
|   "keyboard_shortcuts.reply": "k odpovězení", | ||||
|   "keyboard_shortcuts.search": "k zaměření na vyhledávání", | ||||
|   "keyboard_shortcuts.toggle_hidden": "k zobrazení/skrytí textu za CW", | ||||
|   "keyboard_shortcuts.toot": "k napsání úplně nového tootu", | ||||
|   "keyboard_shortcuts.unfocus": "ke zrušení soustředění na psací prostor/hledání", | ||||
|   "keyboard_shortcuts.up": "k posunutí nahoru v seznamu", | ||||
|   "lightbox.close": "Zavřít", | ||||
|   "lightbox.next": "Další", | ||||
|   "lightbox.previous": "Předchozí", | ||||
|   "lists.account.add": "Přidat do seznamu", | ||||
|   "lists.account.remove": "Odebrat ze seznamu", | ||||
|   "lists.delete": "Smazat seznam", | ||||
|   "lists.edit": "Upravit seznam", | ||||
|   "lists.new.create": "Přidat seznam", | ||||
|   "lists.new.title_placeholder": "Název nového seznamu", | ||||
|   "lists.search": "Hledejte mezi uživateli, které sledujete", | ||||
|   "lists.subheading": "Vaše seznamy", | ||||
|   "loading_indicator.label": "Načítám...", | ||||
|   "media_gallery.toggle_visible": "Přepínat viditelnost", | ||||
|   "missing_indicator.label": "Nenalezeno", | ||||
|   "missing_indicator.sublabel": "Tento zdroj se nepodažilo najít", | ||||
|   "mute_modal.hide_notifications": "Skrýt oznámení před tímto uživatelem?", | ||||
|   "navigation_bar.blocks": "Blokovaní uživatelé", | ||||
|   "navigation_bar.community_timeline": "Místní časová osa", | ||||
|   "navigation_bar.direct": "Přímé zprávy", | ||||
|   "navigation_bar.discover": "Objevujte", | ||||
|   "navigation_bar.domain_blocks": "Skryté domény", | ||||
|   "navigation_bar.edit_profile": "Upravit profil", | ||||
|   "navigation_bar.favourites": "Oblíbené", | ||||
|   "navigation_bar.filters": "Skrytá slova", | ||||
|   "navigation_bar.follow_requests": "Žádosti o sledování", | ||||
|   "navigation_bar.info": "O této instanci", | ||||
|   "navigation_bar.keyboard_shortcuts": "Klávesové zkratky", | ||||
|   "navigation_bar.lists": "Seznamy", | ||||
|   "navigation_bar.logout": "Odhlásit se", | ||||
|   "navigation_bar.mutes": "Ignorovaní uživatelé", | ||||
|   "navigation_bar.personal": "Osobní", | ||||
|   "navigation_bar.pins": "Připnuté tooty", | ||||
|   "navigation_bar.preferences": "Předvolby", | ||||
|   "navigation_bar.public_timeline": "Federovaná časová osa", | ||||
|   "navigation_bar.security": "Zabezpečení", | ||||
|   "notification.favourite": "{name} označil/a váš status jako oblíbený", | ||||
|   "notification.follow": "{name} vás začal/a sledovat", | ||||
|   "notification.mention": "{name} vás zmínil/a", | ||||
|   "notification.reblog": "{name} vám boostnul/a status", | ||||
|   "notifications.clear": "Vymazat oznámení", | ||||
|   "notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?", | ||||
|   "notifications.column_settings.alert": "Desktopová oznámení", | ||||
|   "notifications.column_settings.favourite": "Oblíbené:", | ||||
|   "notifications.column_settings.follow": "Noví sledovatelé:", | ||||
|   "notifications.column_settings.mention": "Zmínky:", | ||||
|   "notifications.column_settings.push": "Push oznámení", | ||||
|   "notifications.column_settings.push_meta": "Toto zařízení", | ||||
|   "notifications.column_settings.reblog": "Boosty:", | ||||
|   "notifications.column_settings.show": "Zobrazit ve sloupci", | ||||
|   "notifications.column_settings.sound": "Přehrát zvuk", | ||||
|   "notifications.group": "{count} oznámení", | ||||
|   "onboarding.done": "Hotovo", | ||||
|   "onboarding.next": "Další", | ||||
|   "onboarding.page_five.public_timelines": "Místní časová osa zobrazuje veřejné příspěvky od všech lidí na {domain}. Federovaná časová osa zobrazuje veřejné příspěvky ode všech, které lidé na {domain} sledují. Toto jsou veřejné časové osy, výborný způsob, jak objevovat nové lidi.", | ||||
|   "onboarding.page_four.home": "Domovská časová osa zobrazuje příspěvky od lidí, které sledujete.", | ||||
|   "onboarding.page_four.notifications": "Sloupec oznámení se zobrazí, když s vámi někdo bude komunikovat.", | ||||
|   "onboarding.page_one.federation": "Mastodon je síť nezávislých serverů, jejichž propojením vzniká jedna velká sociální síť. Těmto serverům říkáme instance.", | ||||
|   "onboarding.page_one.full_handle": "Vaše celá adresa profilu", | ||||
|   "onboarding.page_one.handle_hint": "Tohle je, co byste řekl/a svým přátelům, aby hledali.", | ||||
|   "onboarding.page_one.welcome": "Vítejte na Mastodonu!", | ||||
|   "onboarding.page_six.admin": "Administrátorem vaší instance je {admin}.", | ||||
|   "onboarding.page_six.almost_done": "Skoro hotovo...", | ||||
|   "onboarding.page_six.appetoot": "Bon appetoot!", | ||||
|   "onboarding.page_six.apps_available": "Jsou dostupné {apps} pro iOS, Android a jiné platformy.", | ||||
|   "onboarding.page_six.github": "Mastodon je svobodný a otevřený software. Na {github} můžete nahlásit chyby, požádat o nové funkce, nebo přispívat ke kódu.", | ||||
|   "onboarding.page_six.guidelines": "komunitní pravidla", | ||||
|   "onboarding.page_six.read_guidelines": "Prosím přečtěte si {guidelines} {domain}!", | ||||
|   "onboarding.page_six.various_app": "mobilní aplikace", | ||||
|   "onboarding.page_three.profile": "Upravte si svůj profil a změňte si svůj avatar, popis profilu a zobrazované jméno. V nastaveních najdete i další možnosti.", | ||||
|   "onboarding.page_three.search": "Pomocí vyhledávacího řádku najděte lidi a podívejte se na hashtagy jako {illustration} a {introductions}. Chcete-li najít někoho, kdo není na této instanci, použijte jeho celou adresu profilu.", | ||||
|   "onboarding.page_two.compose": "Příspěvky pište z pole na komponování. Ikonami níže můžete nahrávat obrázky, změnit nastavení soukromí a přidat varování o obsahu.", | ||||
|   "onboarding.skip": "Přeskočit", | ||||
|   "privacy.change": "Změnit viditelnost statusu", | ||||
|   "privacy.direct.long": "Odeslat pouze zmíněným uživatelům", | ||||
|   "privacy.direct.short": "Přímé", | ||||
|   "privacy.private.long": "Odeslat pouze sledovatelům", | ||||
|   "privacy.private.short": "Pouze pro sledovatele", | ||||
|   "privacy.public.long": "Odeslat na veřejné časové osy", | ||||
|   "privacy.public.short": "Veřejné", | ||||
|   "privacy.unlisted.long": "Do not show in public timelines", | ||||
|   "privacy.unlisted.short": "Unlisted", | ||||
|   "regeneration_indicator.label": "Loading…", | ||||
|   "regeneration_indicator.sublabel": "Your home feed is being prepared!", | ||||
|   "privacy.unlisted.short": "Nezobrazované", | ||||
|   "regeneration_indicator.label": "Načítám…", | ||||
|   "regeneration_indicator.sublabel": "Váš domovský proud se připravuje!", | ||||
|   "relative_time.days": "{number}d", | ||||
|   "relative_time.hours": "{number}h", | ||||
|   "relative_time.just_now": "now", | ||||
|   "relative_time.just_now": "teď", | ||||
|   "relative_time.minutes": "{number}m", | ||||
|   "relative_time.seconds": "{number}s", | ||||
|   "reply_indicator.cancel": "Cancel", | ||||
|   "report.forward": "Forward to {target}", | ||||
|   "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", | ||||
|   "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", | ||||
|   "report.placeholder": "Additional comments", | ||||
|   "report.submit": "Submit", | ||||
|   "report.target": "Report {target}", | ||||
|   "search.placeholder": "Search", | ||||
|   "search_popout.search_format": "Advanced search format", | ||||
|   "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", | ||||
|   "reply_indicator.cancel": "Zrušit", | ||||
|   "report.forward": "Přeposlat k {target}", | ||||
|   "report.forward_hint": "Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii?", | ||||
|   "report.hint": "Toto nahlášení bude zasláno moderátorům vaší instance. Níže můžete uvést, proč tento účet nahlašujete:", | ||||
|   "report.placeholder": "Další komentáře", | ||||
|   "report.submit": "Odeslat", | ||||
|   "report.target": "Nahlásit {target}", | ||||
|   "search.placeholder": "Hledat", | ||||
|   "search_popout.search_format": "Pokročilé vyhledávání", | ||||
|   "search_popout.tips.full_text": "Jednoduchý textový výpis statusů, které jste napsal/a, oblíbil/a si, povýšil/a, nebo v nich byl/a zmíněn/a, včetně odpovídajících přezdívek, jmen a hashtagů.", | ||||
|   "search_popout.tips.hashtag": "hashtag", | ||||
|   "search_popout.tips.status": "status", | ||||
|   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", | ||||
|   "search_popout.tips.user": "user", | ||||
|   "search_results.accounts": "People", | ||||
|   "search_results.hashtags": "Hashtags", | ||||
|   "search_results.statuses": "Toots", | ||||
|   "search_results.total": "{count, number} {count, plural, one {result} other {results}}", | ||||
|   "standalone.public_title": "A look inside...", | ||||
|   "status.block": "Block @{name}", | ||||
|   "status.cancel_reblog_private": "Unboost", | ||||
|   "status.cannot_reblog": "This post cannot be boosted", | ||||
|   "search_popout.tips.text": "Jednoduchý textový výpis odpovídajících jmen, přezdívek a hashtagů", | ||||
|   "search_popout.tips.user": "uživatel", | ||||
|   "search_results.accounts": "Lidé", | ||||
|   "search_results.hashtags": "Hashtagy", | ||||
|   "search_results.statuses": "Tooty", | ||||
|   "search_results.total": "{count, number} {count, plural, one {výsledek} other {výsledků}}", | ||||
|   "standalone.public_title": "Nahlédnout dovnitř...", | ||||
|   "status.block": "Zablokovat uživatele @{name}", | ||||
|   "status.cancel_reblog_private": "Zrušit boost", | ||||
|   "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý", | ||||
|   "status.delete": "Delete", | ||||
|   "status.direct": "Direct message @{name}", | ||||
|   "status.embed": "Embed", | ||||
|   "status.favourite": "Favourite", | ||||
|   "status.filtered": "Filtered", | ||||
|   "status.load_more": "Load more", | ||||
|   "status.media_hidden": "Media hidden", | ||||
|   "status.mention": "Mention @{name}", | ||||
|   "status.more": "More", | ||||
|   "status.mute": "Mute @{name}", | ||||
|   "status.mute_conversation": "Mute conversation", | ||||
|   "status.open": "Expand this status", | ||||
|   "status.pin": "Pin on profile", | ||||
|   "status.pinned": "Pinned toot", | ||||
|   "status.reblog": "Boost", | ||||
|   "status.reblog_private": "Boost to original audience", | ||||
|   "status.reblogged_by": "{name} boosted", | ||||
|   "status.redraft": "Delete & re-draft", | ||||
|   "status.reply": "Reply", | ||||
|   "status.replyAll": "Reply to thread", | ||||
|   "status.report": "Report @{name}", | ||||
|   "status.sensitive_toggle": "Click to view", | ||||
|   "status.sensitive_warning": "Sensitive content", | ||||
|   "status.share": "Share", | ||||
|   "status.show_less": "Show less", | ||||
|   "status.show_less_all": "Show less for all", | ||||
|   "status.show_more": "Show more", | ||||
|   "status.show_more_all": "Show more for all", | ||||
|   "status.unmute_conversation": "Unmute conversation", | ||||
|   "status.unpin": "Unpin from profile", | ||||
|   "tabs_bar.federated_timeline": "Federated", | ||||
|   "tabs_bar.home": "Home", | ||||
|   "tabs_bar.local_timeline": "Local", | ||||
|   "tabs_bar.notifications": "Notifications", | ||||
|   "tabs_bar.search": "Search", | ||||
|   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", | ||||
|   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", | ||||
|   "upload_area.title": "Drag & drop to upload", | ||||
|   "upload_button.label": "Add media", | ||||
|   "upload_form.description": "Describe for the visually impaired", | ||||
|   "upload_form.focus": "Crop", | ||||
|   "upload_form.undo": "Delete", | ||||
|   "upload_progress.label": "Uploading...", | ||||
|   "video.close": "Close video", | ||||
|   "video.exit_fullscreen": "Exit full screen", | ||||
|   "video.expand": "Expand video", | ||||
|   "video.fullscreen": "Full screen", | ||||
|   "video.hide": "Hide video", | ||||
|   "video.mute": "Mute sound", | ||||
|   "video.pause": "Pause", | ||||
|   "video.play": "Play", | ||||
|   "video.unmute": "Unmute sound" | ||||
|   "status.direct": "Poslat přímou zprávu uživateli @{name}", | ||||
|   "status.embed": "Vložit", | ||||
|   "status.favourite": "Oblíbit", | ||||
|   "status.filtered": "Filtrováno", | ||||
|   "status.load_more": "Zobrazit více", | ||||
|   "status.media_hidden": "Média skryta", | ||||
|   "status.mention": "Zmínit uživatele @{name}", | ||||
|   "status.more": "Více", | ||||
|   "status.mute": "Ignorovat uživatele @{name}", | ||||
|   "status.mute_conversation": "Ignorovat konverzaci", | ||||
|   "status.open": "Otevřít tento status", | ||||
|   "status.pin": "Připnout na profil", | ||||
|   "status.pinned": "Připnutý toot", | ||||
|   "status.reblog": "Boostnout", | ||||
|   "status.reblog_private": "Boostnout původnímu publiku", | ||||
|   "status.reblogged_by": "{name} boostnul/a", | ||||
|   "status.redraft": "Vymazat a přepsat", | ||||
|   "status.reply": "Odpovědět", | ||||
|   "status.replyAll": "Odpovědět na vlákno", | ||||
|   "status.report": "Nahlásit uživatele @{name}", | ||||
|   "status.sensitive_toggle": "Klikněte pro zobrazení", | ||||
|   "status.sensitive_warning": "Citlivý obsah", | ||||
|   "status.share": "Sdílet", | ||||
|   "status.show_less": "Zobrazit méně", | ||||
|   "status.show_less_all": "Zobrazit méně pro všechny", | ||||
|   "status.show_more": "Zobrazit více", | ||||
|   "status.show_more_all": "Zobrazit více pro všechny", | ||||
|   "status.unmute_conversation": "Přestat ignorovat konverzaci", | ||||
|   "status.unpin": "Odepnout z profilu", | ||||
|   "tabs_bar.federated_timeline": "Federovaná", | ||||
|   "tabs_bar.home": "Domů", | ||||
|   "tabs_bar.local_timeline": "Místní", | ||||
|   "tabs_bar.notifications": "Oznámení", | ||||
|   "tabs_bar.search": "Hledat", | ||||
|   "trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} other {lidí}} diskutuje", | ||||
|   "ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.", | ||||
|   "upload_area.title": "Přetažením nahrajete", | ||||
|   "upload_button.label": "Přidat média", | ||||
|   "upload_form.description": "Popis pro zrakově postižené", | ||||
|   "upload_form.focus": "Vystřihnout", | ||||
|   "upload_form.undo": "Smazat", | ||||
|   "upload_progress.label": "Nahrávám...", | ||||
|   "video.close": "Zavřít video", | ||||
|   "video.exit_fullscreen": "Ukončit celou obrazovku", | ||||
|   "video.expand": "Otevřít video", | ||||
|   "video.fullscreen": "Celá obrazovka", | ||||
|   "video.hide": "Skrýt video", | ||||
|   "video.mute": "Vypnout zvuk", | ||||
|   "video.pause": "Pauza", | ||||
|   "video.play": "Přehrát", | ||||
|   "video.unmute": "Zapnout zvuk" | ||||
| } | ||||
|  |  | |||
|  | @ -167,7 +167,7 @@ | |||
|   "navigation_bar.domain_blocks": "숨겨진 도메인", | ||||
|   "navigation_bar.edit_profile": "프로필 편집", | ||||
|   "navigation_bar.favourites": "즐겨찾기", | ||||
|   "navigation_bar.filters": "Muted words", | ||||
|   "navigation_bar.filters": "뮤트", | ||||
|   "navigation_bar.follow_requests": "팔로우 요청", | ||||
|   "navigation_bar.info": "이 인스턴스에 대해서", | ||||
|   "navigation_bar.keyboard_shortcuts": "단축키", | ||||
|  |  | |||
|  | @ -129,11 +129,11 @@ | |||
|   "keyboard_shortcuts.boost": "para compartilhar", | ||||
|   "keyboard_shortcuts.column": "Focar um status em uma das colunas", | ||||
|   "keyboard_shortcuts.compose": "para focar a área de redação", | ||||
|   "keyboard_shortcuts.description": "Description", | ||||
|   "keyboard_shortcuts.description": "Descrição", | ||||
|   "keyboard_shortcuts.down": "para mover para baixo na lista", | ||||
|   "keyboard_shortcuts.enter": "to open status", | ||||
|   "keyboard_shortcuts.enter": "para expandir um status", | ||||
|   "keyboard_shortcuts.favourite": "para adicionar aos favoritos", | ||||
|   "keyboard_shortcuts.heading": "Keyboard Shortcuts", | ||||
|   "keyboard_shortcuts.heading": "Atalhos de teclado", | ||||
|   "keyboard_shortcuts.hotkey": "Atalho", | ||||
|   "keyboard_shortcuts.legend": "para mostrar essa legenda", | ||||
|   "keyboard_shortcuts.mention": "para mencionar o autor", | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ | |||
|   "compose_form.hashtag_warning": "ఈ టూట్ అన్లిస్టెడ్ కాబట్టి ఏ హాష్ ట్యాగ్ క్రిందకూ రాదు. పబ్లిక్ టూట్ లను మాత్రమే హాష్ ట్యాగ్ ద్వారా శోధించవచ్చు.", | ||||
|   "compose_form.lock_disclaimer": "మీ ఖాతా {locked} చేయబడలేదు. ఎవరైనా మిమ్మల్ని అనుసరించి మీ అనుచరులకు-మాత్రమే పోస్ట్లను వీక్షించవచ్చు.", | ||||
|   "compose_form.lock_disclaimer.lock": "బిగించబడినది", | ||||
|   "compose_form.placeholder": "మీ మనస్సులో ఏమి ఉంది?", | ||||
|   "compose_form.placeholder": "మీ మనస్సులో ఏముంది?", | ||||
|   "compose_form.publish": "టూట్", | ||||
|   "compose_form.publish_loud": "{publish}!", | ||||
|   "compose_form.sensitive.marked": "మీడియా సున్నితమైనదిగా గుర్తించబడింది", | ||||
|  | @ -115,7 +115,7 @@ | |||
|   "follow_request.authorize": "అనుమతించు", | ||||
|   "follow_request.reject": "తిరస్కరించు", | ||||
|   "getting_started.developers": "డెవలపర్లు", | ||||
|   "getting_started.documentation": "Documentation", | ||||
|   "getting_started.documentation": "డాక్యుమెంటేషన్", | ||||
|   "getting_started.find_friends": "ట్విట్టర్ నుండి స్నేహితులను కనుగొనండి", | ||||
|   "getting_started.heading": "మొదలుపెడదాం", | ||||
|   "getting_started.invite": "వ్యక్తులను ఆహ్వానించండి", | ||||
|  | @ -167,7 +167,7 @@ | |||
|   "navigation_bar.domain_blocks": "దాచిన డొమైన్లు", | ||||
|   "navigation_bar.edit_profile": "ప్రొఫైల్ని సవరించండి", | ||||
|   "navigation_bar.favourites": "ఇష్టపడినవి", | ||||
|   "navigation_bar.filters": "Muted words", | ||||
|   "navigation_bar.filters": "మ్యూట్ చేయబడిన పదాలు", | ||||
|   "navigation_bar.follow_requests": "అనుసరించడానికి అభ్యర్ధనలు", | ||||
|   "navigation_bar.info": "ఈ దృష్టాంతం గురించి", | ||||
|   "navigation_bar.keyboard_shortcuts": "హాట్ కీలు", | ||||
|  | @ -258,7 +258,7 @@ | |||
|   "status.direct": "@{name}కు నేరుగా సందేశం పంపు", | ||||
|   "status.embed": "ఎంబెడ్", | ||||
|   "status.favourite": "ఇష్టపడు", | ||||
|   "status.filtered": "Filtered", | ||||
|   "status.filtered": "వడకట్టబడిన", | ||||
|   "status.load_more": "మరిన్ని లోడ్ చేయి", | ||||
|   "status.media_hidden": "మీడియా దాచబడింది", | ||||
|   "status.mention": "@{name}ను ప్రస్తావించు", | ||||
|  |  | |||
|  | @ -5,14 +5,16 @@ import { start } from '../mastodon/common'; | |||
| start(); | ||||
| 
 | ||||
| function main() { | ||||
|   const IntlRelativeFormat = require('intl-relativeformat').default; | ||||
|   const { length } = require('stringz'); | ||||
|   const IntlMessageFormat = require('intl-messageformat').default; | ||||
|   const { timeAgoString } = require('../mastodon/components/relative_timestamp'); | ||||
|   const { delegate } = require('rails-ujs'); | ||||
|   const emojify = require('../mastodon/features/emoji/emoji').default; | ||||
|   const { getLocale } = require('../mastodon/locales'); | ||||
|   const { localeData } = getLocale(); | ||||
|   const { messages } = getLocale(); | ||||
|   const React = require('react'); | ||||
|   const ReactDOM = require('react-dom'); | ||||
| 
 | ||||
|   localeData.forEach(IntlRelativeFormat.__addLocaleData); | ||||
|   const Rellax = require('rellax'); | ||||
| 
 | ||||
|   ready(() => { | ||||
|     const locale = document.documentElement.lang; | ||||
|  | @ -25,8 +27,6 @@ function main() { | |||
|       minute: 'numeric', | ||||
|     }); | ||||
| 
 | ||||
|     const relativeFormat = new IntlRelativeFormat(locale); | ||||
| 
 | ||||
|     [].forEach.call(document.querySelectorAll('.emojify'), (content) => { | ||||
|       content.innerHTML = emojify(content.innerHTML); | ||||
|     }); | ||||
|  | @ -41,12 +41,16 @@ function main() { | |||
| 
 | ||||
|     [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => { | ||||
|       const datetime = new Date(content.getAttribute('datetime')); | ||||
|       const now      = new Date(); | ||||
| 
 | ||||
|       content.title = dateTimeFormat.format(datetime); | ||||
|       content.textContent = relativeFormat.format(datetime); | ||||
|       content.textContent = timeAgoString({ | ||||
|         formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values), | ||||
|         formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), | ||||
|       }, datetime, now, datetime.getFullYear()); | ||||
|     }); | ||||
| 
 | ||||
|     [].forEach.call(document.querySelectorAll('.logo-button'), (content) => { | ||||
|     [].forEach.call(document.querySelectorAll('.modal-button'), (content) => { | ||||
|       content.addEventListener('click', (e) => { | ||||
|         e.preventDefault(); | ||||
|         window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); | ||||
|  | @ -64,6 +68,8 @@ function main() { | |||
|         }) | ||||
|         .catch(error => console.error(error)); | ||||
|     } | ||||
| 
 | ||||
|     new Rellax('.parallax', { speed: -1 }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
| @import 'mastodon/lists'; | ||||
| @import 'mastodon/footer'; | ||||
| @import 'mastodon/compact_header'; | ||||
| @import 'mastodon/landing_strip'; | ||||
| @import 'mastodon/widgets'; | ||||
| @import 'mastodon/forms'; | ||||
| @import 'mastodon/accounts'; | ||||
| @import 'mastodon/stream_entries'; | ||||
|  |  | |||
|  | @ -1115,6 +1115,21 @@ $small-breakpoint: 960px; | |||
|   } | ||||
| 
 | ||||
|   &.tag-page { | ||||
|     @media screen and (max-width: $column-breakpoint) { | ||||
|       padding: 0; | ||||
| 
 | ||||
|       .container { | ||||
|         padding: 0; | ||||
|       } | ||||
| 
 | ||||
|       #mastodon-timeline { | ||||
|         display: block; | ||||
|         width: 100vw; | ||||
|         height: 100vh; | ||||
|         border-radius: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .grid { | ||||
|       @media screen and (min-width: $small-breakpoint) { | ||||
|         grid-template-columns: 33% 67%; | ||||
|  | @ -1146,23 +1161,16 @@ $small-breakpoint: 960px; | |||
| 
 | ||||
|     @media screen and (max-width: $column-breakpoint) { | ||||
|       .grid { | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|           grid-row: 2; | ||||
|         } | ||||
|         grid-gap: 0; | ||||
| 
 | ||||
|         .column-2 { | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|           grid-row: 1; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .brand { | ||||
|         margin: 0; | ||||
|       } | ||||
| 
 | ||||
|       .landing-page__features { | ||||
|         display: none; | ||||
|         .column-2 { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -1,243 +1,100 @@ | |||
| .card { | ||||
|   background-color: $base-shadow-color; | ||||
|   background-size: cover; | ||||
|   background-position: center; | ||||
|   border-radius: 4px 4px 0 0; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
|   display: flex; | ||||
| 
 | ||||
|   &::after { | ||||
|     background: rgba(darken($ui-base-color, 8%), 0.5); | ||||
|   & > a { | ||||
|     display: block; | ||||
|     content: ""; | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     z-index: 1; | ||||
|   } | ||||
|     text-decoration: none; | ||||
|     color: inherit; | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       box-shadow: none; | ||||
|     } | ||||
| 
 | ||||
|   .card__illustration { | ||||
|     padding: 60px 0; | ||||
|     position: relative; | ||||
|     flex: 1 1 auto; | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   } | ||||
| 
 | ||||
|   .card__bio { | ||||
|     max-width: 260px; | ||||
|     flex: 1 1 auto; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: space-between; | ||||
|     background: rgba(darken($ui-base-color, 8%), 0.8); | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|   } | ||||
| 
 | ||||
|   &.compact { | ||||
|     padding: 30px 0; | ||||
|     border-radius: 4px; | ||||
| 
 | ||||
|     .avatar { | ||||
|       margin-bottom: 0; | ||||
| 
 | ||||
|       img { | ||||
|         object-fit: cover; | ||||
|     &:hover, | ||||
|     &:active, | ||||
|     &:focus { | ||||
|       .card__bar { | ||||
|         background: lighten($ui-base-color, 8%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .name { | ||||
|     display: block; | ||||
|     font-size: 20px; | ||||
|     line-height: 18px * 1.5; | ||||
|     color: $primary-text-color; | ||||
|     padding: 10px 15px; | ||||
|     padding-bottom: 0; | ||||
|     font-weight: 500; | ||||
|   &__img { | ||||
|     height: 130px; | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|     margin-bottom: 30px; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| 
 | ||||
|     small { | ||||
|       display: block; | ||||
|       font-size: 14px; | ||||
|       color: $highlight-text-color; | ||||
|       font-weight: 400; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
| 
 | ||||
|       .fa { | ||||
|         margin-left: 3px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .avatar { | ||||
|     width: 120px; | ||||
|     margin: 0 auto; | ||||
|     position: relative; | ||||
|     z-index: 2; | ||||
|     background: darken($ui-base-color, 12%); | ||||
|     border-radius: 4px 4px 0 0; | ||||
| 
 | ||||
|     img { | ||||
|       width: 120px; | ||||
|       height: 120px; | ||||
|       display: block; | ||||
|       border-radius: 120px; | ||||
|       box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .roles { | ||||
|     margin-bottom: 30px; | ||||
|     padding: 0 15px; | ||||
|   } | ||||
| 
 | ||||
|   .details-counters { | ||||
|     margin-top: 30px; | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     width: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .counter { | ||||
|     width: 33.3%; | ||||
|     box-sizing: border-box; | ||||
|     flex: 0 0 auto; | ||||
|     color: $darker-text-color; | ||||
|     padding: 5px 10px 0; | ||||
|     margin-bottom: 10px; | ||||
|     border-right: 1px solid lighten($ui-base-color, 4%); | ||||
|     cursor: default; | ||||
|     text-align: center; | ||||
|     position: relative; | ||||
| 
 | ||||
|     a { | ||||
|       display: block; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       border-right: 0; | ||||
|     } | ||||
| 
 | ||||
|     &::after { | ||||
|       display: block; | ||||
|       content: ""; | ||||
|       position: absolute; | ||||
|       bottom: -10px; | ||||
|       left: 0; | ||||
|       width: 100%; | ||||
|       border-bottom: 4px solid $ui-primary-color; | ||||
|       opacity: 0.5; | ||||
|       transition: all 400ms ease; | ||||
|       height: 100%; | ||||
|       margin: 0; | ||||
|       object-fit: cover; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
| 
 | ||||
|     &.active { | ||||
|       &::after { | ||||
|         border-bottom: 4px solid $highlight-text-color; | ||||
|         opacity: 1; | ||||
|     @media screen and (max-width: 600px) { | ||||
|       height: 200px; | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__bar { | ||||
|     position: relative; | ||||
|     padding: 15px; | ||||
|     display: flex; | ||||
|     justify-content: flex-start; | ||||
|     align-items: center; | ||||
|     background: lighten($ui-base-color, 4%); | ||||
|     border-radius: 0 0 4px 4px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       border-radius: 0; | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       flex: 0 0 auto; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|       padding-top: 2px; | ||||
| 
 | ||||
|       img { | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         display: block; | ||||
|         margin: 0; | ||||
|         border-radius: 4px; | ||||
|         background: darken($ui-base-color, 8%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       &::after { | ||||
|         opacity: 1; | ||||
|         transition-duration: 100ms; | ||||
|     .display-name { | ||||
|       margin-left: 15px; | ||||
|       text-align: left; | ||||
| 
 | ||||
|       strong { | ||||
|         font-size: 15px; | ||||
|         color: $primary-text-color; | ||||
|         font-weight: 500; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     a { | ||||
|       text-decoration: none; | ||||
|       color: inherit; | ||||
|     } | ||||
| 
 | ||||
|     .counter-label { | ||||
|       font-size: 12px; | ||||
|       display: block; | ||||
|       margin-bottom: 5px; | ||||
|     } | ||||
| 
 | ||||
|     .counter-number { | ||||
|       font-weight: 500; | ||||
|       font-size: 18px; | ||||
|       color: $primary-text-color; | ||||
|       font-family: 'mastodon-font-display', sans-serif; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .bio { | ||||
|     font-size: 14px; | ||||
|     line-height: 18px; | ||||
|     padding: 0 15px; | ||||
|     color: $secondary-text-color; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 480px) { | ||||
|     display: block; | ||||
| 
 | ||||
|     .card__bio { | ||||
|       max-width: none; | ||||
|     } | ||||
| 
 | ||||
|     .name, | ||||
|     .roles { | ||||
|       text-align: center; | ||||
|       margin-bottom: 15px; | ||||
|     } | ||||
| 
 | ||||
|     .bio { | ||||
|       margin-bottom: 15px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .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 { | ||||
|       span { | ||||
|         display: block; | ||||
|         font-size: 14px; | ||||
|         color: $darker-text-color; | ||||
|         font-weight: 400; | ||||
|         margin-right: 5px; | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:active, | ||||
|       &:focus { | ||||
|         color: $white; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .account-grid-card .controls { | ||||
|   left: auto; | ||||
|   right: 15px; | ||||
| } | ||||
| 
 | ||||
| .pagination { | ||||
|   padding: 30px 0; | ||||
|   text-align: center; | ||||
|  | @ -314,289 +171,23 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .accounts-grid { | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   background: darken($simple-background-color, 8%); | ||||
|   border-radius: 0 0 4px 4px; | ||||
|   padding: 20px 5px; | ||||
|   padding-bottom: 10px; | ||||
|   overflow: hidden; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   z-index: 2; | ||||
|   position: relative; | ||||
| 
 | ||||
|   &.empty img { | ||||
|     position: absolute; | ||||
|     opacity: 0.2; | ||||
|     height: 200px; | ||||
|     left: 0; | ||||
|     bottom: 0; | ||||
|     pointer-events: none; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
| 
 | ||||
|   .account-grid-card { | ||||
|     box-sizing: border-box; | ||||
|     width: 335px; | ||||
|     background: $simple-background-color; | ||||
|     border-radius: 4px; | ||||
|     color: $inverted-text-color; | ||||
|     margin: 0 5px 10px; | ||||
|     position: relative; | ||||
| 
 | ||||
|     @media screen and (max-width: 740px) { | ||||
|       width: calc(100% - 10px); | ||||
|     } | ||||
| 
 | ||||
|     .account-grid-card__header { | ||||
|       overflow: hidden; | ||||
|       height: 100px; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|       background-color: lighten($inverted-text-color, 4%); | ||||
|       background-size: cover; | ||||
|       background-position: center; | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::after { | ||||
|         background: rgba(darken($ui-base-color, 8%), 0.5); | ||||
|         display: block; | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         left: 0; | ||||
|         top: 0; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         z-index: 1; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .account-grid-card__avatar { | ||||
|       box-sizing: border-box; | ||||
|       padding: 15px; | ||||
|       position: absolute; | ||||
|       z-index: 2; | ||||
|       top: 100px - (40px + 2px); | ||||
|       left: -2px; | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       width: 80px; | ||||
|       height: 80px; | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         width: 80px; | ||||
|         height: 80px; | ||||
|         border-radius: 80px; | ||||
|         border: 2px solid $simple-background-color; | ||||
|         background: $simple-background-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .name { | ||||
|       padding: 15px; | ||||
|       padding-top: 10px; | ||||
|       padding-left: 15px + 80px + 15px; | ||||
| 
 | ||||
|       a { | ||||
|         display: block; | ||||
|         color: $inverted-text-color; | ||||
|         text-decoration: none; | ||||
|         text-overflow: ellipsis; | ||||
|         overflow: hidden; | ||||
|         font-weight: 500; | ||||
| 
 | ||||
|         &:hover { | ||||
|           .display_name { | ||||
|             text-decoration: underline; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .display_name { | ||||
|       font-size: 16px; | ||||
|       display: block; | ||||
|       text-overflow: ellipsis; | ||||
|       overflow: hidden; | ||||
|     } | ||||
| 
 | ||||
|     .username { | ||||
|       color: $lighter-text-color; | ||||
|       font-size: 14px; | ||||
|       font-weight: 400; | ||||
|     } | ||||
| 
 | ||||
|     .account__header__content { | ||||
|       padding: 10px 15px; | ||||
|       padding-top: 15px; | ||||
|       color: $lighter-text-color; | ||||
|       word-wrap: break-word; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       height: 5.5em; | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::after { | ||||
|         display: block; | ||||
|         content: ""; | ||||
|         width: 100%; | ||||
|         height: 100px; | ||||
|         position: absolute; | ||||
|         bottom: 0; | ||||
|         background: linear-gradient(to bottom, rgba($simple-background-color, 0.01) 0%, rgba($simple-background-color, 1) 100%); | ||||
|         left: 0; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         pointer-events: none; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .nothing-here { | ||||
|   width: 100%; | ||||
|   display: block; | ||||
|   background: $ui-base-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   color: $light-text-color; | ||||
|   font-size: 14px; | ||||
|   font-weight: 500; | ||||
|   text-align: center; | ||||
|   padding: 130px 0; | ||||
|   padding-top: 125px; | ||||
|   margin: 0 auto; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   cursor: default; | ||||
| } | ||||
| 
 | ||||
| .account-card { | ||||
|   border-radius: 4px; | ||||
|   text-align: left; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   background: $simple-background-color; | ||||
|   padding: 20px; | ||||
|   min-height: 30vh; | ||||
| 
 | ||||
|   &__header { | ||||
|     background: $base-shadow-color; | ||||
|     background-size: cover; | ||||
|     background-position: center center; | ||||
|     height: 90px; | ||||
|     border-radius: 4px 4px 0 0; | ||||
|   } | ||||
| 
 | ||||
|   & > .detailed-status__display-name { | ||||
|     display: block; | ||||
|     overflow: hidden; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding: 10px; | ||||
| 
 | ||||
|     &:last-child { | ||||
|       margin-bottom: 0; | ||||
|     } | ||||
| 
 | ||||
|     & > div:first-child { | ||||
|       flex: 0 0 auto; | ||||
|       margin-right: 10px; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       display: block; | ||||
|       border-radius: 4px; | ||||
|       margin: 0; | ||||
|     } | ||||
| 
 | ||||
|     .display-name { | ||||
|       flex: 1 0 auto; | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       overflow: hidden; | ||||
|       white-space: nowrap; | ||||
|       text-overflow: ellipsis; | ||||
|       cursor: default; | ||||
| 
 | ||||
|       & > .detailed-status__display-name { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
| 
 | ||||
|       strong { | ||||
|         font-weight: 500; | ||||
|         color: $ui-base-color; | ||||
| 
 | ||||
|         @each $lang in $cjk-langs { | ||||
|           &:lang(#{$lang}) { | ||||
|             font-weight: 700; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       span { | ||||
|         font-size: 14px; | ||||
|         color: $light-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       .display-name { | ||||
|         strong { | ||||
|           text-decoration: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .counter { | ||||
|     box-sizing: border-box; | ||||
|     flex: 0 0 auto; | ||||
|     color: $light-text-color; | ||||
|     padding: 0 10px; | ||||
|     cursor: default; | ||||
|     text-align: center; | ||||
|     position: relative; | ||||
|     line-height: 24px; | ||||
| 
 | ||||
|     .counter-label { | ||||
|       font-size: 12px; | ||||
|       display: block; | ||||
|       text-transform: uppercase; | ||||
|     } | ||||
| 
 | ||||
|     .counter-number { | ||||
|       font-weight: 500; | ||||
|       font-size: 16px; | ||||
|       color: $inverted-text-color; | ||||
|       font-family: 'mastodon-font-display', sans-serif; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .activity-stream-tabs { | ||||
|   background: $simple-background-color; | ||||
|   border-bottom: 1px solid $ui-secondary-color; | ||||
|   position: relative; | ||||
|   z-index: 2; | ||||
| 
 | ||||
|   a { | ||||
|     display: inline-block; | ||||
|     padding: 15px; | ||||
|     text-decoration: none; | ||||
|     color: $highlight-text-color; | ||||
|     text-transform: uppercase; | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:active, | ||||
|     &:focus { | ||||
|       color: lighten($highlight-text-color, 8%); | ||||
|     } | ||||
| 
 | ||||
|     &.active { | ||||
|       color: $inverted-text-color; | ||||
|       cursor: default; | ||||
|     } | ||||
|   &--under-tabs { | ||||
|     border-radius: 0 0 4px 4px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -629,14 +220,14 @@ | |||
|   padding: 0; | ||||
|   margin: 15px -15px -15px; | ||||
|   border: 0 none; | ||||
|   border-top: 1px solid lighten($ui-base-color, 4%); | ||||
|   border-bottom: 1px solid lighten($ui-base-color, 4%); | ||||
|   border-top: 1px solid lighten($ui-base-color, 12%); | ||||
|   border-bottom: 1px solid lighten($ui-base-color, 12%); | ||||
|   font-size: 14px; | ||||
|   line-height: 20px; | ||||
| 
 | ||||
|   dl { | ||||
|     display: flex; | ||||
|     border-bottom: 1px solid lighten($ui-base-color, 4%); | ||||
|     border-bottom: 1px solid lighten($ui-base-color, 12%); | ||||
|   } | ||||
| 
 | ||||
|   dt, | ||||
|  |  | |||
|  | @ -1,13 +1,10 @@ | |||
| body { | ||||
|   font-family: 'mastodon-font-sans-serif', sans-serif; | ||||
|   background: $ui-base-color; | ||||
|   background-size: cover; | ||||
|   background-attachment: fixed; | ||||
|   background: darken($ui-base-color, 8%); | ||||
|   font-size: 13px; | ||||
|   line-height: 18px; | ||||
|   font-weight: 400; | ||||
|   color: $primary-text-color; | ||||
|   padding-bottom: 20px; | ||||
|   text-rendering: optimizelegibility; | ||||
|   font-feature-settings: "kern"; | ||||
|   text-size-adjust: none; | ||||
|  | @ -35,16 +32,24 @@ body { | |||
|     height: 100%; | ||||
|     padding: 0; | ||||
|     background: $ui-base-color; | ||||
| 
 | ||||
|     &.with-modals--active { | ||||
|       overflow-y: hidden; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.about-body { | ||||
|     background: darken($ui-base-color, 8%); | ||||
|     padding-bottom: 0; | ||||
|   &.lighter { | ||||
|     background: $ui-base-color; | ||||
|   } | ||||
| 
 | ||||
|   &.tag-body { | ||||
|     background: darken($ui-base-color, 8%); | ||||
|     padding-bottom: 0; | ||||
|   &.with-modals { | ||||
|     overflow-x: hidden; | ||||
|     overflow-y: scroll; | ||||
| 
 | ||||
|     &--active { | ||||
|       overflow-y: hidden; | ||||
|       margin-right: 13px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.player { | ||||
|  | @ -52,7 +57,7 @@ body { | |||
|   } | ||||
| 
 | ||||
|   &.embed { | ||||
|     background: transparent; | ||||
|     background: lighten($ui-base-color, 4%); | ||||
|     margin: 0; | ||||
|     padding-bottom: 0; | ||||
| 
 | ||||
|  |  | |||
|  | @ -946,6 +946,18 @@ | |||
|   background: lighten($ui-base-color, 4%); | ||||
|   padding: 14px 10px; | ||||
| 
 | ||||
|   &--flex { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     justify-content: space-between; | ||||
|     align-items: flex-start; | ||||
| 
 | ||||
|     .status__content, | ||||
|     .detailed-status__meta { | ||||
|       flex: 100%; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .status__content { | ||||
|     font-size: 19px; | ||||
|     line-height: 24px; | ||||
|  | @ -1224,7 +1236,6 @@ a .account__avatar { | |||
| } | ||||
| 
 | ||||
| .account__action-bar-dropdown { | ||||
|   flex: 0 1 calc(50% - 140px); | ||||
|   padding: 10px; | ||||
| 
 | ||||
|   .icon-button { | ||||
|  | @ -1256,9 +1267,9 @@ a .account__avatar { | |||
| .account__action-bar__tab { | ||||
|   text-decoration: none; | ||||
|   overflow: hidden; | ||||
|   flex: 0 1 80px; | ||||
|   flex: 0 1 100%; | ||||
|   border-right: 1px solid lighten($ui-base-color, 8%); | ||||
|   padding: 10px 5px; | ||||
|   padding: 10px 0; | ||||
| 
 | ||||
|   & > span { | ||||
|     display: block; | ||||
|  |  | |||
|  | @ -60,10 +60,6 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .media-standalone__body { | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .account-header { | ||||
|   width: 400px; | ||||
|   margin: 0 auto; | ||||
|  | @ -118,3 +114,576 @@ | |||
|     margin-left: 8px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .public-layout { | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     padding-top: 48px; | ||||
|   } | ||||
| 
 | ||||
|   .container { | ||||
|     max-width: 960px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       padding: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .header { | ||||
|     background: lighten($ui-base-color, 8%); | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|     border-radius: 4px; | ||||
|     height: 48px; | ||||
|     margin: 10px 0; | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
|     justify-content: center; | ||||
|     flex-wrap: nowrap; | ||||
|     overflow: hidden; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       position: fixed; | ||||
|       width: 100%; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       margin: 0; | ||||
|       border-radius: 0; | ||||
|       box-shadow: none; | ||||
|       z-index: 110; | ||||
|     } | ||||
| 
 | ||||
|     & > div { | ||||
|       flex: 1 1 33.3%; | ||||
|       min-height: 1px; | ||||
|     } | ||||
| 
 | ||||
|     .nav-left { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: flex-start; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .nav-center { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: center; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .nav-right { | ||||
|       display: flex; | ||||
|       align-items: stretch; | ||||
|       justify-content: flex-end; | ||||
|       flex-wrap: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .brand { | ||||
|       display: block; | ||||
|       padding: 15px; | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         height: 18px; | ||||
|         width: auto; | ||||
|         position: relative; | ||||
|         bottom: -2px; | ||||
| 
 | ||||
|         @media screen and (max-width: $no-gap-breakpoint) { | ||||
|           height: 20px; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         background: lighten($ui-base-color, 12%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .nav-link { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       padding: 0 1rem; | ||||
|       font-size: 12px; | ||||
|       font-weight: 500; | ||||
|       text-decoration: none; | ||||
|       color: $darker-text-color; | ||||
|       white-space: nowrap; | ||||
|       text-align: center; | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         text-decoration: underline; | ||||
|         color: $primary-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .nav-button { | ||||
|       background: lighten($ui-base-color, 16%); | ||||
|       margin: 8px; | ||||
|       margin-left: 0; | ||||
|       border-radius: 4px; | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
|         background: lighten($ui-base-color, 20%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   $no-columns-breakpoint: 600px; | ||||
| 
 | ||||
|   .grid { | ||||
|     display: grid; | ||||
|     grid-gap: 10px; | ||||
|     grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); | ||||
|     grid-auto-columns: 25%; | ||||
|     grid-auto-rows: max-content; | ||||
| 
 | ||||
|     .column-0 { | ||||
|       grid-row: 1; | ||||
|       grid-column: 1; | ||||
|     } | ||||
| 
 | ||||
|     .column-1 { | ||||
|       grid-row: 1; | ||||
|       grid-column: 2; | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-columns-breakpoint) { | ||||
|       grid-template-columns: 100%; | ||||
|       grid-gap: 0; | ||||
| 
 | ||||
|       .column-1 { | ||||
|         display: none; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .public-account-header { | ||||
|     overflow: hidden; | ||||
|     margin-bottom: 10px; | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|     &__image { | ||||
|       border-radius: 4px 4px 0 0; | ||||
|       overflow: hidden; | ||||
|       height: 300px; | ||||
|       position: relative; | ||||
|       background: darken($ui-base-color, 12%); | ||||
| 
 | ||||
|       &::after { | ||||
|         content: ""; | ||||
|         display: block; | ||||
|         position: absolute; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|       } | ||||
| 
 | ||||
|       img { | ||||
|         object-fit: cover; | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         margin: 0; | ||||
|         border-radius: 4px 4px 0 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         height: 200px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       margin-bottom: 0; | ||||
|       box-shadow: none; | ||||
| 
 | ||||
|       &__image::after { | ||||
|         display: none; | ||||
|       } | ||||
| 
 | ||||
|       &__image, | ||||
|       &__image img { | ||||
|         border-radius: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__bar { | ||||
|       position: relative; | ||||
|       margin-top: -80px; | ||||
|       display: flex; | ||||
|       justify-content: flex-start; | ||||
| 
 | ||||
|       &::before { | ||||
|         content: ""; | ||||
|         display: block; | ||||
|         background: lighten($ui-base-color, 4%); | ||||
|         position: absolute; | ||||
|         bottom: 0; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         height: 60px; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         z-index: -1; | ||||
|       } | ||||
| 
 | ||||
|       .avatar { | ||||
|         display: block; | ||||
|         width: 120px; | ||||
|         height: 120px; | ||||
|         padding-left: 20px - 4px; | ||||
|         flex: 0 0 auto; | ||||
| 
 | ||||
|         img { | ||||
|           display: block; | ||||
|           width: 100%; | ||||
|           height: 100%; | ||||
|           margin: 0; | ||||
|           border-radius: 50%; | ||||
|           border: 4px solid lighten($ui-base-color, 4%); | ||||
|           background: darken($ui-base-color, 8%); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         margin-top: 0; | ||||
|         background: lighten($ui-base-color, 4%); | ||||
|         border-radius: 0 0 4px 4px; | ||||
|         padding: 5px; | ||||
| 
 | ||||
|         &::before { | ||||
|           display: none; | ||||
|         } | ||||
| 
 | ||||
|         .avatar { | ||||
|           width: 48px; | ||||
|           height: 48px; | ||||
|           padding: 7px 0; | ||||
|           padding-left: 10px; | ||||
| 
 | ||||
|           img { | ||||
|             border: 0; | ||||
|             border-radius: 4px; | ||||
|           } | ||||
| 
 | ||||
|           @media screen and (max-width: 360px) { | ||||
|             display: none; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-gap-breakpoint) { | ||||
|         border-radius: 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-columns-breakpoint) { | ||||
|         flex-wrap: wrap; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__tabs { | ||||
|       flex: 1 1 auto; | ||||
|       margin-left: 20px; | ||||
| 
 | ||||
|       &__name { | ||||
|         padding-top: 20px; | ||||
|         padding-bottom: 8px; | ||||
| 
 | ||||
|         h1 { | ||||
|           font-size: 20px; | ||||
|           line-height: 18px * 1.5; | ||||
|           color: $primary-text-color; | ||||
|           font-weight: 500; | ||||
|           overflow: hidden; | ||||
|           white-space: nowrap; | ||||
|           text-overflow: ellipsis; | ||||
|           text-shadow: 1px 1px 1px $base-shadow-color; | ||||
| 
 | ||||
|           small { | ||||
|             display: block; | ||||
|             font-size: 14px; | ||||
|             color: $primary-text-color; | ||||
|             font-weight: 400; | ||||
|             overflow: hidden; | ||||
|             text-overflow: ellipsis; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         margin-left: 15px; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
| 
 | ||||
|         &__name { | ||||
|           padding-top: 0; | ||||
|           padding-bottom: 0; | ||||
| 
 | ||||
|           h1 { | ||||
|             font-size: 16px; | ||||
|             line-height: 24px; | ||||
|             text-shadow: none; | ||||
| 
 | ||||
|             small { | ||||
|               color: $darker-text-color; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &__tabs { | ||||
|         display: flex; | ||||
|         justify-content: flex-start; | ||||
|         align-items: stretch; | ||||
|         height: 58px; | ||||
| 
 | ||||
|         .details-counters { | ||||
|           display: flex; | ||||
|           flex-direction: row; | ||||
|           min-width: 300px; | ||||
|         } | ||||
| 
 | ||||
|         @media screen and (max-width: $no-columns-breakpoint) { | ||||
|           .details-counters { | ||||
|             display: none; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .counter { | ||||
|           width: 33.3%; | ||||
|           box-sizing: border-box; | ||||
|           flex: 0 0 auto; | ||||
|           color: $darker-text-color; | ||||
|           padding: 10px; | ||||
|           border-right: 1px solid lighten($ui-base-color, 4%); | ||||
|           cursor: default; | ||||
|           text-align: center; | ||||
|           position: relative; | ||||
| 
 | ||||
|           a { | ||||
|             display: block; | ||||
|           } | ||||
| 
 | ||||
|           &:last-child { | ||||
|             border-right: 0; | ||||
|           } | ||||
| 
 | ||||
|           &::after { | ||||
|             display: block; | ||||
|             content: ""; | ||||
|             position: absolute; | ||||
|             bottom: 0; | ||||
|             left: 0; | ||||
|             width: 100%; | ||||
|             border-bottom: 4px solid $ui-primary-color; | ||||
|             opacity: 0.5; | ||||
|             transition: all 400ms ease; | ||||
|           } | ||||
| 
 | ||||
|           &.active { | ||||
|             &::after { | ||||
|               border-bottom: 4px solid $highlight-text-color; | ||||
|               opacity: 1; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           &:hover { | ||||
|             &::after { | ||||
|               opacity: 1; | ||||
|               transition-duration: 100ms; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           a { | ||||
|             text-decoration: none; | ||||
|             color: inherit; | ||||
|           } | ||||
| 
 | ||||
|           .counter-label { | ||||
|             font-size: 12px; | ||||
|             display: block; | ||||
|           } | ||||
| 
 | ||||
|           .counter-number { | ||||
|             font-weight: 500; | ||||
|             font-size: 18px; | ||||
|             margin-bottom: 5px; | ||||
|             color: $primary-text-color; | ||||
|             font-family: 'mastodon-font-display', sans-serif; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .spacer { | ||||
|           flex: 1 1 auto; | ||||
|           height: 1px; | ||||
|         } | ||||
| 
 | ||||
|         &__buttons { | ||||
|           padding: 7px 8px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &__extra { | ||||
|       display: none; | ||||
|       margin-top: 4px; | ||||
| 
 | ||||
|       .public-account-bio { | ||||
|         border-radius: 0; | ||||
|         box-shadow: none; | ||||
|         background: transparent; | ||||
|         margin: 0 -5px; | ||||
| 
 | ||||
|         .account__header__fields { | ||||
|           border-top: 1px solid lighten($ui-base-color, 12%); | ||||
|         } | ||||
| 
 | ||||
|         .roles { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &__links { | ||||
|         margin-top: -15px; | ||||
|         font-size: 14px; | ||||
|         color: $darker-text-color; | ||||
| 
 | ||||
|         a { | ||||
|           display: inline-block; | ||||
|           color: $darker-text-color; | ||||
|           text-decoration: none; | ||||
|           padding: 15px; | ||||
| 
 | ||||
|           strong { | ||||
|             font-weight: 700; | ||||
|             color: $primary-text-color; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-columns-breakpoint) { | ||||
|         display: block; | ||||
|         flex: 100%; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .account__section-headline { | ||||
|     border-radius: 4px 4px 0 0; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       border-radius: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .detailed-status__meta { | ||||
|     margin-top: 25px; | ||||
|   } | ||||
| 
 | ||||
|   .public-account-bio { | ||||
|     background: lighten($ui-base-color, 8%); | ||||
|     box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|     border-radius: 4px; | ||||
|     overflow: hidden; | ||||
|     margin-bottom: 10px; | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       box-shadow: none; | ||||
|       margin-bottom: 0; | ||||
|       border-radius: 0; | ||||
|     } | ||||
| 
 | ||||
|     .account__header__fields { | ||||
|       margin: 0; | ||||
|       border-top: 0; | ||||
| 
 | ||||
|       a { | ||||
|         color: lighten($ui-highlight-color, 8%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .account__header__content { | ||||
|       padding: 20px; | ||||
|       padding-bottom: 0; | ||||
|       color: $primary-text-color; | ||||
|     } | ||||
| 
 | ||||
|     &__extra, | ||||
|     .roles { | ||||
|       padding: 20px; | ||||
|       font-size: 14px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .roles { | ||||
|       padding-bottom: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .static-icon-button { | ||||
|     color: $action-button-color; | ||||
|     font-size: 18px; | ||||
| 
 | ||||
|     & > span { | ||||
|       font-size: 14px; | ||||
|       font-weight: 500; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .card-grid { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     min-width: 100%; | ||||
|     margin: 0 -5px; | ||||
| 
 | ||||
|     & > div { | ||||
|       box-sizing: border-box; | ||||
|       flex: 1 0 auto; | ||||
|       width: 300px; | ||||
|       padding: 0 5px; | ||||
|       margin-bottom: 10px; | ||||
|       max-width: 33.333%; | ||||
| 
 | ||||
|       @media screen and (max-width: 900px) { | ||||
|         max-width: 50%; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         max-width: 100%; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       margin: 0; | ||||
|       border-top: 1px solid lighten($ui-base-color, 8%); | ||||
| 
 | ||||
|       & > div { | ||||
|         width: 100%; | ||||
|         padding: 0; | ||||
|         margin-bottom: 0; | ||||
|         border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||
| 
 | ||||
|         &:last-child { | ||||
|           border-bottom: 0; | ||||
|         } | ||||
| 
 | ||||
|         .card__bar { | ||||
|           background: $ui-base-color; | ||||
| 
 | ||||
|           &:hover, | ||||
|           &:active, | ||||
|           &:focus { | ||||
|             background: lighten($ui-base-color, 4%); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,39 +1,140 @@ | |||
| .footer { | ||||
|   text-align: center; | ||||
|   margin-top: 30px; | ||||
|   padding-bottom: 60px; | ||||
|   font-size: 12px; | ||||
|   color: $darker-text-color; | ||||
| .public-layout { | ||||
|   .footer { | ||||
|     text-align: left; | ||||
|     padding-top: 20px; | ||||
|     padding-bottom: 60px; | ||||
|     font-size: 12px; | ||||
|     color: lighten($ui-base-color, 34%); | ||||
| 
 | ||||
|   .footer__domain { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     a { | ||||
|       color: inherit; | ||||
|       text-decoration: none; | ||||
|     @media screen and (max-width: $no-gap-breakpoint) { | ||||
|       padding-left: 20px; | ||||
|       padding-right: 20px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .powered-by, | ||||
|   .single-user-login { | ||||
|     font-weight: 400; | ||||
|     .grid { | ||||
|       display: grid; | ||||
|       grid-gap: 10px; | ||||
|       grid-template-columns: 1fr 1fr 2fr 1fr 1fr; | ||||
| 
 | ||||
|     a { | ||||
|       color: inherit; | ||||
|       text-decoration: underline; | ||||
|       font-weight: 500; | ||||
|       .column-0 { | ||||
|         grid-column: 1; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       &:hover { | ||||
|       .column-1 { | ||||
|         grid-column: 2; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       .column-2 { | ||||
|         grid-column: 3; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|         text-align: center; | ||||
| 
 | ||||
|         h4 a { | ||||
|           color: lighten($ui-base-color, 34%); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .column-3 { | ||||
|         grid-column: 4; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       .column-4 { | ||||
|         grid-column: 5; | ||||
|         grid-row: 1; | ||||
|         min-width: 0; | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 690px) { | ||||
|         grid-template-columns: 1fr 2fr 1fr; | ||||
| 
 | ||||
|         .column-0, | ||||
|         .column-1 { | ||||
|           grid-column: 1; | ||||
|         } | ||||
| 
 | ||||
|         .column-1 { | ||||
|           grid-row: 2; | ||||
|         } | ||||
| 
 | ||||
|         .column-2 { | ||||
|           grid-column: 2; | ||||
|         } | ||||
| 
 | ||||
|         .column-3, | ||||
|         .column-4 { | ||||
|           grid-column: 3; | ||||
|         } | ||||
| 
 | ||||
|         .column-4 { | ||||
|           grid-row: 2; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: 600px) { | ||||
|         .column-1 { | ||||
|           display: block; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @media screen and (max-width: $no-gap-breakpoint) { | ||||
|         .column-0, | ||||
|         .column-1, | ||||
|         .column-3, | ||||
|         .column-4 { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     h4 { | ||||
|       text-transform: uppercase; | ||||
|       font-weight: 700; | ||||
|       margin-bottom: 8px; | ||||
|       color: $darker-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: inherit; | ||||
|         text-decoration: none; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     img { | ||||
|       margin: 0 4px; | ||||
|       position: relative; | ||||
|       bottom: -1px; | ||||
|       height: 18px; | ||||
|       vertical-align: top; | ||||
|     ul a { | ||||
|       text-decoration: none; | ||||
|       color: lighten($ui-base-color, 34%); | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:active, | ||||
|       &:focus { | ||||
|         text-decoration: underline; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .brand { | ||||
|       svg { | ||||
|         display: block; | ||||
|         height: 36px; | ||||
|         width: auto; | ||||
|         margin: 0 auto; | ||||
| 
 | ||||
|         path { | ||||
|           fill: lighten($ui-base-color, 34%); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|       &:active { | ||||
|         svg path { | ||||
|           fill: lighten($ui-base-color, 38%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,111 +0,0 @@ | |||
| .landing-strip, | ||||
| .memoriam-strip { | ||||
|   background: rgba(darken($ui-base-color, 7%), 0.8); | ||||
|   color: $darker-text-color; | ||||
|   font-weight: 400; | ||||
|   padding: 14px; | ||||
|   border-radius: 4px; | ||||
|   margin-bottom: 20px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
|   } | ||||
| 
 | ||||
|   .logo { | ||||
|     width: 30px; | ||||
|     height: 30px; | ||||
|     flex: 0 0 auto; | ||||
|     margin-right: 15px; | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: 740px) { | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .memoriam-strip { | ||||
|   background: rgba($base-shadow-color, 0.7); | ||||
| } | ||||
| 
 | ||||
| .moved-strip { | ||||
|   padding: 14px; | ||||
|   border-radius: 4px; | ||||
|   background: rgba(darken($ui-base-color, 7%), 0.8); | ||||
|   color: $secondary-text-color; | ||||
|   font-weight: 400; | ||||
|   margin-bottom: 20px; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
| 
 | ||||
|     &.mention { | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         text-decoration: none; | ||||
|       } | ||||
| 
 | ||||
|       &:focus, | ||||
|       &:hover, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
| 
 | ||||
|         span { | ||||
|           text-decoration: underline; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__message { | ||||
|     margin-bottom: 15px; | ||||
| 
 | ||||
|     .fa { | ||||
|       margin-right: 5px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__card { | ||||
|     .detailed-status__display-avatar { | ||||
|       position: relative; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       margin-bottom: 0; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         color: $highlight-text-color; | ||||
|         font-weight: 400; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,367 +1,145 @@ | |||
| .activity-stream { | ||||
|   clear: both; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   border-radius: 4px; | ||||
|   overflow: hidden; | ||||
|   margin-bottom: 10px; | ||||
| 
 | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     margin-bottom: 0; | ||||
|     border-radius: 0; | ||||
|     box-shadow: none; | ||||
|   } | ||||
| 
 | ||||
|   &--headless { | ||||
|     border-radius: 0; | ||||
|     margin: 0; | ||||
|     box-shadow: none; | ||||
| 
 | ||||
|     .detailed-status, | ||||
|     .status { | ||||
|       border-radius: 0 !important; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   div[data-component] { | ||||
|     width: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .entry { | ||||
|     background: $simple-background-color; | ||||
|     background: $ui-base-color; | ||||
| 
 | ||||
|     .detailed-status.light, | ||||
|     .status.light, | ||||
|     .more.light { | ||||
|       border-bottom: 1px solid $ui-secondary-color; | ||||
|     .detailed-status, | ||||
|     .status, | ||||
|     .load-more { | ||||
|       animation: none; | ||||
|     } | ||||
| 
 | ||||
|     &:last-child { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-bottom: 0; | ||||
|         border-radius: 0 0 4px 4px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:first-child { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-radius: 4px 4px 0 0; | ||||
|       } | ||||
| 
 | ||||
|       &:last-child { | ||||
|         &, | ||||
|         .detailed-status.light, | ||||
|         .status.light { | ||||
|         .detailed-status, | ||||
|         .status { | ||||
|           border-radius: 4px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @media screen and (max-width: 740px) { | ||||
|       &, | ||||
|       .detailed-status.light, | ||||
|       .status.light { | ||||
|       .detailed-status, | ||||
|       .status { | ||||
|         border-radius: 0 !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   &.with-header { | ||||
|     .entry { | ||||
|       &:first-child { | ||||
|         &, | ||||
|         .detailed-status.light, | ||||
|         .status.light { | ||||
|           border-radius: 0; | ||||
|         } | ||||
| .button.logo-button { | ||||
|   flex: 0 auto; | ||||
|   font-size: 14px; | ||||
|   background: $ui-highlight-color; | ||||
|   color: $primary-text-color; | ||||
|   text-transform: none; | ||||
|   line-height: 36px; | ||||
|   height: auto; | ||||
|   padding: 3px 15px; | ||||
|   border: 0; | ||||
| 
 | ||||
|         &:last-child { | ||||
|           &, | ||||
|           .detailed-status.light, | ||||
|           .status.light { | ||||
|             border-radius: 0 0 4px 4px; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|   svg { | ||||
|     width: 20px; | ||||
|     height: auto; | ||||
|     vertical-align: middle; | ||||
|     margin-right: 5px; | ||||
| 
 | ||||
|     path:first-child { | ||||
|       fill: $primary-text-color; | ||||
|     } | ||||
| 
 | ||||
|     path:last-child { | ||||
|       fill: $ui-highlight-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .media-gallery__gifv__label { | ||||
|     bottom: 9px; | ||||
|   } | ||||
|   &:active, | ||||
|   &:focus, | ||||
|   &:hover { | ||||
|     background: lighten($ui-highlight-color, 10%); | ||||
| 
 | ||||
|   .status.light { | ||||
|     padding: 14px 14px 14px (48px + 14px * 2); | ||||
|     position: relative; | ||||
|     min-height: 48px; | ||||
|     cursor: default; | ||||
| 
 | ||||
|     .status__header { | ||||
|       font-size: 15px; | ||||
| 
 | ||||
|       .status__meta { | ||||
|         float: right; | ||||
|         font-size: 14px; | ||||
| 
 | ||||
|         .status__relative-time { | ||||
|           color: $lighter-text-color; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       padding-right: 25px; | ||||
|       color: $inverted-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .status__avatar { | ||||
|       position: absolute; | ||||
|       left: 14px; | ||||
|       top: 14px; | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
| 
 | ||||
|       & > div { | ||||
|         width: 48px; | ||||
|         height: 48px; | ||||
|       } | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         border-radius: 4px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .display-name { | ||||
|       display: block; | ||||
|       max-width: 100%; | ||||
|       overflow: hidden; | ||||
|       white-space: nowrap; | ||||
|       text-overflow: ellipsis; | ||||
| 
 | ||||
|       strong { | ||||
|         font-weight: 500; | ||||
|         color: $inverted-text-color; | ||||
| 
 | ||||
|         @each $lang in $cjk-langs { | ||||
|           &:lang(#{$lang}) { | ||||
|             font-weight: 700; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       span { | ||||
|         font-size: 14px; | ||||
|         color: $light-text-color; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__content { | ||||
|       color: $inverted-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: $highlight-text-color; | ||||
|       } | ||||
| 
 | ||||
|       a.status__content__spoiler-link { | ||||
|         color: $primary-text-color; | ||||
|         background: $ui-base-color; | ||||
| 
 | ||||
|         &:hover { | ||||
|           background: lighten($ui-base-color, 8%); | ||||
|         } | ||||
|       } | ||||
|     svg path:last-child { | ||||
|       fill: lighten($ui-highlight-color, 10%); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .detailed-status.light { | ||||
|     padding: 14px; | ||||
|     background: $simple-background-color; | ||||
|     cursor: default; | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       display: block; | ||||
|       overflow: hidden; | ||||
|       margin-bottom: 15px; | ||||
| 
 | ||||
|       & > div { | ||||
|         float: left; | ||||
|         margin-right: 10px; | ||||
|       } | ||||
| 
 | ||||
|       .display-name { | ||||
|         display: block; | ||||
|         max-width: 100%; | ||||
|         overflow: hidden; | ||||
|         white-space: nowrap; | ||||
|         text-overflow: ellipsis; | ||||
| 
 | ||||
|         strong { | ||||
|           font-weight: 500; | ||||
|           color: $inverted-text-color; | ||||
| 
 | ||||
|           @each $lang in $cjk-langs { | ||||
|             &:lang(#{$lang}) { | ||||
|               font-weight: 700; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         span { | ||||
|           font-size: 14px; | ||||
|           color: $light-text-color; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .avatar { | ||||
|       width: 48px; | ||||
|       height: 48px; | ||||
| 
 | ||||
|       img { | ||||
|         display: block; | ||||
|         border-radius: 4px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status__content { | ||||
|       color: $inverted-text-color; | ||||
| 
 | ||||
|       a { | ||||
|         color: $highlight-text-color; | ||||
|       } | ||||
| 
 | ||||
|       a.status__content__spoiler-link { | ||||
|         color: $primary-text-color; | ||||
|         background: $ui-base-color; | ||||
| 
 | ||||
|         &:hover { | ||||
|           background: lighten($ui-base-color, 8%); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__meta { | ||||
|       margin-top: 15px; | ||||
|       color: $light-text-color; | ||||
|       font-size: 14px; | ||||
|       line-height: 18px; | ||||
| 
 | ||||
|       a { | ||||
|         color: inherit; | ||||
|       } | ||||
| 
 | ||||
|       span > span { | ||||
|         font-weight: 500; | ||||
|         font-size: 12px; | ||||
|         margin-left: 6px; | ||||
|         display: inline-block; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status-card { | ||||
|       border-color: lighten($ui-secondary-color, 4%); | ||||
|       color: $lighter-text-color; | ||||
| 
 | ||||
|       &:hover { | ||||
|         background: lighten($ui-secondary-color, 4%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .status-card__title, | ||||
|     .status-card__description { | ||||
|       color: $inverted-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .status-card__image { | ||||
|       background: $ui-secondary-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .media-spoiler { | ||||
|     background: $ui-base-color; | ||||
|     color: $darker-text-color; | ||||
|   } | ||||
| 
 | ||||
|   .pre-header { | ||||
|     padding: 14px 0; | ||||
|     padding-left: (48px + 14px * 2); | ||||
|     padding-bottom: 0; | ||||
|     margin-bottom: -4px; | ||||
|     color: $light-text-color; | ||||
|     font-size: 14px; | ||||
|     position: relative; | ||||
| 
 | ||||
|     .pre-header__icon { | ||||
|       position: absolute; | ||||
|       left: (48px + 14px * 2 - 30px); | ||||
|     } | ||||
| 
 | ||||
|     .status__display-name.muted strong { | ||||
|       color: $light-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .open-in-web-link { | ||||
|     text-decoration: none; | ||||
| 
 | ||||
|     &:hover { | ||||
|       text-decoration: underline; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .more { | ||||
|     color: $darker-text-color; | ||||
|     display: block; | ||||
|     padding: 14px; | ||||
|     text-align: center; | ||||
| 
 | ||||
|     &:not(:hover) { | ||||
|       text-decoration: none; | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     svg { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .embed { | ||||
|   .activity-stream { | ||||
|     box-shadow: none; | ||||
| .embed, | ||||
| .public-layout { | ||||
|   .detailed-status { | ||||
|     padding: 15px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .entry { | ||||
|   .detailed-status.light { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     justify-content: space-between; | ||||
|     align-items: flex-start; | ||||
|   .status { | ||||
|     padding: 15px 15px 15px (48px + 15px * 2); | ||||
|     min-height: 48px + 2px; | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       flex: 1; | ||||
|       margin: 0 5px 15px 0; | ||||
|     &__avatar { | ||||
|       left: 15px; | ||||
|       top: 17px; | ||||
|     } | ||||
| 
 | ||||
|     .button.button-secondary.logo-button { | ||||
|       flex: 0 auto; | ||||
|       font-size: 14px; | ||||
|       background: $ui-highlight-color; | ||||
|       color: $primary-text-color; | ||||
|       border: 0; | ||||
| 
 | ||||
|       svg { | ||||
|         width: 20px; | ||||
|         height: auto; | ||||
|         vertical-align: middle; | ||||
|         margin-right: 5px; | ||||
| 
 | ||||
|         path:first-child { | ||||
|           fill: $primary-text-color; | ||||
|         } | ||||
| 
 | ||||
|         path:last-child { | ||||
|           fill: $ui-highlight-color; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       &:active, | ||||
|       &:focus, | ||||
|       &:hover { | ||||
|         background: lighten($ui-highlight-color, 10%); | ||||
| 
 | ||||
|         svg path:last-child { | ||||
|           fill: lighten($ui-highlight-color, 10%); | ||||
|         } | ||||
|       } | ||||
|     &__content { | ||||
|       padding-top: 5px; | ||||
|     } | ||||
| 
 | ||||
|     .status__content, | ||||
|     .detailed-status__meta { | ||||
|       flex: 100%; | ||||
|     &__prepend { | ||||
|       margin-left: 48px + 15px * 2; | ||||
|       padding-top: 15px; | ||||
|     } | ||||
| 
 | ||||
|     &__prepend-icon-wrapper { | ||||
|       left: -32px; | ||||
|     } | ||||
| 
 | ||||
|     .media-gallery, | ||||
|     &__action-bar, | ||||
|     .video-player { | ||||
|       margin-top: 10px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -46,3 +46,5 @@ $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; | |||
| $media-modal-media-max-width: 100%; | ||||
| // put margins on top and bottom of image to avoid the screen covered by image. | ||||
| $media-modal-media-max-height: 80%; | ||||
| 
 | ||||
| $no-gap-breakpoint: 415px; | ||||
|  |  | |||
							
								
								
									
										161
									
								
								app/javascript/styles/mastodon/widgets.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								app/javascript/styles/mastodon/widgets.scss
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | |||
| .hero-widget { | ||||
|   margin-bottom: 10px; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
| 
 | ||||
|   &__img { | ||||
|     width: 100%; | ||||
|     height: 167px; | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     border-radius: 4px 4px 0 0; | ||||
|     background: $base-shadow-color; | ||||
| 
 | ||||
|     img { | ||||
|       object-fit: cover; | ||||
|       display: block; | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       margin: 0; | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__text { | ||||
|     background: $ui-base-color; | ||||
|     padding: 20px; | ||||
|     border-radius: 0 0 4px 4px; | ||||
|     font-size: 15px; | ||||
|     color: $darker-text-color; | ||||
|     line-height: 20px; | ||||
|     word-wrap: break-word; | ||||
|     font-weight: 400; | ||||
| 
 | ||||
|     .emojione { | ||||
|       width: 20px; | ||||
|       height: 20px; | ||||
|       margin: -3px 0 0; | ||||
|     } | ||||
| 
 | ||||
|     p { | ||||
|       margin-bottom: 20px; | ||||
| 
 | ||||
|       &:last-child { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     em { | ||||
|       display: inline; | ||||
|       margin: 0; | ||||
|       padding: 0; | ||||
|       font-weight: 700; | ||||
|       background: transparent; | ||||
|       font-family: inherit; | ||||
|       font-size: inherit; | ||||
|       line-height: inherit; | ||||
|       color: lighten($darker-text-color, 10%); | ||||
|     } | ||||
| 
 | ||||
|     a { | ||||
|       color: $secondary-text-color; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       &:hover { | ||||
|         text-decoration: underline; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .moved-account-widget { | ||||
|   padding: 15px; | ||||
|   padding-bottom: 20px; | ||||
|   border-radius: 4px; | ||||
|   background: $ui-base-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   color: $secondary-text-color; | ||||
|   font-weight: 400; | ||||
|   margin-bottom: 10px; | ||||
| 
 | ||||
|   strong, | ||||
|   a { | ||||
|     font-weight: 500; | ||||
| 
 | ||||
|     @each $lang in $cjk-langs { | ||||
|       &:lang(#{$lang}) { | ||||
|         font-weight: 700; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
| 
 | ||||
|     &.mention { | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         text-decoration: none; | ||||
|       } | ||||
| 
 | ||||
|       &:focus, | ||||
|       &:hover, | ||||
|       &:active { | ||||
|         text-decoration: none; | ||||
| 
 | ||||
|         span { | ||||
|           text-decoration: underline; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__message { | ||||
|     margin-bottom: 15px; | ||||
| 
 | ||||
|     .fa { | ||||
|       margin-right: 5px; | ||||
|       color: $darker-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &__card { | ||||
|     .detailed-status__display-avatar { | ||||
|       position: relative; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .detailed-status__display-name { | ||||
|       margin-bottom: 0; | ||||
|       text-decoration: none; | ||||
| 
 | ||||
|       span { | ||||
|         font-weight: 400; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .memoriam-widget { | ||||
|   padding: 20px; | ||||
|   border-radius: 4px; | ||||
|   background: $base-shadow-color; | ||||
|   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||
|   font-size: 14px; | ||||
|   color: $darker-text-color; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| .moved-account-widget, | ||||
| .memoriam-widget { | ||||
|   @media screen and (max-width: $no-gap-breakpoint) { | ||||
|     margin-bottom: 0; | ||||
|     box-shadow: none; | ||||
|     border-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | @ -9,7 +9,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base | |||
|       { | ||||
|         'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', | ||||
|         'sensitive'                 => 'as:sensitive', | ||||
|         'movedTo'                   => 'as:movedTo', | ||||
|         'movedTo'                   => { '@id' => 'as:movedTo', '@type' => '@id' }, | ||||
|         'Hashtag'                   => 'as:Hashtag', | ||||
|         'ostatus'                   => 'http://ostatus.org#', | ||||
|         'atomUri'                   => 'ostatus:atomUri', | ||||
|  | @ -18,7 +18,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base | |||
|         'toot'                      => 'http://joinmastodon.org/ns#', | ||||
|         'Emoji'                     => 'toot:Emoji', | ||||
|         'focalPoint'                => { '@container' => '@list', '@id' => 'toot:focalPoint' }, | ||||
|         'featured'                  => 'toot:featured', | ||||
|         'featured'                  => { '@id' => 'toot:featured', '@type' => '@id' }, | ||||
|         'schema'                    => 'http://schema.org#', | ||||
|         'PropertyValue'             => 'schema:PropertyValue', | ||||
|         'value'                     => 'schema:value', | ||||
|  |  | |||
|  | @ -70,6 +70,7 @@ class Account < ApplicationRecord | |||
| 
 | ||||
|   # Remote user validations | ||||
|   validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? } | ||||
|   validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, if: -> { !local? && will_save_change_to_username? } | ||||
| 
 | ||||
|   # Local user validations | ||||
|   validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } | ||||
|  |  | |||
|  | @ -5,11 +5,12 @@ module AccountHeader | |||
| 
 | ||||
|   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze | ||||
|   LIMIT = 2.megabytes | ||||
|   MAX_PIXELS = 750_000 # 1500x500px | ||||
| 
 | ||||
|   class_methods do | ||||
|     def header_styles(file) | ||||
|       styles = { original: { geometry: '700x335#', file_geometry_parser: FastGeometryParser } } | ||||
|       styles[:static] = { geometry: '700x335#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif' | ||||
|       styles = { original: { pixels: MAX_PIXELS, file_geometry_parser: FastGeometryParser } } | ||||
|       styles[:static] = { format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif' | ||||
|       styles | ||||
|     end | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,12 +25,13 @@ class MediaAttachment < ApplicationRecord | |||
|   enum type: [:image, :gifv, :video, :audio, :unknown] | ||||
| 
 | ||||
|   IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze | ||||
|   VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze | ||||
|   VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze | ||||
|   AUDIO_FILE_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.ogg'].freeze | ||||
| 
 | ||||
|   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze | ||||
|   VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze | ||||
|   AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze | ||||
|   IMAGE_MIME_TYPES             = ['image/jpeg', 'image/png', 'image/gif'].freeze | ||||
|   VIDEO_MIME_TYPES             = ['video/webm', 'video/mp4', 'video/quicktime'].freeze | ||||
|   VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze | ||||
|   AUDIO_MIME_TYPES             = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze | ||||
| 
 | ||||
|   IMAGE_STYLES = { | ||||
|     original: { | ||||
|  | @ -72,7 +73,25 @@ class MediaAttachment < ApplicationRecord | |||
|     }, | ||||
|   }.freeze | ||||
| 
 | ||||
|   LIMIT = 8.megabytes | ||||
|   VIDEO_FORMAT = { | ||||
|     format: 'mp4', | ||||
|     convert_options: { | ||||
|       output: { | ||||
|         'movflags' => 'faststart', | ||||
|         'pix_fmt'  => 'yuv420p', | ||||
|         'vf'       => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', | ||||
|         'vsync'    => 'cfr', | ||||
|         'c:v'      => 'h264', | ||||
|         'b:v'      => '500K', | ||||
|         'maxrate'  => '1300K', | ||||
|         'bufsize'  => '1300K', | ||||
|         'crf'      => 18, | ||||
|       }, | ||||
|     }, | ||||
|   }.freeze | ||||
| 
 | ||||
|   IMAGE_LIMIT = 8.megabytes | ||||
|   VIDEO_LIMIT = 40.megabytes | ||||
| 
 | ||||
|   belongs_to :account, inverse_of: :media_attachments, optional: true | ||||
|   belongs_to :status,  inverse_of: :media_attachments, optional: true | ||||
|  | @ -82,11 +101,10 @@ class MediaAttachment < ApplicationRecord | |||
|                     processors: ->(f) { file_processors f }, | ||||
|                     convert_options: { all: '-quality 90 -strip' } | ||||
| 
 | ||||
|   include Remotable | ||||
| 
 | ||||
|   validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES | ||||
|   validates_attachment_size :file, less_than: LIMIT | ||||
|   remotable_attachment :file, LIMIT | ||||
|   validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video? | ||||
|   validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video? | ||||
|   remotable_attachment :file, VIDEO_LIMIT | ||||
| 
 | ||||
|   include Attachmentable | ||||
| 
 | ||||
|  | @ -142,27 +160,17 @@ class MediaAttachment < ApplicationRecord | |||
|       if f.instance.file_content_type == 'image/gif' | ||||
|         { | ||||
|           small: IMAGE_STYLES[:small], | ||||
|           original: { | ||||
|             format: 'mp4', | ||||
|             convert_options: { | ||||
|               output: { | ||||
|                 'movflags' => 'faststart', | ||||
|                 'pix_fmt'  => 'yuv420p', | ||||
|                 'vf'       => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', | ||||
|                 'vsync'    => 'cfr', | ||||
|                 'c:v'      => 'h264', | ||||
|                 'b:v'      => '500K', | ||||
|                 'maxrate'  => '1300K', | ||||
|                 'bufsize'  => '1300K', | ||||
|                 'crf'      => 18, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|           original: VIDEO_FORMAT, | ||||
|         } | ||||
|       elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type | ||||
|         IMAGE_STYLES | ||||
|       elsif AUDIO_MIME_TYPES.include? f.instance.file_content_type | ||||
|         AUDIO_STYLES | ||||
|       elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type) | ||||
|         { | ||||
|           small: VIDEO_STYLES[:small], | ||||
|           original: VIDEO_FORMAT, | ||||
|         } | ||||
|       else | ||||
|         VIDEO_STYLES | ||||
|       end | ||||
|  |  | |||
|  | @ -7,14 +7,14 @@ class ActivityPub::FetchRemoteAccountService < BaseService | |||
| 
 | ||||
|   # Should be called when uri has already been checked for locality | ||||
|   # Does a WebFinger roundtrip on each call | ||||
|   def call(uri, id: true, prefetched_body: nil) | ||||
|   def call(uri, id: true, prefetched_body: nil, break_on_redirect: false) | ||||
|     @json = if prefetched_body.nil? | ||||
|               fetch_resource(uri, id) | ||||
|             else | ||||
|               body_to_json(prefetched_body) | ||||
|             end | ||||
| 
 | ||||
|     return unless supported_context? && expected_type? | ||||
|     return if !supported_context? || !expected_type? || (break_on_redirect && @json['movedTo'].present?) | ||||
| 
 | ||||
|     @uri      = @json['id'] | ||||
|     @username = @json['preferredUsername'] | ||||
|  |  | |||
|  | @ -175,7 +175,7 @@ class ActivityPub::ProcessAccountService < BaseService | |||
| 
 | ||||
|   def moved_account | ||||
|     account   = ActivityPub::TagManager.instance.uri_to_resource(@json['movedTo'], Account) | ||||
|     account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true) | ||||
|     account ||= ActivityPub::FetchRemoteAccountService.new.call(@json['movedTo'], id: true, break_on_redirect: true) | ||||
|     account | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										15
									
								
								app/views/accounts/_bio.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/views/accounts/_bio.html.haml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| .public-account-bio | ||||
|   - unless account.fields.empty? | ||||
|     .account__header__fields | ||||
|       - account.fields.each do |field| | ||||
|         %dl | ||||
|           %dt.emojify{ title: field.name }= field.name | ||||
|           %dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true) | ||||
| 
 | ||||
|   = account_badge(account) | ||||
| 
 | ||||
|   - if account.note.present? | ||||
|     .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) | ||||
| 
 | ||||
|   .public-account-bio__extra | ||||
|     = t 'accounts.joined', date: l(account.created_at, format: :month) | ||||
|  | @ -1,28 +0,0 @@ | |||
| - 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.local? ? account_unfollow_path(account) : remote_unfollow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do | ||||
|           = fa_icon 'user-times' | ||||
|           = t('accounts.unfollow') | ||||
|       - else | ||||
|         = link_to (account.local? ? account_follow_path(account) : authorize_follow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do | ||||
|           = fa_icon 'user-plus' | ||||
|           = t('accounts.follow') | ||||
|   - elsif user_signed_in? && current_account.id == account.id | ||||
|     .controls | ||||
|       = link_to settings_profile_url, class: 'icon-button' do | ||||
|         = fa_icon 'pencil' | ||||
|         = t('settings.edit_profile') | ||||
|   - elsif !user_signed_in? | ||||
|     .controls | ||||
|       .remote-follow | ||||
|         = link_to (account.local? ? account_remote_follow_path(account) : "web+mastodon://follow?uri=#{account.uri}"), class: 'icon-button' do | ||||
|           = fa_icon 'user-plus' | ||||
|           = t('accounts.remote_follow') | ||||
|  | @ -1,8 +0,0 @@ | |||
| .accounts-grid{ class: accounts.empty? ? 'empty' : '' } | ||||
|   - if accounts.empty? | ||||
|     = image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational' | ||||
|     = render partial: 'accounts/nothing_here' | ||||
|   - else | ||||
|     = render partial: 'accounts/grid_card', collection: accounts, as: :account, cached: !user_signed_in? | ||||
| 
 | ||||
| = paginate follows | ||||
|  | @ -1,3 +0,0 @@ | |||
| .accounts-grid.empty | ||||
|   = image_tag asset_pack_path('elephant_ui_greeting.svg'), alt: '', role: 'presentational' | ||||
|   %p.nothing-here= t('accounts.network_hidden') | ||||
|  | @ -1,12 +0,0 @@ | |||
| .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, custom_emojify: true) | ||||
|       %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) | ||||
|  | @ -1,51 +1,43 @@ | |||
| .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } | ||||
|   .card__illustration | ||||
|     = render 'accounts/follow_button', account: account | ||||
|     .avatar= image_tag account.avatar.url(:original), class: 'u-photo' | ||||
| .public-account-header | ||||
|   .public-account-header__image | ||||
|     = image_tag account.header.url, class: 'parallax' | ||||
|   .public-account-header__bar | ||||
|     = link_to short_account_url(account), class: 'avatar' do | ||||
|       = image_tag account.avatar.url | ||||
|     .public-account-header__tabs | ||||
|       .public-account-header__tabs__name | ||||
|         %h1 | ||||
|           = display_name(account) | ||||
|           %small | ||||
|             = acct(account) | ||||
|             = fa_icon('lock') if account.locked? | ||||
|       .public-account-header__tabs__tabs | ||||
|         .details-counters | ||||
|           .counter{ class: active_nav_class(short_account_url(account)) } | ||||
|             = link_to short_account_url(account), class: 'u-url u-uid' do | ||||
|               %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true | ||||
|               %span.counter-label= t('accounts.posts') | ||||
| 
 | ||||
|   .card__bio | ||||
|     %h1.name | ||||
|       %span.p-name.emojify= display_name(account, custom_emojify: true) | ||||
|       %small< | ||||
|         %span>< @#{account.local_username_and_domain} | ||||
|         = fa_icon('lock') if account.locked? | ||||
|           .counter{ class: active_nav_class(account_following_index_url(account)) } | ||||
|             = link_to account_following_index_url(account) do | ||||
|               %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true | ||||
|               %span.counter-label= t('accounts.following') | ||||
| 
 | ||||
|     - if account.bot? | ||||
|       .roles | ||||
|         .account-role.bot | ||||
|           = t 'accounts.roles.bot' | ||||
|     - elsif Setting.show_staff_badge | ||||
|       - if account.user_admin? | ||||
|         .roles | ||||
|           .account-role.admin | ||||
|             = t 'accounts.roles.admin' | ||||
|       - elsif account.user_moderator? | ||||
|         .roles | ||||
|           .account-role.moderator | ||||
|             = t 'accounts.roles.moderator' | ||||
|           .counter{ class: active_nav_class(account_followers_url(account)) } | ||||
|             = link_to account_followers_url(account) do | ||||
|               %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true | ||||
|               %span.counter-label= t('accounts.followers') | ||||
|         .spacer | ||||
|         .public-account-header__tabs__tabs__buttons | ||||
|           = account_action_button(account) | ||||
| 
 | ||||
|     .bio | ||||
|       .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) | ||||
|     .public-account-header__extra | ||||
|       = render 'accounts/bio', account: account | ||||
| 
 | ||||
|       - unless account.fields.empty? | ||||
|         .account__header__fields | ||||
|           - account.fields.each do |field| | ||||
|             %dl | ||||
|               %dt.emojify{ title: field.name }= field.name | ||||
|               %dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true) | ||||
| 
 | ||||
|     .details-counters | ||||
|       .counter{ class: active_nav_class(short_account_url(account)) } | ||||
|         = link_to short_account_url(account), class: 'u-url u-uid' do | ||||
|           %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true | ||||
|           %span.counter-label= t('accounts.posts') | ||||
| 
 | ||||
|       .counter{ class: active_nav_class(account_following_index_url(account)) } | ||||
|       .public-account-header__extra__links | ||||
|         = link_to account_following_index_url(account) do | ||||
|           %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true | ||||
|           %span.counter-label= t('accounts.following') | ||||
| 
 | ||||
|       .counter{ class: active_nav_class(account_followers_url(account)) } | ||||
|           %strong= number_to_human account.following_count, strip_insignificant_zeros: true | ||||
|           = t('accounts.following') | ||||
|         = link_to account_followers_url(account) do | ||||
|           %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true | ||||
|           %span.counter-label= t('accounts.followers') | ||||
|           %strong= number_to_human account.followers_count, strip_insignificant_zeros: true | ||||
|           = t('accounts.followers') | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| - moved_to_account = account.moved_to_account | ||||
| 
 | ||||
| .moved-strip | ||||
|   .moved-strip__message | ||||
| .moved-account-widget | ||||
|   .moved-account-widget__message | ||||
|     = fa_icon 'suitcase' | ||||
|     = t('accounts.moved_html', name: content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention')) | ||||
|     = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), TagManager.instance.url_for(moved_to_account), class: 'mention')) | ||||
| 
 | ||||
|   .moved-strip__card | ||||
|   .moved-account-widget__card | ||||
|     = link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do | ||||
|       .detailed-status__display-avatar | ||||
|         .account__avatar-overlay | ||||
|  | @ -13,5 +13,6 @@ | |||
|           .account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" } | ||||
| 
 | ||||
|       %span.display-name | ||||
|         %strong.emojify= display_name(moved_to_account, custom_emojify: true) | ||||
|         %bdi | ||||
|           %strong.emojify= display_name(moved_to_account, custom_emojify: true) | ||||
|         %span @#{moved_to_account.acct} | ||||
|  | @ -1 +0,0 @@ | |||
| %p.nothing-here= t('accounts.nothing_here') | ||||
|  | @ -20,36 +20,39 @@ | |||
|   = opengraph 'og:type', 'profile' | ||||
|   = render 'og', account: @account, url: short_account_url(@account, only_path: false) | ||||
| 
 | ||||
| - if @account.memorial? | ||||
|   .memoriam-strip= t('in_memoriam_html') | ||||
| - elsif @account.moved? | ||||
|   = render partial: 'moved_strip', locals: { account: @account } | ||||
| - elsif show_landing_strip? | ||||
|   = render partial: 'shared/landing_strip', locals: { account: @account } | ||||
| 
 | ||||
| .h-feed | ||||
|   %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/ | ||||
| = render 'header', account: @account, with_bio: true | ||||
| 
 | ||||
|   = render 'header', account: @account | ||||
| .grid | ||||
|   .column-0 | ||||
|     .h-feed | ||||
|       %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/ | ||||
| 
 | ||||
|   .activity-stream-tabs | ||||
|     = active_link_to t('accounts.posts'), short_account_url(@account) | ||||
|     = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) | ||||
|     = active_link_to t('accounts.media'), short_account_media_url(@account) | ||||
|       .account__section-headline | ||||
|         = active_link_to t('accounts.posts'), short_account_url(@account) | ||||
|         = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) | ||||
|         = active_link_to t('accounts.media'), short_account_media_url(@account) | ||||
| 
 | ||||
|   - if @statuses.empty? | ||||
|     .accounts-grid | ||||
|       = render 'nothing_here' | ||||
|   - else | ||||
|     .activity-stream.with-header | ||||
|       - if params[:page].to_i.zero? | ||||
|         = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } | ||||
|       - if @statuses.empty? | ||||
|         = nothing_here 'nothing-here--under-tabs' | ||||
|       - else | ||||
|         .activity-stream | ||||
|           - if params[:page].to_i.zero? | ||||
|             = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } | ||||
| 
 | ||||
|       = render partial: 'stream_entries/status', collection: @statuses, as: :status | ||||
|           - if @newer_url | ||||
|             .entry= link_to_more @newer_url | ||||
| 
 | ||||
|   - if @newer_url || @older_url | ||||
|     .pagination | ||||
|       - if @older_url | ||||
|         = link_to safe_join([fa_icon('chevron-left'), t('pagination.older')], ' '), @older_url, class: 'older', rel: 'next' | ||||
|       - if @newer_url | ||||
|         = link_to safe_join([t('pagination.newer'), fa_icon('chevron-right')], ' '), @newer_url, class: 'newer', rel: 'prev' | ||||
|           = render partial: 'stream_entries/status', collection: @statuses, as: :status | ||||
| 
 | ||||
|           - if @older_url | ||||
|             .entry= link_to_more @older_url | ||||
| 
 | ||||
|   .column-1 | ||||
|     - if @account.memorial? | ||||
|       .memoriam-widget= t('in_memoriam_html') | ||||
|     - elsif @account.moved? | ||||
|       = render 'moved', account: @account | ||||
| 
 | ||||
|     = render 'bio', account: @account | ||||
|     = render 'application/sidebar' | ||||
|  |  | |||
							
								
								
									
										16
									
								
								app/views/application/_card.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/views/application/_card.html.haml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| - account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account) | ||||
| 
 | ||||
| .card.h-card | ||||
|   = link_to account_url, target: '_blank', rel: 'noopener' do | ||||
|     .card__img | ||||
|       = image_tag account.header.url, alt: '' | ||||
|     .card__bar | ||||
|       .avatar | ||||
|         = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' | ||||
| 
 | ||||
|       .display-name | ||||
|         %bdi | ||||
|           %strong.emojify.p-name= display_name(account, custom_emojify: true) | ||||
|         %span | ||||
|           = acct(account) | ||||
|           = fa_icon('lock') if account.locked? | ||||
							
								
								
									
										6
									
								
								app/views/application/_sidebar.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/views/application/_sidebar.html.haml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| .hero-widget | ||||
|   .hero-widget__img | ||||
|     = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title | ||||
| 
 | ||||
|   .hero-widget__text | ||||
|     %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname) | ||||
|  | @ -10,7 +10,7 @@ | |||
|   - if @invite.present? && @invite.autofollow? | ||||
|     .fields-group{ style: 'margin-bottom: 30px' } | ||||
|       %p.hint{ style: 'text-align: center' }= t('invites.invited_by') | ||||
|       = render 'authorize_follows/card', account: @invite.user.account | ||||
|       = render 'application/card', account: @invite.user.account | ||||
| 
 | ||||
|   = f.simple_fields_for :account do |ff| | ||||
|     .input-with-append | ||||
|  |  | |||
|  | @ -1,23 +0,0 @@ | |||
| .account-card | ||||
|   .account-card__header{ style: "background-image: url(#{account.header.url(:original)})" } | ||||
|   .detailed-status__display-name | ||||
|     %div | ||||
|       = image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar' | ||||
| 
 | ||||
|     %span.display-name | ||||
|       - account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account) | ||||
|       = link_to account_url, class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do | ||||
|         %strong.emojify= display_name(account, custom_emojify: true) | ||||
|         %span @#{account.acct} | ||||
| 
 | ||||
|     .counter | ||||
|       %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true | ||||
|       %span.counter-label= t('accounts.posts') | ||||
| 
 | ||||
|     .counter | ||||
|       %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true | ||||
|       %span.counter-label= t('accounts.following') | ||||
| 
 | ||||
|     .counter | ||||
|       %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true | ||||
|       %span.counter-label= t('accounts.followers') | ||||
|  | @ -3,7 +3,7 @@ | |||
| 
 | ||||
| .form-container | ||||
|   .follow-prompt | ||||
|     = render 'card', account: @account | ||||
|     = render 'application/card', account: @account | ||||
| 
 | ||||
|   - if current_account.following?(@account) | ||||
|     .flash-message | ||||
|  |  | |||
|  | @ -8,6 +8,6 @@ | |||
|     - else | ||||
|       %h2= t('authorize_follow.following') | ||||
| 
 | ||||
|     = render 'card', account: @account | ||||
|     = render 'application/card', account: @account | ||||
| 
 | ||||
|   = render 'post_follow_actions' | ||||
|  |  | |||
|  | @ -8,6 +8,11 @@ | |||
| = render 'accounts/header', account: @account | ||||
| 
 | ||||
| - if @account.user_hides_network? | ||||
|   = render 'accounts/follow_grid_hidden' | ||||
|   .nothing-here= t('accounts.network_hidden') | ||||
| - elsif @follows.empty? | ||||
|   = nothing_here | ||||
| - else | ||||
|   = render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:account) | ||||
|   .card-grid | ||||
|     = render partial: 'application/card', collection: @follows.map(&:account), as: :account | ||||
| 
 | ||||
|   = paginate @follows | ||||
|  |  | |||
|  | @ -8,6 +8,11 @@ | |||
| = render 'accounts/header', account: @account | ||||
| 
 | ||||
| - if @account.user_hides_network? | ||||
|   = render 'accounts/follow_grid_hidden' | ||||
|   .nothing-here= t('accounts.network_hidden') | ||||
| - elsif @follows.empty? | ||||
|   = nothing_here | ||||
| - else | ||||
|   = render 'accounts/follow_grid', follows: @follows, accounts: @follows.map(&:target_account) | ||||
|   .card-grid | ||||
|     = render partial: 'application/card', collection: @follows.map(&:target_account), as: :account | ||||
| 
 | ||||
|   = paginate @follows | ||||
|  |  | |||
|  | @ -1,14 +1,47 @@ | |||
| - content_for :content do | ||||
|   .container-alt= yield | ||||
|   .footer | ||||
|     - if !user_signed_in? && single_user_mode? | ||||
|       %span.single-user-login | ||||
|         = link_to t('auth.login'), new_user_session_path | ||||
|         — | ||||
|       %span.footer__domain= link_to site_hostname, about_path | ||||
|     - else | ||||
|       %span.footer__domain= link_to site_hostname, root_path | ||||
|     %span.powered-by | ||||
|       != t('generic.powered_by', link: link_to('https://joinmastodon.org') { image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' }) | ||||
|   .public-layout | ||||
|     .container | ||||
|       %nav.header | ||||
|         .nav-left | ||||
|           = link_to root_url, class: 'brand' do | ||||
|             = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' | ||||
|         .nav-center | ||||
|         .nav-right | ||||
|           - if user_signed_in? | ||||
|             = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn' | ||||
|           - else | ||||
|             = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn nav-link nav-button' | ||||
|             = link_to t('auth.register'), new_user_registration_path, class: 'webapp-btn nav-link nav-button' | ||||
| 
 | ||||
|     .container= yield | ||||
| 
 | ||||
|     .container | ||||
|       .footer | ||||
|         .grid | ||||
|           .column-0 | ||||
|             %h4= t 'footer.resources' | ||||
|             %ul | ||||
|               %li= link_to t('about.terms'), terms_path | ||||
|               %li= link_to t('about.privacy_policy'), terms_path | ||||
|           .column-1 | ||||
|             %h4= t 'footer.developers' | ||||
|             %ul | ||||
|               %li= link_to t('about.documentation'), 'https://github.com/tootsuite/documentation' | ||||
|               %li= link_to t('about.api'), 'https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md' | ||||
|           .column-2 | ||||
|             %h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/' | ||||
| 
 | ||||
|             = link_to root_url, class: 'brand' do | ||||
|               = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') | ||||
|           .column-3 | ||||
|             %h4= site_hostname | ||||
|             %ul | ||||
|               %li= link_to t('about.about_this'), about_more_path | ||||
|               %li= "v#{Mastodon::Version.to_s}" | ||||
|           .column-4 | ||||
|             %h4= t 'footer.more' | ||||
|             %ul | ||||
|               %li= link_to t('about.source_code'), Mastodon::Version.source_url | ||||
|               %li= link_to 'joinmastodon.org', 'https://joinmastodon.org' | ||||
| 
 | ||||
| = render template: 'layouts/application' | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
|   .follow-prompt | ||||
|     %h2= t('remote_follow.prompt') | ||||
| 
 | ||||
|     = render partial: 'authorize_follows/card', locals: { account: @account } | ||||
|     = render partial: 'application/card', locals: { account: @account } | ||||
| 
 | ||||
|   = simple_form_for @remote_follow, as: :remote_follow, url: account_remote_follow_path(@account) do |f| | ||||
|     = render 'shared/error_messages', object: @remote_follow | ||||
|  |  | |||
|  | @ -5,6 +5,6 @@ | |||
|   .follow-prompt | ||||
|     %h2= t('remote_unfollow.unfollowed') | ||||
| 
 | ||||
|     = render 'card', account: @account | ||||
|     = render 'application/card', account: @account | ||||
| 
 | ||||
|   = render 'post_follow_actions' | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
|     %p.hint= t('migrations.currently_redirecting') | ||||
| 
 | ||||
|     .fields-group | ||||
|       = render partial: 'authorize_follows/card', locals: { account: @migration.account } | ||||
|       = render partial: 'application/card', locals: { account: @migration.account } | ||||
| 
 | ||||
|   = render 'shared/error_messages', object: @migration | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,13 +8,12 @@ | |||
|     = f.input :display_name, placeholder: t('simple_form.labels.defaults.display_name'), hint: t('simple_form.hints.defaults.display_name', count: 30 - @account.display_name.size).html_safe | ||||
|     = f.input :note, placeholder: t('simple_form.labels.defaults.note'), hint: t('simple_form.hints.defaults.note', count: 500 - @account.note.size).html_safe | ||||
| 
 | ||||
|   .card.compact{ style: "background-image: url(#{@account.header.url(:original)})", data: { original_src: @account.header.url(:original) } } | ||||
|     .avatar= image_tag @account.avatar.url(:original), data: { original_src: @account.avatar.url(:original) } | ||||
|   = render 'application/card', account: @account | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar') | ||||
|     = f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT)) | ||||
| 
 | ||||
|     = f.input :header, wrapper: :with_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header') | ||||
|     = f.input :header, wrapper: :with_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(AccountHeader::LIMIT)) | ||||
| 
 | ||||
|   .fields-group | ||||
|     = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked') | ||||
|  |  | |||
|  | @ -1,6 +0,0 @@ | |||
| .landing-strip | ||||
|   = image_tag asset_pack_path('logo.svg'), class: 'logo' | ||||
| 
 | ||||
|   %div | ||||
|     = t('landing_strip_html', name: content_tag(:span, display_name(account, custom_emojify: true), class: :emojify), link_to_root_path: link_to(content_tag(:strong, site_hostname), root_path)) | ||||
|     = t('landing_strip_signup_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started') | ||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
		Reference in a new issue