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