Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
commit
37be893d12
|
@ -1,16 +0,0 @@
|
|||
import { saveSettings } from './settings';
|
||||
|
||||
export const COLUMN_PARAMS_CHANGE = 'COLUMN_PARAMS_CHANGE';
|
||||
|
||||
export function changeColumnParams(uuid, path, value) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: COLUMN_PARAMS_CHANGE,
|
||||
uuid,
|
||||
path,
|
||||
value,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
}
|
|
@ -6,7 +6,7 @@ import { tagHistory } from '../settings';
|
|||
import { useEmoji } from './emojis';
|
||||
import resizeImage from '../utils/resize_image';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { updateTimeline } from './timelines';
|
||||
import { updateTimeline, dequeueTimeline } from './timelines';
|
||||
import { showAlertForError } from './alerts';
|
||||
import { showAlert } from './alerts';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
@ -168,6 +168,10 @@ export function submitCompose(routerHistory) {
|
|||
const timeline = getState().getIn(['timelines', timelineId]);
|
||||
|
||||
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
|
||||
let dequeueArgs = {};
|
||||
if (timelineId === 'community') dequeueArgs.onlyMedia = getState().getIn(['settings', 'community', 'other', 'onlyMedia']),
|
||||
|
||||
dispatch(dequeueTimeline(timelineId, null, dequeueArgs));
|
||||
dispatch(updateTimeline(timelineId, { ...response.data }));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,6 +16,8 @@ import { me } from 'gabsocial/initial_state';
|
|||
|
||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
||||
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
||||
export const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE';
|
||||
export const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE';
|
||||
|
||||
export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
|
||||
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
|
||||
|
@ -26,6 +28,8 @@ export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
|
|||
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
|
||||
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
|
||||
|
||||
export const MAX_QUEUED_NOTIFICATIONS = 40;
|
||||
|
||||
defineMessages({
|
||||
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
||||
group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
|
||||
|
@ -42,18 +46,6 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
|
|||
export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||
return (dispatch, getState) => {
|
||||
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
|
||||
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
|
||||
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
|
||||
const filters = getFilters(getState(), { contextType: 'notifications' });
|
||||
|
||||
let filtered = false;
|
||||
|
||||
if (notification.type === 'mention') {
|
||||
const regex = regexFromFilters(filters);
|
||||
const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
|
||||
|
||||
filtered = regex && regex.test(searchIndex);
|
||||
}
|
||||
|
||||
if (showInColumn) {
|
||||
dispatch(importFetchedAccount(notification.account));
|
||||
|
@ -65,21 +57,33 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
|||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE,
|
||||
notification,
|
||||
meta: (playSound && !filtered) ? { sound: 'ribbit' } : undefined,
|
||||
});
|
||||
|
||||
fetchRelatedRelationships(dispatch, [notification]);
|
||||
} else if (playSound && !filtered) {
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE_NOOP,
|
||||
meta: { sound: 'ribbit' },
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function updateNotificationsQueue(notification, intlMessages, intlLocale, curPath) {
|
||||
return (dispatch, getState) => {
|
||||
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
|
||||
const filters = getFilters(getState(), { contextType: 'notifications' });
|
||||
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
|
||||
|
||||
let filtered = false;
|
||||
|
||||
const isOnNotificationsPage = curPath === '/notifications';
|
||||
|
||||
if (notification.type === 'mention') {
|
||||
const regex = regexFromFilters(filters);
|
||||
const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
|
||||
filtered = regex && regex.test(searchIndex);
|
||||
}
|
||||
|
||||
// Desktop notifications
|
||||
if (typeof window.Notification !== 'undefined' && showAlert && !filtered) {
|
||||
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 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 });
|
||||
|
||||
|
@ -88,7 +92,49 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
|||
notify.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (playSound && !filtered) {
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE_NOOP,
|
||||
meta: { sound: 'ribbit' },
|
||||
});
|
||||
}
|
||||
|
||||
if (isOnNotificationsPage) {
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE_QUEUE,
|
||||
notification,
|
||||
intlMessages,
|
||||
intlLocale,
|
||||
});
|
||||
}
|
||||
else {
|
||||
dispatch(updateNotifications(notification, intlMessages, intlLocale));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function dequeueNotifications() {
|
||||
return (dispatch, getState) => {
|
||||
const queuedNotifications = getState().getIn(['notifications', 'queuedNotifications'], ImmutableList());
|
||||
const totalQueuedNotificationsCount = getState().getIn(['notifications', 'totalQueuedNotificationsCount'], 0);
|
||||
|
||||
if (totalQueuedNotificationsCount == 0) {
|
||||
return;
|
||||
}
|
||||
else if (totalQueuedNotificationsCount > 0 && totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||
queuedNotifications.forEach(block => {
|
||||
dispatch(updateNotifications(block.notification, block.intlMessages, block.intlLocale));
|
||||
});
|
||||
}
|
||||
else {
|
||||
dispatch(expandNotifications());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_DEQUEUE,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||
|
@ -169,7 +215,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
|
|||
export function clearNotifications() {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return;
|
||||
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_CLEAR,
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { connectStream } from '../stream';
|
||||
import {
|
||||
updateTimeline,
|
||||
deleteFromTimelines,
|
||||
expandHomeTimeline,
|
||||
connectTimeline,
|
||||
disconnectTimeline,
|
||||
updateTimelineQueue,
|
||||
} from './timelines';
|
||||
import { updateNotifications, expandNotifications } from './notifications';
|
||||
import { updateNotificationsQueue, expandNotifications } from './notifications';
|
||||
import { updateConversations } from './conversations';
|
||||
import { fetchFilters } from './filters';
|
||||
import { getLocale } from '../locales';
|
||||
|
@ -30,13 +30,13 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
|
|||
onReceive (data) {
|
||||
switch(data.event) {
|
||||
case 'update':
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), accept));
|
||||
dispatch(updateTimelineQueue(timelineId, JSON.parse(data.payload), accept));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
|
||||
dispatch(updateNotificationsQueue(JSON.parse(data.payload), messages, locale, window.location.pathname));
|
||||
break;
|
||||
case 'conversation':
|
||||
dispatch(updateConversations(JSON.parse(data.payload)));
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import api, { getLinks } from '../api';
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable';
|
||||
|
||||
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
||||
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
||||
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
|
||||
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE';
|
||||
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE';
|
||||
|
||||
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
|
||||
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
|
||||
|
@ -13,6 +15,8 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
|
|||
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
|
||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
|
||||
|
||||
export const MAX_QUEUED_ITEMS = 40;
|
||||
|
||||
export function updateTimeline(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
|
@ -29,6 +33,64 @@ export function updateTimeline(timeline, status, accept) {
|
|||
};
|
||||
};
|
||||
|
||||
export function updateTimelineQueue(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE_QUEUE,
|
||||
timeline,
|
||||
status,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) {
|
||||
return (dispatch, getState) => {
|
||||
const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList());
|
||||
const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0);
|
||||
|
||||
let shouldDispatchDequeue = true;
|
||||
|
||||
if (totalQueuedItemsCount == 0) {
|
||||
return;
|
||||
}
|
||||
else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
|
||||
queuedItems.forEach(status => {
|
||||
dispatch(updateTimeline(timeline, status.toJS(), null));
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (typeof expandFunc === 'function') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
expandFunc();
|
||||
}
|
||||
else {
|
||||
if (timeline === 'home') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandHomeTimeline(optionalExpandArgs));
|
||||
}
|
||||
else if (timeline === 'community') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandCommunityTimeline(optionalExpandArgs));
|
||||
}
|
||||
else {
|
||||
shouldDispatchDequeue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldDispatchDequeue) return;
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_DEQUEUE,
|
||||
timeline,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function deleteFromTimelines(id) {
|
||||
return (dispatch, getState) => {
|
||||
const accountId = getState().getIn(['statuses', id, 'account']);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { ScrollContainer } from 'react-router-scroll-4';
|
||||
import PropTypes from 'prop-types';
|
||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
||||
import LoadMore from './load_more';
|
||||
|
|
|
@ -7,6 +7,7 @@ import StatusContainer from '../containers/status_container';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import LoadGap from './load_gap';
|
||||
import ScrollableList from './scrollable_list';
|
||||
import TimelineQueueButtonHeader from './timeline_queue_button_header';
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
|
@ -22,6 +23,12 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
emptyMessage: PropTypes.node,
|
||||
alwaysPrepend: PropTypes.bool,
|
||||
timelineId: PropTypes.string,
|
||||
queuedItemSize: PropTypes.number,
|
||||
onDequeueTimeline: PropTypes.func,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.handleDequeueTimeline();
|
||||
};
|
||||
|
||||
getFeaturedStatusCount = () => {
|
||||
|
@ -64,13 +71,18 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleDequeueTimeline = () => {
|
||||
const { onDequeueTimeline, timelineId } = this.props;
|
||||
if (!onDequeueTimeline || !timelineId) return;
|
||||
onDequeueTimeline(timelineId);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other } = this.props;
|
||||
const { isLoading, isPartial } = other;
|
||||
const { statusIds, featuredStatusIds, onLoadMore, timelineId, totalQueuedItemsCount, isLoading, isPartial, ...other } = this.props;
|
||||
|
||||
if (isPartial) {
|
||||
return (
|
||||
|
@ -119,11 +131,12 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
)).concat(scrollableContent);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollableList {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
|
||||
return [
|
||||
<TimelineQueueButtonHeader key='timeline-queue-button-header' onClick={this.handleDequeueTimeline} count={totalQueuedItemsCount} itemType='gab' />,
|
||||
<ScrollableList key='scrollable-list' {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { shortNumberFormat } from '../utils/numbers';
|
||||
|
||||
export default class TimelineQueueButtonHeader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
count: PropTypes.number,
|
||||
itemType: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
count: 0,
|
||||
itemType: 'item',
|
||||
};
|
||||
|
||||
render () {
|
||||
const { count, itemType, onClick } = this.props;
|
||||
|
||||
if (count <= 0) return null;
|
||||
|
||||
return (
|
||||
<div className='timeline-queue-header'>
|
||||
<a className='timeline-queue-header__btn' onClick={onClick}>
|
||||
<FormattedMessage
|
||||
id='timeline_queue.label'
|
||||
defaultMessage='Click to see {count} new {type}'
|
||||
values={{
|
||||
count: shortNumberFormat(count),
|
||||
type: count == 1 ? itemType : `${itemType}s`,
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ const messages = defineMessages({
|
|||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
|
@ -311,7 +311,7 @@ class Header extends ImmutablePureComponent {
|
|||
{actionBtn}
|
||||
{account.get('id') !== me &&
|
||||
<Button className='button button-alternative-2' onClick={this.props.onMention}>
|
||||
<FormattedMessage id='account.mention' defaultMessage='Mention @{name}' values={{
|
||||
<FormattedMessage id='account.mention' defaultMessage='Mention' values={{
|
||||
name: account.get('acct')
|
||||
}} />
|
||||
</Button>
|
||||
|
|
|
@ -212,6 +212,13 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
|
||||
))}
|
||||
|
||||
{
|
||||
attachments.size == 0 &&
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.'/>
|
||||
</div>
|
||||
}
|
||||
|
||||
{loadOlder}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.accountId));
|
||||
}
|
||||
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.accountId, { withReplies: nextProps.params.withReplies }));
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.accountId, { withReplies: nextProps.withReplies }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ class ColumnSettings extends React.PureComponent {
|
|||
settings: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
};
|
||||
|
||||
render () {
|
||||
|
@ -21,6 +20,7 @@ class ColumnSettings extends React.PureComponent {
|
|||
<div>
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} />
|
||||
<SettingToggle settings={settings} settingPath={['other', 'allFediverse']} onChange={onChange} label={<FormattedMessage id='community.column_settings.all_fediverse' defaultMessage='All Fediverse' />} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
import { changeColumnParams } from '../../../actions/columns';
|
||||
|
||||
const mapStateToProps = (state, { columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
const mapStateToProps = state => ({
|
||||
settings: state.getIn(['settings', 'community']),
|
||||
});
|
||||
|
||||
return {
|
||||
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { columnId }) => {
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
onChange (key, checked) {
|
||||
if (columnId) {
|
||||
dispatch(changeColumnParams(columnId, key, checked));
|
||||
} else {
|
||||
dispatch(changeSetting(['community', ...key], checked));
|
||||
}
|
||||
dispatch(changeSetting(['community', ...key], checked));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,23 +4,32 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import { expandCommunityTimeline } from '../../actions/timelines';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { connectCommunityStream } from '../../actions/streaming';
|
||||
import HomeColumnHeader from '../../components/home_column_header';
|
||||
import {
|
||||
expandCommunityTimeline,
|
||||
expandPublicTimeline,
|
||||
} from '../../actions/timelines';
|
||||
import {
|
||||
connectCommunityStream,
|
||||
connectPublicStream,
|
||||
} from '../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.community', defaultMessage: 'Local timeline' },
|
||||
title: { id: 'column.community', defaultMessage: 'Community timeline' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { onlyMedia, columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
const mapStateToProps = state => {
|
||||
const allFediverse = state.getIn(['settings', 'community', 'other', 'allFediverse']);
|
||||
const onlyMedia = state.getIn(['settings', 'community', 'other', 'onlyMedia']);
|
||||
|
||||
const timelineId = allFediverse ? 'public' : 'community';
|
||||
|
||||
return {
|
||||
hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
|
||||
onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
|
||||
timelineId,
|
||||
allFediverse,
|
||||
onlyMedia,
|
||||
hasUnread: state.getIn(['timelines', `${timelineId}${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -34,30 +43,45 @@ class CommunityTimeline extends React.PureComponent {
|
|||
|
||||
static defaultProps = {
|
||||
onlyMedia: false,
|
||||
allFediverse: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
onlyMedia: PropTypes.bool,
|
||||
allFediverse: PropTypes.bool,
|
||||
timelineId: PropTypes.string,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
const { dispatch, onlyMedia, allFediverse } = this.props;
|
||||
|
||||
dispatch(expandCommunityTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
|
||||
if (allFediverse) {
|
||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
||||
}
|
||||
else {
|
||||
dispatch(expandCommunityTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.onlyMedia !== this.props.onlyMedia) {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.allFediverse !== this.props.allFediverse) {
|
||||
const { dispatch, onlyMedia, allFediverse } = this.props;
|
||||
|
||||
this.disconnect();
|
||||
dispatch(expandCommunityTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
|
||||
|
||||
if (allFediverse) {
|
||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
||||
}
|
||||
else {
|
||||
dispatch(expandCommunityTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,27 +93,29 @@ class CommunityTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
const { dispatch, onlyMedia, allFediverse } = this.props;
|
||||
|
||||
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
|
||||
if (allFediverse) {
|
||||
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
|
||||
}
|
||||
else {
|
||||
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, onlyMedia } = this.props;
|
||||
const { intl, hasUnread, onlyMedia, timelineId, allFediverse } = this.props;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
<HomeColumnHeader
|
||||
activeItem='all'
|
||||
active={hasUnread}
|
||||
>
|
||||
<HomeColumnHeader activeItem='all' active={hasUnread} >
|
||||
<ColumnSettingsContainer />
|
||||
</HomeColumnHeader>
|
||||
<StatusListContainer
|
||||
scrollKey={`community_timeline-${columnId}`}
|
||||
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
||||
scrollKey={`${timelineId}_timeline`}
|
||||
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
|
||||
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The community timeline is empty. Write something publicly to get the ball rolling!' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -68,6 +68,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
anyMedia: PropTypes.bool,
|
||||
shouldCondense: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
isModalOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -150,6 +151,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.autosuggestTextarea) return;
|
||||
|
||||
// This statement does several things:
|
||||
// - If we're beginning a reply, and,
|
||||
// - Replying to zero or one users, places the cursor at the end of the textbox.
|
||||
|
@ -203,7 +206,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, showSearch, anyMedia, shouldCondense, autoFocus } = this.props;
|
||||
const { intl, onPaste, showSearch, anyMedia, shouldCondense, autoFocus, isModalOpen } = this.props;
|
||||
const condensed = shouldCondense && !this.props.text && !this.state.composeFocused;
|
||||
const disabled = this.props.isSubmitting;
|
||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||
|
@ -252,7 +255,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<AutosuggestTextarea
|
||||
ref={this.setAutosuggestTextarea}
|
||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
disabled={disabled}
|
||||
value={this.props.text}
|
||||
|
|
|
@ -25,6 +25,7 @@ const mapStateToProps = state => ({
|
|||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
|
|
@ -18,7 +18,6 @@ class DirectTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
};
|
||||
|
@ -45,14 +44,14 @@ class DirectTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId } = this.props;
|
||||
const { intl, hasUnread } = this.props;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader icon='envelope' active={hasUnread} title={intl.formatMessage(messages.title)} />
|
||||
|
||||
<ConversationsListContainer
|
||||
scrollKey={`direct_timeline-${columnId}`}
|
||||
scrollKey='direct_timeline'
|
||||
timelineId='direct'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
|
|
|
@ -29,7 +29,6 @@ class Favourites extends ImmutablePureComponent {
|
|||
dispatch: PropTypes.func.isRequired,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
isMyAccount: PropTypes.bool.isRequired,
|
||||
|
@ -44,7 +43,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, statusIds, columnId, hasMore, isLoading, isMyAccount } = this.props;
|
||||
const { intl, statusIds, hasMore, isLoading, isMyAccount } = this.props;
|
||||
|
||||
if (!isMyAccount) {
|
||||
return (
|
||||
|
@ -60,7 +59,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
<Column>
|
||||
<StatusList
|
||||
statusIds={statusIds}
|
||||
scrollKey={`favourited_statuses-${columnId}`}
|
||||
scrollKey='favourited_statuses'
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -30,7 +30,6 @@ class GroupTimeline extends React.PureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -59,7 +58,7 @@ class GroupTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, group } = this.props;
|
||||
const { hasUnread, group } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const title = group ? group.get('title') : id;
|
||||
|
||||
|
@ -93,7 +92,7 @@ class GroupTimeline extends React.PureComponent {
|
|||
<StatusListContainer
|
||||
prepend={<HeaderContainer groupId={id} />}
|
||||
alwaysPrepend
|
||||
scrollKey={`group_timeline-${columnId}`}
|
||||
scrollKey='group_timeline'
|
||||
timelineId={`group:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.group' defaultMessage='There is nothing in this group yet. When members of this group post new statuses, they will appear here.' />}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Toggle from 'react-toggle';
|
||||
import AsyncSelect from 'react-select/lib/Async';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
|
||||
noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onLoad: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: this.hasTags(),
|
||||
};
|
||||
|
||||
hasTags () {
|
||||
return ['all', 'any', 'none'].map(mode => this.tags(mode).length > 0).includes(true);
|
||||
}
|
||||
|
||||
tags (mode) {
|
||||
let tags = this.props.settings.getIn(['tags', mode]) || [];
|
||||
|
||||
if (tags.toJSON) {
|
||||
return tags.toJSON();
|
||||
} else {
|
||||
return tags;
|
||||
}
|
||||
};
|
||||
|
||||
onSelect = mode => value => this.props.onChange(['tags', mode], value);
|
||||
|
||||
onToggle = () => {
|
||||
if (this.state.open && this.hasTags()) {
|
||||
this.props.onChange('tags', {});
|
||||
}
|
||||
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
|
||||
|
||||
modeSelect (mode) {
|
||||
return (
|
||||
<div className='column-settings__row'>
|
||||
<span className='column-settings__section'>
|
||||
{this.modeLabel(mode)}
|
||||
</span>
|
||||
|
||||
<AsyncSelect
|
||||
isMulti
|
||||
autoFocus
|
||||
value={this.tags(mode)}
|
||||
onChange={this.onSelect(mode)}
|
||||
loadOptions={this.props.onLoad}
|
||||
className='column-select__container'
|
||||
classNamePrefix='column-select'
|
||||
name='tags'
|
||||
placeholder={this.props.intl.formatMessage(messages.placeholder)}
|
||||
noOptionsMessage={this.noOptionsMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
modeLabel (mode) {
|
||||
switch(mode) {
|
||||
case 'any':
|
||||
return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
|
||||
case 'all':
|
||||
return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
|
||||
case 'none':
|
||||
return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className='column-settings__row'>
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} />
|
||||
|
||||
<span className='setting-toggle__label'>
|
||||
<FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.open && (
|
||||
<div className='column-settings__hashtags'>
|
||||
{this.modeSelect('any')}
|
||||
{this.modeSelect('all')}
|
||||
{this.modeSelect('none')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeColumnParams } from '../../../actions/columns';
|
||||
import api from '../../../api';
|
||||
|
||||
const mapStateToProps = (state, { columnId }) => {
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === columnId);
|
||||
|
||||
if (!(columnId && index >= 0)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { settings: columns.get(index).get('params') };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { columnId }) => ({
|
||||
onChange (key, value) {
|
||||
dispatch(changeColumnParams(columnId, key, value));
|
||||
},
|
||||
|
||||
onLoad (value) {
|
||||
return api().get('/api/v2/search', { params: { q: value } }).then(response => {
|
||||
return (response.data.hashtags || []).map((tag) => {
|
||||
return { value: tag.name, label: `#${tag.name}` };
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connectHashtagStream } from '../../actions/streaming';
|
||||
|
@ -21,7 +20,6 @@ class HashtagTimeline extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
};
|
||||
|
@ -104,17 +102,14 @@ class HashtagTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId } = this.props;
|
||||
const { hasUnread } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
return (
|
||||
<Column label={`#${id}`}>
|
||||
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()}>
|
||||
{columnId && <ColumnSettingsContainer columnId={columnId} />}
|
||||
</ColumnHeader>
|
||||
|
||||
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()} />
|
||||
<StatusListContainer
|
||||
scrollKey={`hashtag_timeline-${columnId}`}
|
||||
scrollKey='hashtag_timeline'
|
||||
timelineId={`hashtag:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
|
||||
|
|
|
@ -26,7 +26,6 @@ class HomeTimeline extends React.PureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
columnId: PropTypes.string,
|
||||
};
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
|
@ -67,7 +66,7 @@ class HomeTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId } = this.props;
|
||||
const { intl, hasUnread } = this.props;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
|
@ -78,7 +77,7 @@ class HomeTimeline extends React.PureComponent {
|
|||
<ColumnSettingsContainer />
|
||||
</HomeColumnHeader>
|
||||
<StatusListContainer
|
||||
scrollKey={`home_timeline-${columnId}`}
|
||||
scrollKey='home_timeline'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty. Start following other users to recieve their content here.'/>}
|
||||
|
|
|
@ -36,7 +36,6 @@ class ListTimeline extends React.PureComponent {
|
|||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -69,7 +68,7 @@ class ListTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
const { dispatch, columnId, intl } = this.props;
|
||||
const { dispatch, intl } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
dispatch(openModal('CONFIRM', {
|
||||
|
@ -77,18 +76,13 @@ class ListTimeline extends React.PureComponent {
|
|||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => {
|
||||
dispatch(deleteList(id));
|
||||
|
||||
if (!!columnId) {
|
||||
//
|
||||
} else {
|
||||
this.context.router.history.push('/lists');
|
||||
}
|
||||
this.context.router.history.push('/lists');
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, list } = this.props;
|
||||
const { hasUnread, list } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const title = list ? list.get('title') : id;
|
||||
|
||||
|
@ -126,7 +120,7 @@ class ListTimeline extends React.PureComponent {
|
|||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
scrollKey={`list_timeline-${columnId}`}
|
||||
scrollKey='list_timeline'
|
||||
timelineId={`list:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
||||
|
|
|
@ -4,7 +4,11 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
|
||||
import {
|
||||
expandNotifications,
|
||||
scrollTopNotifications,
|
||||
dequeueNotifications,
|
||||
} from '../../actions/notifications';
|
||||
import NotificationContainer from './containers/notification_container';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
|
@ -14,6 +18,7 @@ import { List as ImmutableList } from 'immutable';
|
|||
import { debounce } from 'lodash';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import LoadGap from '../../components/load_gap';
|
||||
import TimelineQueueButtonHeader from '../../components/timeline_queue_button_header';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||
|
@ -40,6 +45,7 @@ const mapStateToProps = state => ({
|
|||
isLoading: state.getIn(['notifications', 'isLoading'], true),
|
||||
isUnread: state.getIn(['notifications', 'unread']) > 0,
|
||||
hasMore: state.getIn(['notifications', 'hasMore']),
|
||||
totalQueuedNotificationsCount: state.getIn(['notifications', 'totalQueuedNotificationsCount'], 0),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
|
@ -47,7 +53,6 @@ export default @connect(mapStateToProps)
|
|||
class Notifications extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
columnId: PropTypes.string,
|
||||
notifications: ImmutablePropTypes.list.isRequired,
|
||||
showFilterBar: PropTypes.bool.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
|
@ -55,6 +60,8 @@ class Notifications extends React.PureComponent {
|
|||
isLoading: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
dequeueNotifications: PropTypes.func,
|
||||
totalQueuedNotificationsCount: PropTypes.number,
|
||||
};
|
||||
|
||||
componentWillUnmount () {
|
||||
|
@ -65,6 +72,7 @@ class Notifications extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.handleDequeueNotifications();
|
||||
this.props.dispatch(scrollTopNotifications(true));
|
||||
}
|
||||
|
||||
|
@ -113,8 +121,12 @@ class Notifications extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleDequeueNotifications = () => {
|
||||
this.props.dispatch(dequeueNotifications());
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, notifications, isLoading, isUnread, columnId, hasMore, showFilterBar } = this.props;
|
||||
const { intl, notifications, isLoading, isUnread, hasMore, showFilterBar, totalQueuedNotificationsCount } = this.props;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
|
||||
|
||||
let scrollableContent = null;
|
||||
|
@ -150,7 +162,7 @@ class Notifications extends React.PureComponent {
|
|||
|
||||
const scrollContainer = (
|
||||
<ScrollableList
|
||||
scrollKey={`notifications-${columnId}`}
|
||||
scrollKey='notifications'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && notifications.size === 0}
|
||||
hasMore={hasMore}
|
||||
|
@ -169,6 +181,7 @@ class Notifications extends React.PureComponent {
|
|||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
{filterBarContainer}
|
||||
<TimelineQueueButtonHeader onClick={this.handleDequeueNotifications} count={totalQueuedNotificationsCount} itemType='notification' />
|
||||
{scrollContainer}
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
|
||||
import Column from '../ui/components/column';
|
||||
import StatusList from '../../components/status_list';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { meUsername } from 'gabsocial/initial_state';
|
||||
import MissingIndicator from 'gabsocial/components/missing_indicator';
|
||||
|
@ -51,6 +51,7 @@ class PinnedStatuses extends ImmutablePureComponent {
|
|||
statusIds={statusIds}
|
||||
scrollKey='pinned_statuses'
|
||||
hasMore={hasMore}
|
||||
emptyMessage={<FormattedMessage id='pinned_statuses.none' defaultMessage='No pins to show.'/>}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ColumnSettings from '../../community_timeline/components/column_settings';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
import { changeColumnParams } from '../../../actions/columns';
|
||||
|
||||
const mapStateToProps = (state, { columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
|
||||
return {
|
||||
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'public']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { columnId }) => {
|
||||
return {
|
||||
onChange (key, checked) {
|
||||
if (columnId) {
|
||||
dispatch(changeColumnParams(columnId, key, checked));
|
||||
} else {
|
||||
dispatch(changeSetting(['public', ...key], checked));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
|
|
@ -1,96 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { expandPublicTimeline } from '../../actions/timelines';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import { connectPublicStream } from '../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { onlyMedia, columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
|
||||
return {
|
||||
hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
|
||||
onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']),
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class PublicTimeline extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onlyMedia: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
onlyMedia: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
|
||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.onlyMedia !== this.props.onlyMedia) {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
|
||||
this.disconnect();
|
||||
dispatch(expandPublicTimeline({ onlyMedia }));
|
||||
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { dispatch, onlyMedia } = this.props;
|
||||
|
||||
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, onlyMedia } = this.props;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader icon='globe' active={hasUnread} title={intl.formatMessage(messages.title)}>
|
||||
<ColumnSettingsContainer columnId={columnId} />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
timelineId={`public${onlyMedia ? ':media' : ''}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
scrollKey={`public_timeline-${columnId}`}
|
||||
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,7 @@ import BundleContainer from '../containers/bundle_container';
|
|||
import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import BundleColumnError from './bundle_column_error';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
|
||||
import Icon from 'gabsocial/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -6,7 +6,6 @@ import { me } from '../../../initial_state';
|
|||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ComposeFormContainer from '../../compose/containers/compose_form_container';
|
||||
import IconButton from 'gabsocial/components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
|||
import { createSelector } from 'reselect';
|
||||
import { debounce } from 'lodash';
|
||||
import { me } from '../../../initial_state';
|
||||
import { dequeueTimeline } from 'gabsocial/actions/timelines';
|
||||
|
||||
const makeGetStatusIds = () => createSelector([
|
||||
(state, { type }) => state.getIn(['settings', type], ImmutableMap()),
|
||||
|
@ -28,17 +29,22 @@ const makeGetStatusIds = () => createSelector([
|
|||
});
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const mapStateToProps = (state, {timelineId}) => {
|
||||
const getStatusIds = makeGetStatusIds();
|
||||
|
||||
const mapStateToProps = (state, { timelineId }) => ({
|
||||
return {
|
||||
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']),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
totalQueuedItemsCount: state.getIn(['timelines', timelineId, 'totalQueuedItemsCount']),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(makeMapStateToProps)(StatusList);
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onDequeueTimeline(timelineId) {
|
||||
dispatch(dequeueTimeline(timelineId, ownProps.onLoadMore));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StatusList);
|
||||
|
|
|
@ -29,7 +29,6 @@ import SearchPage from 'gabsocial/pages/search_page';
|
|||
import HomePage from 'gabsocial/pages/home_page';
|
||||
|
||||
import {
|
||||
Compose,
|
||||
Status,
|
||||
GettingStarted,
|
||||
CommunityTimeline,
|
||||
|
|
|
@ -14,10 +14,6 @@ export function HomeTimeline () {
|
|||
return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline');
|
||||
}
|
||||
|
||||
export function PublicTimeline () {
|
||||
return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline');
|
||||
}
|
||||
|
||||
export function CommunityTimeline () {
|
||||
return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline');
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "تم التحقق مِن مِلْكية هذا الرابط بتاريخ {date}",
|
||||
"account.locked_info": "تم تأمين خصوصية هذا الحساب عبر قفل. صاحب الحساب يُراجِع يدويا طلبات المتابَعة و الاشتراك بحسابه.",
|
||||
"account.media": "وسائط",
|
||||
"account.mention": "أُذكُر/ي @{name}",
|
||||
"account.mention": "أُذكُر/ي",
|
||||
"account.moved_to": "{name} إنتقل إلى :",
|
||||
"account.mute": "كتم @{name}",
|
||||
"account.mute_notifications": "كتم الإخطارات من @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mentar a @{name}",
|
||||
"account.mention": "Mentar",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Silenciar a @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "এই লিংকের মালিকানা চেক করা হয়েছে {date} তারিকে",
|
||||
"account.locked_info": "এই নিবন্ধনের গোপনীয়তার ক্ষেত্র তালা দেওয়া আছে। নিবন্ধনকারী অনুসরণ করার অনুমতি যাদেরকে দেবেন, শুধু তারাই অনুসরণ করতে পারবেন।",
|
||||
"account.media": "ছবি বা ভিডিও",
|
||||
"account.mention": "@{name} কে উল্লেখ করুন",
|
||||
"account.mention": "কে উল্লেখ করুন",
|
||||
"account.moved_to": "{name} চলে গেছে এখানে:",
|
||||
"account.mute": "@{name}র কার্যক্রম সরিয়ে ফেলুন",
|
||||
"account.mute_notifications": "@{name}র প্রজ্ঞাপন আপনার কাছ থেকে সরিয়ে ফেলুন",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "La propietat d'aquest enllaç es va verificar el dia {date}",
|
||||
"account.locked_info": "Aquest estat de privadesa del compte està definit com a bloquejat. El propietari revisa manualment qui pot seguir-lo.",
|
||||
"account.media": "Mèdia",
|
||||
"account.mention": "Esmentar @{name}",
|
||||
"account.mention": "Esmentar",
|
||||
"account.moved_to": "{name} s'ha mogut a:",
|
||||
"account.mute": "Silencia @{name}",
|
||||
"account.mute_notifications": "Notificacions desactivades de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}",
|
||||
"account.locked_info": "U statutu di vita privata di u contu hè chjosu. U pruprietariu esamina manualmente e dumande d'abbunamentu.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mintuvà @{name}",
|
||||
"account.mention": "Mintuvà",
|
||||
"account.moved_to": "{name} hè partutu nant'à:",
|
||||
"account.mute": "Piattà @{name}",
|
||||
"account.mute_notifications": "Piattà nutificazione da @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}",
|
||||
"account.locked_info": "Stav soukromí tohoto účtu je nastaven na zamčeno. Jeho vlastník ručně posuzuje, kdo ho může sledovat.",
|
||||
"account.media": "Média",
|
||||
"account.mention": "Zmínit uživatele @{name}",
|
||||
"account.mention": "Zmínit uživatele",
|
||||
"account.moved_to": "{name} se přesunul/a na:",
|
||||
"account.mute": "Skrýt uživatele @{name}",
|
||||
"account.mute_notifications": "Skrýt oznámení od uživatele @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
|
||||
"account.locked_info": "Mae'r statws preifatrwydd cyfrif hwn wedi'i osod i gloi. Mae'r perchennog yn adolygu'r sawl sy'n gallu eu dilyn.",
|
||||
"account.media": "Cyfryngau",
|
||||
"account.mention": "Crybwyll @{name}",
|
||||
"account.mention": "Crybwyll",
|
||||
"account.moved_to": "Mae @{name} wedi symud i:",
|
||||
"account.mute": "Tawelu @{name}",
|
||||
"account.mute_notifications": "Cuddio hysbysiadau o @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ejerskabet af dette link blev tjekket den %{date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Medie",
|
||||
"account.mention": "Nævn @{name}",
|
||||
"account.mention": "Nævn",
|
||||
"account.moved_to": "{name} er flyttet til:",
|
||||
"account.mute": "Dæmp @{name}",
|
||||
"account.mute_notifications": "Dæmp notifikationer fra @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
|
||||
"account.locked_info": "Der Privatsphärenstatus dieses Accounts wurde auf gesperrt gesetzt. Die Person bestimmt manuell wer ihm/ihr folgen darf.",
|
||||
"account.media": "Medien",
|
||||
"account.mention": "@{name} erwähnen",
|
||||
"account.mention": "erwähnen",
|
||||
"account.moved_to": "{name} ist umgezogen auf:",
|
||||
"account.mute": "@{name} stummschalten",
|
||||
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου εκλέχθηκε την {date}",
|
||||
"account.locked_info": "Η κατάσταση απορρήτου αυτού του λογαριασμού είναι κλειδωμένη. Ο ιδιοκτήτης επιβεβαιώνει χειροκίνητα ποιος μπορεί να τον ακολουθήσει.",
|
||||
"account.media": "Πολυμέσα",
|
||||
"account.mention": "Ανάφερε @{name}",
|
||||
"account.mention": "Ανάφερε",
|
||||
"account.moved_to": "{name} μεταφέρθηκε στο:",
|
||||
"account.mute": "Σώπασε τον/την @{name}",
|
||||
"account.mute_notifications": "Σώπασε τις ειδοποιήσεις από τον/την @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.mention": "Mention",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
|
||||
"account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
|
||||
"account.media": "Aŭdovidaĵoj",
|
||||
"account.mention": "Mencii @{name}",
|
||||
"account.mention": "Mencii",
|
||||
"account.moved_to": "{name} moviĝis al:",
|
||||
"account.mute": "Silentigi @{name}",
|
||||
"account.mute_notifications": "Silentigi sciigojn el @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mencionar a @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} se ha mudado a:",
|
||||
"account.mute": "Silenciar a @{name}",
|
||||
"account.mute_notifications": "Silenciar notificaciones de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Esteka honen jabetzaren egiaztaketa data: {date}",
|
||||
"account.locked_info": "Kontu honen pribatutasun egoera blokeatuta gisa ezarri da. Jabeak eskuz erabakitzen du nork jarraitu diezaioken.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Aipatu @{name}",
|
||||
"account.mention": "Aipatu",
|
||||
"account.moved_to": "{name} hona lekualdatu da:",
|
||||
"account.mute": "Mututu @{name}",
|
||||
"account.mute_notifications": "Mututu @{name}(r)en jakinarazpenak",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "مالکیت این نشانی در تایخ {date} بررسی شد",
|
||||
"account.locked_info": "این حساب خصوصی است. صاحب این حساب تصمیم میگیرد که چه کسی میتواند پیگیرش باشد.",
|
||||
"account.media": "عکس و ویدیو",
|
||||
"account.mention": "نامبردن از @{name}",
|
||||
"account.mention": "نامبردن از",
|
||||
"account.moved_to": "{name} منتقل شده است به:",
|
||||
"account.mute": "بیصدا کردن @{name}",
|
||||
"account.mute_notifications": "بیصداکردن اعلانها از طرف @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Tämän linkin omistaja tarkistettiin {date}",
|
||||
"account.locked_info": "Tämän tili on yksityinen. Käyttäjä vahvistaa itse kuka voi seurata häntä.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mainitse @{name}",
|
||||
"account.mention": "Mainitse",
|
||||
"account.moved_to": "{name} on muuttanut instanssiin:",
|
||||
"account.mute": "Mykistä @{name}",
|
||||
"account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
|
||||
"account.locked_info": "Ce compte est verrouillé. Son propriétaire approuve manuellement qui peut le ou la suivre.",
|
||||
"account.media": "Média",
|
||||
"account.mention": "Mentionner @{name}",
|
||||
"account.mention": "Mentionner",
|
||||
"account.moved_to": "{name} a déménagé vers :",
|
||||
"account.mute": "Masquer @{name}",
|
||||
"account.mute_notifications": "Ignorer les notifications de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "A propiedade de esta ligazón foi comprobada en {date}",
|
||||
"account.locked_info": "O estado da intimidade de esta conta estableceuse en pechado. A persoa dona da conta revisa quen pode seguila.",
|
||||
"account.media": "Medios",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} marchou a:",
|
||||
"account.mute": "Acalar @{name}",
|
||||
"account.mute_notifications": "Acalar as notificacións de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "מדיה",
|
||||
"account.mention": "אזכור של @{name}",
|
||||
"account.mention": "אזכור של",
|
||||
"account.moved_to": "החשבון {name} הועבר אל:",
|
||||
"account.mute": "להשתיק את @{name}",
|
||||
"account.mute_notifications": "להסתיר התראות מאת @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.mention": "Mention",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Spomeni @{name}",
|
||||
"account.mention": "Spomeni",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Utišaj @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Média",
|
||||
"account.mention": "@{name} említése",
|
||||
"account.mention": "említése",
|
||||
"account.moved_to": "{name} átköltözött:",
|
||||
"account.mute": "@{name} némítása",
|
||||
"account.mute_notifications": "@{name} értesítések némítása",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Մեդիա",
|
||||
"account.mention": "Նշել @{name}֊ին",
|
||||
"account.mention": "Նշել",
|
||||
"account.moved_to": "{name}֊ը տեղափոխվել է՝",
|
||||
"account.mute": "Լռեցնել @{name}֊ին",
|
||||
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Balasan @{name}",
|
||||
"account.mention": "Balasan",
|
||||
"account.moved_to": "{name} telah pindah ke:",
|
||||
"account.mute": "Bisukan @{name}",
|
||||
"account.mute_notifications": "Sembunyikan notifikasi dari @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Celar @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
|
||||
"account.locked_info": "Il livello di privacy di questo account è impostato a \"bloccato\". Il proprietario esamina manualmente le richieste di seguirlo.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Menziona @{name}",
|
||||
"account.mention": "Menziona",
|
||||
"account.moved_to": "{name} si è trasferito su:",
|
||||
"account.mute": "Silenzia @{name}",
|
||||
"account.mute_notifications": "Silenzia notifiche da @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "このリンクの所有権は{date}に確認されました",
|
||||
"account.locked_info": "このアカウントは承認制アカウントです。相手が承認するまでフォローは完了しません。",
|
||||
"account.media": "メディア",
|
||||
"account.mention": "@{name}さんにトゥート",
|
||||
"account.mention": "さんにトゥート",
|
||||
"account.moved_to": "{name}さんは引っ越しました:",
|
||||
"account.mute": "@{name}さんをミュート",
|
||||
"account.mute_notifications": "@{name}さんからの通知を受け取らない",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "მედია",
|
||||
"account.mention": "ასახელეთ @{name}",
|
||||
"account.mention": "ასახელეთ",
|
||||
"account.moved_to": "{name} გადავიდა:",
|
||||
"account.mute": "გააჩუმე @{name}",
|
||||
"account.mute_notifications": "გააჩუმე შეტყობინებები @{name}-სგან",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Сілтеме меншігі расталған күн {date}",
|
||||
"account.locked_info": "Бұл қолданушы өзі туралы мәліметтерді жасырған. Тек жазылғандар ғана көре алады.",
|
||||
"account.media": "Медиа",
|
||||
"account.mention": "Аталым @{name}",
|
||||
"account.mention": "Аталым",
|
||||
"account.moved_to": "{name} көшіп кетті:",
|
||||
"account.mute": "Үнсіз қылу @{name}",
|
||||
"account.mute_notifications": "@{name} туралы ескертпелерді жасыру",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "{date}에 이 링크의 소유권이 확인 됨",
|
||||
"account.locked_info": "이 계정의 프라이버시 설정은 잠금으로 설정되어 있습니다. 계정 소유자가 수동으로 팔로어를 승인합니다.",
|
||||
"account.media": "미디어",
|
||||
"account.mention": "@{name}에게 글쓰기",
|
||||
"account.mention": "에게 글쓰기",
|
||||
"account.moved_to": "{name}는 계정을 이동했습니다:",
|
||||
"account.mute": "@{name} 뮤트",
|
||||
"account.mute_notifications": "@{name}의 알림을 뮤트",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Šīs saites piederība ir pārbaudīta {date}",
|
||||
"account.locked_info": "Šī konta privātuma status ir iestatīts slēgts. Īpašnieks izskatīs un izvēlēsies kas viņam drīkst sekot.",
|
||||
"account.media": "Mēdiji",
|
||||
"account.mention": "Piemin @{name}",
|
||||
"account.mention": "Piemin",
|
||||
"account.moved_to": "{name} ir pārvācies uz:",
|
||||
"account.mute": "Apklusināt @{name}",
|
||||
"account.mute_notifications": "Nerādīt paziņojumus no @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.mention": "Mention",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
|
||||
"account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie hen kan volgen.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Vermeld @{name}",
|
||||
"account.mention": "Vermeld",
|
||||
"account.moved_to": "{name} is verhuisd naar:",
|
||||
"account.mute": "Negeer @{name}",
|
||||
"account.mute_notifications": "Negeer meldingen van @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Nevn @{name}",
|
||||
"account.mention": "Nevn",
|
||||
"account.moved_to": "{name} har flyttet til:",
|
||||
"account.mute": "Demp @{name}",
|
||||
"account.mute_notifications": "Ignorer varsler fra @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "La proprietat d’aqueste ligam foguèt verificada lo {date}",
|
||||
"account.locked_info": "L’estatut de privacitat del compte es configurat sus clavat. Lo proprietari causís qual pòt sègre son compte.",
|
||||
"account.media": "Mèdias",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} a mudat los catons a :",
|
||||
"account.mute": "Rescondre @{name}",
|
||||
"account.mute_notifications": "Rescondre las notificacions de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Własność tego odnośnika została potwierdzona {date}",
|
||||
"account.locked_info": "To konto jest prywatne. Właściciel ręcznie wybiera kto może go śledzić.",
|
||||
"account.media": "Zawartość multimedialna",
|
||||
"account.mention": "Wspomnij o @{name}",
|
||||
"account.mention": "Wspomnij",
|
||||
"account.moved_to": "{name} przeniósł(-osła) się do:",
|
||||
"account.mute": "Wycisz @{name}",
|
||||
"account.mute_notifications": "Wycisz powiadomienia o @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "A posse desse link foi verificada em {date}",
|
||||
"account.locked_info": "Essa conta está trancada. Se você a seguir sua solicitação será revisada manualmente.",
|
||||
"account.media": "Mídia",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} se mudou para:",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.mute_notifications": "Silenciar notificações de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "A posse deste link foi verificada em {date}",
|
||||
"account.locked_info": "O estatuto de privacidade desta conta é fechado. O dono revê manualmente que a pode seguir.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mention": "Mencionar",
|
||||
"account.moved_to": "{name} mudou a sua conta para:",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.mute_notifications": "Silenciar notificações de @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Deținerea acestui link a fost verificată la {date}",
|
||||
"account.locked_info": "Acest profil este privat. Această persoană gestioneaz manual cine o urmărește.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Menționează @{name}",
|
||||
"account.mention": "Menționează",
|
||||
"account.moved_to": "{name} a fost mutat la:",
|
||||
"account.mute": "Oprește @{name}",
|
||||
"account.mute_notifications": "Oprește notificările de la @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Vlastníctvo tohto odkazu bolo skontrolované {date}",
|
||||
"account.locked_info": "Stav súkromia pre tento účet je nastavený na zamknutý. Jeho vlastník sám prehodnocuje, kto ho môže sledovať.",
|
||||
"account.media": "Médiá",
|
||||
"account.mention": "Spomeň @{name}",
|
||||
"account.mention": "Spomeň",
|
||||
"account.moved_to": "{name} sa presunul/a na:",
|
||||
"account.mute": "Ignorovať @{name}",
|
||||
"account.mute_notifications": "Stĺm oboznámenia od @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Mediji",
|
||||
"account.mention": "Omeni @{name}",
|
||||
"account.mention": "Omeni",
|
||||
"account.moved_to": "{name} se je premaknil na:",
|
||||
"account.mute": "Utišaj @{name}",
|
||||
"account.mute_notifications": "Utišaj obvestila od @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Pronësia e kësaj lidhjeje qe kontrolluar më {date}",
|
||||
"account.locked_info": "Gjendja e privatësisë së kësaj llogarie është caktuar si e kyçur. I zoti merr dorazi në shqyrtim cilët mund ta ndjekin.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Përmendni @{name}",
|
||||
"account.mention": "Përmendni",
|
||||
"account.moved_to": "{name} ka kaluar te:",
|
||||
"account.mute": "Heshtoni @{name}",
|
||||
"account.mute_notifications": "Heshtoji njoftimet prej @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Медији",
|
||||
"account.mention": "Помени корисника @{name}",
|
||||
"account.mention": "Помени корисника",
|
||||
"account.moved_to": "{name} се померио на:",
|
||||
"account.mute": "Ућуткај корисника @{name}",
|
||||
"account.mute_notifications": "Искључи обавештења од корисника @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Nämna @{name}",
|
||||
"account.mention": "Nämna",
|
||||
"account.moved_to": "{name} har flyttat till:",
|
||||
"account.mute": "Tysta @{name}",
|
||||
"account.mute_notifications": "Stäng av notifieringar från @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.mention": "Mention",
|
||||
"account.moved_to": "{name} has moved to:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "ఈ లంకె యొక్క యాజమాన్యం {date}న పరీక్షించబడింది",
|
||||
"account.locked_info": "ఈ ఖాతా యొక్క గోప్యత స్థితి లాక్ చేయబడి వుంది. ఈ ఖాతాను ఎవరు అనుసరించవచ్చో యజమానే నిర్ణయం తీసుకుంటారు.",
|
||||
"account.media": "మీడియా",
|
||||
"account.mention": "@{name}ను ప్రస్తావించు",
|
||||
"account.mention": "ప్రస్తావించు",
|
||||
"account.moved_to": "{name} ఇక్కడికి మారారు:",
|
||||
"account.mute": "@{name}ను మ్యూట్ చెయ్యి",
|
||||
"account.mute_notifications": "@{name}నుంచి ప్రకటనలను మ్యూట్ చెయ్యి",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "สื่อ",
|
||||
"account.mention": "กล่าวถึง @{name}",
|
||||
"account.mention": "กล่าวถึง",
|
||||
"account.moved_to": "{name} ได้ย้ายไปยัง:",
|
||||
"account.mute": "ปิดเสียง @{name}",
|
||||
"account.mute_notifications": "ปิดเสียงการแจ้งเตือนจาก @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Bu bağlantının mülkiyeti {date} tarihinde kontrol edildi",
|
||||
"account.locked_info": "Bu hesabın gizlilik durumu kilitli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini elle inceler.",
|
||||
"account.media": "Medya",
|
||||
"account.mention": "@{name} kullanıcısından bahset",
|
||||
"account.mention": "kullanıcısından bahset",
|
||||
"account.moved_to": "{name} şuraya taşındı:",
|
||||
"account.mute": "@{name} kullanıcısını sessize al",
|
||||
"account.mute_notifications": "@{name} kullanıcısının bildirimlerini kapat",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "Медіа",
|
||||
"account.mention": "Згадати @{name}",
|
||||
"account.mention": "Згадати",
|
||||
"account.moved_to": "{name} переїхав на:",
|
||||
"account.mute": "Заглушити @{name}",
|
||||
"account.mute_notifications": "Не показувати сповіщення від @{name}",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "媒体",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.mention": "提及",
|
||||
"account.moved_to": "{name} 已经迁移到:",
|
||||
"account.mute": "隐藏 @{name}",
|
||||
"account.mute_notifications": "隐藏来自 @{name} 的通知",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||
"account.media": "媒體",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.mention": "提及",
|
||||
"account.moved_to": "{name} 已經遷移到:",
|
||||
"account.mute": "將 @{name} 靜音",
|
||||
"account.mute_notifications": "將來自 @{name} 的通知靜音",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"account.link_verified_on": "此連結的所有權已在 {date} 檢查",
|
||||
"account.locked_info": "此帳號的隱私狀態被設為鎖定,擁有者將手動審核可關注此帳號的人。",
|
||||
"account.media": "媒體",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.mention": "提及",
|
||||
"account.moved_to": "{name} 已遷移至:",
|
||||
"account.mute": "靜音 @{name}",
|
||||
"account.mute_notifications": "靜音來自 @{name} 的通知",
|
||||
|
|
|
@ -6,6 +6,9 @@ import {
|
|||
NOTIFICATIONS_FILTER_SET,
|
||||
NOTIFICATIONS_CLEAR,
|
||||
NOTIFICATIONS_SCROLL_TOP,
|
||||
NOTIFICATIONS_UPDATE_QUEUE,
|
||||
NOTIFICATIONS_DEQUEUE,
|
||||
MAX_QUEUED_NOTIFICATIONS,
|
||||
} from '../actions/notifications';
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
|
@ -21,6 +24,8 @@ const initialState = ImmutableMap({
|
|||
top: false,
|
||||
unread: 0,
|
||||
isLoading: false,
|
||||
queuedNotifications: ImmutableList(), //max = MAX_QUEUED_NOTIFICATIONS
|
||||
totalQueuedNotificationsCount: 0, //used for queuedItems overflow for MAX_QUEUED_NOTIFICATIONS+
|
||||
});
|
||||
|
||||
const notificationToMap = notification => ImmutableMap({
|
||||
|
@ -93,6 +98,32 @@ const deleteByStatus = (state, statusId) => {
|
|||
return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
|
||||
};
|
||||
|
||||
const updateNotificationsQueue = (state, notification, intlMessages, intlLocale) => {
|
||||
const queuedNotifications = state.getIn(['queuedNotifications'], ImmutableList());
|
||||
const listedNotifications = state.getIn(['items'], ImmutableList());
|
||||
const totalQueuedNotificationsCount = state.getIn(['totalQueuedNotificationsCount'], 0);
|
||||
|
||||
let alreadyExists = queuedNotifications.find(existingQueuedNotification => existingQueuedNotification.id === notification.id);
|
||||
if (!alreadyExists) alreadyExists = listedNotifications.find(existingListedNotification => existingListedNotification.get('id') === notification.id);
|
||||
|
||||
if (alreadyExists) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let newQueuedNotifications = queuedNotifications;
|
||||
|
||||
return state.withMutations(mutable => {
|
||||
if (totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||
mutable.set('queuedNotifications', newQueuedNotifications.push({
|
||||
notification,
|
||||
intlMessages,
|
||||
intlLocale,
|
||||
}));
|
||||
}
|
||||
mutable.set('totalQueuedNotificationsCount', totalQueuedNotificationsCount + 1);
|
||||
});
|
||||
};
|
||||
|
||||
export default function notifications(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case NOTIFICATIONS_EXPAND_REQUEST:
|
||||
|
@ -105,6 +136,13 @@ export default function notifications(state = initialState, action) {
|
|||
return updateTop(state, action.top);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return normalizeNotification(state, action.notification);
|
||||
case NOTIFICATIONS_UPDATE_QUEUE:
|
||||
return updateNotificationsQueue(state, action.notification, action.intlMessages, action.intlLocale);
|
||||
case NOTIFICATIONS_DEQUEUE:
|
||||
return state.withMutations(mutable => {
|
||||
mutable.set('queuedNotifications', ImmutableList())
|
||||
mutable.set('totalQueuedNotificationsCount', 0)
|
||||
});
|
||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||
return expandNormalizedNotifications(state, action.notifications, action.next);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
|
||||
import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications';
|
||||
import { COLUMN_PARAMS_CHANGE } from '../actions/columns';
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
import { EMOJI_USE } from '../actions/emojis';
|
||||
import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists';
|
||||
|
@ -88,17 +87,6 @@ const defaultColumns = fromJS([
|
|||
|
||||
const hydrate = (state, settings) => state.mergeDeep(settings).update('columns', (val = defaultColumns) => val);
|
||||
|
||||
const changeColumnParams = (state, uuid, path, value) => {
|
||||
const columns = state.get('columns');
|
||||
const index = columns.findIndex(item => item.get('uuid') === uuid);
|
||||
|
||||
const newColumns = columns.update(index, column => column.updateIn(['params', ...path], () => value));
|
||||
|
||||
return state
|
||||
.set('columns', newColumns)
|
||||
.set('saved', false);
|
||||
};
|
||||
|
||||
const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false);
|
||||
|
||||
const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId));
|
||||
|
@ -112,8 +100,6 @@ export default function settings(state = initialState, action) {
|
|||
return state
|
||||
.setIn(action.path, action.value)
|
||||
.set('saved', false);
|
||||
case COLUMN_PARAMS_CHANGE:
|
||||
return changeColumnParams(state, action.uuid, action.path, action.value);
|
||||
case EMOJI_USE:
|
||||
return updateFrequentEmojis(state, action.emoji);
|
||||
case SETTING_SAVE:
|
||||
|
|
|
@ -7,6 +7,9 @@ import {
|
|||
TIMELINE_EXPAND_FAIL,
|
||||
TIMELINE_CONNECT,
|
||||
TIMELINE_DISCONNECT,
|
||||
TIMELINE_UPDATE_QUEUE,
|
||||
TIMELINE_DEQUEUE,
|
||||
MAX_QUEUED_ITEMS,
|
||||
} from '../actions/timelines';
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
|
@ -25,6 +28,8 @@ const initialTimeline = ImmutableMap({
|
|||
isLoading: false,
|
||||
hasMore: true,
|
||||
items: ImmutableList(),
|
||||
queuedItems: ImmutableList(), //max= MAX_QUEUED_ITEMS
|
||||
totalQueuedItemsCount: 0, //used for queuedItems overflow for MAX_QUEUED_ITEMS+
|
||||
});
|
||||
|
||||
const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
|
||||
|
@ -77,6 +82,28 @@ const updateTimeline = (state, timeline, status) => {
|
|||
}));
|
||||
};
|
||||
|
||||
const updateTimelineQueue = (state, timeline, status) => {
|
||||
const queuedStatuses = state.getIn([timeline, 'queuedItems'], ImmutableList());
|
||||
const listedStatuses = state.getIn([timeline, 'items'], ImmutableList());
|
||||
const totalQueuedItemsCount = state.getIn([timeline, 'totalQueuedItemsCount'], 0);
|
||||
|
||||
let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === status.get('id'));
|
||||
if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === status.get('id'));
|
||||
|
||||
if (alreadyExists) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let newQueuedStatuses = queuedStatuses;
|
||||
|
||||
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
||||
if (totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
|
||||
mMap.set('queuedItems', newQueuedStatuses.push(status));
|
||||
}
|
||||
mMap.set('totalQueuedItemsCount', totalQueuedItemsCount + 1);
|
||||
}));
|
||||
};
|
||||
|
||||
const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
|
||||
state.keySeq().forEach(timeline => {
|
||||
if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`)))
|
||||
|
@ -126,6 +153,13 @@ export default function timelines(state = initialState, action) {
|
|||
return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent);
|
||||
case TIMELINE_UPDATE:
|
||||
return updateTimeline(state, action.timeline, fromJS(action.status));
|
||||
case TIMELINE_UPDATE_QUEUE:
|
||||
return updateTimelineQueue(state, action.timeline, fromJS(action.status));
|
||||
case TIMELINE_DEQUEUE:
|
||||
return state.update(action.timeline, initialTimeline, map => map.withMutations(mMap => {
|
||||
mMap.set('queuedItems', ImmutableList())
|
||||
mMap.set('totalQueuedItemsCount', 0)
|
||||
}));
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
|
||||
case TIMELINE_CLEAR:
|
||||
|
|
|
@ -1380,6 +1380,7 @@ a.account__display-name {
|
|||
.ui {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 0 100px 0;
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
|
@ -5103,3 +5104,25 @@ noscript {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-queue-header {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
position: relative;
|
||||
background-color: darken($ui-base-color, 8%);
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
border-color: darken($ui-base-color, 4%);
|
||||
|
||||
&__btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
line-height: 52px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: $secondary-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,8 @@
|
|||
height: 100%;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
background: darken($ui-primary-color, 14%);
|
||||
|
||||
&.leading {
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
background: rgba($gab-placeholder-accent, .3);
|
||||
&.leading {background: rgba($gab-placeholder-accent, .6);}
|
||||
}
|
||||
|
||||
&__text {
|
||||
|
@ -31,7 +28,7 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
color: #fff;
|
||||
input[type=radio],
|
||||
input[type=checkbox] {
|
||||
display: none;
|
||||
|
|
|
@ -2,41 +2,35 @@
|
|||
// Content containers are meant to behave much differently than the mastodon default UI
|
||||
// For now linking default gab colors to replace the base UI colors and formulas
|
||||
|
||||
html {
|
||||
scrollbar-color: lighten($gab-background-container, 4%) $gab-background-container;
|
||||
}
|
||||
|
||||
html {scrollbar-color: lighten($gab-background-container, 4%) $gab-background-container;}
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: lighten($gab-background-container, 4%);
|
||||
border: none;
|
||||
border: none;
|
||||
background: rgba($gab-placeholder-accent, .5);
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-container-light, .3);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: lighten($gab-background-container, 6%);
|
||||
background: rgba($gab-placeholder-accent, .75);
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-container-light, .4);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: lighten($gab-background-container, 4%);
|
||||
background: $gab-placeholder-accent;
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-container-light, .5);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border: none;
|
||||
background: $gab-background-container;
|
||||
border: none;
|
||||
background: rgba($gab-background-container, .5);
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-base, .3);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background: lighten($gab-background-container, 4%);
|
||||
background: rgba($gab-background-container, .75);
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-base, .4);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:active {
|
||||
background: $gab-background-container;
|
||||
background: $gab-background-container;
|
||||
@at-root body.theme-gabsocial-light#{&} {background: rgba($gab-background-base, .5);}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-corner {background: transparent;}
|
|
@ -30,7 +30,7 @@ class FeedManager
|
|||
def push_to_home(account, status)
|
||||
return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||
trim(:home, account.id)
|
||||
#PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
|
||||
PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -48,7 +48,7 @@ class FeedManager
|
|||
end
|
||||
return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
|
||||
trim(:list, list.id)
|
||||
#PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
|
||||
PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class FanOutOnWriteService < BaseService
|
|||
|
||||
Rails.logger.debug "Delivering status #{status.id} to group"
|
||||
|
||||
# Redis.current.publish("timeline:group:#{status.group_id}", @payload)
|
||||
Redis.current.publish("timeline:group:#{status.group_id}", @payload)
|
||||
end
|
||||
|
||||
def deliver_to_mentioned_followers(status)
|
||||
|
@ -89,15 +89,15 @@ class FanOutOnWriteService < BaseService
|
|||
def deliver_to_public(status)
|
||||
Rails.logger.debug "Delivering status #{status.id} to public timeline"
|
||||
|
||||
# Redis.current.publish('timeline:public', @payload)
|
||||
# Redis.current.publish('timeline:public:local', @payload) if status.local?
|
||||
Redis.current.publish('timeline:public', @payload)
|
||||
Redis.current.publish('timeline:public:local', @payload) if status.local?
|
||||
end
|
||||
|
||||
def deliver_to_media(status)
|
||||
Rails.logger.debug "Delivering status #{status.id} to media timeline"
|
||||
|
||||
# Redis.current.publish('timeline:public:media', @payload)
|
||||
# Redis.current.publish('timeline:public:local:media', @payload) if status.local?
|
||||
Redis.current.publish('timeline:public:media', @payload)
|
||||
Redis.current.publish('timeline:public:local:media', @payload) if status.local?
|
||||
end
|
||||
|
||||
def deliver_to_own_conversation(status)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
= invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false
|
||||
|
||||
.fields-group
|
||||
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: homepage_path, about_tos_path: about_tos_path), disabled: closed_registrations?
|
||||
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', about_tos_path: about_tos_path), disabled: closed_registrations?
|
||||
|
||||
.actions
|
||||
= f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations?
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
= f.input :invite_code, as: :hidden
|
||||
|
||||
.fields-group
|
||||
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_privacy_path, about_tos_path: about_tos_path)
|
||||
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', about_tos_path: about_tos_path)
|
||||
|
||||
.actions
|
||||
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
|
||||
|
|
|
@ -7,7 +7,7 @@ class PushConversationWorker
|
|||
conversation = AccountConversation.find(conversation_account_id)
|
||||
message = InlineRenderer.render(conversation, conversation.account, :conversation)
|
||||
timeline_id = "timeline:direct:#{conversation.account_id}"
|
||||
# Redis.current.publish(timeline_id, Oj.dump(event: :conversation, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
|
||||
Redis.current.publish(timeline_id, Oj.dump(event: :conversation, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
|
||||
true
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
|
|
|
@ -9,7 +9,7 @@ class PushUpdateWorker
|
|||
message = InlineRenderer.render(status, account, :status)
|
||||
timeline_id = "timeline:#{account.id}" if timeline_id.nil?
|
||||
|
||||
# Redis.current.publish(timeline_id, Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
|
||||
Redis.current.publish(timeline_id, Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
|
||||
true
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
|
|
|
@ -515,7 +515,7 @@ ca:
|
|||
auth:
|
||||
apply_for_account: Demana una invitació
|
||||
change_password: Contrasenya
|
||||
checkbox_agreement_html: Estic d'acord amb les <a href="%{rules_path}" target="_blank">normes del servidor</a> i <a href="%{about_tos_path}" target="_blank"> els termes del servei</a>
|
||||
checkbox_agreement_html: Estic d’acord amb els <a href="%{about_tos_path}" target="_blank">termes del servei</a>.
|
||||
confirm_email: Confirmar correu electrònic
|
||||
delete_account: Suprimeix el compte
|
||||
delete_account_html: Si vols suprimir el compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
|
||||
|
|
|
@ -515,7 +515,7 @@ co:
|
|||
auth:
|
||||
apply_for_account: Dumandà un'invitazione
|
||||
change_password: Chjave d’accessu
|
||||
checkbox_agreement_html: Sò d'accunsentu cù e <a href="%{rules_path}" target="_blank">regule di u servore</a> è i <a href="%{about_tos_path}" target="_blank">termini di u serviziu</a>
|
||||
checkbox_agreement_html: Sò in conformità à a <a href="%{about_tos_path}" target="_blank">termina di u serviziu</a>
|
||||
confirm_email: Cunfirmà l’e-mail
|
||||
delete_account: Sguassà u contu
|
||||
delete_account_html: S’è voi vulete toglie u vostru contu <a href="%{path}">ghjè quì</a>. Duverete cunfirmà a vostra scelta.
|
||||
|
|
|
@ -521,7 +521,7 @@ cs:
|
|||
auth:
|
||||
apply_for_account: Vyžádat si pozvánku
|
||||
change_password: Heslo
|
||||
checkbox_agreement_html: Souhlasím s <a href="%{rules_path}" target="_blank">pravidly serveru</a> a <a href="%{about_tos_path}" target="_blank">podmínkami používání</a>
|
||||
checkbox_agreement_html: Souhlasím s <a href="%{about_tos_path}" target="_blank">Smluvními podmínkami</a>
|
||||
confirm_email: Potvrdit e-mail
|
||||
delete_account: Odstranit účet
|
||||
delete_account_html: Chcete-li odstranit svůj účet, <a href="%{path}">pokračujte zde</a>. Budete požádán/a o potvrzení.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue