From 4d706f9976e705c70f32dfbbd78abb8ab7f1dd7a Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 12 May 2018 19:08:12 +0900 Subject: [PATCH 01/18] Downgrade doorkeeper to version 4.2.6 (#7456) ref https://github.com/doorkeeper-gem/doorkeeper/pull/1060 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2beac94d2c5..5c2f33de434 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.2' -gem 'doorkeeper', '~> 4.3' +gem 'doorkeeper', '~> 4.2', '< 4.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 6a579d53bb4..4507cc14683 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,7 +167,7 @@ GEM docile (1.3.0) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.3.2) + doorkeeper (4.2.6) railties (>= 4.2) dotenv (2.2.2) dotenv-rails (2.2.2) @@ -659,7 +659,7 @@ DEPENDENCIES devise (~> 4.4) devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.1) - doorkeeper (~> 4.3) + doorkeeper (~> 4.2, < 4.3) dotenv-rails (~> 2.2, < 2.3) fabrication (~> 2.20) faker (~> 1.8) From f9afd06221baf7f635b346dfbe350652ba6ffbd0 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 12 May 2018 22:30:06 +0900 Subject: [PATCH 02/18] Combine similar components into one on public UI (#7458) --- .../mastodon/containers/cards_container.js | 59 ------------------- ...leries_container.js => media_container.js} | 39 +++++++----- .../mastodon/containers/video_container.js | 26 -------- app/javascript/packs/public.js | 33 +++-------- .../styles/mastodon/containers.scss | 3 +- 5 files changed, 33 insertions(+), 127 deletions(-) delete mode 100644 app/javascript/mastodon/containers/cards_container.js rename app/javascript/mastodon/containers/{media_galleries_container.js => media_container.js} (53%) delete mode 100644 app/javascript/mastodon/containers/video_container.js diff --git a/app/javascript/mastodon/containers/cards_container.js b/app/javascript/mastodon/containers/cards_container.js deleted file mode 100644 index 894bf4ef991..00000000000 --- a/app/javascript/mastodon/containers/cards_container.js +++ /dev/null @@ -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 ( - - - {[].map.call(cards, container => { - const { card, ...props } = JSON.parse(container.getAttribute('data-props')); - - return ReactDOM.createPortal( - , - container, - ); - })} - - {this.state.media && ( - - )} - - - - ); - } - -} diff --git a/app/javascript/mastodon/containers/media_galleries_container.js b/app/javascript/mastodon/containers/media_container.js similarity index 53% rename from app/javascript/mastodon/containers/media_galleries_container.js rename to app/javascript/mastodon/containers/media_container.js index d77bd688bfa..eb2d540cb3d 100644 --- a/app/javascript/mastodon/containers/media_galleries_container.js +++ b/app/javascript/mastodon/containers/media_container.js @@ -1,9 +1,11 @@ -import React from 'react'; +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 { fromJS } from 'immutable'; @@ -11,11 +13,13 @@ import { fromJS } from 'immutable'; const { localeData, messages } = getLocale(); addLocaleData(localeData); -export default class MediaGalleriesContainer extends React.PureComponent { +const MEDIA_COMPONENTS = { MediaGallery, Video, Card }; + +export default class MediaContainer extends PureComponent { static propTypes = { locale: PropTypes.string.isRequired, - galleries: PropTypes.object.isRequired, + components: PropTypes.object.isRequired, }; state = { @@ -24,31 +28,34 @@ export default class MediaGalleriesContainer extends React.PureComponent { }; handleOpenMedia = (media, index) => { - document.body.classList.add('media-gallery-standalone__body'); + document.body.classList.add('media-standalone__body'); this.setState({ media, index }); } handleCloseMedia = () => { - document.body.classList.remove('media-gallery-standalone__body'); + document.body.classList.remove('media-standalone__body'); this.setState({ media: null, index: null }); } render () { - const { locale, galleries } = this.props; + const { locale, components } = this.props; return ( - - {[].map.call(galleries, gallery => { - const { media, ...props } = JSON.parse(gallery.getAttribute('data-props')); + + {[].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) } : {}), + }); return ReactDOM.createPortal( - , - gallery + , + component, ); })} @@ -60,7 +67,7 @@ export default class MediaGalleriesContainer extends React.PureComponent { /> )} - + ); } diff --git a/app/javascript/mastodon/containers/video_container.js b/app/javascript/mastodon/containers/video_container.js deleted file mode 100644 index 2fd353096d4..00000000000 --- a/app/javascript/mastodon/containers/video_container.js +++ /dev/null @@ -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 ( - - - ); - } - -} diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 3a1f1a16b4d..d5e5b7fe00d 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -24,7 +24,6 @@ function main() { const emojify = require('../mastodon/features/emoji/emoji').default; const { getLocale } = require('../mastodon/locales'); const { localeData } = getLocale(); - const VideoContainer = require('../mastodon/containers/video_container').default; const React = require('react'); const ReactDOM = require('react-dom'); @@ -69,30 +68,16 @@ function main() { }); }); - [].forEach.call(document.querySelectorAll('[data-component="Video"]'), (content) => { - const props = JSON.parse(content.getAttribute('data-props')); - ReactDOM.render(, content); - }); + const reactComponents = document.querySelectorAll('[data-component]'); + if (reactComponents.length > 0) { + import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container') + .then(({ default: MediaContainer }) => { + const content = document.createElement('div'); - 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'); - - ReactDOM.render(, 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(, content); - document.body.appendChild(content); + ReactDOM.render(, content); + document.body.appendChild(content); + }) + .catch(error => console.error(error)); } }); diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index c40b38a5a53..ac648c8680e 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -60,8 +60,7 @@ } } -.card-standalone__body, -.media-gallery-standalone__body { +.media-standalone__body { overflow: hidden; } From 7467361d706e38aa53b6bd2fcb919153327175ed Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 12 May 2018 16:48:32 +0200 Subject: [PATCH 03/18] Fetch boosted statuses on behalf of a follower (fixes #7426) (#7459) When an ActivityPub Announce is processed and the boosted toot is not known, fetch it on behalf of one of the booster's followers. This is to allow fetching self-boosts of previously-unknown private toots. If fetching on behalf of a user fails, try fetching it anonymously: the selected follower of a boosting user may be banned by the boosted toot's author. --- app/helpers/jsonld_helper.rb | 15 ++++++++++----- app/lib/activitypub/activity/announce.rb | 2 +- .../activitypub/fetch_remote_status_service.rb | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index e9056166c10..9d2b6cf00d2 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -52,18 +52,22 @@ module JsonLdHelper graph.dump(:normalize) end - def fetch_resource(uri, id) + def fetch_resource(uri, id, on_behalf_of = nil) unless id - json = fetch_resource_without_id_validation(uri) + json = fetch_resource_without_id_validation(uri, on_behalf_of) return unless json uri = json['id'] 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 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| response.code == 200 ? body_to_json(response.body_with_limit) : nil end @@ -85,8 +89,9 @@ module JsonLdHelper private - def build_request(uri) + def build_request(uri, on_behalf_of = nil) 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 end diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 7e146ea8c43..f810c88a27a 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -30,7 +30,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity if object_uri.start_with?('http') 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? ::FetchRemoteStatusService.new.call(@object['url']) end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index b6c00a9e7e2..2b447abb32e 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -4,9 +4,9 @@ class ActivityPub::FetchRemoteStatusService < BaseService include JsonLdHelper # 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? - fetch_resource(uri, id) + fetch_resource(uri, id, on_behalf_of) else body_to_json(prefetched_body) end From 0f2fbf7d05825447a0f2a1e82b6137c069cb1658 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sat, 12 May 2018 17:44:15 +0200 Subject: [PATCH 04/18] Improvements to toots display in admin view (#7452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Distinguish boosts from original statuses in the admin panel (fixes #7449) * Show the “show more” button in admin view to make CWs clearer (fixes #7451) * Make content warnings swag --- .../admin/account_moderation_notes_helper.rb | 10 ++++-- app/javascript/styles/mastodon/admin.scss | 33 +++++++++++++------ app/javascript/styles/mastodon/tables.scss | 11 +++++++ app/views/admin/reports/_status.html.haml | 26 ++++++++------- config/locales/en.yml | 1 + 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index fdfadef0801..49e764cefd6 100644 --- a/app/helpers/admin/account_moderation_notes_helper.rb +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -10,10 +10,16 @@ module Admin::AccountModerationNotesHelper 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 - def name_tag_classes(account) - classes = ['name-tag'] + def name_tag_classes(account, inline = false) + classes = [inline ? 'inline-name-tag' : 'name-tag'] classes << 'suspended' if account.suspended? classes.join(' ') end diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 1948a2a2311..560b11ddf4e 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -484,19 +484,12 @@ } a.name-tag, -.name-tag { - display: flex; - align-items: center; +.name-tag, +a.inline-name-tag, +.inline-name-tag { text-decoration: none; color: $secondary-text-color; - .avatar { - display: block; - margin: 0; - margin-right: 5px; - border-radius: 50%; - } - .username { 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 { margin-bottom: 20px; border-left: 4px solid $ui-highlight-color; diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index fa876e6031a..982bfd99008 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -1,3 +1,9 @@ +@keyframes Swag { + 0% { background-position: 0% 0%; } + 50% { background-position: 100% 0%; } + 100% { background-position: 200% 0%; } +} + .table { width: 100%; max-width: 100%; @@ -187,6 +193,11 @@ a.table-action-link { strong { 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; } } } diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index 9057e6048df..5e174f312ec 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -3,26 +3,30 @@ = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id .batch-table__row__content .status__content>< - - unless status.spoiler_text.blank? + - unless status.proper.spoiler_text.blank? %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? - - if status.media_attachments.first.video? - - video = status.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 + - unless status.proper.media_attachments.empty? + - if status.proper.media_attachments.first.video? + - video = status.proper.media_attachments.first + = 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 - = 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 = 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) · - = fa_visibility_icon(status) - = t("statuses.visibilities.#{status.visibility}") - - if status.sensitive? + - 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) + = t("statuses.visibilities.#{status.visibility}") + - if status.proper.sensitive? · = fa_icon('eye-slash fw') = t('stream_entries.sensitive_content') diff --git a/config/locales/en.yml b/config/locales/en.yml index 5369311ac5c..c074fa5b041 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -682,6 +682,7 @@ en: video: one: "%{count} video" other: "%{count} videos" + boosted_from_html: Boosted from %{acct_link} content_warning: 'Content warning: %{warning}' disallowed_hashtags: one: 'contained a disallowed hashtag: %{tags}' From f77b11cd10cdc7488e0eb2c07c73b4f4a57ab8b7 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 13 May 2018 18:32:46 +0900 Subject: [PATCH 05/18] Update http_parser.rb to head version (#7467) --- Gemfile | 1 + Gemfile.lock | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5c2f33de434..1799c2981c0 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ gem 'redis-namespace', '~> 1.5' gem 'htmlentities', '~> 4.3' gem 'http', '~> 3.2' 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 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' diff --git a/Gemfile.lock b/Gemfile.lock index 4507cc14683..6b7651b7736 100644 --- a/Gemfile.lock +++ b/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 remote: https://rubygems.org/ specs: @@ -252,7 +259,6 @@ GEM domain_name (~> 0.5) http-form_data (2.1.0) http_accept_language (2.1.1) - http_parser.rb (0.6.0) httplog (1.0.2) colorize (~> 0.8) rack (>= 1.0) @@ -675,6 +681,7 @@ DEPENDENCIES htmlentities (~> 4.3) http (~> 3.2) http_accept_language (~> 2.1) + http_parser.rb (~> 0.6)! httplog (~> 1.0) i18n-tasks (~> 0.9) idn-ruby From d9b2f84c92f24067b12be81837240bf6c8930236 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 13 May 2018 20:48:14 +0900 Subject: [PATCH 06/18] Open video modal on public UI (#7469) --- app/javascript/mastodon/components/status.js | 4 +-- .../mastodon/containers/media_container.js | 25 +++++++++++++++---- .../status/components/detailed_status.js | 4 +-- .../features/ui/components/media_modal.js | 17 +++++++++++++ .../mastodon/features/video/index.js | 25 +++++++++++++++++-- .../styles/mastodon/components.scss | 4 +++ 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 953d98c20d4..fd08ff3b7ce 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -84,8 +84,8 @@ export default class Status extends ImmutablePureComponent { return
; } - handleOpenVideo = startTime => { - this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime); + handleOpenVideo = (media, startTime) => { + this.props.onOpenVideo(media, startTime); } handleHotkeyReply = e => { diff --git a/app/javascript/mastodon/containers/media_container.js b/app/javascript/mastodon/containers/media_container.js index eb2d540cb3d..1700fba05a9 100644 --- a/app/javascript/mastodon/containers/media_container.js +++ b/app/javascript/mastodon/containers/media_container.js @@ -8,7 +8,7 @@ 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 { fromJS } from 'immutable'; +import { List as ImmutableList, fromJS } from 'immutable'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -25,6 +25,7 @@ export default class MediaContainer extends PureComponent { state = { media: null, index: null, + time: null, }; handleOpenMedia = (media, index) => { @@ -32,9 +33,16 @@ export default class MediaContainer extends PureComponent { 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 }); + this.setState({ media: null, index: null, time: null }); } render () { @@ -51,18 +59,25 @@ export default class MediaContainer extends PureComponent { Object.assign(props, { ...(media ? { media: fromJS(media) } : {}), ...(card ? { card: fromJS(card) } : {}), + + ...(componentName === 'Video' ? { + onOpenVideo: this.handleOpenVideo, + } : { + onOpenMedia: this.handleOpenMedia, + }), }); return ReactDOM.createPortal( - , + , component, ); })} - {this.state.media === null || this.state.index === null ? null : ( + {this.state.media && ( )} diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index b5f5160326c..4177190044e 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -34,8 +34,8 @@ export default class DetailedStatus extends ImmutablePureComponent { e.stopPropagation(); } - handleOpenVideo = startTime => { - this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime); + handleOpenVideo = (media, startTime) => { + this.props.onOpenVideo(media, startTime); } handleExpandedToggle = () => { diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index fb76270fad3..f4d6b5c4e6f 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactSwipeableViews from 'react-swipeable-views'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; +import Video from '../../video'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; import classNames from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; @@ -112,6 +113,22 @@ export default class MediaModal extends ImmutablePureComponent { onClick={this.toggleNavigation} /> ); + } else if (image.get('type') === 'video') { + const { time } = this.props; + + return ( +