From 5f63339744eecd75b1065135559ddbe688636be1 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 12 Aug 2019 22:26:07 +0200 Subject: [PATCH 01/47] Fix more ActivityPub queries setting cookies and preventing caching (#11557) --- app/controllers/follower_accounts_controller.rb | 2 ++ app/controllers/following_accounts_controller.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index 6e873de5b62..892c51cf498 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -7,6 +7,8 @@ class FollowerAccountsController < ApplicationController before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_cache_headers + skip_around_action :set_locale, if: -> { request.format == :json } + def index respond_to do |format| format.html do diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 07d62f7dd1b..653d9a486e2 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -7,6 +7,8 @@ class FollowingAccountsController < ApplicationController before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_cache_headers + skip_around_action :set_locale, if: -> { request.format == :json } + def index respond_to do |format| format.html do From c09ecbc53eb3d3a8a1e2a1e61e20c9e5dbd4f560 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 13 Aug 2019 12:22:16 +0200 Subject: [PATCH 02/47] Add indicator of unread content to window title when web UI is out of focus (#11560) Fix #1288 --- app/javascript/mastodon/actions/app.js | 10 +++++ .../features/ui/components/document_title.js | 41 +++++++++++++++++++ app/javascript/mastodon/features/ui/index.js | 18 +++++++- app/javascript/mastodon/initial_state.js | 1 + app/javascript/mastodon/reducers/index.js | 2 + .../mastodon/reducers/missed_updates.js | 23 +++++++++++ app/serializers/initial_state_serializer.rb | 1 + 7 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 app/javascript/mastodon/actions/app.js create mode 100644 app/javascript/mastodon/features/ui/components/document_title.js create mode 100644 app/javascript/mastodon/reducers/missed_updates.js diff --git a/app/javascript/mastodon/actions/app.js b/app/javascript/mastodon/actions/app.js new file mode 100644 index 00000000000..414968f7de4 --- /dev/null +++ b/app/javascript/mastodon/actions/app.js @@ -0,0 +1,10 @@ +export const APP_FOCUS = 'APP_FOCUS'; +export const APP_UNFOCUS = 'APP_UNFOCUS'; + +export const focusApp = () => ({ + type: APP_FOCUS, +}); + +export const unfocusApp = () => ({ + type: APP_UNFOCUS, +}); diff --git a/app/javascript/mastodon/features/ui/components/document_title.js b/app/javascript/mastodon/features/ui/components/document_title.js new file mode 100644 index 00000000000..cd081b20c7e --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/document_title.js @@ -0,0 +1,41 @@ +import { PureComponent } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { title } from 'mastodon/initial_state'; + +const mapStateToProps = state => ({ + unread: state.getIn(['missed_updates', 'unread']), +}); + +export default @connect(mapStateToProps) +class DocumentTitle extends PureComponent { + + static propTypes = { + unread: PropTypes.number.isRequired, + }; + + componentDidMount () { + this._sideEffects(); + } + + componentDidUpdate() { + this._sideEffects(); + } + + _sideEffects () { + const { unread } = this.props; + + if (unread > 99) { + document.title = `(*) ${title}`; + } else if (unread > 0) { + document.title = `(${unread}) ${title}`; + } else { + document.title = title; + } + } + + render () { + return null; + } + +} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index d1a3dc9495f..f0c3eff834e 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -15,9 +15,11 @@ import { expandHomeTimeline } from '../../actions/timelines'; import { expandNotifications } from '../../actions/notifications'; import { fetchFilters } from '../../actions/filters'; import { clearHeight } from '../../actions/height_cache'; +import { focusApp, unfocusApp } from 'mastodon/actions/app'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; +import DocumentTitle from './components/document_title'; import { Compose, Status, @@ -226,7 +228,7 @@ class UI extends React.PureComponent { draggingOver: false, }; - handleBeforeUnload = (e) => { + handleBeforeUnload = e => { const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props; if (isComposing && (hasComposingText || hasMediaAttachments)) { @@ -237,6 +239,14 @@ class UI extends React.PureComponent { } } + handleWindowFocus = () => { + this.props.dispatch(focusApp()); + } + + handleWindowBlur = () => { + this.props.dispatch(unfocusApp()); + } + handleLayoutChange = () => { // The cached heights are no longer accurate, invalidate this.props.dispatch(clearHeight()); @@ -314,6 +324,8 @@ class UI extends React.PureComponent { } componentWillMount () { + window.addEventListener('focus', this.handleWindowFocus, false); + window.addEventListener('blur', this.handleWindowBlur, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false); document.addEventListener('dragenter', this.handleDragEnter, false); @@ -343,7 +355,10 @@ class UI extends React.PureComponent { } componentWillUnmount () { + window.removeEventListener('focus', this.handleWindowFocus); + window.removeEventListener('blur', this.handleWindowBlur); window.removeEventListener('beforeunload', this.handleBeforeUnload); + document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragover', this.handleDragOver); document.removeEventListener('drop', this.handleDrop); @@ -502,6 +517,7 @@ class UI extends React.PureComponent { + ); diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 38e7b0595ad..56fb5854669 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -23,5 +23,6 @@ export const forceSingleColumn = !getMeta('advanced_layout'); export const useBlurhash = getMeta('use_blurhash'); export const usePendingItems = getMeta('use_pending_items'); export const showTrends = getMeta('trends'); +export const title = getMeta('title'); export default initialState; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 3b60878eb79..0f4b209d45d 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -32,6 +32,7 @@ import suggestions from './suggestions'; import polls from './polls'; import identity_proofs from './identity_proofs'; import trends from './trends'; +import missed_updates from './missed_updates'; const reducers = { dropdown_menu, @@ -67,6 +68,7 @@ const reducers = { suggestions, polls, trends, + missed_updates, }; export default combineReducers(reducers); diff --git a/app/javascript/mastodon/reducers/missed_updates.js b/app/javascript/mastodon/reducers/missed_updates.js new file mode 100644 index 00000000000..eeb8b40f67f --- /dev/null +++ b/app/javascript/mastodon/reducers/missed_updates.js @@ -0,0 +1,23 @@ +import { Map as ImmutableMap } from 'immutable'; +import { NOTIFICATIONS_UPDATE } from 'mastodon/actions/notifications'; +import { TIMELINE_UPDATE } from 'mastodon/actions/timelines'; +import { APP_FOCUS, APP_UNFOCUS } from 'mastodon/actions/app'; + +const initialState = ImmutableMap({ + focused: true, + unread: 0, +}); + +export default function missed_updates(state = initialState, action) { + switch(action.type) { + case APP_FOCUS: + return state.set('focused', true).set('unread', 0); + case APP_UNFOCUS: + return state.set('focused', false); + case NOTIFICATIONS_UPDATE: + case TIMELINE_UPDATE: + return state.get('focused') ? state : state.update('unread', x => x + 1); + default: + return state; + } +}; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index c92c5e606b6..2cebef2c00d 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -12,6 +12,7 @@ class InitialStateSerializer < ActiveModel::Serializer access_token: object.token, locale: I18n.locale, domain: Rails.configuration.x.local_domain, + title: instance_presenter.site_title, admin: object.admin&.id&.to_s, search_enabled: Chewy.enabled?, repository: Mastodon::Version.repository, From 0e9668051e58b2ff4e48b82fa7cc17d385c7a5c9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 13 Aug 2019 12:22:33 +0200 Subject: [PATCH 03/47] Fix short number formatting for numbers above million in web UI (#11559) --- app/javascript/mastodon/utils/numbers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/utils/numbers.js b/app/javascript/mastodon/utils/numbers.js index fdd8269ae27..f7e4ceb9354 100644 --- a/app/javascript/mastodon/utils/numbers.js +++ b/app/javascript/mastodon/utils/numbers.js @@ -4,7 +4,9 @@ import { FormattedNumber } from 'react-intl'; export const shortNumberFormat = number => { if (number < 1000) { return ; - } else { + } else if (number < 1000000) { return K; + } else { + return M; } }; From 7ffec882ae79a49d6eed42361f7e8d5de99e1d74 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 13 Aug 2019 15:30:37 +0200 Subject: [PATCH 04/47] Fix reverse-proxy caching of instance actor object (#11561) --- app/controllers/instance_actors_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb index 41f33602e69..6f02d6a358f 100644 --- a/app/controllers/instance_actors_controller.rb +++ b/app/controllers/instance_actors_controller.rb @@ -3,6 +3,8 @@ class InstanceActorsController < ApplicationController include AccountControllerConcern + skip_around_action :set_locale + def show expires_in 10.minutes, public: true render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to From 23f7afa562c49b24e979505680463bc712b11d94 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 14 Aug 2019 04:07:32 +0200 Subject: [PATCH 05/47] Add media editing modal (#11563) Move media description input to a modal and unite that modal with the focal point modal. Add a hint about choosing focal points, as well as a preview of a 16:9 thumbnail. Enable the user to watch the video next to the media description input. Fix #8320 Fix #6713 --- .../features/compose/components/upload.js | 85 +----------- .../compose/containers/upload_container.js | 6 +- .../ui/components/focal_point_modal.js | 123 +++++++++++++++--- .../mastodon/features/video/index.js | 11 +- .../styles/mastodon/components.scss | 56 +++++--- 5 files changed, 156 insertions(+), 125 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index 629cbc36ac3..b9f0fbe3abd 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -4,16 +4,11 @@ import PropTypes from 'prop-types'; import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; -const messages = defineMessages({ - description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' }, -}); - -export default @injectIntl -class Upload extends ImmutablePureComponent { +export default class Upload extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -21,30 +16,10 @@ class Upload extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, onUndo: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, onOpenFocalPoint: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, }; - state = { - hovered: false, - focused: false, - dirtyDescription: null, - }; - - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit = () => { - this.handleInputBlur(); - this.props.onSubmit(this.context.router.history); - } - handleUndoClick = e => { e.stopPropagation(); this.props.onUndo(this.props.media.get('id')); @@ -55,69 +30,21 @@ class Upload extends ImmutablePureComponent { this.props.onOpenFocalPoint(this.props.media.get('id')); } - handleInputChange = e => { - this.setState({ dirtyDescription: e.target.value }); - } - - handleMouseEnter = () => { - this.setState({ hovered: true }); - } - - handleMouseLeave = () => { - this.setState({ hovered: false }); - } - - handleInputFocus = () => { - this.setState({ focused: true }); - } - - handleClick = () => { - this.setState({ focused: true }); - } - - handleInputBlur = () => { - const { dirtyDescription } = this.state; - - this.setState({ focused: false, dirtyDescription: null }); - - if (dirtyDescription !== null) { - this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription); - } - } - render () { - const { intl, media } = this.props; - const active = this.state.hovered || this.state.focused; - const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; + const { media } = this.props; const focusX = media.getIn(['meta', 'focus', 'x']); const focusY = media.getIn(['meta', 'focus', 'y']); const x = ((focusX / 2) + .5) * 100; const y = ((focusY / -2) + .5) * 100; return ( -
+
{({ scale }) => (
-
+
- {media.get('type') === 'image' && } -
- -
-