sforkowany z mirror/soapbox
Merge branch 'chat_notifications' into 'develop'
Chat notifications Closes #398 and #354 See merge request soapbox-pub/soapbox-fe!211better-alerts
commit
efe66d4301
|
@ -188,6 +188,8 @@ Customization details can be found in the [Customization doc](docs/customization
|
|||
|
||||
Soapbox FE is based on [Gab Social](https://code.gab.com/gab/social/gab-social)'s frontend which is in turn based on [Mastodon](https://github.com/tootsuite/mastodon/)'s frontend.
|
||||
|
||||
- `static/sounds/chat.mp3` and `static/sounds/chat.oga` are from [notificationsounds.com](https://notificationsounds.com/notification-sounds/intuition-561) licensed under CC BY 4.0.
|
||||
|
||||
Soapbox FE is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
|
|
|
@ -32,6 +32,7 @@ const defaultSettings = ImmutableMap({
|
|||
chats: ImmutableMap({
|
||||
panes: ImmutableList(),
|
||||
mainWindow: 'minimized',
|
||||
sound: true,
|
||||
}),
|
||||
|
||||
home: ImmutableMap({
|
||||
|
|
|
@ -55,7 +55,17 @@ export function connectTimelineStream(timelineId, path, pollingRefresh = null, a
|
|||
dispatch(fetchFilters());
|
||||
break;
|
||||
case 'pleroma:chat_update':
|
||||
dispatch({ type: STREAMING_CHAT_UPDATE, chat: JSON.parse(data.payload), me: getState().get('me') });
|
||||
dispatch((dispatch, getState) => {
|
||||
const chat = JSON.parse(data.payload);
|
||||
const messageOwned = !(chat.last_message && chat.last_message.account_id !== getState().get('me'));
|
||||
|
||||
dispatch({
|
||||
type: STREAMING_CHAT_UPDATE,
|
||||
chat,
|
||||
// Only play sounds for recipient messages
|
||||
meta: !messageOwned && getSettings(getState()).getIn(['chats', 'sound']) && { sound: 'chat' },
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -36,6 +36,7 @@ class SoapboxHelmet extends React.Component {
|
|||
<Helmet
|
||||
titleTemplate={this.addCounter(`%s | ${siteTitle}`)}
|
||||
defaultTitle={this.addCounter(siteTitle)}
|
||||
defer={false}
|
||||
>
|
||||
{children}
|
||||
</Helmet>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
||||
import SettingToggle from 'soapbox/features/notifications/components/setting_toggle';
|
||||
|
||||
const messages = defineMessages({
|
||||
switchToOn: { id: 'chats.audio_toggle_on', defaultMessage: 'Audio notification on' },
|
||||
switchToOff: { id: 'chats.audio_toggle_off', defaultMessage: 'Audio notification off' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
settings: getSettings(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleAudio(setting) {
|
||||
dispatch(changeSetting(['chats', 'sound'], setting));
|
||||
},
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class AudioToggle extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
toggleAudio: PropTypes.func,
|
||||
showLabel: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleToggleAudio = () => {
|
||||
this.props.toggleAudio(this.props.settings.getIn(['chats', 'sound']) === true ? false : true);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, settings, showLabel } = this.props;
|
||||
let toggle = (
|
||||
<SettingToggle settings={settings} settingPath={['chats', 'sound']} onChange={this.handleToggleAudio} icons={{ checked: <Icon id='volume-up' />, unchecked: <Icon id='volume-off' /> }} ariaLabel={settings.get('chats', 'sound') === true ? intl.formatMessage(messages.switchToOff) : intl.formatMessage(messages.switchToOn)} />
|
||||
);
|
||||
|
||||
if (showLabel) {
|
||||
toggle = (
|
||||
<SettingToggle settings={settings} settingPath={['chats', 'sound']} onChange={this.handleToggleAudio} icons={{ checked: <Icon id='volume-up' />, unchecked: <Icon id='volume-off' /> }} label={settings.get('chats', 'sound') === true ? intl.formatMessage(messages.switchToOff) : intl.formatMessage(messages.switchToOn)} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='audio-toggle react-toggle--mini'>
|
||||
{toggle}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -94,6 +94,7 @@ class ChatBox extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
this.markRead();
|
||||
if (e.key === 'Enter' && e.shiftKey) {
|
||||
this.insertLine();
|
||||
e.preventDefault();
|
||||
|
@ -122,17 +123,6 @@ class ChatBox extends ImmutablePureComponent {
|
|||
onSetInputRef(el);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const markReadConditions = [
|
||||
() => this.props.chat !== undefined,
|
||||
() => document.activeElement === this.inputElem,
|
||||
() => this.props.chat.get('unread') > 0,
|
||||
];
|
||||
|
||||
if (markReadConditions.every(c => c() === true))
|
||||
this.markRead();
|
||||
}
|
||||
|
||||
handleRemoveFile = (e) => {
|
||||
this.setState({ attachment: undefined, resetFileKey: fileKeyGen() });
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { makeGetChat } from 'soapbox/selectors';
|
|||
import { openChat, toggleMainWindow } from 'soapbox/actions/chats';
|
||||
import ChatWindow from './chat_window';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
|
||||
|
||||
const addChatsToPanes = (state, panesData) => {
|
||||
const getChat = makeGetChat();
|
||||
|
@ -62,6 +63,7 @@ class ChatPanes extends ImmutablePureComponent {
|
|||
<button className='pane__title' onClick={this.handleMainWindowToggle}>
|
||||
<FormattedMessage id='chat_panels.main_window.title' defaultMessage='Chats' />
|
||||
</button>
|
||||
<AudioToggle />
|
||||
</div>
|
||||
<div className='pane__content'>
|
||||
<ChatList
|
||||
|
|
|
@ -36,6 +36,16 @@ export default function soundsMiddleware() {
|
|||
type: 'audio/mpeg',
|
||||
},
|
||||
]),
|
||||
chat: createAudio([
|
||||
{
|
||||
src: '/sounds/chat.oga',
|
||||
type: 'audio/ogg',
|
||||
},
|
||||
{
|
||||
src: '/sounds/chat.mp3',
|
||||
type: 'audio/mpeg',
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
return () => next => action => {
|
||||
|
|
|
@ -94,6 +94,41 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-toggle .react-toggle-thumb {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border: 1px solid var(--brand-color--med);
|
||||
}
|
||||
|
||||
.audio-toggle .react-toggle {
|
||||
height: 16px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.audio-toggle .react-toggle-track {
|
||||
height: 16px;
|
||||
width: 34px;
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.audio-toggle .react-toggle-track-check {
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.react-toggle--checked .react-toggle-thumb {
|
||||
left: 19px;
|
||||
}
|
||||
|
||||
.audio-toggle .react-toggle-track-x {
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.fa {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
|
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Ładowanie…
Reference in New Issue