Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge upstream #234

Merged
merged 41 commits into from
Oct 16, 2020
Merged
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9114b84
Bump babel-jest from 26.3.0 to 26.5.2 (#14945)
dependabot[bot] Oct 7, 2020
0b03ee3
Bump @github/webauthn-json from 0.5.5 to 0.5.6 (#14946)
dependabot[bot] Oct 7, 2020
e2d20be
Bump sass from 1.26.11 to 1.26.12 (#14947)
dependabot[bot] Oct 7, 2020
dcaff7d
Bump eslint-plugin-react from 7.21.2 to 7.21.3 (#14950)
dependabot[bot] Oct 7, 2020
d175a3b
Bump mini-css-extract-plugin from 0.11.0 to 0.11.3 (#14949)
dependabot[bot] Oct 7, 2020
3f6ab34
Bump jest from 26.4.2 to 26.5.2 (#14951)
dependabot[bot] Oct 7, 2020
a37732e
Bump eslint from 7.6.0 to 7.10.0 (#14948)
dependabot[bot] Oct 7, 2020
d9f8890
update themes
mashirozx Oct 7, 2020
7d985f2
Remove dependency on goldfinger gem (#14919)
Gargron Oct 7, 2020
dac3e36
Fix unread notification marker not updating when mounting column (#14…
ClearlyClaire Oct 7, 2020
dc52a77
Fix issue checking for last unread notification when there are gaps (…
ClearlyClaire Oct 9, 2020
c1d9995
add & fix themes
mashirozx Oct 9, 2020
aeccdb6
Merge branch 'master' into dev
mashirozx Oct 9, 2020
6271102
update theme
mashirozx Oct 9, 2020
f3f410b
Merge branch 'dev' of github.com:mashirozx/mastodon into dev
mashirozx Oct 9, 2020
4926488
fix theme
mashirozx Oct 9, 2020
e024c43
fix theme
mashirozx Oct 9, 2020
5e1364c
Add IP-based rules (#14963)
Gargron Oct 12, 2020
f54ca3d
Fix browser notification permission request logic (#13543)
ClearlyClaire Oct 12, 2020
9676175
Add duration parameter to muting. (#13831)
aquarla Oct 12, 2020
53b22d2
helm: add optional cron job to run `tootctl remove media` (#14396)
dunn Oct 12, 2020
4c45b43
Change how CDN_HOST is passed down to make assets build reproducible …
ClearlyClaire Oct 12, 2020
3e5636b
handle conflict
mashirozx Oct 13, 2020
3547009
Bump compression-webpack-plugin from 6.0.2 to 6.0.3 (#14979)
dependabot[bot] Oct 13, 2020
fc87b15
Bump sass-loader from 10.0.2 to 10.0.3 (#14977)
dependabot[bot] Oct 13, 2020
658dbd7
Bump imports-loader from 1.1.0 to 1.2.0 (#14976)
dependabot[bot] Oct 13, 2020
5b131f0
Bump tzinfo-data from 1.2020.1 to 1.2020.2 (#14966)
dependabot[bot] Oct 13, 2020
06c3d36
Bump rubocop from 0.92.0 to 0.93.0 (#14967)
dependabot[bot] Oct 13, 2020
5de8665
Bump file-loader from 6.1.0 to 6.1.1 (#14974)
dependabot[bot] Oct 13, 2020
a8e8ee2
Bump eslint-plugin-react from 7.21.3 to 7.21.4 (#14968)
dependabot[bot] Oct 13, 2020
df8cbbf
Bump terser-webpack-plugin from 4.2.2 to 4.2.3 (#14971)
dependabot[bot] Oct 13, 2020
37295d5
Bump eslint from 7.10.0 to 7.11.0 (#14975)
dependabot[bot] Oct 13, 2020
ec1d8b7
Bump sass from 1.26.12 to 1.27.0 (#14973)
dependabot[bot] Oct 13, 2020
bb18092
Bump jest from 26.5.2 to 26.5.3 (#14969)
dependabot[bot] Oct 13, 2020
b4c4af1
Fix a bear check when the activity object is nil (#14981)
noellabo Oct 13, 2020
a69ca29
Change how missing desktop notifications permission is displayed (#14…
Gargron Oct 15, 2020
fb5f3be
Fix strings that could not be translated (#14980)
mayaeh Oct 15, 2020
4130aef
Fix translation string (#14986)
mayaeh Oct 16, 2020
abae994
update theme
mashirozx Oct 16, 2020
9e55cba
handle conflict
mashirozx Oct 16, 2020
fe5f6d3
Merge branch 'master' into dev
mashirozx Oct 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix browser notification permission request logic (mastodon#13543)
* Add notification permission handling code

* Request notification permission when enabling any notification setting

* Add badge to notification settings when permissions insufficient

* Disable alerts by default, requesting permission and enable them on onboarding
ClearlyClaire authored Oct 12, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit f54ca3d08e068af07a5b7a8b139e7658b3236db8
45 changes: 45 additions & 0 deletions app/javascript/mastodon/actions/notifications.js
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import { getFiltersRegex } from '../selectors';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
import compareId from 'mastodon/compare_id';
import { searchTextFromRawStatus } from 'mastodon/actions/importer/normalizer';
import { requestNotificationPermission } from '../utils/notifications';

export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
@@ -33,8 +34,12 @@ export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';


export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';

export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';

defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
@@ -235,6 +240,46 @@ export const unmountNotifications = () => ({
type: NOTIFICATIONS_UNMOUNT,
});


export const markNotificationsAsRead = () => ({
type: NOTIFICATIONS_MARK_AS_READ,
});

// Browser support
export function setupBrowserNotifications() {
return dispatch => {
dispatch(setBrowserSupport('Notification' in window));
if ('Notification' in window) {
dispatch(setBrowserPermission(Notification.permission));
}

if ('Notification' in window && 'permissions' in navigator) {
navigator.permissions.query({ name: 'notifications' }).then((status) => {
status.onchange = () => dispatch(setBrowserPermission(Notification.permission));
});
}
};
}

export function requestBrowserPermission(callback = noOp) {
return dispatch => {
requestNotificationPermission((permission) => {
dispatch(setBrowserPermission(permission));
callback(permission);
});
};
};

export function setBrowserSupport (value) {
return {
type: NOTIFICATIONS_SET_BROWSER_SUPPORT,
value,
};
}

export function setBrowserPermission (value) {
return {
type: NOTIFICATIONS_SET_BROWSER_PERMISSION,
value,
};
}
12 changes: 12 additions & 0 deletions app/javascript/mastodon/actions/onboarding.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { changeSetting, saveSettings } from './settings';
import { requestBrowserPermission } from './notifications';

export const INTRODUCTION_VERSION = 20181216044202;

export const closeOnboarding = () => dispatch => {
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
dispatch(saveSettings());

dispatch(requestBrowserPermission((permission) => {
if (permission === 'granted') {
dispatch(changeSetting(['notifications', 'alerts', 'follow'], true));
dispatch(changeSetting(['notifications', 'alerts', 'favourite'], true));
dispatch(changeSetting(['notifications', 'alerts', 'reblog'], true));
dispatch(changeSetting(['notifications', 'alerts', 'mention'], true));
dispatch(changeSetting(['notifications', 'alerts', 'poll'], true));
dispatch(saveSettings());
}
}));
};
18 changes: 16 additions & 2 deletions app/javascript/mastodon/components/column_header.js
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ class ColumnHeader extends React.PureComponent {
onMove: PropTypes.func,
onClick: PropTypes.func,
appendContent: PropTypes.node,
collapseIssues: PropTypes.bool,
};

state = {
@@ -83,7 +84,7 @@ class ColumnHeader extends React.PureComponent {
}

render () {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent } = this.props;
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { collapsed, animating } = this.state;

const wrapperClassName = classNames('column-header__wrapper', {
@@ -145,7 +146,20 @@ class ColumnHeader extends React.PureComponent {
}

if (children || (multiColumn && this.props.onPin)) {
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
collapseButton = (
<button
className={collapsibleButtonClassName}
title={formatMessage(collapsed ? messages.show : messages.hide)}
aria-label={formatMessage(collapsed ? messages.show : messages.hide)}
aria-pressed={collapsed ? 'false' : 'true'}
onClick={this.handleToggleClick}
>
<i className='icon-with-badge'>
<Icon id='sliders' />
{collapseIssues && <i className='icon-with-badge__issue-badge' />}
</i>
</button>
);
}

const hasTitle = icon && title;
4 changes: 3 additions & 1 deletion app/javascript/mastodon/components/icon_with_badge.js
Original file line number Diff line number Diff line change
@@ -4,16 +4,18 @@ import Icon from 'mastodon/components/icon';

const formatNumber = num => num > 40 ? '40+' : num;

const IconWithBadge = ({ id, count, className }) => (
const IconWithBadge = ({ id, count, issueBadge, className }) => (
<i className='icon-with-badge'>
<Icon id={id} fixedWidth className={className} />
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
{issueBadge && <i className='icon-with-badge__issue-badge' />}
</i>
);

IconWithBadge.propTypes = {
id: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
issueBadge: PropTypes.bool,
className: PropTypes.string,
};

Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import ClearColumnButton from './clear_column_button';
import SettingToggle from './setting_toggle';
import Icon from 'mastodon/components/icon';

export default class ColumnSettings extends React.PureComponent {

@@ -12,14 +13,18 @@ export default class ColumnSettings extends React.PureComponent {
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onRequestNotificationPermission: PropTypes.func.isRequired,
alertsEnabled: PropTypes.bool,
browserSupport: PropTypes.bool,
browserPermission: PropTypes.bool,
};

onPushChange = (path, checked) => {
this.props.onChange(['push', ...path], checked);
}

render () {
const { settings, pushSettings, onChange, onClear } = this.props;
const { settings, pushSettings, onChange, onClear, onRequestNotificationPermission, alertsEnabled, browserSupport, browserPermission } = this.props;

const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
@@ -30,8 +35,40 @@ export default class ColumnSettings extends React.PureComponent {
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;

const settingsIssues = [];

if (alertsEnabled && browserSupport && browserPermission !== 'granted') {
if (browserPermission === 'denied') {
settingsIssues.push(
<button
className='text-btn column-header__issue-btn'
tabIndex='0'
onClick={onRequestNotificationPermission}
>
<Icon id='exclamation-circle' /> <FormattedMessage id='notifications.permission_denied' defaultMessage='Mastodon cannot show notifications because the permission has been denied' />
</button>
);
} else if (browserPermission === 'default') {
settingsIssues.push(
<button
className='text-btn column-header__issue-btn'
tabIndex='0'
onClick={onRequestNotificationPermission}
>
<Icon id='exclamation-circle' /> <FormattedMessage id='notifications.request_permission' defaultMessage='Enable browser notifications' />
</button>
);
}
}

return (
<div>
{settingsIssues && (
<div className='column-settings__row'>
{settingsIssues}
</div>
)}

<div className='column-settings__row'>
<ClearColumnButton onClick={onClear} />
</div>
Original file line number Diff line number Diff line change
@@ -3,28 +3,55 @@ import { defineMessages, injectIntl } from 'react-intl';
import ColumnSettings from '../components/column_settings';
import { changeSetting } from '../../../actions/settings';
import { setFilter } from '../../../actions/notifications';
import { clearNotifications } from '../../../actions/notifications';
import { clearNotifications, requestBrowserPermission } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { openModal } from '../../../actions/modal';
import { showAlert } from '../../../actions/alerts';

const messages = defineMessages({
clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' },
permissionDenied: { id: 'notifications.permission_denied', defaultMessage: 'Cannot enable desktop notifications as permission has been denied.' },
});

const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'),
alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true),
browserSupport: state.getIn(['notifications', 'browserSupport']),
browserPermission: state.getIn(['notifications', 'browserPermission']),
});

const mapDispatchToProps = (dispatch, { intl }) => ({

onChange (path, checked) {
if (path[0] === 'push') {
dispatch(changePushNotifications(path.slice(1), checked));
if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
dispatch(requestBrowserPermission((permission) => {
if (permission === 'granted') {
dispatch(changePushNotifications(path.slice(1), checked));
} else {
dispatch(showAlert(undefined, messages.permissionDenied));
}
}));
} else {
dispatch(changePushNotifications(path.slice(1), checked));
}
} else if (path[0] === 'quickFilter') {
dispatch(changeSetting(['notifications', ...path], checked));
dispatch(setFilter('all'));
} else if (path[0] === 'alerts' && checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
dispatch(requestBrowserPermission((permission) => {
if (permission === 'granted') {
dispatch(changeSetting(['notifications', ...path], checked));
} else {
dispatch(showAlert(undefined, messages.permissionDenied));
}
}));
} else {
dispatch(changeSetting(['notifications', ...path], checked));
}
} else {
dispatch(changeSetting(['notifications', ...path], checked));
}
@@ -38,6 +65,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}));
},

onRequestNotificationPermission () {
dispatch(requestBrowserPermission());
},

});

export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings));
3 changes: 3 additions & 0 deletions app/javascript/mastodon/features/notifications/index.js
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@ const mapStateToProps = state => ({
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
lastReadId: state.getIn(['notifications', 'readMarkerId']),
canMarkAsRead: state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) !== 'granted',
});

export default @connect(mapStateToProps)
@@ -75,6 +76,7 @@ class Notifications extends React.PureComponent {
numPending: PropTypes.number,
lastReadId: PropTypes.string,
canMarkAsRead: PropTypes.bool,
needsNotificationPermission: PropTypes.bool,
};

static defaultProps = {
@@ -250,6 +252,7 @@ class Notifications extends React.PureComponent {
pinned={pinned}
multiColumn={multiColumn}
extraButton={extraButton}
collapseIssues={this.props.needsNotificationPermission}
>
<ColumnSettingsContainer />
</ColumnHeader>
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import IconWithBadge from 'mastodon/components/icon_with_badge';

const mapStateToProps = state => ({
count: state.getIn(['notifications', 'unread']),
issueBadge: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) !== 'granted',
id: 'bell',
});

4 changes: 0 additions & 4 deletions app/javascript/mastodon/features/ui/index.js
Original file line number Diff line number Diff line change
@@ -366,10 +366,6 @@ class UI extends React.PureComponent {
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
}

if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
}

this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
2 changes: 2 additions & 0 deletions app/javascript/mastodon/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as registerPushNotifications from './actions/push_notifications';
import { setupBrowserNotifications } from './actions/notifications';
import { default as Mastodon, store } from './containers/mastodon';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -22,6 +23,7 @@ function main() {
const props = JSON.parse(mountNode.getAttribute('data-props'));

ReactDOM.render(<Mastodon {...props} />, mountNode);
store.dispatch(setupBrowserNotifications());
if (process.env.NODE_ENV === 'production') {
// avoid offline in dev mode because it's harder to debug
require('offline-plugin/runtime').install();
8 changes: 8 additions & 0 deletions app/javascript/mastodon/reducers/notifications.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ import {
NOTIFICATIONS_MOUNT,
NOTIFICATIONS_UNMOUNT,
NOTIFICATIONS_MARK_AS_READ,
NOTIFICATIONS_SET_BROWSER_SUPPORT,
NOTIFICATIONS_SET_BROWSER_PERMISSION,
} from '../actions/notifications';
import {
ACCOUNT_BLOCK_SUCCESS,
@@ -40,6 +42,8 @@ const initialState = ImmutableMap({
readMarkerId: '0',
isTabVisible: true,
isLoading: false,
browserSupport: false,
browserPermission: 'default',
});

const notificationToMap = notification => ImmutableMap({
@@ -242,6 +246,10 @@ export default function notifications(state = initialState, action) {
case NOTIFICATIONS_MARK_AS_READ:
const lastNotification = state.get('items').find(item => item !== null);
return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
case NOTIFICATIONS_SET_BROWSER_SUPPORT:
return state.set('browserSupport', action.value);
case NOTIFICATIONS_SET_BROWSER_PERMISSION:
return state.set('browserPermission', action.value);
default:
return state;
}
10 changes: 5 additions & 5 deletions app/javascript/mastodon/reducers/settings.js
Original file line number Diff line number Diff line change
@@ -29,12 +29,12 @@ const initialState = ImmutableMap({

notifications: ImmutableMap({
alerts: ImmutableMap({
follow: true,
follow: false,
follow_request: false,
favourite: true,
reblog: true,
mention: true,
poll: true,
favourite: false,
reblog: false,
mention: false,
poll: false,
}),

quickFilter: ImmutableMap({
Loading