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> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
							
								
								
									
										90
									
								
								app/javascript/mastodon/containers/media_container.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								app/javascript/mastodon/containers/media_container.js
									
									
									
									
									
										Normal file
									
								
							| @ -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) { | ||||||
|  |           return fetch(event.request).then(fetched => { | ||||||
|  |             if (fetched.ok) { | ||||||
|  |               const put = cache.put(event.request.url, fetched.clone()); | ||||||
| 
 | 
 | ||||||
|       if (cached === undefined) { |               put.catch(() => freeStorage()); | ||||||
|         const fetched = await fetch(event.request); |  | ||||||
| 
 | 
 | ||||||
|         if (fetched.ok) { |               return put.then(() => { | ||||||
|           try { |                 freeStorage(); | ||||||
|             await cache.put(event.request.url, fetched.clone()); |                 return fetched; | ||||||
|           } finally { |               }); | ||||||
|             freeStorage(); |             } | ||||||
|           } | 
 | ||||||
|  |             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 content = document.createElement('div'); | ||||||
| 
 | 
 | ||||||
|     const cards = document.querySelectorAll('[data-component="Card"]'); |           ReactDOM.render(<MediaContainer locale={locale} components={reactComponents} />, content); | ||||||
| 
 |           document.body.appendChild(content); | ||||||
|     if (cards.length > 0) { |         }) | ||||||
|       import(/* webpackChunkName: "containers/cards_container" */ '../mastodon/containers/cards_container').then(({ default: CardsContainer }) => { |         .catch(error => console.error(error)); | ||||||
|         const content = document.createElement('div'); |  | ||||||
| 
 |  | ||||||
|         ReactDOM.render(<CardsContainer locale={locale} cards={cards} />, 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); |  | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -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,16 +74,7 @@ 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) | ||||||
|     relations = if account.present? |  | ||||||
|                   { |  | ||||||
|                     blocking: Account.blocking_map(account_ids, account.id), |  | ||||||
|                     blocked_by: Account.blocked_by_map(account_ids, account.id), |  | ||||||
|                     muting: Account.muting_map(account_ids, account.id), |  | ||||||
|                     following: Account.following_map(account_ids, account.id), |  | ||||||
|                     domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id), |  | ||||||
|                   } |  | ||||||
|                 end |  | ||||||
| 
 | 
 | ||||||
|     statuses.reject! { |status| filter_from_context?(status, account, relations) } |     statuses.reject! { |status| filter_from_context?(status, account, relations) } | ||||||
| 
 | 
 | ||||||
| @ -91,6 +82,18 @@ module StatusThreadingConcern | |||||||
|     statuses.sort_by! { |status| ids.index(status.id) } |     statuses.sort_by! { |status| ids.index(status.id) } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def relations_map_for_account(account, account_ids, domains) | ||||||
|  |     return {} if account.nil? | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       blocking: Account.blocking_map(account_ids, account.id), | ||||||
|  |       blocked_by: Account.blocked_by_map(account_ids, account.id), | ||||||
|  |       muting: Account.muting_map(account_ids, account.id), | ||||||
|  |       following: Account.following_map(account_ids, account.id), | ||||||
|  |       domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.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) | ||||||
|       · |       · | ||||||
|       = fa_visibility_icon(status) |       - if status.reblog? | ||||||
|       = t("statuses.visibilities.#{status.visibility}") |         = fa_icon('retweet fw') | ||||||
|       - if status.sensitive? |         = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account)) | ||||||
|  |       - else | ||||||
|  |         = fa_visibility_icon(status) | ||||||
|  |         = t("statuses.visibilities.#{status.visibility}") | ||||||
|  |       - 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user