This commit is contained in:
mgabdev 2020-04-09 15:18:14 -04:00
parent 2b61a7c067
commit 7249143d9f
22 changed files with 166 additions and 440 deletions

View File

@ -7,27 +7,26 @@ import {
importFetchedStatus, importFetchedStatus,
importFetchedStatuses, importFetchedStatuses,
} from './importer'; } from './importer';
import { saveSettings } from './settings';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from '../utils/html'; import { unescapeHTML } from '../utils/html';
import { getFilters, regexFromFilters } from '../selectors'; import { getFilters, regexFromFilters } from '../selectors';
import { me } from '../initial_state'; import { me } from '../initial_state';
export const NOTIFICATIONS_INITIALIZE = 'NOTIFICATIONS_INITIALIZE'; export const NOTIFICATIONS_INITIALIZE = 'NOTIFICATIONS_INITIALIZE';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE'; export const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE';
export const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE'; export const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE';
export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
export const NOTIFICATIONS_MARK_READ = 'NOTIFICATIONS_MARK_READ'; export const NOTIFICATIONS_MARK_READ = 'NOTIFICATIONS_MARK_READ';
export const MAX_QUEUED_NOTIFICATIONS = 40 export const MAX_QUEUED_NOTIFICATIONS = 40
@ -52,7 +51,7 @@ export function initializeNotifications() {
export function updateNotifications(notification, intlMessages, intlLocale) { export function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => { return (dispatch, getState) => {
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); const showInColumn = getState().getIn(['notifications', 'filter', notification.type], true);
if (showInColumn) { if (showInColumn) {
dispatch(importFetchedAccount(notification.account)); dispatch(importFetchedAccount(notification.account));
@ -73,7 +72,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
export function updateNotificationsQueue(notification, intlMessages, intlLocale, curPath) { export function updateNotificationsQueue(notification, intlMessages, intlLocale, curPath) {
return (dispatch, getState) => { return (dispatch, getState) => {
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); // : todo :
// const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
const filters = getFilters(getState(), { contextType: 'notifications' }); const filters = getFilters(getState(), { contextType: 'notifications' });
let filtered = false; let filtered = false;
@ -87,17 +87,18 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale,
} }
// Desktop notifications // Desktop notifications
if (typeof window.Notification !== 'undefined' && showAlert && !filtered) { // : todo :
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); // if (typeof window.Notification !== 'undefined' && showAlert && !filtered) {
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); // const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
// const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); // const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
notify.addEventListener('click', () => { // notify.addEventListener('click', () => {
window.focus(); // window.focus();
notify.close(); // notify.close();
}); // });
} // }
if (isOnNotificationsPage) { if (isOnNotificationsPage) {
dispatch({ dispatch({
@ -134,22 +135,22 @@ export function dequeueNotifications() {
} }
}; };
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
const excludeTypesFromFilter = filter => { const excludeTypesFromFilter = filter => {
const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']); const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']);
return allTypes.filterNot(item => item === filter).toJS(); return allTypes.filterNot(item => item === filter).toJS();
}; };
const noOp = () => {}; const noOp = () => { };
export function expandNotifications({ maxId } = {}, done = noOp) { export function expandNotifications({ maxId } = {}, done = noOp) {
return (dispatch, getState) => { return (dispatch, getState) => {
if (!me) return; if (!me) return
const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']); const onlyVerified = getState().getIn(['notifications', 'filter', 'onlyVerified'])
const notifications = getState().get('notifications'); const onlyFollowing = getState().getIn(['notifications', 'filter', 'onlyFollowing'])
const isLoadingMore = !!maxId; const activeFilter = getState().getIn(['notifications', 'filter', 'active'])
const notifications = getState().get('notifications')
const isLoadingMore = !!maxId
if (notifications.get('isLoading')) { if (notifications.get('isLoading')) {
done(); done();
@ -157,16 +158,16 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
} }
console.log('activeFilter:', activeFilter) console.log('activeFilter:', activeFilter)
console.log('excludeTypesFromSettings(getState()):', excludeTypesFromSettings(getState())) // console.log('excludeTypesFromSettings(getState()):', excludeTypesFromSettings(getState()))
console.log('excludeTypesFromFilter(activeFilter):', excludeTypesFromFilter(activeFilter)) console.log('excludeTypesFromFilter(activeFilter):', excludeTypesFromFilter(activeFilter))
// : todo : // : todo :
// filter verified and following here too // filter verified and following here too
const params = { const params = {
max_id: maxId, max_id: maxId,
exclude_types: activeFilter === 'all' only_verified: onlyVerified,
? excludeTypesFromSettings(getState()) only_following: onlyFollowing,
: excludeTypesFromFilter(activeFilter), exclude_types: activeFilter === 'all' ? [] : excludeTypesFromFilter(activeFilter),
}; };
if (!maxId && notifications.get('items').size > 0) { if (!maxId && notifications.get('items').size > 0) {
@ -237,17 +238,16 @@ export function scrollTopNotifications(top) {
} }
}; };
export function setFilter (filterType) { export function setFilter(path, value) {
return dispatch => { return dispatch => {
dispatch({ dispatch({
type: NOTIFICATIONS_FILTER_SET, type: NOTIFICATIONS_FILTER_SET,
path: ['notifications', 'quickFilter', 'active'], path: path,
value: filterType, value: value,
}); })
dispatch(expandNotifications()); dispatch(expandNotifications())
dispatch(saveSettings()); }
}; }
};
export function markReadNotifications() { export function markReadNotifications() {
return (dispatch, getState) => { return (dispatch, getState) => {

View File

@ -1,10 +1,9 @@
import api from '../api'; import api from '../api'
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce'
// import { showAlertForError } from './alerts'; import { me } from '../initial_state'
import { me } from '../initial_state';
export const SETTING_CHANGE = 'SETTING_CHANGE'; export const SETTING_CHANGE = 'SETTING_CHANGE'
export const SETTING_SAVE = 'SETTING_SAVE'; export const SETTING_SAVE = 'SETTING_SAVE'
export function changeSetting(path, value) { export function changeSetting(path, value) {
return dispatch => { return dispatch => {
@ -12,26 +11,24 @@ export function changeSetting(path, value) {
type: SETTING_CHANGE, type: SETTING_CHANGE,
path, path,
value, value,
}); })
dispatch(saveSettings()); dispatch(saveSettings())
}; }
}; }
const debouncedSave = debounce((dispatch, getState) => { const debouncedSave = debounce((dispatch, getState) => {
if (!me) return; if (!me) return
if (getState().getIn(['settings', 'saved'])) { if (getState().getIn(['settings', 'saved'])) return
return;
}
const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS(); const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS()
api().put('/api/web/settings', { data }) api().put('/api/web/settings', { data })
.then(() => dispatch({ type: SETTING_SAVE })) .then(() => dispatch({ type: SETTING_SAVE }))
// .catch(error => dispatch(showAlertForError(error))); .catch(() => { /* */ })
}, 5000, { trailing: true }); }, 5000, { trailing: true })
export function saveSettings() { export function saveSettings() {
return (dispatch, getState) => debouncedSave(dispatch, getState); return (dispatch, getState) => debouncedSave(dispatch, getState)
}; }

View File

@ -0,0 +1,26 @@
const PencilIcon = ({
className = '',
width = '16px',
height = '16px',
viewBox = '0 0 64 64',
title = 'Pencil',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d='M 22.609375 56.027344 L 8.085938 41.503906 L 43.214844 6.371094 L 57.742188 20.898438 Z M 6.726562 44.0625 L 20.050781 57.386719 L 0.0742188 64.039062 Z M 62.207031 16.449219 L 59.6875 18.96875 L 45.144531 4.421875 L 47.664062 1.90625 C 50.203125 -0.636719 54.316406 -0.636719 56.855469 1.90625 L 62.207031 7.257812 C 64.726562 9.804688 64.726562 13.902344 62.207031 16.449219 Z M 62.207031 16.449219' />
</g>
</svg>
)
export default PencilIcon

View File

@ -1,4 +1,3 @@
import Icon from './icon'
import Button from './button' import Button from './button'
export default class FloatingActionButton extends PureComponent { export default class FloatingActionButton extends PureComponent {
@ -21,13 +20,12 @@ export default class FloatingActionButton extends PureComponent {
<Button <Button
onClick={onClick} onClick={onClick}
color='white' color='white'
className={[_s.positionFixed, _s.z4, _s.bottom0, _s.right0].join(' ')} backgroundColor='brand'
className={[_s.positionFixed, _s.z4, _s.py15, _s.mb15, _s.mr15, _s.bottom0, _s.right0].join(' ')}
title={message} title={message}
aria-label={message} aria-label={message}
> icon='pencil'
<Icon id='compose' /> />
[...]
</Button>
) )
} }
} }

View File

@ -44,6 +44,7 @@ import MoreIcon from '../assets/more_icon'
import NotificationsIcon from '../assets/notifications_icon' import NotificationsIcon from '../assets/notifications_icon'
import OLListIcon from '../assets/ol_list_icon' import OLListIcon from '../assets/ol_list_icon'
import PauseIcon from '../assets/pause_icon' import PauseIcon from '../assets/pause_icon'
import PencilIcon from '../assets/pencil_icon'
import PinIcon from '../assets/pin_icon' import PinIcon from '../assets/pin_icon'
import PlayIcon from '../assets/play_icon' import PlayIcon from '../assets/play_icon'
import PollIcon from '../assets/poll_icon' import PollIcon from '../assets/poll_icon'
@ -111,6 +112,7 @@ const ICONS = {
'notifications': NotificationsIcon, 'notifications': NotificationsIcon,
'ol-list': OLListIcon, 'ol-list': OLListIcon,
'pause': PauseIcon, 'pause': PauseIcon,
'pencil': PencilIcon,
'pin': PinIcon, 'pin': PinIcon,
'play': PlayIcon, 'play': PlayIcon,
'poll': PollIcon, 'poll': PollIcon,

View File

@ -1,7 +1,7 @@
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import { changeSetting, saveSettings } from '../../actions/settings' import { setFilter } from '../../actions/notifications'
import PanelLayout from './panel_layout' import PanelLayout from './panel_layout'
import SettingSwitch from '../setting_switch' import SettingSwitch from '../setting_switch'
@ -12,14 +12,13 @@ const messages = defineMessages({
}) })
const mapStateToProps = state => ({ const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']), settings: state.getIn(['notifications', 'filter']),
}) })
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
onChange(key, checked) { onChange(path, value) {
dispatch(changeSetting(['notifications', ...key], checked)) dispatch(setFilter(path, value))
dispatch(saveSettings())
}, },
} }
} }
@ -43,7 +42,7 @@ class NotificationFilterPanel extends ImmutablePureComponent {
<SettingSwitch <SettingSwitch
prefix='notification' prefix='notification'
settings={settings} settings={settings}
settingPath={['quickFilter', 'onlyVerifed']} settingPath={'onlyVerified'}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(messages.onlyVerified)} label={intl.formatMessage(messages.onlyVerified)}
/> />
@ -51,7 +50,7 @@ class NotificationFilterPanel extends ImmutablePureComponent {
<SettingSwitch <SettingSwitch
prefix='notification' prefix='notification'
settings={settings} settings={settings}
settingPath={['quickFilter', 'onlyFollowing']} settingPath={'onlyFollowing'}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(messages.onlyFollowing)} label={intl.formatMessage(messages.onlyFollowing)}
/> />

View File

@ -7,7 +7,10 @@ export default class SettingSwitch extends ImmutablePureComponent {
static propTypes = { static propTypes = {
prefix: PropTypes.string, prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired, settingPath: PropTypes.oneOfType([
PropTypes.array,
PropTypes.string,
]).isRequired,
description: PropTypes.string, description: PropTypes.string,
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -26,14 +29,17 @@ export default class SettingSwitch extends ImmutablePureComponent {
description description
} = this.props } = this.props
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-') const isArray = Array.isArray(settingPath)
const checked = isArray ? settings.getIn(settingPath) : settings.get(settingPath)
const idVal = isArray ? settingPath.join('-') : settingPath
const id = ['setting-toggle', prefix, idVal].filter(Boolean).join('-')
return ( return (
<Switch <Switch
description={description} description={description}
label={label} label={label}
id={id} id={id}
checked={settings.getIn(settingPath)} checked={checked}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
/> />

View File

@ -1,12 +1,14 @@
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { injectIntl, defineMessages } from 'react-intl' import { injectIntl, defineMessages } from 'react-intl'
import * as Constants from '../constants'
import Button from './button' import Button from './button'
import { closeSidebar } from '../actions/sidebar' import { closeSidebar } from '../actions/sidebar'
import { openModal } from '../actions/modal' import { openModal } from '../actions/modal'
import { openPopover } from '../actions/popover' import { openPopover } from '../actions/popover'
import { me } from '../initial_state' import { me } from '../initial_state'
import { makeGetAccount } from '../selectors' import { makeGetAccount } from '../selectors'
import Responsive from '../features/ui/util/responsive_component'
import SidebarSectionTitle from './sidebar_section_title' import SidebarSectionTitle from './sidebar_section_title'
import SidebarSectionItem from './sidebar_section_item' import SidebarSectionItem from './sidebar_section_item'
import SidebarHeader from './sidebar_header' import SidebarHeader from './sidebar_header'
@ -197,7 +199,7 @@ class Sidebar extends ImmutablePureComponent {
<header role='banner' className={[_s.default, _s.flexGrow1, _s.z3, _s.alignItemsEnd].join(' ')}> <header role='banner' className={[_s.default, _s.flexGrow1, _s.z3, _s.alignItemsEnd].join(' ')}>
<div className={[_s.default, _s.width240PX].join(' ')}> <div className={[_s.default, _s.width240PX].join(' ')}>
<div className={[_s.default, _s.positionFixed, _s.top0, _s.height100PC].join(' ')}> <div className={[_s.default, _s.positionFixed, _s.top0, _s.height100PC].join(' ')}>
<div className={[_s.default, _s.height100PC, _s.width240PX, _s.pr15, _s.py10, _s.overflowYScroll].join(' ')}> <div className={[_s.default, _s.height100PC, _s.alignItemsStart, _s.width240PX, _s.pr15, _s.py10, _s.overflowYScroll].join(' ')}>
<SidebarHeader /> <SidebarHeader />
@ -226,13 +228,23 @@ class Sidebar extends ImmutablePureComponent {
} }
</nav> </nav>
<Button <Responsive min={Constants.BREAKPOINT_SMALL}>
block <Button
onClick={this.handleOpenComposeModal} block
className={[_s.py15, _s.fontSize15PX, _s.fontWeightBold].join(' ')} onClick={this.handleOpenComposeModal}
> className={[_s.py15, _s.fontSize15PX, _s.fontWeightBold].join(' ')}
Gab >
</Button> Gab
</Button>
</Responsive>
<Responsive max={Constants.BREAKPOINT_SMALL}>
<Button
onClick={this.handleOpenComposeModal}
className={_s.py15}
icon='pencil'
/>
</Responsive>
</div> </div>
</div> </div>

View File

@ -387,6 +387,7 @@ class Status extends ImmutablePureComponent {
const containerClasses = cx({ const containerClasses = cx({
default: 1, default: 1,
radiusSmall: !borderless && !isChild,
mb15: !borderless && !isChild, mb15: !borderless && !isChild,
backgroundColorPrimary: 1, backgroundColorPrimary: 1,
pb15: featured, pb15: featured,

View File

@ -57,6 +57,7 @@ class TabBarItem extends PureComponent {
justifyContentCenter: 1, justifyContentCenter: 1,
borderBottom2PX: 1, borderBottom2PX: 1,
py5: 1, py5: 1,
outlineNone: 1,
cursorPointer: 1, cursorPointer: 1,
backgroundTransparent: 1, backgroundTransparent: 1,
borderColorTransparent: !isActive, borderColorTransparent: !isActive,

View File

@ -3,12 +3,12 @@ import { injectIntl, defineMessages } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { HotKeys } from 'react-hotkeys' import { HotKeys } from 'react-hotkeys'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import StatusContainer from '../../../../containers/status_container' import StatusContainer from '../../../containers/status_container'
import AccountContainer from '../../../../containers/account_container' import AccountContainer from '../../../containers/account_container'
import Avatar from '../../../../components/avatar' import Avatar from '../../../components/avatar'
import Icon from '../../../../components/icon' import Icon from '../../../components/icon'
import Text from '../../../../components/text' import Text from '../../../components/text'
import DisplayName from '../../../../components/display_name' import DisplayName from '../../../components/display_name'
const messages = defineMessages({ const messages = defineMessages({
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }, poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },

View File

@ -1 +0,0 @@
export { default } from './notification'

View File

@ -1,274 +0,0 @@
import { injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusContainer from '../../../../containers/status_container';
import AccountContainer from '../../../../containers/account_container';
import Button from '../../../../components/button'
import Icon from '../../../../components/icon';
const notificationForScreenReader = (intl, message, timestamp) => {
const output = [message];
output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
return output.join(', ');
};
export default
@injectIntl
class Notification extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
notification: ImmutablePropTypes.map.isRequired,
hidden: PropTypes.bool,
onMoveUp: PropTypes.func.isRequired,
onMoveDown: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onFavorite: PropTypes.func.isRequired,
onRepost: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
};
handleMoveUp = () => {
const { notification, onMoveUp } = this.props;
onMoveUp(notification.get('id'));
}
handleMoveDown = () => {
const { notification, onMoveDown } = this.props;
onMoveDown(notification.get('id'));
}
handleOpen = () => {
const { notification } = this.props;
if (notification.get('status')) {
this.context.router.history.push(`/${notification.getIn(['account', 'acct'])}/posts/${notification.get('status')}`);
} else {
this.handleOpenProfile();
}
}
handleOpenProfile = () => {
const { notification } = this.props;
this.context.router.history.push(`/${notification.getIn(['account', 'acct'])}`);
}
handleMention = e => {
e.preventDefault();
const { notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history);
}
handleHotkeyFavorite = () => {
const { status } = this.props;
if (status) this.props.onFavorite(status);
}
handleHotkeyBoost = e => {
const { status } = this.props;
if (status) this.props.onRepost(status, e);
}
handleHotkeyToggleHidden = () => {
const { status } = this.props;
if (status) this.props.onToggleHidden(status);
}
getHandlers() {
return {
reply: this.handleMention,
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleMention,
open: this.handleOpen,
openProfile: this.handleOpenProfile,
moveUp: this.handleMoveUp,
moveDown: this.handleMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
};
}
renderFollow(notification, account, link) {
const { intl } = this.props;
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__message'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='user-plus' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
</span>
</div>
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
);
}
renderMention(notification) {
return (
<StatusContainer
id={notification.get('status')}
withDismiss
hidden={this.props.hidden}
onMoveDown={this.handleMoveDown}
onMoveUp={this.handleMoveUp}
contextType='notifications'
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/>
);
}
renderFavorite(notification, link) {
const { intl } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--favorite focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.favorite', defaultMessage: '{name} favorited your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='star' className='star-icon' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.favorite' defaultMessage='{name} favorited your status' values={{ name: link }} />
</span>
</div>
{ /*
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={!!this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/> */ }
</div>
</HotKeys>
);
}
renderRepost(notification, link) {
const { intl } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<div className='notification notification--repost focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.repost', defaultMessage: '{name} reposted your status' }, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favorite-icon-wrapper'>
<Icon id='retweet' fixedWidth />
</div>
<span title={notification.get('created_at')}>
<FormattedMessage id='notification.repost' defaultMessage='{name} reposted your status' values={{ name: link }} />
</span>
</div>
{ /*
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/> */ }
</div>
</HotKeys>
);
}
renderPoll(notification) {
const { intl } = this.props;
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__message'>
<div className='notification__favorite-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' />
</span>
</div>
{ /*
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
muted
withDismiss
hidden={this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
/> */}
</div>
</HotKeys>
);
}
render() {
const { notification } = this.props;
const account = notification.get('account');
const displayNameHtml = { __html: account.get('display_name_html') };
const link = (
<bdi>
<Button
className='notification__display-name'
href={`/${account.get('acct')}`}
title={account.get('acct')}
to={`/${account.get('acct')}`}
dangerouslySetInnerHTML={displayNameHtml}
/>
</bdi>
);
// console.log("notification:", notification)
switch (notification.get('type')) {
// case 'follow':
// return this.renderFollow(notification, account, link);
// case 'mention':
// return this.renderMention(notification);
case 'favourite':
return this.renderFavorite(notification, link);
// case 'reblog':
// return this.renderRepost(notification, link);
// case 'poll':
// return this.renderPoll(notification);
}
return null;
}
}

View File

@ -1,43 +0,0 @@
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 { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { openModal } from '../../../actions/modal';
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' },
});
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'),
});
// : todo : put all notification settings actually IN settings
const mapDispatchToProps = (dispatch, { intl }) => ({
onChange (path, checked) {
if (path[0] === 'push') {
dispatch(changePushNotifications(path.slice(1), checked));
} else if (path[0] === 'quickFilter') {
dispatch(changeSetting(['notifications', ...path], checked));
dispatch(setFilter('all'));
} else {
dispatch(changeSetting(['notifications', ...path], checked));
}
},
onClear () {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.clearMessage),
confirm: intl.formatMessage(messages.clearConfirm),
onConfirm: () => dispatch(clearNotifications()),
}));
},
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings));

View File

@ -13,7 +13,7 @@ import {
} from '../../../actions/statuses' } from '../../../actions/statuses'
import { boostModal } from '../../../initial_state' import { boostModal } from '../../../initial_state'
import { makeGetNotification } from '../../../selectors' import { makeGetNotification } from '../../../selectors'
import Notification from '../components/notification/notification-alt' import Notification from '../components/notification'
const getAccountFromState = (state, accountId) => { const getAccountFromState = (state, accountId) => {
return state.getIn(['accounts', accountId]) return state.getIn(['accounts', accountId])

View File

@ -17,12 +17,11 @@ import TimelineQueueButtonHeader from '../../components/timeline_queue_button_h
import Block from '../../components/block' import Block from '../../components/block'
const getNotifications = createSelector([ const getNotifications = createSelector([
state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']), state => state.getIn(['notifications', 'filter', 'active']),
state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()), state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
state => state.getIn(['notifications', 'items']), state => state.getIn(['notifications', 'items']),
], (showFilterBar, allowedType, excludedTypes, notifications) => { ], (allowedType, excludedTypes, notifications) => {
if (!showFilterBar || allowedType === 'all') { if (allowedType === 'all') {
// used if user changed the notification settings after loading the notifications from the server // used if user changed the notification settings after loading the notifications from the server
// otherwise a list of notifications will come pre-filtered from the backend // otherwise a list of notifications will come pre-filtered from the backend
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
@ -32,7 +31,6 @@ const getNotifications = createSelector([
}) })
const mapStateToProps = state => ({ const mapStateToProps = state => ({
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
notifications: getNotifications(state), notifications: getNotifications(state),
isLoading: state.getIn(['notifications', 'isLoading'], true), isLoading: state.getIn(['notifications', 'isLoading'], true),
isUnread: state.getIn(['notifications', 'unread']) > 0, isUnread: state.getIn(['notifications', 'unread']) > 0,
@ -47,7 +45,6 @@ class Notifications extends ImmutablePureComponent {
static propTypes = { static propTypes = {
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -125,7 +122,6 @@ class Notifications extends ImmutablePureComponent {
isLoading, isLoading,
isUnread, isUnread,
hasMore, hasMore,
showFilterBar,
totalQueuedNotificationsCount totalQueuedNotificationsCount
} = this.props } = this.props

View File

@ -41,8 +41,6 @@ export default class Responsive extends PureComponent {
const shouldRender = this.shouldRender(min, max, width) const shouldRender = this.shouldRender(min, max, width)
console.log("shouldRender:", min, max, width, shouldRender)
return shouldRender ? children : null return shouldRender ? children : null
} }
} }

View File

@ -18,13 +18,12 @@ const messages = defineMessages({
}); });
const makeMapStateToProps = state => ({ const makeMapStateToProps = state => ({
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']), selectedFilter: state.getIn(['notifications', 'filter', 'active']),
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
selectFilter(newActiveFilter) { selectFilter(value) {
console.log("newActiveFilter:", newActiveFilter) dispatch(setFilter('active', value))
dispatch(setFilter(newActiveFilter))
}, },
}); });
@ -33,6 +32,10 @@ export default
@connect(makeMapStateToProps, mapDispatchToProps) @connect(makeMapStateToProps, mapDispatchToProps)
class NotificationsPage extends PureComponent { class NotificationsPage extends PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
}
static propTypes = { static propTypes = {
selectFilter: PropTypes.func.isRequired, selectFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired, selectedFilter: PropTypes.string.isRequired,
@ -43,8 +46,16 @@ class NotificationsPage extends PureComponent {
document.title = 'Notifications - Gab' document.title = 'Notifications - Gab'
} }
// : todo : on pop change filter active type
onClick(notificationType) { onClick(notificationType) {
this.props.selectFilter(notificationType); this.props.selectFilter(notificationType)
if (notificationType === 'all') {
this.context.router.history.push('/notifications')
} else {
this.context.router.history.push(`/notifications?only=${notificationType}`)
}
} }
render() { render() {

View File

@ -30,6 +30,11 @@ const initialState = ImmutableMap({
queuedNotifications: ImmutableList(), //max = MAX_QUEUED_NOTIFICATIONS queuedNotifications: ImmutableList(), //max = MAX_QUEUED_NOTIFICATIONS
totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+ totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+
lastRead: -1, lastRead: -1,
filter: ImmutableMap({
active: 'all',
onlyVerified: false,
onlyFollowing: false,
}),
}); });
const notificationToMap = notification => ImmutableMap({ const notificationToMap = notification => ImmutableMap({
@ -64,7 +69,7 @@ const expandNormalizedNotifications = (state, notifications, next) => {
let items = ImmutableList() let items = ImmutableList()
console.log("notifications:", notificationss) console.log("notifications:", notifications)
notifications.forEach((n) => { notifications.forEach((n) => {
const notification = notificationToMap(n) const notification = notificationToMap(n)
@ -211,7 +216,10 @@ export default function notifications(state = initialState, action) {
case NOTIFICATIONS_EXPAND_FAIL: case NOTIFICATIONS_EXPAND_FAIL:
return state.set('isLoading', false); return state.set('isLoading', false);
case NOTIFICATIONS_FILTER_SET: case NOTIFICATIONS_FILTER_SET:
return state.set('items', ImmutableList()).set('hasMore', true); return state.withMutations(mutable => {
mutable.set('items', ImmutableList()).set('hasMore', true)
mutable.setIn(['filter', action.path], action.value)
})
case NOTIFICATIONS_SCROLL_TOP: case NOTIFICATIONS_SCROLL_TOP:
return updateTop(state, action.top); return updateTop(state, action.top);
case NOTIFICATIONS_UPDATE: case NOTIFICATIONS_UPDATE:

View File

@ -1,5 +1,4 @@
import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings'; import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications';
import { STORE_HYDRATE } from '../actions/store'; import { STORE_HYDRATE } from '../actions/store';
import { EMOJI_USE } from '../actions/emojis'; import { EMOJI_USE } from '../actions/emojis';
import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists'; import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists';
@ -43,15 +42,6 @@ const initialState = ImmutableMap({
}), }),
}), }),
notifications: ImmutableMap({
// : todo : put all notification settings actually IN settings
quickFilter: ImmutableMap({
active: 'all',
onlyVerifed: false,
onlyFollowing: false,
}),
}),
community: ImmutableMap({ community: ImmutableMap({
shows: ImmutableMap({ shows: ImmutableMap({
inSidebar: false, inSidebar: false,
@ -76,7 +66,6 @@ export default function settings(state = initialState, action) {
switch(action.type) { switch(action.type) {
case STORE_HYDRATE: case STORE_HYDRATE:
return hydrate(state, action.state.get('settings')); return hydrate(state, action.state.get('settings'));
case NOTIFICATIONS_FILTER_SET:
case SETTING_CHANGE: case SETTING_CHANGE:
return state return state
.setIn(action.path, action.value) .setIn(action.path, action.value)

View File

@ -19,15 +19,15 @@ export function breakpointExtraLarge(width) {
} }
export function breakpointLarge(width) { export function breakpointLarge(width) {
return width > BREAKPOINT_MEDIUM && width < BREAKPOINT_LARGE return width < BREAKPOINT_LARGE
} }
export function breakpointMedium(width) { export function breakpointMedium(width) {
return width > BREAKPOINT_SMALL && width < BREAKPOINT_MEDIUM return width < BREAKPOINT_MEDIUM
} }
export function breakpointSmall(width) { export function breakpointSmall(width) {
return width > BREAKPOINT_EXTRA_SMALL && width < BREAKPOINT_SMALL return width < BREAKPOINT_SMALL
} }
export function breakpointExtraSmall(width) { export function breakpointExtraSmall(width) {

View File

@ -55,7 +55,7 @@ body {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
flex-shrink: 0; flex-shrink: 0;
border: 0 solid black; border: 0 solid #2d3436;
z-index: 0; z-index: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -313,7 +313,7 @@ body {
} }
.backgroundColorBlack { .backgroundColorBlack {
background-color: #3B3B3B; background-color: #2d3436;
} }
.backgroundColorBlackOpaque_onHover:hover { .backgroundColorBlackOpaque_onHover:hover {
@ -353,7 +353,7 @@ body {
} }
.colorPrimary { .colorPrimary {
color: #000; color: #2d3436;
} }
.colorWhite { .colorWhite {
@ -381,7 +381,7 @@ body {
} }
.fillColorBlack { .fillColorBlack {
fill: #000; fill: #2d3436;
} }
.fillColorWhite { .fillColorWhite {