Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `package.json`
This commit is contained in:
Thibaut Girka
2019-11-06 14:38:28 +01:00
31 changed files with 492 additions and 312 deletions

View File

@@ -10,6 +10,12 @@ const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
return obj;
}, {});
export function searchTextFromRawStatus (status) {
const spoilerText = status.spoiler_text || '';
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
return domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
}
export function normalizeAccount(account) {
account = { ...account };

View File

@@ -14,6 +14,7 @@ import { unescapeHTML } from '../utils/html';
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';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
@@ -60,7 +61,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
if (notification.type === 'mention') {
const dropRegex = filters[0];
const regex = filters[1];
const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
const searchIndex = searchTextFromRawStatus(notification.status);
if (dropRegex && dropRegex.test(searchIndex)) {
return;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import 'wicg-inert';
export default class ModalRoot extends React.PureComponent {

View File

@@ -39,7 +39,8 @@ class Poll extends ImmutablePureComponent {
static getDerivedStateFromProps (props, state) {
const { poll, intl } = props;
const expired = poll.get('expired') || (new Date(poll.get('expires_at'))).getTime() < intl.now();
const expires_at = poll.get('expires_at');
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now();
return (expired === state.expired) ? null : { expired };
}

View File

@@ -1,13 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusContainer from '../../../containers/status_container';
import AccountContainer from '../../../containers/account_container';
import { injectIntl, FormattedMessage } from 'react-intl';
import Permalink from '../../../components/permalink';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
import { HotKeys } from 'react-hotkeys';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from 'mastodon/initial_state';
import StatusContainer from 'mastodon/containers/status_container';
import AccountContainer from 'mastodon/containers/account_container';
import Icon from 'mastodon/components/icon';
import Permalink from 'mastodon/components/permalink';
const messages = defineMessages({
favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' },
follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
});
const notificationForScreenReader = (intl, message, timestamp) => {
const output = [message];
@@ -107,7 +116,7 @@ class Notification extends ImmutablePureComponent {
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow', defaultMessage: '{name} followed you' }, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification notification-follow focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='user-plus' fixedWidth />
@@ -146,7 +155,7 @@ class Notification extends ImmutablePureComponent {
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification notification-favourite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='star' className='star-icon' fixedWidth />
@@ -178,7 +187,7 @@ class Notification extends ImmutablePureComponent {
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.reblog', defaultMessage: '{name} boosted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification notification-reblog focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='retweet' fixedWidth />
@@ -205,25 +214,31 @@ class Notification extends ImmutablePureComponent {
);
}
renderPoll (notification) {
renderPoll (notification, account) {
const { intl } = this.props;
const ownPoll = me === account.get('id');
const message = ownPoll ? intl.formatMessage(messages.ownPoll) : intl.formatMessage(messages.poll);
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}>
<div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='tasks' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
{ownPoll ? (
<FormattedMessage id='notification.own_poll' defaultMessage='Your poll has ended' />
) : (
<FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
)}
</span>
</div>
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
account={account}
muted
withDismiss
hidden={this.props.hidden}
@@ -253,7 +268,7 @@ class Notification extends ImmutablePureComponent {
case 'reblog':
return this.renderReblog(notification, link);
case 'poll':
return this.renderPoll(notification);
return this.renderPoll(notification, account);
}
return null;

View File

@@ -184,6 +184,15 @@ class FocalPointModal extends ImmutablePureComponent {
this.setState({ description: e.target.value, dirty: true });
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
e.stopPropagation();
this.setState({ description: e.target.value, dirty: true });
this.handleSubmit();
}
}
handleSubmit = () => {
this.props.onSave(this.state.description, this.state.focusX, this.state.focusY);
this.props.onClose();
@@ -254,6 +263,7 @@ class FocalPointModal extends ImmutablePureComponent {
className='setting-text light'
value={detecting ? '…' : description}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
disabled={detecting}
autoFocus
/>

View File

@@ -6,9 +6,9 @@ import { createSelector } from 'reselect';
import { debounce } from 'lodash';
import { me } from '../../../initial_state';
const makeGetStatusIds = () => createSelector([
const makeGetStatusIds = (pending = false) => createSelector([
(state, { type }) => state.getIn(['settings', type], ImmutableMap()),
(state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()),
(state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()),
(state) => state.get('statuses'),
], (columnSettings, statusIds, statuses) => {
return statusIds.filter(id => {
@@ -31,13 +31,14 @@ const makeGetStatusIds = () => createSelector([
const makeMapStateToProps = () => {
const getStatusIds = makeGetStatusIds();
const getPendingStatusIds = makeGetStatusIds(true);
const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),
numPending: state.getIn(['timelines', timelineId, 'pendingItems'], ImmutableList()).size,
numPending: getPendingStatusIds(state, { type: timelineId }).size,
});
return mapStateToProps;

View File

@@ -164,7 +164,9 @@ class SwitchingColumnsArea extends React.PureComponent {
}
setRef = c => {
this.node = c.getWrappedInstance();
if (c) {
this.node = c.getWrappedInstance();
}
}
render () {

View File

@@ -0,0 +1,16 @@
// On KaiOS, we may not be able to use a mouse cursor or navigate using Tab-based focus, so we install
// special left/right focus navigation keyboard listeners, at least on public pages (i.e. so folks
// can at least log in using KaiOS devices).
function importArrowKeyNavigation() {
return import(/* webpackChunkName: "arrow-key-navigation" */ 'arrow-key-navigation');
}
export default function loadKeyboardExtensions() {
if (/KAIOS/.test(navigator.userAgent)) {
return importArrowKeyNavigation().then(arrowKeyNav => {
arrowKeyNav.register();
});
}
return Promise.resolve();
}

View File

@@ -2078,21 +2078,25 @@
},
{
"descriptors": [
{
"defaultMessage": "{name} followed you",
"id": "notification.follow"
},
{
"defaultMessage": "{name} favourited your status",
"id": "notification.favourite"
},
{
"defaultMessage": "{name} boosted your status",
"id": "notification.reblog"
"defaultMessage": "{name} followed you",
"id": "notification.follow"
},
{
"defaultMessage": "Your poll has ended",
"id": "notification.own_poll"
},
{
"defaultMessage": "A poll you have voted in has ended",
"id": "notification.poll"
},
{
"defaultMessage": "{name} boosted your status",
"id": "notification.reblog"
}
],
"path": "app/javascript/mastodon/features/notifications/components/notification.json"
@@ -2771,6 +2775,10 @@
"defaultMessage": "Exit full screen",
"id": "video.exit_fullscreen"
},
{
"defaultMessage": "Download file",
"id": "video.download"
},
{
"defaultMessage": "Sensitive content",
"id": "status.sensitive_warning"

View File

@@ -279,6 +279,7 @@
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",
"notifications.clear": "Clear notifications",
@@ -417,6 +418,7 @@
"upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.download": "Download file",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",

View File

@@ -1,6 +1,7 @@
import loadPolyfills from '../mastodon/load_polyfills';
import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
start();
@@ -122,6 +123,9 @@ function main() {
});
}
loadPolyfills().then(main).catch(error => {
console.error(error);
});
loadPolyfills()
.then(main)
.then(loadKeyboardExtensions)
.catch(error => {
console.error(error);
});