Merge pull request #481 from ThibG/glitch-soc/merge
Merge upstream changes
This commit is contained in:
		
						commit
						b5684e9874
					
				
							
								
								
									
										3
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										3
									
								
								Gemfile
								
								
								
								
							| 
						 | 
					@ -42,7 +42,7 @@ gem 'omniauth-cas', '~> 1.1'
 | 
				
			||||||
gem 'omniauth-saml', '~> 1.10'
 | 
					gem 'omniauth-saml', '~> 1.10'
 | 
				
			||||||
gem 'omniauth', '~> 1.2'
 | 
					gem 'omniauth', '~> 1.2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'doorkeeper', '~> 4.3'
 | 
					gem 'doorkeeper', '~> 4.2', '< 4.3'
 | 
				
			||||||
gem 'fast_blank', '~> 1.0'
 | 
					gem 'fast_blank', '~> 1.0'
 | 
				
			||||||
gem 'fastimage'
 | 
					gem 'fastimage'
 | 
				
			||||||
gem 'goldfinger', '~> 2.1'
 | 
					gem 'goldfinger', '~> 2.1'
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,7 @@ gem 'html2text'
 | 
				
			||||||
gem 'htmlentities', '~> 4.3'
 | 
					gem 'htmlentities', '~> 4.3'
 | 
				
			||||||
gem 'http', '~> 3.2'
 | 
					gem 'http', '~> 3.2'
 | 
				
			||||||
gem 'http_accept_language', '~> 2.1'
 | 
					gem 'http_accept_language', '~> 2.1'
 | 
				
			||||||
 | 
					gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
 | 
				
			||||||
gem 'httplog', '~> 1.0'
 | 
					gem 'httplog', '~> 1.0'
 | 
				
			||||||
gem 'idn-ruby', require: 'idn'
 | 
					gem 'idn-ruby', require: 'idn'
 | 
				
			||||||
gem 'kaminari', '~> 1.1'
 | 
					gem 'kaminari', '~> 1.1'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										13
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										13
									
								
								Gemfile.lock
								
								
								
								
							| 
						 | 
					@ -1,3 +1,10 @@
 | 
				
			||||||
 | 
					GIT
 | 
				
			||||||
 | 
					  remote: https://github.com/tmm1/http_parser.rb
 | 
				
			||||||
 | 
					  revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
 | 
				
			||||||
 | 
					  ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
 | 
				
			||||||
 | 
					  specs:
 | 
				
			||||||
 | 
					    http_parser.rb (0.6.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GEM
 | 
					GEM
 | 
				
			||||||
  remote: https://rubygems.org/
 | 
					  remote: https://rubygems.org/
 | 
				
			||||||
  specs:
 | 
					  specs:
 | 
				
			||||||
| 
						 | 
					@ -167,7 +174,7 @@ GEM
 | 
				
			||||||
    docile (1.3.0)
 | 
					    docile (1.3.0)
 | 
				
			||||||
    domain_name (0.5.20180417)
 | 
					    domain_name (0.5.20180417)
 | 
				
			||||||
      unf (>= 0.0.5, < 1.0.0)
 | 
					      unf (>= 0.0.5, < 1.0.0)
 | 
				
			||||||
    doorkeeper (4.3.2)
 | 
					    doorkeeper (4.2.6)
 | 
				
			||||||
      railties (>= 4.2)
 | 
					      railties (>= 4.2)
 | 
				
			||||||
    dotenv (2.2.2)
 | 
					    dotenv (2.2.2)
 | 
				
			||||||
    dotenv-rails (2.2.2)
 | 
					    dotenv-rails (2.2.2)
 | 
				
			||||||
| 
						 | 
					@ -254,7 +261,6 @@ GEM
 | 
				
			||||||
      domain_name (~> 0.5)
 | 
					      domain_name (~> 0.5)
 | 
				
			||||||
    http-form_data (2.1.0)
 | 
					    http-form_data (2.1.0)
 | 
				
			||||||
    http_accept_language (2.1.1)
 | 
					    http_accept_language (2.1.1)
 | 
				
			||||||
    http_parser.rb (0.6.0)
 | 
					 | 
				
			||||||
    httplog (1.0.2)
 | 
					    httplog (1.0.2)
 | 
				
			||||||
      colorize (~> 0.8)
 | 
					      colorize (~> 0.8)
 | 
				
			||||||
      rack (>= 1.0)
 | 
					      rack (>= 1.0)
 | 
				
			||||||
| 
						 | 
					@ -661,7 +667,7 @@ DEPENDENCIES
 | 
				
			||||||
  devise (~> 4.4)
 | 
					  devise (~> 4.4)
 | 
				
			||||||
  devise-two-factor (~> 3.0)
 | 
					  devise-two-factor (~> 3.0)
 | 
				
			||||||
  devise_pam_authenticatable2 (~> 9.1)
 | 
					  devise_pam_authenticatable2 (~> 9.1)
 | 
				
			||||||
  doorkeeper (~> 4.3)
 | 
					  doorkeeper (~> 4.2, < 4.3)
 | 
				
			||||||
  dotenv-rails (~> 2.2, < 2.3)
 | 
					  dotenv-rails (~> 2.2, < 2.3)
 | 
				
			||||||
  fabrication (~> 2.20)
 | 
					  fabrication (~> 2.20)
 | 
				
			||||||
  faker (~> 1.8)
 | 
					  faker (~> 1.8)
 | 
				
			||||||
| 
						 | 
					@ -678,6 +684,7 @@ DEPENDENCIES
 | 
				
			||||||
  htmlentities (~> 4.3)
 | 
					  htmlentities (~> 4.3)
 | 
				
			||||||
  http (~> 3.2)
 | 
					  http (~> 3.2)
 | 
				
			||||||
  http_accept_language (~> 2.1)
 | 
					  http_accept_language (~> 2.1)
 | 
				
			||||||
 | 
					  http_parser.rb (~> 0.6)!
 | 
				
			||||||
  httplog (~> 1.0)
 | 
					  httplog (~> 1.0)
 | 
				
			||||||
  i18n-tasks (~> 0.9)
 | 
					  i18n-tasks (~> 0.9)
 | 
				
			||||||
  idn-ruby
 | 
					  idn-ruby
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,12 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
 | 
				
			||||||
    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
 | 
					    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def show
 | 
				
			||||||
 | 
					    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def update
 | 
					  def update
 | 
				
			||||||
    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
 | 
					    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,10 +10,16 @@ module Admin::AccountModerationNotesHelper
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def admin_account_inline_link_to(account)
 | 
				
			||||||
 | 
					    link_to admin_account_path(account.id), class: name_tag_classes(account, true) do
 | 
				
			||||||
 | 
					      content_tag(:span, account.acct, class: 'username')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def name_tag_classes(account)
 | 
					  def name_tag_classes(account, inline = false)
 | 
				
			||||||
    classes = ['name-tag']
 | 
					    classes = [inline ? 'inline-name-tag' : 'name-tag']
 | 
				
			||||||
    classes << 'suspended' if account.suspended?
 | 
					    classes << 'suspended' if account.suspended?
 | 
				
			||||||
    classes.join(' ')
 | 
					    classes.join(' ')
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,18 +52,22 @@ module JsonLdHelper
 | 
				
			||||||
    graph.dump(:normalize)
 | 
					    graph.dump(:normalize)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def fetch_resource(uri, id)
 | 
					  def fetch_resource(uri, id, on_behalf_of = nil)
 | 
				
			||||||
    unless id
 | 
					    unless id
 | 
				
			||||||
      json = fetch_resource_without_id_validation(uri)
 | 
					      json = fetch_resource_without_id_validation(uri, on_behalf_of)
 | 
				
			||||||
      return unless json
 | 
					      return unless json
 | 
				
			||||||
      uri = json['id']
 | 
					      uri = json['id']
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    json = fetch_resource_without_id_validation(uri)
 | 
					    json = fetch_resource_without_id_validation(uri, on_behalf_of)
 | 
				
			||||||
    json.present? && json['id'] == uri ? json : nil
 | 
					    json.present? && json['id'] == uri ? json : nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def fetch_resource_without_id_validation(uri)
 | 
					  def fetch_resource_without_id_validation(uri, on_behalf_of = nil)
 | 
				
			||||||
 | 
					    build_request(uri, on_behalf_of).perform do |response|
 | 
				
			||||||
 | 
					      return body_to_json(response.body_with_limit) if response.code == 200
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    # If request failed, retry without doing it on behalf of a user
 | 
				
			||||||
    build_request(uri).perform do |response|
 | 
					    build_request(uri).perform do |response|
 | 
				
			||||||
      response.code == 200 ? body_to_json(response.body_with_limit) : nil
 | 
					      response.code == 200 ? body_to_json(response.body_with_limit) : nil
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -85,8 +89,9 @@ module JsonLdHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_request(uri)
 | 
					  def build_request(uri, on_behalf_of = nil)
 | 
				
			||||||
    request = Request.new(:get, uri)
 | 
					    request = Request.new(:get, uri)
 | 
				
			||||||
 | 
					    request.on_behalf_of(on_behalf_of) if on_behalf_of
 | 
				
			||||||
    request.add_headers('Accept' => 'application/activity+json, application/ld+json')
 | 
					    request.add_headers('Accept' => 'application/activity+json, application/ld+json')
 | 
				
			||||||
    request
 | 
					    request
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,7 @@ module SettingsHelper
 | 
				
			||||||
    'pt-BR': 'Português do Brasil',
 | 
					    'pt-BR': 'Português do Brasil',
 | 
				
			||||||
    ru: 'Русский',
 | 
					    ru: 'Русский',
 | 
				
			||||||
    sk: 'Slovensky',
 | 
					    sk: 'Slovensky',
 | 
				
			||||||
 | 
					    sl: 'Slovenščina',
 | 
				
			||||||
    sr: 'Српски',
 | 
					    sr: 'Српски',
 | 
				
			||||||
    'sr-Latn': 'Srpski (latinica)',
 | 
					    'sr-Latn': 'Srpski (latinica)',
 | 
				
			||||||
    sv: 'Svenska',
 | 
					    sv: 'Svenska',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,8 +84,8 @@ export default class Status extends ImmutablePureComponent {
 | 
				
			||||||
    return <div className='media-spoiler-video' style={{ height: '110px' }} />;
 | 
					    return <div className='media-spoiler-video' style={{ height: '110px' }} />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleOpenVideo = startTime => {
 | 
					  handleOpenVideo = (media, startTime) => {
 | 
				
			||||||
    this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime);
 | 
					    this.props.onOpenVideo(media, startTime);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleHotkeyReply = e => {
 | 
					  handleHotkeyReply = e => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,59 +0,0 @@
 | 
				
			||||||
import React, { Fragment } from 'react';
 | 
					 | 
				
			||||||
import ReactDOM from 'react-dom';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { IntlProvider, addLocaleData } from 'react-intl';
 | 
					 | 
				
			||||||
import { getLocale } from '../locales';
 | 
					 | 
				
			||||||
import Card from '../features/status/components/card';
 | 
					 | 
				
			||||||
import ModalRoot from '../components/modal_root';
 | 
					 | 
				
			||||||
import MediaModal from '../features/ui/components/media_modal';
 | 
					 | 
				
			||||||
import { fromJS } from 'immutable';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { localeData, messages } = getLocale();
 | 
					 | 
				
			||||||
addLocaleData(localeData);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class CardsContainer extends React.PureComponent {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    locale: PropTypes.string,
 | 
					 | 
				
			||||||
    cards: PropTypes.object.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  state = {
 | 
					 | 
				
			||||||
    media: null,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleOpenCard = (media) => {
 | 
					 | 
				
			||||||
    document.body.classList.add('card-standalone__body');
 | 
					 | 
				
			||||||
    this.setState({ media });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleCloseCard = () => {
 | 
					 | 
				
			||||||
    document.body.classList.remove('card-standalone__body');
 | 
					 | 
				
			||||||
    this.setState({ media: null });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { locale, cards } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages}>
 | 
					 | 
				
			||||||
        <Fragment>
 | 
					 | 
				
			||||||
          {[].map.call(cards, container => {
 | 
					 | 
				
			||||||
            const { card, ...props } = JSON.parse(container.getAttribute('data-props'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ReactDOM.createPortal(
 | 
					 | 
				
			||||||
              <Card card={fromJS(card)} onOpenMedia={this.handleOpenCard} {...props} />,
 | 
					 | 
				
			||||||
              container,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          })}
 | 
					 | 
				
			||||||
          <ModalRoot onClose={this.handleCloseCard}>
 | 
					 | 
				
			||||||
            {this.state.media && (
 | 
					 | 
				
			||||||
              <MediaModal media={this.state.media} index={0} onClose={this.handleCloseCard} />
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          </ModalRoot>
 | 
					 | 
				
			||||||
        </Fragment>
 | 
					 | 
				
			||||||
      </IntlProvider>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					import React, { PureComponent, Fragment } from 'react';
 | 
				
			||||||
 | 
					import ReactDOM from 'react-dom';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import { IntlProvider, addLocaleData } from 'react-intl';
 | 
				
			||||||
 | 
					import { getLocale } from '../locales';
 | 
				
			||||||
 | 
					import MediaGallery from '../components/media_gallery';
 | 
				
			||||||
 | 
					import Video from '../features/video';
 | 
				
			||||||
 | 
					import Card from '../features/status/components/card';
 | 
				
			||||||
 | 
					import ModalRoot from '../components/modal_root';
 | 
				
			||||||
 | 
					import MediaModal from '../features/ui/components/media_modal';
 | 
				
			||||||
 | 
					import { List as ImmutableList, fromJS } from 'immutable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { localeData, messages } = getLocale();
 | 
				
			||||||
 | 
					addLocaleData(localeData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MEDIA_COMPONENTS = { MediaGallery, Video, Card };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class MediaContainer extends PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static propTypes = {
 | 
				
			||||||
 | 
					    locale: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    components: PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state = {
 | 
				
			||||||
 | 
					    media: null,
 | 
				
			||||||
 | 
					    index: null,
 | 
				
			||||||
 | 
					    time: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleOpenMedia = (media, index) => {
 | 
				
			||||||
 | 
					    document.body.classList.add('media-standalone__body');
 | 
				
			||||||
 | 
					    this.setState({ media, index });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleOpenVideo = (video, time) => {
 | 
				
			||||||
 | 
					    const media = ImmutableList([video]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.body.classList.add('media-standalone__body');
 | 
				
			||||||
 | 
					    this.setState({ media, time });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleCloseMedia = () => {
 | 
				
			||||||
 | 
					    document.body.classList.remove('media-standalone__body');
 | 
				
			||||||
 | 
					    this.setState({ media: null, index: null, time: null });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { locale, components } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <IntlProvider locale={locale} messages={messages}>
 | 
				
			||||||
 | 
					        <Fragment>
 | 
				
			||||||
 | 
					          {[].map.call(components, (component, i) => {
 | 
				
			||||||
 | 
					            const componentName = component.getAttribute('data-component');
 | 
				
			||||||
 | 
					            const Component = MEDIA_COMPONENTS[componentName];
 | 
				
			||||||
 | 
					            const { media, card, ...props } = JSON.parse(component.getAttribute('data-props'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Object.assign(props, {
 | 
				
			||||||
 | 
					              ...(media ? { media: fromJS(media) } : {}),
 | 
				
			||||||
 | 
					              ...(card  ? { card:  fromJS(card)  } : {}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              ...(componentName === 'Video' ? {
 | 
				
			||||||
 | 
					                onOpenVideo: this.handleOpenVideo,
 | 
				
			||||||
 | 
					              } : {
 | 
				
			||||||
 | 
					                onOpenMedia: this.handleOpenMedia,
 | 
				
			||||||
 | 
					              }),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return ReactDOM.createPortal(
 | 
				
			||||||
 | 
					              <Component {...props} key={`media-${i}`} />,
 | 
				
			||||||
 | 
					              component,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          })}
 | 
				
			||||||
 | 
					          <ModalRoot onClose={this.handleCloseMedia}>
 | 
				
			||||||
 | 
					            {this.state.media && (
 | 
				
			||||||
 | 
					              <MediaModal
 | 
				
			||||||
 | 
					                media={this.state.media}
 | 
				
			||||||
 | 
					                index={this.state.index || 0}
 | 
				
			||||||
 | 
					                time={this.state.time}
 | 
				
			||||||
 | 
					                onClose={this.handleCloseMedia}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </ModalRoot>
 | 
				
			||||||
 | 
					        </Fragment>
 | 
				
			||||||
 | 
					      </IntlProvider>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,68 +0,0 @@
 | 
				
			||||||
import React from 'react';
 | 
					 | 
				
			||||||
import ReactDOM from 'react-dom';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { IntlProvider, addLocaleData } from 'react-intl';
 | 
					 | 
				
			||||||
import { getLocale } from '../locales';
 | 
					 | 
				
			||||||
import MediaGallery from '../components/media_gallery';
 | 
					 | 
				
			||||||
import ModalRoot from '../components/modal_root';
 | 
					 | 
				
			||||||
import MediaModal from '../features/ui/components/media_modal';
 | 
					 | 
				
			||||||
import { fromJS } from 'immutable';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { localeData, messages } = getLocale();
 | 
					 | 
				
			||||||
addLocaleData(localeData);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class MediaGalleriesContainer extends React.PureComponent {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    galleries: PropTypes.object.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  state = {
 | 
					 | 
				
			||||||
    media: null,
 | 
					 | 
				
			||||||
    index: null,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleOpenMedia = (media, index) => {
 | 
					 | 
				
			||||||
    document.body.classList.add('media-gallery-standalone__body');
 | 
					 | 
				
			||||||
    this.setState({ media, index });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleCloseMedia = () => {
 | 
					 | 
				
			||||||
    document.body.classList.remove('media-gallery-standalone__body');
 | 
					 | 
				
			||||||
    this.setState({ media: null, index: null });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { locale, galleries } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages}>
 | 
					 | 
				
			||||||
        <React.Fragment>
 | 
					 | 
				
			||||||
          {[].map.call(galleries, gallery => {
 | 
					 | 
				
			||||||
            const { media, ...props } = JSON.parse(gallery.getAttribute('data-props'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ReactDOM.createPortal(
 | 
					 | 
				
			||||||
              <MediaGallery
 | 
					 | 
				
			||||||
                {...props}
 | 
					 | 
				
			||||||
                media={fromJS(media)}
 | 
					 | 
				
			||||||
                onOpenMedia={this.handleOpenMedia}
 | 
					 | 
				
			||||||
              />,
 | 
					 | 
				
			||||||
              gallery
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          })}
 | 
					 | 
				
			||||||
          <ModalRoot onClose={this.handleCloseMedia}>
 | 
					 | 
				
			||||||
            {this.state.media === null || this.state.index === null ? null : (
 | 
					 | 
				
			||||||
              <MediaModal
 | 
					 | 
				
			||||||
                media={this.state.media}
 | 
					 | 
				
			||||||
                index={this.state.index}
 | 
					 | 
				
			||||||
                onClose={this.handleCloseMedia}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          </ModalRoot>
 | 
					 | 
				
			||||||
        </React.Fragment>
 | 
					 | 
				
			||||||
      </IntlProvider>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,26 +0,0 @@
 | 
				
			||||||
import React from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { IntlProvider, addLocaleData } from 'react-intl';
 | 
					 | 
				
			||||||
import { getLocale } from '../locales';
 | 
					 | 
				
			||||||
import Video from '../features/video';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { localeData, messages } = getLocale();
 | 
					 | 
				
			||||||
addLocaleData(localeData);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class VideoContainer extends React.PureComponent {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    locale: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { locale, ...props } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <IntlProvider locale={locale} messages={messages}>
 | 
					 | 
				
			||||||
        <Video {...props} />
 | 
					 | 
				
			||||||
      </IntlProvider>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -34,8 +34,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
 | 
				
			||||||
    e.stopPropagation();
 | 
					    e.stopPropagation();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleOpenVideo = startTime => {
 | 
					  handleOpenVideo = (media, startTime) => {
 | 
				
			||||||
    this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
 | 
					    this.props.onOpenVideo(media, startTime);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleExpandedToggle = () => {
 | 
					  handleExpandedToggle = () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import React from 'react';
 | 
				
			||||||
import ReactSwipeableViews from 'react-swipeable-views';
 | 
					import ReactSwipeableViews from 'react-swipeable-views';
 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import Video from '../../video';
 | 
				
			||||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
 | 
					import ExtendedVideoPlayer from '../../../components/extended_video_player';
 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
					import { defineMessages, injectIntl } from 'react-intl';
 | 
				
			||||||
| 
						 | 
					@ -112,6 +113,22 @@ export default class MediaModal extends ImmutablePureComponent {
 | 
				
			||||||
            onClick={this.toggleNavigation}
 | 
					            onClick={this.toggleNavigation}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					      } else if (image.get('type') === 'video') {
 | 
				
			||||||
 | 
					        const { time } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Video
 | 
				
			||||||
 | 
					            preview={image.get('preview_url')}
 | 
				
			||||||
 | 
					            src={image.get('url')}
 | 
				
			||||||
 | 
					            width={image.get('width')}
 | 
				
			||||||
 | 
					            height={image.get('height')}
 | 
				
			||||||
 | 
					            startTime={time || 0}
 | 
				
			||||||
 | 
					            onCloseVideo={onClose}
 | 
				
			||||||
 | 
					            detailed
 | 
				
			||||||
 | 
					            description={image.get('description')}
 | 
				
			||||||
 | 
					            key={image.get('url')}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      } else if (image.get('type') === 'gifv') {
 | 
					      } else if (image.get('type') === 'gifv') {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
          <ExtendedVideoPlayer
 | 
					          <ExtendedVideoPlayer
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
					import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					import { fromJS } from 'immutable';
 | 
				
			||||||
import { throttle } from 'lodash';
 | 
					import { throttle } from 'lodash';
 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
 | 
					import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
 | 
				
			||||||
| 
						 | 
					@ -131,6 +132,8 @@ export default class Video extends React.PureComponent {
 | 
				
			||||||
    this.seek = c;
 | 
					    this.seek = c;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleClickRoot = e => e.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handlePlay = () => {
 | 
					  handlePlay = () => {
 | 
				
			||||||
    this.setState({ paused: false });
 | 
					    this.setState({ paused: false });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -244,8 +247,17 @@ export default class Video extends React.PureComponent {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleOpenVideo = () => {
 | 
					  handleOpenVideo = () => {
 | 
				
			||||||
 | 
					    const { src, preview, width, height } = this.props;
 | 
				
			||||||
 | 
					    const media = fromJS({
 | 
				
			||||||
 | 
					      type: 'video',
 | 
				
			||||||
 | 
					      url: src,
 | 
				
			||||||
 | 
					      preview_url: preview,
 | 
				
			||||||
 | 
					      width,
 | 
				
			||||||
 | 
					      height,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.video.pause();
 | 
					    this.video.pause();
 | 
				
			||||||
    this.props.onOpenVideo(this.video.currentTime);
 | 
					    this.props.onOpenVideo(media, this.video.currentTime);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleCloseVideo = () => {
 | 
					  handleCloseVideo = () => {
 | 
				
			||||||
| 
						 | 
					@ -270,7 +282,16 @@ export default class Video extends React.PureComponent {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })} style={playerStyle} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
 | 
					      <div
 | 
				
			||||||
 | 
					        role='menuitem'
 | 
				
			||||||
 | 
					        className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })}
 | 
				
			||||||
 | 
					        style={playerStyle}
 | 
				
			||||||
 | 
					        ref={this.setPlayerRef}
 | 
				
			||||||
 | 
					        onMouseEnter={this.handleMouseEnter}
 | 
				
			||||||
 | 
					        onMouseLeave={this.handleMouseLeave}
 | 
				
			||||||
 | 
					        onClick={this.handleClickRoot}
 | 
				
			||||||
 | 
					        tabIndex={0}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
        <video
 | 
					        <video
 | 
				
			||||||
          ref={this.setVideoRef}
 | 
					          ref={this.setVideoRef}
 | 
				
			||||||
          src={src}
 | 
					          src={src}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,11 +28,10 @@ self.addEventListener('fetch', function(event) {
 | 
				
			||||||
    const asyncResponse = fetchRoot();
 | 
					    const asyncResponse = fetchRoot();
 | 
				
			||||||
    const asyncCache = openWebCache();
 | 
					    const asyncCache = openWebCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event.respondWith(asyncResponse.then(async response => {
 | 
					    event.respondWith(asyncResponse.then(response => {
 | 
				
			||||||
      if (response.ok) {
 | 
					      if (response.ok) {
 | 
				
			||||||
        const cache = await asyncCache;
 | 
					        return asyncCache.then(cache => cache.put('/', response))
 | 
				
			||||||
        await cache.put('/', response);
 | 
					                         .then(() => response.clone());
 | 
				
			||||||
        return response.clone();
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      throw null;
 | 
					      throw null;
 | 
				
			||||||
| 
						 | 
					@ -41,35 +40,38 @@ self.addEventListener('fetch', function(event) {
 | 
				
			||||||
    const asyncResponse = fetch(event.request);
 | 
					    const asyncResponse = fetch(event.request);
 | 
				
			||||||
    const asyncCache = openWebCache();
 | 
					    const asyncCache = openWebCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event.respondWith(asyncResponse.then(async response => {
 | 
					    event.respondWith(asyncResponse.then(response => {
 | 
				
			||||||
      if (response.ok || response.type === 'opaqueredirect') {
 | 
					      if (response.ok || response.type === 'opaqueredirect') {
 | 
				
			||||||
        await Promise.all([
 | 
					        return Promise.all([
 | 
				
			||||||
          asyncCache.then(cache => cache.delete('/')),
 | 
					          asyncCache.then(cache => cache.delete('/')),
 | 
				
			||||||
          indexedDB.deleteDatabase('mastodon'),
 | 
					          indexedDB.deleteDatabase('mastodon'),
 | 
				
			||||||
        ]);
 | 
					        ]).then(() => response);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return response;
 | 
					      return response;
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
  } else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
 | 
					  } else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
 | 
				
			||||||
    event.respondWith(openSystemCache().then(async cache => {
 | 
					    event.respondWith(openSystemCache().then(cache => {
 | 
				
			||||||
      const cached = await cache.match(event.request.url);
 | 
					      return cache.match(event.request.url).then(cached => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (cached === undefined) {
 | 
					        if (cached === undefined) {
 | 
				
			||||||
        const fetched = await fetch(event.request);
 | 
					          return fetch(event.request).then(fetched => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (fetched.ok) {
 | 
					            if (fetched.ok) {
 | 
				
			||||||
          try {
 | 
					              const put = cache.put(event.request.url, fetched.clone());
 | 
				
			||||||
            await cache.put(event.request.url, fetched.clone());
 | 
					
 | 
				
			||||||
          } finally {
 | 
					              put.catch(() => freeStorage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              return put.then(() => {
 | 
				
			||||||
                freeStorage();
 | 
					                freeStorage();
 | 
				
			||||||
          }
 | 
					                return fetched;
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return fetched;
 | 
					            return fetched;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return cached;
 | 
					        return cached;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,7 +182,9 @@ export function putStatuses(records) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function freeStorage() {
 | 
					export function freeStorage() {
 | 
				
			||||||
  return navigator.storage.estimate().then(({ quota, usage }) => {
 | 
					  // navigator.storage is not present on:
 | 
				
			||||||
 | 
					  // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.100 Safari/537.36 Edge/16.16299
 | 
				
			||||||
 | 
					  return 'storage' in navigator && navigator.storage.estimate().then(({ quota, usage }) => {
 | 
				
			||||||
    if (usage + storageMargin < quota) {
 | 
					    if (usage + storageMargin < quota) {
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ function main() {
 | 
				
			||||||
  const emojify = require('../mastodon/features/emoji/emoji').default;
 | 
					  const emojify = require('../mastodon/features/emoji/emoji').default;
 | 
				
			||||||
  const { getLocale } = require('../mastodon/locales');
 | 
					  const { getLocale } = require('../mastodon/locales');
 | 
				
			||||||
  const { localeData } = getLocale();
 | 
					  const { localeData } = getLocale();
 | 
				
			||||||
  const VideoContainer = require('../mastodon/containers/video_container').default;
 | 
					 | 
				
			||||||
  const React = require('react');
 | 
					  const React = require('react');
 | 
				
			||||||
  const ReactDOM = require('react-dom');
 | 
					  const ReactDOM = require('react-dom');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,30 +50,16 @@ function main() {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [].forEach.call(document.querySelectorAll('[data-component="Video"]'), (content) => {
 | 
					    const reactComponents = document.querySelectorAll('[data-component]');
 | 
				
			||||||
      const props = JSON.parse(content.getAttribute('data-props'));
 | 
					    if (reactComponents.length > 0) {
 | 
				
			||||||
      ReactDOM.render(<VideoContainer locale={locale} {...props} />, content);
 | 
					      import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container')
 | 
				
			||||||
    });
 | 
					        .then(({ default: MediaContainer }) => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const cards = document.querySelectorAll('[data-component="Card"]');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (cards.length > 0) {
 | 
					 | 
				
			||||||
      import(/* webpackChunkName: "containers/cards_container" */ '../mastodon/containers/cards_container').then(({ default: CardsContainer }) => {
 | 
					 | 
				
			||||||
          const content = document.createElement('div');
 | 
					          const content = document.createElement('div');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ReactDOM.render(<CardsContainer locale={locale} cards={cards} />, content);
 | 
					          ReactDOM.render(<MediaContainer locale={locale} components={reactComponents} />, content);
 | 
				
			||||||
        document.body.appendChild(content);
 | 
					 | 
				
			||||||
      }).catch(error => console.error(error));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const mediaGalleries = document.querySelectorAll('[data-component="MediaGallery"]');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (mediaGalleries.length > 0) {
 | 
					 | 
				
			||||||
      const MediaGalleriesContainer = require('../mastodon/containers/media_galleries_container').default;
 | 
					 | 
				
			||||||
      const content = document.createElement('div');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      ReactDOM.render(<MediaGalleriesContainer locale={locale} galleries={mediaGalleries} />, content);
 | 
					 | 
				
			||||||
          document.body.appendChild(content);
 | 
					          document.body.appendChild(content);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch(error => console.error(error));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -484,19 +484,12 @@
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a.name-tag,
 | 
					a.name-tag,
 | 
				
			||||||
.name-tag {
 | 
					.name-tag,
 | 
				
			||||||
  display: flex;
 | 
					a.inline-name-tag,
 | 
				
			||||||
  align-items: center;
 | 
					.inline-name-tag {
 | 
				
			||||||
  text-decoration: none;
 | 
					  text-decoration: none;
 | 
				
			||||||
  color: $secondary-text-color;
 | 
					  color: $secondary-text-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .avatar {
 | 
					 | 
				
			||||||
    display: block;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    margin-right: 5px;
 | 
					 | 
				
			||||||
    border-radius: 50%;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .username {
 | 
					  .username {
 | 
				
			||||||
    font-weight: 500;
 | 
					    font-weight: 500;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -514,6 +507,26 @@ a.name-tag,
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.name-tag,
 | 
				
			||||||
 | 
					.name-tag {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .avatar {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &.suspended {
 | 
				
			||||||
 | 
					    .avatar {
 | 
				
			||||||
 | 
					      filter: grayscale(100%);
 | 
				
			||||||
 | 
					      opacity: 0.8;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.speech-bubble {
 | 
					.speech-bubble {
 | 
				
			||||||
  margin-bottom: 20px;
 | 
					  margin-bottom: 20px;
 | 
				
			||||||
  border-left: 4px solid $ui-highlight-color;
 | 
					  border-left: 4px solid $ui-highlight-color;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4432,6 +4432,10 @@ a.status-card {
 | 
				
			||||||
  max-width: 100%;
 | 
					  max-width: 100%;
 | 
				
			||||||
  border-radius: 4px;
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &:focus {
 | 
				
			||||||
 | 
					    outline: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  video {
 | 
					  video {
 | 
				
			||||||
    max-width: 100vw;
 | 
					    max-width: 100vw;
 | 
				
			||||||
    max-height: 80vh;
 | 
					    max-height: 80vh;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,8 +60,7 @@
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.card-standalone__body,
 | 
					.media-standalone__body {
 | 
				
			||||||
.media-gallery-standalone__body {
 | 
					 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,9 @@
 | 
				
			||||||
 | 
					@keyframes Swag {
 | 
				
			||||||
 | 
					  0% { background-position: 0% 0%; }
 | 
				
			||||||
 | 
					  50% { background-position: 100% 0%; }
 | 
				
			||||||
 | 
					  100% { background-position: 200% 0%; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.table {
 | 
					.table {
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  max-width: 100%;
 | 
					  max-width: 100%;
 | 
				
			||||||
| 
						 | 
					@ -187,6 +193,11 @@ a.table-action-link {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    strong {
 | 
					    strong {
 | 
				
			||||||
      font-weight: 700;
 | 
					      font-weight: 700;
 | 
				
			||||||
 | 
					      background: linear-gradient(to right, orange , yellow, green, cyan, blue, violet,orange , yellow, green, cyan, blue, violet);
 | 
				
			||||||
 | 
					      background-size: 200% 100%;
 | 
				
			||||||
 | 
					      background-clip: text;
 | 
				
			||||||
 | 
					      color: transparent;
 | 
				
			||||||
 | 
					      animation: Swag 2s linear 0s infinite;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
 | 
				
			||||||
    if object_uri.start_with?('http')
 | 
					    if object_uri.start_with?('http')
 | 
				
			||||||
      return if ActivityPub::TagManager.instance.local_uri?(object_uri)
 | 
					      return if ActivityPub::TagManager.instance.local_uri?(object_uri)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true)
 | 
					      ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
 | 
				
			||||||
    elsif @object['url'].present?
 | 
					    elsif @object['url'].present?
 | 
				
			||||||
      ::FetchRemoteStatusService.new.call(@object['url'])
 | 
					      ::FetchRemoteStatusService.new.call(@object['url'])
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,8 +74,17 @@ module StatusThreadingConcern
 | 
				
			||||||
    statuses    = statuses_with_accounts(ids).to_a
 | 
					    statuses    = statuses_with_accounts(ids).to_a
 | 
				
			||||||
    account_ids = statuses.map(&:account_id).uniq
 | 
					    account_ids = statuses.map(&:account_id).uniq
 | 
				
			||||||
    domains     = statuses.map(&:account_domain).compact.uniq
 | 
					    domains     = statuses.map(&:account_domain).compact.uniq
 | 
				
			||||||
 | 
					    relations   = relations_map_for_account(account, account_ids, domains)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    statuses.reject! { |status| filter_from_context?(status, account, relations) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Order ancestors/descendants by tree path
 | 
				
			||||||
 | 
					    statuses.sort_by! { |status| ids.index(status.id) }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def relations_map_for_account(account, account_ids, domains)
 | 
				
			||||||
 | 
					    return {} if account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    relations = if account.present?
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      blocking: Account.blocking_map(account_ids, account.id),
 | 
					      blocking: Account.blocking_map(account_ids, account.id),
 | 
				
			||||||
      blocked_by: Account.blocked_by_map(account_ids, account.id),
 | 
					      blocked_by: Account.blocked_by_map(account_ids, account.id),
 | 
				
			||||||
| 
						 | 
					@ -85,12 +94,6 @@ module StatusThreadingConcern
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    statuses.reject! { |status| filter_from_context?(status, account, relations) }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Order ancestors/descendants by tree path
 | 
					 | 
				
			||||||
    statuses.sort_by! { |status| ids.index(status.id) }
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def statuses_with_accounts(ids)
 | 
					  def statuses_with_accounts(ids)
 | 
				
			||||||
    Status.where(id: ids).includes(:account)
 | 
					    Status.where(id: ids).includes(:account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,9 +4,9 @@ class ActivityPub::FetchRemoteStatusService < BaseService
 | 
				
			||||||
  include JsonLdHelper
 | 
					  include JsonLdHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Should be called when uri has already been checked for locality
 | 
					  # Should be called when uri has already been checked for locality
 | 
				
			||||||
  def call(uri, id: true, prefetched_body: nil)
 | 
					  def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
 | 
				
			||||||
    @json = if prefetched_body.nil?
 | 
					    @json = if prefetched_body.nil?
 | 
				
			||||||
              fetch_resource(uri, id)
 | 
					              fetch_resource(uri, id, on_behalf_of)
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
              body_to_json(prefetched_body)
 | 
					              body_to_json(prefetched_body)
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ class PostStatusService < BaseService
 | 
				
			||||||
    media  = validate_media!(options[:media_ids])
 | 
					    media  = validate_media!(options[:media_ids])
 | 
				
			||||||
    status = nil
 | 
					    status = nil
 | 
				
			||||||
    text   = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
 | 
					    text   = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
 | 
				
			||||||
    text   = '.' if text.blank? && !media.empty?
 | 
					    text   = '.' if text.blank? && media.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ApplicationRecord.transaction do
 | 
					    ApplicationRecord.transaction do
 | 
				
			||||||
      status = account.statuses.create!(text: text,
 | 
					      status = account.statuses.create!(text: text,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,26 +3,30 @@
 | 
				
			||||||
    = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
 | 
					    = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
 | 
				
			||||||
  .batch-table__row__content
 | 
					  .batch-table__row__content
 | 
				
			||||||
    .status__content><
 | 
					    .status__content><
 | 
				
			||||||
      - unless status.spoiler_text.blank?
 | 
					      - unless status.proper.spoiler_text.blank?
 | 
				
			||||||
        %p><
 | 
					        %p><
 | 
				
			||||||
          %strong= Formatter.instance.format_spoiler(status)
 | 
					          %strong> Content warning: #{Formatter.instance.format_spoiler(status.proper)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      = Formatter.instance.format(status, custom_emojify: true)
 | 
					      = Formatter.instance.format(status.proper, custom_emojify: true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - unless status.media_attachments.empty?
 | 
					    - unless status.proper.media_attachments.empty?
 | 
				
			||||||
      - if status.media_attachments.first.video?
 | 
					      - if status.proper.media_attachments.first.video?
 | 
				
			||||||
        - video = status.media_attachments.first
 | 
					        - video = status.proper.media_attachments.first
 | 
				
			||||||
        = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
 | 
					        = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 | 
					        = react_component :media_gallery, height: 343, sensitive: status.proper.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .detailed-status__meta
 | 
					    .detailed-status__meta
 | 
				
			||||||
      = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
 | 
					      = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
 | 
				
			||||||
        %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
 | 
					        %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
 | 
				
			||||||
      ·
 | 
					      ·
 | 
				
			||||||
 | 
					      - if status.reblog?
 | 
				
			||||||
 | 
					        = fa_icon('retweet fw')
 | 
				
			||||||
 | 
					        = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account))
 | 
				
			||||||
 | 
					      - else
 | 
				
			||||||
        = fa_visibility_icon(status)
 | 
					        = fa_visibility_icon(status)
 | 
				
			||||||
        = t("statuses.visibilities.#{status.visibility}")
 | 
					        = t("statuses.visibilities.#{status.visibility}")
 | 
				
			||||||
      - if status.sensitive?
 | 
					      - if status.proper.sensitive?
 | 
				
			||||||
        ·
 | 
					        ·
 | 
				
			||||||
        = fa_icon('eye-slash fw')
 | 
					        = fa_icon('eye-slash fw')
 | 
				
			||||||
        = t('stream_entries.sensitive_content')
 | 
					        = t('stream_entries.sensitive_content')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,7 @@ module Mastodon
 | 
				
			||||||
      :'pt-BR',
 | 
					      :'pt-BR',
 | 
				
			||||||
      :ru,
 | 
					      :ru,
 | 
				
			||||||
      :sk,
 | 
					      :sk,
 | 
				
			||||||
 | 
					      :sl,
 | 
				
			||||||
      :sr,
 | 
					      :sr,
 | 
				
			||||||
      :'sr-Latn',
 | 
					      :'sr-Latn',
 | 
				
			||||||
      :sv,
 | 
					      :sv,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -693,6 +693,7 @@ en:
 | 
				
			||||||
      video:
 | 
					      video:
 | 
				
			||||||
        one: "%{count} video"
 | 
					        one: "%{count} video"
 | 
				
			||||||
        other: "%{count} videos"
 | 
					        other: "%{count} videos"
 | 
				
			||||||
 | 
					    boosted_from_html: Boosted from %{acct_link}
 | 
				
			||||||
    content_warning: 'Content warning: %{warning}'
 | 
					    content_warning: 'Content warning: %{warning}'
 | 
				
			||||||
    disallowed_hashtags:
 | 
					    disallowed_hashtags:
 | 
				
			||||||
      one: 'contained a disallowed hashtag: %{tags}'
 | 
					      one: 'contained a disallowed hashtag: %{tags}'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -326,7 +326,7 @@ Rails.application.routes.draw do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      namespace :push do
 | 
					      namespace :push do
 | 
				
			||||||
        resource :subscription, only: [:create, :update, :destroy]
 | 
					        resource :subscription, only: [:create, :show, :update, :destroy]
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,10 @@ module.exports = {
 | 
				
			||||||
  settings,
 | 
					  settings,
 | 
				
			||||||
  core,
 | 
					  core,
 | 
				
			||||||
  flavours,
 | 
					  flavours,
 | 
				
			||||||
  env,
 | 
					  env: {
 | 
				
			||||||
 | 
					    CDN_HOST: env.CDN_HOST,
 | 
				
			||||||
 | 
					    NODE_ENV: env.NODE_ENV,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  loadersDir,
 | 
					  loadersDir,
 | 
				
			||||||
  output,
 | 
					  output,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ const CompressionPlugin = require('compression-webpack-plugin');
 | 
				
			||||||
const sharedConfig = require('./shared.js');
 | 
					const sharedConfig = require('./shared.js');
 | 
				
			||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 | 
					const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 | 
				
			||||||
const OfflinePlugin = require('offline-plugin');
 | 
					const OfflinePlugin = require('offline-plugin');
 | 
				
			||||||
const { publicPath } = require('./configuration.js');
 | 
					const { env, publicPath } = require('./configuration.js');
 | 
				
			||||||
const path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let compressionAlgorithm;
 | 
					let compressionAlgorithm;
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ module.exports = merge(sharedConfig, {
 | 
				
			||||||
        '**/*.woff',
 | 
					        '**/*.woff',
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      ServiceWorker: {
 | 
					      ServiceWorker: {
 | 
				
			||||||
        entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(process.env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`,
 | 
					        entry: `imports-loader?process.env=>${encodeURIComponent(JSON.stringify(env))}!${encodeURI(path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'))}`,
 | 
				
			||||||
        cacheName: 'mastodon',
 | 
					        cacheName: 'mastodon',
 | 
				
			||||||
        output: '../assets/sw.js',
 | 
					        output: '../assets/sw.js',
 | 
				
			||||||
        publicPath: '/sw.js',
 | 
					        publicPath: '/sw.js',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ImproveIndexOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migration[5.1]
 | 
				
			||||||
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def change
 | 
				
			||||||
 | 
					    add_index :statuses, [:account_id, :id, :visibility], where: 'visibility IN (0, 1, 2)', algorithm: :concurrently
 | 
				
			||||||
 | 
					    add_index :statuses, [:account_id, :id], where: 'visibility = 3', algorithm: :concurrently
 | 
				
			||||||
 | 
					    remove_index :statuses, column: [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RevertIndexChangeOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migration[5.1]
 | 
				
			||||||
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def change
 | 
				
			||||||
 | 
					    safety_assured do
 | 
				
			||||||
 | 
					      add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    remove_index :statuses, column: [:account_id, :id, :visibility], where: 'visibility IN (0, 1, 2)', algorithm: :concurrently
 | 
				
			||||||
 | 
					    remove_index :statuses, column: [:account_id, :id], where: 'visibility = 3', algorithm: :concurrently
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# It's strongly recommended that you check this file into your version control system.
 | 
					# It's strongly recommended that you check this file into your version control system.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ActiveRecord::Schema.define(version: 2018_05_10_230049) do
 | 
					ActiveRecord::Schema.define(version: 2018_05_14_140000) do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # These are extensions that must be enabled in order to support this database
 | 
					  # These are extensions that must be enabled in order to support this database
 | 
				
			||||||
  enable_extension "plpgsql"
 | 
					  enable_extension "plpgsql"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ module Mastodon
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def flags
 | 
					    def flags
 | 
				
			||||||
      'rc1'
 | 
					      'rc3'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_a
 | 
					    def to_a
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue