Skip to content
Extraits de code Groupes Projets
Non vérifiée Valider c09ecbc5 rédigé par Eugen Rochko's avatar Eugen Rochko Validation de GitHub
Parcourir les fichiers

Add indicator of unread content to window title when web UI is out of focus (#11560)

Fix #1288
parent 5f633397
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
export const APP_FOCUS = 'APP_FOCUS';
export const APP_UNFOCUS = 'APP_UNFOCUS';
export const focusApp = () => ({
type: APP_FOCUS,
});
export const unfocusApp = () => ({
type: APP_UNFOCUS,
});
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;
}
}
...@@ -15,9 +15,11 @@ import { expandHomeTimeline } from '../../actions/timelines'; ...@@ -15,9 +15,11 @@ import { expandHomeTimeline } from '../../actions/timelines';
import { expandNotifications } from '../../actions/notifications'; import { expandNotifications } from '../../actions/notifications';
import { fetchFilters } from '../../actions/filters'; import { fetchFilters } from '../../actions/filters';
import { clearHeight } from '../../actions/height_cache'; import { clearHeight } from '../../actions/height_cache';
import { focusApp, unfocusApp } from 'mastodon/actions/app';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area'; import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container'; import ColumnsAreaContainer from './containers/columns_area_container';
import DocumentTitle from './components/document_title';
import { import {
Compose, Compose,
Status, Status,
...@@ -226,7 +228,7 @@ class UI extends React.PureComponent { ...@@ -226,7 +228,7 @@ class UI extends React.PureComponent {
draggingOver: false, draggingOver: false,
}; };
handleBeforeUnload = (e) => { handleBeforeUnload = e => {
const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props; const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
if (isComposing && (hasComposingText || hasMediaAttachments)) { if (isComposing && (hasComposingText || hasMediaAttachments)) {
...@@ -237,6 +239,14 @@ class UI extends React.PureComponent { ...@@ -237,6 +239,14 @@ class UI extends React.PureComponent {
} }
} }
handleWindowFocus = () => {
this.props.dispatch(focusApp());
}
handleWindowBlur = () => {
this.props.dispatch(unfocusApp());
}
handleLayoutChange = () => { handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate // The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight()); this.props.dispatch(clearHeight());
...@@ -314,6 +324,8 @@ class UI extends React.PureComponent { ...@@ -314,6 +324,8 @@ class UI extends React.PureComponent {
} }
componentWillMount () { componentWillMount () {
window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false);
window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false);
document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragenter', this.handleDragEnter, false);
...@@ -343,7 +355,10 @@ class UI extends React.PureComponent { ...@@ -343,7 +355,10 @@ class UI extends React.PureComponent {
} }
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('focus', this.handleWindowFocus);
window.removeEventListener('blur', this.handleWindowBlur);
window.removeEventListener('beforeunload', this.handleBeforeUnload); window.removeEventListener('beforeunload', this.handleBeforeUnload);
document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver); document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop); document.removeEventListener('drop', this.handleDrop);
...@@ -502,6 +517,7 @@ class UI extends React.PureComponent { ...@@ -502,6 +517,7 @@ class UI extends React.PureComponent {
<LoadingBarContainer className='loading-bar' /> <LoadingBarContainer className='loading-bar' />
<ModalContainer /> <ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} /> <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
<DocumentTitle />
</div> </div>
</HotKeys> </HotKeys>
); );
......
...@@ -23,5 +23,6 @@ export const forceSingleColumn = !getMeta('advanced_layout'); ...@@ -23,5 +23,6 @@ export const forceSingleColumn = !getMeta('advanced_layout');
export const useBlurhash = getMeta('use_blurhash'); export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items'); export const usePendingItems = getMeta('use_pending_items');
export const showTrends = getMeta('trends'); export const showTrends = getMeta('trends');
export const title = getMeta('title');
export default initialState; export default initialState;
...@@ -32,6 +32,7 @@ import suggestions from './suggestions'; ...@@ -32,6 +32,7 @@ import suggestions from './suggestions';
import polls from './polls'; import polls from './polls';
import identity_proofs from './identity_proofs'; import identity_proofs from './identity_proofs';
import trends from './trends'; import trends from './trends';
import missed_updates from './missed_updates';
const reducers = { const reducers = {
dropdown_menu, dropdown_menu,
...@@ -67,6 +68,7 @@ const reducers = { ...@@ -67,6 +68,7 @@ const reducers = {
suggestions, suggestions,
polls, polls,
trends, trends,
missed_updates,
}; };
export default combineReducers(reducers); export default combineReducers(reducers);
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;
}
};
...@@ -12,6 +12,7 @@ class InitialStateSerializer < ActiveModel::Serializer ...@@ -12,6 +12,7 @@ class InitialStateSerializer < ActiveModel::Serializer
access_token: object.token, access_token: object.token,
locale: I18n.locale, locale: I18n.locale,
domain: Rails.configuration.x.local_domain, domain: Rails.configuration.x.local_domain,
title: instance_presenter.site_title,
admin: object.admin&.id&.to_s, admin: object.admin&.id&.to_s,
search_enabled: Chewy.enabled?, search_enabled: Chewy.enabled?,
repository: Mastodon::Version.repository, repository: Mastodon::Version.repository,
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter