diff --git a/Gemfile b/Gemfile index 71f5823df..0ead19733 100644 --- a/Gemfile +++ b/Gemfile @@ -42,7 +42,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' @@ -52,6 +52,7 @@ gem 'html2text' 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 41307654c..0656f16fd 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: @@ -167,7 +174,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) @@ -254,7 +261,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) @@ -661,7 +667,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) @@ -678,6 +684,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 diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 5038cc03c..1a19bd0ef 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -20,6 +20,12 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer end + def show + raise ActiveRecord::RecordNotFound if @web_subscription.nil? + + render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer + end + def update raise ActiveRecord::RecordNotFound if @web_subscription.nil? diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index fdfadef08..49e764cef 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/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index e9056166c..9d2b6cf00 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/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index f6d86a18e..ba728eb32 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -33,6 +33,7 @@ module SettingsHelper 'pt-BR': 'Português do Brasil', ru: 'Русский', sk: 'Slovensky', + sl: 'Slovenščina', sr: 'Српски', 'sr-Latn': 'Srpski (latinica)', sv: 'Svenska', diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 953d98c20..fd08ff3b7 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/cards_container.js b/app/javascript/mastodon/containers/cards_container.js deleted file mode 100644 index 894bf4ef9..000000000 --- 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_container.js b/app/javascript/mastodon/containers/media_container.js new file mode 100644 index 000000000..1700fba05 --- /dev/null +++ b/app/javascript/mastodon/containers/media_container.js @@ -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 ( + + + {[].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, + ); + })} + + {this.state.media && ( + + )} + + + + ); + } + +} diff --git a/app/javascript/mastodon/containers/media_galleries_container.js b/app/javascript/mastodon/containers/media_galleries_container.js deleted file mode 100644 index d77bd688b..000000000 --- a/app/javascript/mastodon/containers/media_galleries_container.js +++ /dev/null @@ -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 ( - - - {[].map.call(galleries, gallery => { - const { media, ...props } = JSON.parse(gallery.getAttribute('data-props')); - - return ReactDOM.createPortal( - , - gallery - ); - })} - - {this.state.media === null || this.state.index === null ? null : ( - - )} - - - - ); - } - -} diff --git a/app/javascript/mastodon/containers/video_container.js b/app/javascript/mastodon/containers/video_container.js deleted file mode 100644 index 2fd353096..000000000 --- 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/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index b5f516032..417719004 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 fb76270fa..f4d6b5c4e 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 ( +