Merge remote-tracking branch 'origin/styling/add-groups-link' into groups-updates

* origin/styling/add-groups-link: (31 commits)
  Comment out the "groups" button until ready to reveal.
  Changed the method of adding main navigation icons. Created a png sprite sized @2x based on largest usage (for retina). This will fix some rendering issues caused by using svg images. It will allow adding depth and more color / shading if we choose later.
  intents fix
  federation fix
  two more federation fixes
  Removed unused imports
  Removed unused PublicTimeline component
  Updated CommunityTimeline to add option for "all federated" content
  Removed unused import in unauthorized_modal
  Updated registration legal links
  Updated compose_form to account for if compose modal open
  Added empty message to pinned statuses page
  Updated nextProps withReplies for account timeline
  Added empty message to account_gallery media page
  Updated timeline/notification dequeue to be in componentDidMount
  Added TimelineQueueButtonHeader to status_list
  Added queue functionality status_list_container for status timelines
  Updated all Redis.current.publish, PushUpdateWorker.perform_async to work again
  Added timeline dequeue functionality to onSubmitCompose action
  Added redux functionality for queueing/dequeueing timelines
  ...
This commit is contained in:
2458773093 2019-07-16 14:29:38 +03:00
commit c56a8914f3
136 changed files with 606 additions and 618 deletions

View File

@ -5,7 +5,7 @@ class IntentsController < ApplicationController
rescue_from Addressable::URI::InvalidURIError, with: :handle_invalid_uri
def show
if uri.scheme == 'web+gabsocial'
if uri.scheme == 'web+mastodon'
case uri.host
when 'follow'
return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, ''))

View File

@ -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());
};
}

View File

@ -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';
@ -169,6 +169,10 @@ export function submitCompose(routerHistory, group) {
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 }));
}
};

View File

@ -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,15 +57,27 @@ 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
@ -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();

View File

@ -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)));

View File

@ -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']);

View File

@ -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';

View File

@ -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>
);
];
}
}

View File

@ -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>
);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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 }));
}
}

View File

@ -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>
);

View File

@ -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));
}
},
};
};

View File

@ -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,32 +43,47 @@ 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;
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();
if (allFediverse) {
dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
}
else {
dispatch(expandCommunityTimeline({ onlyMedia }));
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
}
}
}
componentWillUnmount () {
if (this.disconnect) {
@ -69,27 +93,29 @@ class CommunityTimeline extends React.PureComponent {
}
handleLoadMore = maxId => {
const { dispatch, onlyMedia } = this.props;
const { dispatch, onlyMedia, allFediverse } = this.props;
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>
);

View File

@ -69,6 +69,7 @@ class ComposeForm extends ImmutablePureComponent {
shouldCondense: PropTypes.bool,
autoFocus: PropTypes.bool,
group: ImmutablePropTypes.map,
isModalOpen: PropTypes.bool,
};
static defaultProps = {
@ -151,6 +152,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.
@ -204,7 +207,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('');
@ -253,7 +256,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}

View File

@ -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) => ({

View File

@ -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." />}

View File

@ -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}

View File

@ -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>
);
}
}

View File

@ -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);

View File

@ -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.' />}

View File

@ -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.'/>}

View File

@ -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');
}
},
}));
}
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.' />}

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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);

View File

@ -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>
);
}
}

View File

@ -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({

View File

@ -27,9 +27,6 @@ export const privateLinks = [
<NavLink className='tabs-bar__link groups' to='/groups' data-preview-title-id='column.groups' >
<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />
</NavLink>,
// <NavLink className='tabs-bar__link home' to='/groups' data-preview-title-id='column.groups' >
// <FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />
// </NavLink>,
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' >
<FormattedMessage id='tabs_bar.search' defaultMessage='Search' />
</NavLink>,

View File

@ -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({

View File

@ -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);

View File

@ -31,7 +31,6 @@ import SearchPage from 'gabsocial/pages/search_page';
import HomePage from 'gabsocial/pages/home_page';
import {
Compose,
Status,
GettingStarted,
CommunityTimeline,

View File

@ -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');
}

View File

@ -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}",

View File

@ -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}",

View File

@ -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}র প্রজ্ঞাপন আপনার কাছ থেকে সরিয়ে ফেলুন",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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",

View File

@ -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}֊ից",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}さんからの通知を受け取らない",

View File

@ -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}-სგან",

View File

@ -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} туралы ескертпелерді жасыру",

View File

@ -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}의 알림을 뮤트",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -18,7 +18,7 @@
"account.link_verified_on": "La proprietat daqueste ligam foguèt verificada lo {date}",
"account.locked_info": "Lestatut 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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}",

View File

@ -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}నుంచి ప్రకటనలను మ్యూట్ చెయ్యి",

View File

@ -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}",

View File

@ -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",

View File

@ -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}",

View File

@ -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} 的通知",

View File

@ -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} 的通知靜音",

View File

@ -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} 的通知",

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -1 +0,0 @@
<svg id="icon-explore" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><defs><style>.cls-1{fill:#00d177;}</style></defs><title>icon-explore-00d177</title><path id="_00d177" data-name="00d177" class="cls-1" d="M10,0a7,7,0,1,0,7,7A7,7,0,0,0,10,0Zm2.32,10.09-.32.32a.75.75,0,0,0-.17.3,3.79,3.79,0,0,1-.14.48l-.49,1.32a5.26,5.26,0,0,1-1.2.14v-.78a2.11,2.11,0,0,0-.64-1.44.9.9,0,0,1-.26-.64V8.88a.9.9,0,0,0-.47-.79c-.4-.22-1-.53-1.38-.73a4.18,4.18,0,0,1-.89-.62l0,0a3.12,3.12,0,0,1-.51-.58l-1-1.45A5.68,5.68,0,0,1,7.77,1.82l.67.34a.46.46,0,0,0,.66-.41V1.43a5.06,5.06,0,0,1,.69-.06l.79.79a.45.45,0,0,1,0,.64l-.13.14-.29.29a.22.22,0,0,0,0,.32l.13.13a.22.22,0,0,1,0,.32l-.22.22a.21.21,0,0,1-.16.07H9.65a.2.2,0,0,0-.15.06l-.28.28a.21.21,0,0,0-.05.26l.44.88a.22.22,0,0,1-.2.33H9.25A.22.22,0,0,1,9.1,6l-.26-.23a.46.46,0,0,0-.44-.08L7.52,6a.33.33,0,0,0-.23.32.33.33,0,0,0,.19.3l.31.16a2,2,0,0,0,.86.2c.29,0,.63.77.9.9h1.88a.91.91,0,0,1,.64.27l.39.38a.88.88,0,0,1,.25.61A1.29,1.29,0,0,1,12.32,10.09Zm2.45-2.57a.74.74,0,0,1-.4-.29l-.51-.76a.68.68,0,0,1,0-.75l.56-.83a.58.58,0,0,1,.26-.23L15,4.48A5.48,5.48,0,0,1,15.65,7a5.83,5.83,0,0,1-.06.72Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg id="icon-explore" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><defs><style>.cls-1{fill:#d8d8d8;}</style></defs><title>icon-explore-d8d8d8</title><path id="d8d8d8" class="cls-1" d="M10,0a7,7,0,1,0,7,7A7,7,0,0,0,10,0Zm2.32,10.09-.32.32a.75.75,0,0,0-.17.3,3.79,3.79,0,0,1-.14.48l-.49,1.32a5.26,5.26,0,0,1-1.2.14v-.78a2.11,2.11,0,0,0-.64-1.44.9.9,0,0,1-.26-.64V8.88a.9.9,0,0,0-.47-.79c-.4-.22-1-.53-1.38-.73a4.18,4.18,0,0,1-.89-.62l0,0a3.12,3.12,0,0,1-.51-.58l-1-1.45A5.68,5.68,0,0,1,7.77,1.82l.67.34a.46.46,0,0,0,.66-.41V1.43a5.06,5.06,0,0,1,.69-.06l.79.79a.45.45,0,0,1,0,.64l-.13.14-.29.29a.22.22,0,0,0,0,.32l.13.13a.22.22,0,0,1,0,.32l-.22.22a.21.21,0,0,1-.16.07H9.65a.2.2,0,0,0-.15.06l-.28.28a.21.21,0,0,0-.05.26l.44.88a.22.22,0,0,1-.2.33H9.25A.22.22,0,0,1,9.1,6l-.26-.23a.46.46,0,0,0-.44-.08L7.52,6a.33.33,0,0,0-.23.32.33.33,0,0,0,.19.3l.31.16a2,2,0,0,0,.86.2c.29,0,.63.77.9.9h1.88a.91.91,0,0,1,.64.27l.39.38a.88.88,0,0,1,.25.61A1.29,1.29,0,0,1,12.32,10.09Zm2.45-2.57a.74.74,0,0,1-.4-.29l-.51-.76a.68.68,0,0,1,0-.75l.56-.83a.58.58,0,0,1,.26-.23L15,4.48A5.48,5.48,0,0,1,15.65,7a5.83,5.83,0,0,1-.06.72Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg id="icon-explore" xmlns="http://www.w3.org/2000/svg" width="14" height="114" viewBox="0 0 14 114"><defs><style>.cls-1{fill:#d8d8d8;}.cls-2{fill:#00d177;}</style></defs><title>icon-explore-sprite</title><path id="d8d8d8" class="cls-1" d="M10,100a7,7,0,1,0,7,7A7,7,0,0,0,10,100Zm2.32,10.09-.32.32a.75.75,0,0,0-.17.3,3.79,3.79,0,0,1-.14.48l-.49,1.32a5.26,5.26,0,0,1-1.2.14v-.78a2.11,2.11,0,0,0-.64-1.44.9.9,0,0,1-.26-.64v-.91a.9.9,0,0,0-.47-.79c-.4-.22-1-.53-1.38-.73a4.18,4.18,0,0,1-.89-.62l0,0a3.12,3.12,0,0,1-.51-.58l-1-1.45a5.68,5.68,0,0,1,2.92-2.87l.67.34a.46.46,0,0,0,.66-.41v-.32a5.06,5.06,0,0,1,.69-.06l.79.79a.45.45,0,0,1,0,.64l-.13.14-.29.29a.22.22,0,0,0,0,.32l.13.13a.22.22,0,0,1,0,.32l-.22.22a.21.21,0,0,1-.16.07H9.65a.2.2,0,0,0-.15.06l-.28.28a.21.21,0,0,0-.05.26l.44.88a.22.22,0,0,1-.2.33H9.25A.22.22,0,0,1,9.1,106l-.26-.23a.46.46,0,0,0-.44-.08l-.88.29a.33.33,0,0,0-.23.32.33.33,0,0,0,.19.3l.31.16a2,2,0,0,0,.86.2c.29,0,.63.77.9.9h1.88a.91.91,0,0,1,.64.27l.39.38a.88.88,0,0,1,.25.61A1.29,1.29,0,0,1,12.32,110.09Zm2.45-2.57a.74.74,0,0,1-.4-.29l-.51-.76a.68.68,0,0,1,0-.75l.56-.83a.58.58,0,0,1,.26-.23l.36-.18a5.48,5.48,0,0,1,.61,2.52,5.83,5.83,0,0,1-.06.72Z" transform="translate(-3)"/><path id="_00d177" data-name="00d177" class="cls-2" d="M10,0a7,7,0,1,0,7,7A7,7,0,0,0,10,0Zm2.32,10.09-.32.32a.75.75,0,0,0-.17.3,3.79,3.79,0,0,1-.14.48l-.49,1.32a5.26,5.26,0,0,1-1.2.14v-.78a2.11,2.11,0,0,0-.64-1.44.9.9,0,0,1-.26-.64V8.88a.9.9,0,0,0-.47-.79c-.4-.22-1-.53-1.38-.73a4.18,4.18,0,0,1-.89-.62l0,0a3.12,3.12,0,0,1-.51-.58l-1-1.45A5.68,5.68,0,0,1,7.77,1.82l.67.34a.46.46,0,0,0,.66-.41V1.43a5.06,5.06,0,0,1,.69-.06l.79.79a.45.45,0,0,1,0,.64l-.13.14-.29.29a.22.22,0,0,0,0,.32l.13.13a.22.22,0,0,1,0,.32l-.22.22a.21.21,0,0,1-.16.07H9.65a.2.2,0,0,0-.15.06l-.28.28a.21.21,0,0,0-.05.26l.44.88a.22.22,0,0,1-.2.33H9.25A.22.22,0,0,1,9.1,6l-.26-.23a.46.46,0,0,0-.44-.08L7.52,6a.33.33,0,0,0-.23.32.33.33,0,0,0,.19.3l.31.16a2,2,0,0,0,.86.2c.29,0,.63.77.9.9h1.88a.91.91,0,0,1,.64.27l.39.38a.88.88,0,0,1,.25.61A1.29,1.29,0,0,1,12.32,10.09Zm2.45-2.57a.74.74,0,0,1-.4-.29l-.51-.76a.68.68,0,0,1,0-.75l.56-.83a.58.58,0,0,1,.26-.23L15,4.48A5.48,5.48,0,0,1,15.65,7a5.83,5.83,0,0,1-.06.72Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1 +0,0 @@
<svg id="icon-home" xmlns="http://www.w3.org/2000/svg" width="16.11" height="14" viewBox="0 0 16.11 14"><defs><style>.cls-1{fill:#00d177;}</style></defs><title>navigation-icon-master</title><path id="_00d177" data-name="00d177" class="cls-1" d="M17.69,6.34,15.94,4.81V1.59A.79.79,0,0,0,15.15.8H14.09a.79.79,0,0,0-.79.79v.93L10.7.26A1,1,0,0,0,10.27,0h0l-.16,0H9.91L9.75,0h0A1,1,0,0,0,9.3.26l-7,6.08A1.06,1.06,0,0,0,2.2,7.83,1.12,1.12,0,0,0,3,8.19H4.06v4.75A1.06,1.06,0,0,0,5.11,14h3.6V9.51h2.74V14h3.44a1.06,1.06,0,0,0,1-1.06V8.19H17a1.12,1.12,0,0,0,.78-.36A1.06,1.06,0,0,0,17.69,6.34Z" transform="translate(-1.95)"/></svg>

Before

Width:  |  Height:  |  Size: 622 B

View File

@ -1 +0,0 @@
<svg id="icon-home" xmlns="http://www.w3.org/2000/svg" width="16.11" height="14" viewBox="0 0 16.11 14"><defs><style>.cls-1{fill:#d8d8d8;}</style></defs><title>navigation-icon-master</title><path id="d8d8d8" class="cls-1" d="M17.69,6.34,15.94,4.81V1.59A.79.79,0,0,0,15.15.8H14.09a.79.79,0,0,0-.79.79v.93L10.7.26A1,1,0,0,0,10.27,0h0l-.16,0H9.91L9.75,0h0A1,1,0,0,0,9.3.26l-7,6.08A1.06,1.06,0,0,0,2.2,7.83,1.12,1.12,0,0,0,3,8.19H4.06v4.75A1.06,1.06,0,0,0,5.11,14h3.6V9.51h2.74V14h3.44a1.06,1.06,0,0,0,1-1.06V8.19H17a1.12,1.12,0,0,0,.78-.36A1.06,1.06,0,0,0,17.69,6.34Z" transform="translate(-1.95)"/></svg>

Before

Width:  |  Height:  |  Size: 602 B

View File

@ -1 +0,0 @@
<svg id="icon-home" xmlns="http://www.w3.org/2000/svg" width="16.11" height="114" viewBox="0 0 16.11 114"><defs><style>.cls-1{fill:#d8d8d8;}.cls-2{fill:#00d177;}</style></defs><title>icon-home-sprite</title><path id="d8d8d8" class="cls-1" d="M17.69,106.34l-1.75-1.53v-3.22a.79.79,0,0,0-.79-.79H14.09a.79.79,0,0,0-.79.79v.93l-2.6-2.26a1,1,0,0,0-.43-.23h0l-.16,0H9.91l-.16,0h0a1,1,0,0,0-.43.23l-7,6.08a1.06,1.06,0,0,0-.11,1.49,1.12,1.12,0,0,0,.78.36H4.06v4.75A1.06,1.06,0,0,0,5.11,114h3.6v-4.49h2.74V114h3.44a1.06,1.06,0,0,0,1-1.06v-4.75H17a1.12,1.12,0,0,0,.78-.36A1.06,1.06,0,0,0,17.69,106.34Z" transform="translate(-1.95)"/><path id="_00d177" data-name="00d177" class="cls-2" d="M17.69,6.34,15.94,4.81V1.59A.79.79,0,0,0,15.15.8H14.09a.79.79,0,0,0-.79.79v.93L10.7.26A1,1,0,0,0,10.27,0h0l-.16,0H9.91L9.75,0h0A1,1,0,0,0,9.3.26l-7,6.08A1.06,1.06,0,0,0,2.2,7.83,1.12,1.12,0,0,0,3,8.19H4.06v4.75A1.06,1.06,0,0,0,5.11,14h3.6V9.51h2.74V14h3.44a1.06,1.06,0,0,0,1-1.06V8.19H17a1.12,1.12,0,0,0,.78-.36A1.06,1.06,0,0,0,17.69,6.34Z" transform="translate(-1.95)"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1 +0,0 @@
<svg id="icon-messages" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><defs><style>.cls-1{fill:#00d177;}</style></defs><title>icon-messages-00d177</title><path id="_00d177" data-name="00d177" class="cls-1" d="M17,14,15.4,9.21a6.52,6.52,0,1,0-3.19,3.19Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 319 B

View File

@ -1 +0,0 @@
<svg id="icon-messages" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><defs><style>.cls-1{fill:#d8d8d8;}</style></defs><title>icon-messages-d8d8d8</title><path id="d8d8d8" class="cls-1" d="M17,14,15.4,9.21a6.52,6.52,0,1,0-3.19,3.19Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 299 B

View File

@ -1 +0,0 @@
<svg id="icon-messages" xmlns="http://www.w3.org/2000/svg" width="14" height="114" viewBox="0 0 14 114"><defs><style>.cls-1{fill:#d8d8d8;}.cls-2{fill:#00d177;}</style></defs><title>icon-messages-sprite</title><path id="d8d8d8" class="cls-1" d="M17,114l-1.6-4.79a6.52,6.52,0,1,0-3.19,3.19Z" transform="translate(-3)"/><path id="_00d177" data-name="00d177" class="cls-2" d="M17,14,15.4,9.21a6.52,6.52,0,1,0-3.19,3.19Z" transform="translate(-3)"/></svg>

Before

Width:  |  Height:  |  Size: 450 B

View File

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="16" height="14" viewBox="0 0 16 14"><defs><style>.cls-1{fill:#b2f1d5;}.cls-2{fill:#333;}</style></defs><title>icon-new-gab</title><path class="cls-1" d="M8,11.5a8.14,8.14,0,0,1-2.45-.38l-.71-.22-.61.43a6.62,6.62,0,0,1-1.79.9A8.09,8.09,0,0,0,3.06,11l.33-.88-.64-.68A4.28,4.28,0,0,1,1.5,6.5c0-2.76,2.92-5,6.5-5s6.5,2.24,6.5,5S11.59,11.5,8,11.5Z"/><path class="cls-2" d="M4.5,5.5a1,1,0,1,0,1,1A1,1,0,0,0,4.5,5.5ZM8,5.5a1,1,0,1,0,1,1A1,1,0,0,0,8,5.5Zm3.5,0a1,1,0,1,0,1,1A1,1,0,0,0,11.5,5.5ZM8,0C3.58,0,0,2.91,0,6.5a5.72,5.72,0,0,0,1.66,3.95A8,8,0,0,1,.21,12.73.75.75,0,0,0,.75,14,7.55,7.55,0,0,0,5.1,12.55,9.47,9.47,0,0,0,8,13c4.42,0,8-2.91,8-6.5S12.42,0,8,0ZM8,11.5a8.14,8.14,0,0,1-2.45-.38l-.71-.22-.61.43a6.62,6.62,0,0,1-1.79.9A8.09,8.09,0,0,0,3.06,11l.33-.88-.64-.68A4.28,4.28,0,0,1,1.5,6.5c0-2.76,2.92-5,6.5-5s6.5,2.24,6.5,5S11.59,11.5,8,11.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 933 B

View File

@ -1 +0,0 @@
<svg id="icon-notifications" xmlns="http://www.w3.org/2000/svg" width="13" height="14" viewBox="0 0 13 14"><defs><style>.cls-1{fill:#00d177;}</style></defs><title>icon-notifications-00d177</title><g id="_00d177" data-name="00d177"><path class="cls-1" d="M10,14a2,2,0,0,0,2-2H8A2,2,0,0,0,10,14Z" transform="translate(-3.5)"/><path class="cls-1" d="M16.29,10.73,15.1,9.4a3.14,3.14,0,0,1-.68-1.79V5.7a4.41,4.41,0,0,0-3.58-4.34V.84a.84.84,0,0,0-1.68,0v.52A4.41,4.41,0,0,0,5.58,5.7V7.61A3.14,3.14,0,0,1,4.9,9.4L3.71,10.73c-.38.42-.22.77.34.77H16C16.51,11.5,16.67,11.15,16.29,10.73Z" transform="translate(-3.5)"/></g></svg>

Before

Width:  |  Height:  |  Size: 617 B

Some files were not shown because too many files have changed in this diff Show More