mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-26 15:31:52 +00:00
Compare commits
1 Commits
e961f4a39c
...
01b389a016
Author | SHA1 | Date | |
---|---|---|---|
|
01b389a016 |
15
Gemfile.lock
15
Gemfile.lock
|
@ -93,9 +93,10 @@ GEM
|
|||
annotaterb (4.13.0)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.2)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1013.0)
|
||||
aws-sdk-core (3.214.0)
|
||||
aws-partitions (1.1012.0)
|
||||
aws-sdk-core (3.213.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
|
@ -103,7 +104,7 @@ GEM
|
|||
aws-sdk-kms (1.96.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.174.0)
|
||||
aws-sdk-s3 (1.173.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
|
@ -348,8 +349,7 @@ GEM
|
|||
json-schema (5.1.0)
|
||||
addressable (~> 2.8)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.9.3)
|
||||
base64
|
||||
jwt (2.7.1)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
|
@ -752,7 +752,7 @@ GEM
|
|||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
securerandom (0.3.2)
|
||||
selenium-webdriver (4.27.0)
|
||||
selenium-webdriver (4.26.0)
|
||||
base64 (~> 0.2)
|
||||
logger (~> 1.4)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
|
@ -844,8 +844,9 @@ GEM
|
|||
public_suffix
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webauthn (3.2.2)
|
||||
webauthn (3.1.0)
|
||||
android_key_attestation (~> 0.3.0)
|
||||
awrence (~> 1.1)
|
||||
bindata (~> 2.4)
|
||||
cbor (~> 0.5.9)
|
||||
cose (~> 1.1)
|
||||
|
|
|
@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :use_seamless_external_login?
|
||||
helper_method :sso_account_settings
|
||||
helper_method :limited_federation_mode?
|
||||
helper_method :body_class_string
|
||||
helper_method :skip_csrf_meta_tags?
|
||||
|
||||
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||
|
@ -157,6 +158,10 @@ class ApplicationController < ActionController::Base
|
|||
current_user.setting_theme
|
||||
end
|
||||
|
||||
def body_class_string
|
||||
@body_classes || ''
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
||||
|
|
|
@ -28,7 +28,7 @@ module CacheConcern
|
|||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields]&.join(',')].compact.join(':')
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||
expires_in = options.delete(:expires_in) || 3.minutes
|
||||
body = Rails.cache.read(key, raw: true)
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def body_classes
|
||||
output = []
|
||||
output = body_class_string.split
|
||||
output << content_for(:body_classes)
|
||||
output << "theme-#{current_theme.parameterize}"
|
||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
||||
});
|
||||
|
||||
class FilterBar extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
selectFilter: PropTypes.func.isRequired,
|
||||
selectedFilter: PropTypes.string.isRequired,
|
||||
advancedMode: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
onClick (notificationType) {
|
||||
return () => this.props.selectFilter(notificationType);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { selectedFilter, advancedMode, intl } = this.props;
|
||||
const renderedElement = !advancedMode ? (
|
||||
<div className='notification__filter-bar'>
|
||||
<button
|
||||
className={selectedFilter === 'all' ? 'active' : ''}
|
||||
onClick={this.onClick('all')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='notifications.filter.all'
|
||||
defaultMessage='All'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'mention' ? 'active' : ''}
|
||||
onClick={this.onClick('mention')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='notifications.filter.mentions'
|
||||
defaultMessage='Mentions'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className='notification__filter-bar'>
|
||||
<button
|
||||
className={selectedFilter === 'all' ? 'active' : ''}
|
||||
onClick={this.onClick('all')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='notifications.filter.all'
|
||||
defaultMessage='All'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'mention' ? 'active' : ''}
|
||||
onClick={this.onClick('mention')}
|
||||
title={intl.formatMessage(tooltips.mentions)}
|
||||
>
|
||||
<Icon id='reply-all' icon={ReplyAllIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'favourite' ? 'active' : ''}
|
||||
onClick={this.onClick('favourite')}
|
||||
title={intl.formatMessage(tooltips.favourites)}
|
||||
>
|
||||
<Icon id='star' icon={StarIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'reblog' ? 'active' : ''}
|
||||
onClick={this.onClick('reblog')}
|
||||
title={intl.formatMessage(tooltips.boosts)}
|
||||
>
|
||||
<Icon id='retweet' icon={RepeatIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'poll' ? 'active' : ''}
|
||||
onClick={this.onClick('poll')}
|
||||
title={intl.formatMessage(tooltips.polls)}
|
||||
>
|
||||
<Icon id='tasks' icon={InsertChartIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'status' ? 'active' : ''}
|
||||
onClick={this.onClick('status')}
|
||||
title={intl.formatMessage(tooltips.statuses)}
|
||||
>
|
||||
<Icon id='home' icon={HomeIcon} />
|
||||
</button>
|
||||
<button
|
||||
className={selectedFilter === 'follow' ? 'active' : ''}
|
||||
onClick={this.onClick('follow')}
|
||||
title={intl.formatMessage(tooltips.follows)}
|
||||
>
|
||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
return renderedElement;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(FilterBar);
|
|
@ -0,0 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { setFilter } from '../../../actions/notifications';
|
||||
import FilterBar from '../components/filter_bar';
|
||||
|
||||
const makeMapStateToProps = state => ({
|
||||
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
|
||||
advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
selectFilter (newActiveFilter) {
|
||||
dispatch(setFilter(newActiveFilter));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);
|
308
app/javascript/mastodon/features/notifications/index.jsx
Normal file
308
app/javascript/mastodon/features/notifications/index.jsx
Normal file
|
@ -0,0 +1,308 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import { compareId } from 'mastodon/compare_id';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { submitMarkers } from '../../actions/markers';
|
||||
import {
|
||||
expandNotifications,
|
||||
scrollTopNotifications,
|
||||
loadPending,
|
||||
mountNotifications,
|
||||
unmountNotifications,
|
||||
markNotificationsAsRead,
|
||||
} from '../../actions/notifications';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { LoadGap } from '../../components/load_gap';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
|
||||
import {
|
||||
FilteredNotificationsBanner,
|
||||
FilteredNotificationsIconButton,
|
||||
} from './components/filtered_notifications_banner';
|
||||
import NotificationsPermissionBanner from './components/notifications_permission_banner';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import FilterBarContainer from './containers/filter_bar_container';
|
||||
import NotificationContainer from './containers/notification_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||
markAsRead : { id: 'notifications.mark_as_read', defaultMessage: 'Mark every notification as read' },
|
||||
});
|
||||
|
||||
const getExcludedTypes = createSelector([
|
||||
state => state.getIn(['settings', 'notifications', 'shows']),
|
||||
], (shows) => {
|
||||
return ImmutableList(shows.filter(item => !item).keys());
|
||||
});
|
||||
|
||||
const getNotifications = createSelector([
|
||||
state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
|
||||
state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
|
||||
getExcludedTypes,
|
||||
state => state.getIn(['notifications', 'items']),
|
||||
], (showFilterBar, allowedType, excludedTypes, notifications) => {
|
||||
if (!showFilterBar || allowedType === 'all') {
|
||||
// used if user changed the notification settings after loading the notifications from the server
|
||||
// otherwise a list of notifications will come pre-filtered from the backend
|
||||
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
|
||||
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
|
||||
}
|
||||
return notifications.filter(item => item === null || allowedType === item.get('type'));
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
notifications: getNotifications(state),
|
||||
isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0,
|
||||
isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
|
||||
hasMore: state.getIn(['notifications', 'hasMore']),
|
||||
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
|
||||
lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
|
||||
canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
|
||||
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
|
||||
});
|
||||
|
||||
class Notifications extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
columnId: PropTypes.string,
|
||||
notifications: ImmutablePropTypes.list.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
numPending: PropTypes.number,
|
||||
lastReadId: PropTypes.string,
|
||||
canMarkAsRead: PropTypes.bool,
|
||||
needsNotificationPermission: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
trackScroll: true,
|
||||
};
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.props.dispatch(mountNotifications());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.handleLoadOlder.cancel();
|
||||
this.handleScrollToTop.cancel();
|
||||
this.handleScroll.cancel();
|
||||
this.props.dispatch(scrollTopNotifications(false));
|
||||
this.props.dispatch(unmountNotifications());
|
||||
}
|
||||
|
||||
handleLoadGap = (maxId) => {
|
||||
this.props.dispatch(expandNotifications({ maxId }));
|
||||
};
|
||||
|
||||
handleLoadOlder = debounce(() => {
|
||||
const last = this.props.notifications.last();
|
||||
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
|
||||
}, 300, { leading: true });
|
||||
|
||||
handleLoadPending = () => {
|
||||
this.props.dispatch(loadPending());
|
||||
};
|
||||
|
||||
handleScrollToTop = debounce(() => {
|
||||
this.props.dispatch(scrollTopNotifications(true));
|
||||
}, 100);
|
||||
|
||||
handleScroll = debounce(() => {
|
||||
this.props.dispatch(scrollTopNotifications(false));
|
||||
}, 100);
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('NOTIFICATIONS', {}));
|
||||
}
|
||||
};
|
||||
|
||||
handleMove = (dir) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
dispatch(moveColumn(columnId, dir));
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
};
|
||||
|
||||
setColumnRef = c => {
|
||||
this.column = c;
|
||||
};
|
||||
|
||||
handleMoveUp = id => {
|
||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
||||
this._selectChild(elementIndex, true);
|
||||
};
|
||||
|
||||
handleMoveDown = id => {
|
||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
||||
this._selectChild(elementIndex, false);
|
||||
};
|
||||
|
||||
_selectChild (index, align_top) {
|
||||
const container = this.column.node;
|
||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
||||
|
||||
if (element) {
|
||||
if (align_top && container.scrollTop > element.offsetTop) {
|
||||
element.scrollIntoView(true);
|
||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||
element.scrollIntoView(false);
|
||||
}
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleMarkAsRead = () => {
|
||||
this.props.dispatch(markNotificationsAsRead());
|
||||
this.props.dispatch(submitMarkers({ immediate: true }));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
|
||||
const pinned = !!columnId;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
|
||||
const { signedIn } = this.props.identity;
|
||||
|
||||
let scrollableContent = null;
|
||||
|
||||
const filterBarContainer = signedIn
|
||||
? (<FilterBarContainer />)
|
||||
: null;
|
||||
|
||||
if (isLoading && this.scrollableContent) {
|
||||
scrollableContent = this.scrollableContent;
|
||||
} else if (notifications.size > 0 || hasMore) {
|
||||
scrollableContent = notifications.map((item, index) => item === null ? (
|
||||
<LoadGap
|
||||
key={'gap:' + notifications.getIn([index + 1, 'id'])}
|
||||
disabled={isLoading}
|
||||
param={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
|
||||
onClick={this.handleLoadGap}
|
||||
/>
|
||||
) : (
|
||||
<NotificationContainer
|
||||
key={item.get('id')}
|
||||
notification={item}
|
||||
accountId={item.get('account')}
|
||||
onMoveUp={this.handleMoveUp}
|
||||
onMoveDown={this.handleMoveDown}
|
||||
unread={lastReadId !== '0' && compareId(item.get('id'), lastReadId) > 0}
|
||||
/>
|
||||
));
|
||||
} else {
|
||||
scrollableContent = null;
|
||||
}
|
||||
|
||||
this.scrollableContent = scrollableContent;
|
||||
|
||||
let scrollContainer;
|
||||
|
||||
const prepend = (
|
||||
<>
|
||||
{needsNotificationPermission && <NotificationsPermissionBanner />}
|
||||
<FilteredNotificationsBanner />
|
||||
</>
|
||||
);
|
||||
|
||||
if (signedIn) {
|
||||
scrollContainer = (
|
||||
<ScrollableList
|
||||
scrollKey={`notifications-${columnId}`}
|
||||
trackScroll={!pinned}
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && notifications.size === 0}
|
||||
hasMore={hasMore}
|
||||
numPending={numPending}
|
||||
prepend={prepend}
|
||||
alwaysPrepend
|
||||
emptyMessage={emptyMessage}
|
||||
onLoadMore={this.handleLoadOlder}
|
||||
onLoadPending={this.handleLoadPending}
|
||||
onScrollToTop={this.handleScrollToTop}
|
||||
onScroll={this.handleScroll}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{scrollableContent}
|
||||
</ScrollableList>
|
||||
);
|
||||
} else {
|
||||
scrollContainer = <NotSignedInIndicator />;
|
||||
}
|
||||
|
||||
const extraButton = (
|
||||
<>
|
||||
<FilteredNotificationsIconButton className='column-header__button' />
|
||||
{canMarkAsRead && (
|
||||
<button
|
||||
aria-label={intl.formatMessage(messages.markAsRead)}
|
||||
title={intl.formatMessage(messages.markAsRead)}
|
||||
onClick={this.handleMarkAsRead}
|
||||
className='column-header__button'
|
||||
>
|
||||
<Icon id='done-all' icon={DoneAllIcon} />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='bell'
|
||||
iconComponent={NotificationsIcon}
|
||||
active={isUnread}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
extraButton={extraButton}
|
||||
>
|
||||
<ColumnSettingsContainer />
|
||||
</ColumnHeader>
|
||||
|
||||
{filterBarContainer}
|
||||
|
||||
{scrollContainer}
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.title)}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));
|
|
@ -0,0 +1,9 @@
|
|||
import Notifications_v2 from 'mastodon/features/notifications_v2';
|
||||
|
||||
export const NotificationsWrapper = (props) => {
|
||||
return (
|
||||
<Notifications_v2 {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationsWrapper;
|
|
@ -8,7 +8,7 @@ import { scrollRight } from '../../../scroll';
|
|||
import BundleContainer from '../containers/bundle_container';
|
||||
import {
|
||||
Compose,
|
||||
Notifications,
|
||||
NotificationsWrapper,
|
||||
HomeTimeline,
|
||||
CommunityTimeline,
|
||||
PublicTimeline,
|
||||
|
@ -30,7 +30,7 @@ import NavigationPanel from './navigation_panel';
|
|||
const componentMap = {
|
||||
'COMPOSE': Compose,
|
||||
'HOME': HomeTimeline,
|
||||
'NOTIFICATIONS': Notifications,
|
||||
'NOTIFICATIONS': NotificationsWrapper,
|
||||
'PUBLIC': PublicTimeline,
|
||||
'REMOTE': PublicTimeline,
|
||||
'COMMUNITY': CommunityTimeline,
|
||||
|
|
|
@ -49,7 +49,7 @@ import {
|
|||
Favourites,
|
||||
DirectTimeline,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
NotificationsWrapper,
|
||||
NotificationRequests,
|
||||
NotificationRequest,
|
||||
FollowRequests,
|
||||
|
@ -211,7 +211,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/lists/:id/edit' component={ListEdit} content={children} />
|
||||
<WrappedRoute path='/lists/:id/members' component={ListMembers} content={children} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} exact />
|
||||
<WrappedRoute path='/notifications' component={NotificationsWrapper} content={children} exact />
|
||||
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
||||
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
|
|
|
@ -7,7 +7,15 @@ export function Compose () {
|
|||
}
|
||||
|
||||
export function Notifications () {
|
||||
return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2');
|
||||
return import(/* webpackChunkName: "features/notifications_v1" */'../../notifications');
|
||||
}
|
||||
|
||||
export function Notifications_v2 () {
|
||||
return import(/* webpackChunkName: "features/notifications_v2" */'../../notifications_v2');
|
||||
}
|
||||
|
||||
export function NotificationsWrapper () {
|
||||
return import(/* webpackChunkName: "features/notifications" */'../../notifications_wrapper');
|
||||
}
|
||||
|
||||
export function HomeTimeline () {
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
"account.domain_blocked": "域名已屏蔽",
|
||||
"account.edit_profile": "修改个人资料",
|
||||
"account.enable_notifications": "当 @{name} 发布嘟文时通知我",
|
||||
"account.endorse": "在账户页推荐此用户",
|
||||
"account.featured_tags.last_status_at": "上次发言于 {date}",
|
||||
"account.endorse": "在个人资料中推荐此用户",
|
||||
"account.featured_tags.last_status_at": "最近发言于 {date}",
|
||||
"account.featured_tags.last_status_never": "暂无嘟文",
|
||||
"account.featured_tags.title": "{name} 的精选标签",
|
||||
"account.follow": "关注",
|
||||
|
@ -105,7 +105,7 @@
|
|||
"annual_report.summary.new_posts.new_posts": "发嘟",
|
||||
"annual_report.summary.percentile.text": "<topLabel>这使你跻身 Mastodon 用户的前</topLabel><percentage></percentage><bottomLabel></bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉扣税国王的(他知道的话要来收你发嘟税了)。",
|
||||
"annual_report.summary.thanks": "感谢你这一年和 Mastodon 上的大家一起嘟嘟!",
|
||||
"annual_report.summary.thanks": "感谢你这一年与 Mastodon 一路同行!",
|
||||
"attachments_list.unprocessed": "(未处理)",
|
||||
"audio.hide": "隐藏音频",
|
||||
"block_modal.remote_users_caveat": "我们将要求服务器 {domain} 尊重你的决定。然而,我们无法保证对方一定遵从,因为某些服务器可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录的用户可见。",
|
||||
|
@ -138,7 +138,7 @@
|
|||
"closed_registrations_modal.title": "注册 Mastodon 账号",
|
||||
"column.about": "关于",
|
||||
"column.blocks": "屏蔽的用户",
|
||||
"column.bookmarks": "收藏夹",
|
||||
"column.bookmarks": "书签",
|
||||
"column.community": "本站时间线",
|
||||
"column.create_list": "创建列表",
|
||||
"column.direct": "私下提及",
|
||||
|
@ -510,7 +510,7 @@
|
|||
"navigation_bar.administration": "管理",
|
||||
"navigation_bar.advanced_interface": "在高级网页界面中打开",
|
||||
"navigation_bar.blocks": "已屏蔽的用户",
|
||||
"navigation_bar.bookmarks": "收藏夹",
|
||||
"navigation_bar.bookmarks": "书签",
|
||||
"navigation_bar.community_timeline": "本站时间线",
|
||||
"navigation_bar.compose": "撰写新嘟文",
|
||||
"navigation_bar.direct": "私下提及",
|
||||
|
@ -555,7 +555,7 @@
|
|||
"notification.label.reply": "回复",
|
||||
"notification.mention": "提及",
|
||||
"notification.mentioned_you": "{name} 提到了你",
|
||||
"notification.moderation-warning.learn_more": "详细了解",
|
||||
"notification.moderation-warning.learn_more": "了解更多",
|
||||
"notification.moderation_warning": "你收到了一条管理警告",
|
||||
"notification.moderation_warning.action_delete_statuses": "你的一些嘟文已被移除。",
|
||||
"notification.moderation_warning.action_disable": "你的账号已被禁用。",
|
||||
|
@ -571,7 +571,7 @@
|
|||
"notification.relationships_severance_event": "与 {name} 的联系已断开",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} 的管理员封禁了 {target},这意味着你将无法再收到对方的更新或与其互动。",
|
||||
"notification.relationships_severance_event.domain_block": "{from} 的管理员屏蔽了 {target},其中包括你的 {followersCount} 个关注者和 {followingCount, plural, other {# 个关注}}。",
|
||||
"notification.relationships_severance_event.learn_more": "详细了解",
|
||||
"notification.relationships_severance_event.learn_more": "了解更多",
|
||||
"notification.relationships_severance_event.user_domain_block": "你已经屏蔽了 {target},移除了你的 {followersCount} 个关注者和 {followingCount, plural, other {# 个关注}}。",
|
||||
"notification.status": "{name} 刚刚发布嘟文",
|
||||
"notification.update": "{name} 编辑了嘟文",
|
||||
|
@ -717,11 +717,11 @@
|
|||
"regeneration_indicator.label": "加载中…",
|
||||
"regeneration_indicator.sublabel": "你的主页动态正在准备中!",
|
||||
"relative_time.days": "{number} 天前",
|
||||
"relative_time.full.days": "{number, plural, other {# 天}}前",
|
||||
"relative_time.full.hours": "{number, plural, other {# 小时}}前",
|
||||
"relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
|
||||
"relative_time.full.hours": "{number, plural, one {# 小时} other {# 小时}}前",
|
||||
"relative_time.full.just_now": "刚刚",
|
||||
"relative_time.full.minutes": "{number, plural, other {# 分钟}}前",
|
||||
"relative_time.full.seconds": "{number, plural, other {# 秒}}前",
|
||||
"relative_time.full.minutes": "{number, plural, one {# 分钟} other {# 分钟}}前",
|
||||
"relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
|
||||
"relative_time.hours": "{number} 小时前",
|
||||
"relative_time.just_now": "刚刚",
|
||||
"relative_time.minutes": "{number} 分钟前",
|
||||
|
@ -817,7 +817,7 @@
|
|||
"status.admin_domain": "打开 {domain} 的管理界面",
|
||||
"status.admin_status": "打开此帖的管理界面",
|
||||
"status.block": "屏蔽 @{name}",
|
||||
"status.bookmark": "收藏",
|
||||
"status.bookmark": "添加到书签",
|
||||
"status.cancel_reblog_private": "取消转贴",
|
||||
"status.cannot_reblog": "这条嘟文不允许被转嘟",
|
||||
"status.continued_thread": "上接嘟文串",
|
||||
|
@ -853,7 +853,7 @@
|
|||
"status.reblogs": "{count, plural, other {次转嘟}}",
|
||||
"status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。",
|
||||
"status.redraft": "删除并重新编辑",
|
||||
"status.remove_bookmark": "取消收藏",
|
||||
"status.remove_bookmark": "移除书签",
|
||||
"status.replied_in_thread": "回复给嘟文串",
|
||||
"status.replied_to": "回复给 {name}",
|
||||
"status.reply": "回复",
|
||||
|
@ -875,11 +875,11 @@
|
|||
"subscribed_languages.target": "更改 {target} 的订阅语言",
|
||||
"tabs_bar.home": "主页",
|
||||
"tabs_bar.notifications": "通知",
|
||||
"time_remaining.days": "剩余 {number, plural, other {# 天}}",
|
||||
"time_remaining.hours": "剩余 {number, plural, other {# 小时}}",
|
||||
"time_remaining.minutes": "剩余 {number, plural, other {# 分钟}}",
|
||||
"time_remaining.days": "剩余 {number, plural, one {# 天} other {# 天}}",
|
||||
"time_remaining.hours": "剩余 {number, plural, one {# 小时} other {# 小时}}",
|
||||
"time_remaining.minutes": "剩余 {number, plural, one {# 分钟} other {# 分钟}}",
|
||||
"time_remaining.moments": "即将结束",
|
||||
"time_remaining.seconds": "剩余 {number, plural, other {# 秒}}",
|
||||
"time_remaining.seconds": "剩余 {number, plural, one {# 秒} other {# 秒}}",
|
||||
"trends.counter_by_accounts": "过去 {days, plural, other {{days} 天}}有{count, plural, other { {counter} 人}}讨论",
|
||||
"trends.trending_now": "当前热门",
|
||||
"ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会丢失。",
|
||||
|
|
|
@ -142,10 +142,6 @@ class UserRole < ApplicationRecord
|
|||
other_role.nil? || position > other_role.position
|
||||
end
|
||||
|
||||
def bypass_block?(role)
|
||||
overrides?(role) && highlighted? && can?(*Flags::CATEGORIES[:moderation])
|
||||
end
|
||||
|
||||
def computed_permissions
|
||||
# If called on the everyone role, no further computation needed
|
||||
return permissions if everyone?
|
||||
|
|
|
@ -134,7 +134,7 @@ class NotifyService < BaseService
|
|||
end
|
||||
|
||||
def from_staff?
|
||||
@sender.local? && @sender.user.present? && @sender.user_role&.bypass_block?(@recipient.user_role)
|
||||
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
|
||||
end
|
||||
|
||||
def from_self?
|
||||
|
|
|
@ -226,26 +226,20 @@ bg:
|
|||
approve_appeal_html: "%{name} одобри обжалването на решение за модериране от %{target}"
|
||||
approve_user_html: "%{name} одобри регистрирането от %{target}"
|
||||
assigned_to_self_report_html: "%{name} възложи на себе си доклад %{target}"
|
||||
change_email_user_html: "%{name} промени адреса на имейла на потребителя %{target}"
|
||||
change_role_user_html: "%{name} промени ролята на %{target}"
|
||||
confirm_user_html: "%{name} потвърди адреса на имейла на потребителя %{target}"
|
||||
create_account_warning_html: "%{name} изпрати предупреждение до %{target}"
|
||||
create_announcement_html: "%{name} създаде ново оповестяване %{target}"
|
||||
create_canonical_email_block_html: "%{name} блокира имейл с хеш %{target}"
|
||||
create_custom_emoji_html: "%{name} качи ново емоджи %{target}"
|
||||
create_domain_allow_html: "%{name} позволи федерирането с домейн %{target}"
|
||||
create_domain_block_html: "%{name} блокира домейн %{target}"
|
||||
create_email_domain_block_html: "%{name} блокира домейн за е-поща %{target}"
|
||||
create_ip_block_html: "%{name} създаде правило за IP %{target}"
|
||||
create_unavailable_domain_html: "%{name} спря доставянето до домейн %{target}"
|
||||
create_user_role_html: "%{name} създаде роля %{target}"
|
||||
demote_user_html: "%{name} понижи потребителя %{target}"
|
||||
destroy_announcement_html: "%{name} изтри оповестяване %{target}"
|
||||
destroy_canonical_email_block_html: "%{name} отблокира имейла с хеш %{target}"
|
||||
destroy_custom_emoji_html: "%{name} изтри емоджито %{target}"
|
||||
destroy_domain_allow_html: "%{name} забрани федерирация с домейн %{target}"
|
||||
destroy_domain_block_html: "%{name} отблокира домейн %{target}"
|
||||
destroy_email_domain_block_html: "%{name} отблокира домейн за е-поща %{target}"
|
||||
destroy_instance_html: "%{name} прочисти домейн %{target}"
|
||||
destroy_ip_block_html: "%{name} изтри правило за IP %{target}"
|
||||
destroy_status_html: "%{name} премахна публикация от %{target}"
|
||||
|
|
|
@ -1180,12 +1180,12 @@ de:
|
|||
use_security_key: Sicherheitsschlüssel verwenden
|
||||
author_attribution:
|
||||
example_title: Beispieltext
|
||||
hint_html: Schreibst du außerhalb von Mastodon journalistische Artikel oder andere Texte, beispielsweise in einem Blog? Lege hier fest, wann auf dein Profil verwiesen werden soll, wenn Links zu deinen Werken auf Mastodon geteilt werden.
|
||||
instructions: 'Der nachfolgende Code muss im HTML-Header deines zu verlinkenden Textes stehen:'
|
||||
hint_html: Schreibst du außerhalb von Mastodon Nachrichtenartikel oder betreibst du einen Blog? Bestimme, wie du Anerkennungen durch geteilte Links auf Mastodon handhaben möchtest.
|
||||
instructions: 'Der nachfolgende Code muss im HTML-Code deines Artikels sein:'
|
||||
more_from_html: Mehr von %{name}
|
||||
s_blog: Blog von %{name}
|
||||
then_instructions: Ergänze anschließend im unteren Feld die Domain, auf der sich deine Inhalte befinden.
|
||||
title: Verifizierung als Autor*in
|
||||
then_instructions: Ergänze die Domain, auf der deine Inhalte veröffentlicht werden in das unten stehende Feld.
|
||||
title: Anerkennung als Autor*in
|
||||
challenge:
|
||||
confirm: Fortfahren
|
||||
hint_html: "<strong>Hinweis:</strong> Wir werden dich für die nächste Stunde nicht erneut nach deinem Passwort fragen."
|
||||
|
@ -1625,7 +1625,7 @@ de:
|
|||
posting_defaults: Standardeinstellungen für Beiträge
|
||||
public_timelines: Öffentliche Timelines
|
||||
privacy:
|
||||
hint_html: "<strong>Bestimme selbst, wie dein Profil und deine Beiträge gefunden werden sollen.</strong> Zahlreiche Mastodon-Funktionen können dir für eine größere Reichweite behilflich sein. Nimm dir einen Moment Zeit, um diese Einstellungen zu überprüfen."
|
||||
hint_html: "<strong>Bestimme, wie dein Profil und deine Beiträge gefunden werden sollen.</strong> Eine Vielzahl von Funktionen in Mastodon können dir helfen, eine größere Reichweite zu erlangen, wenn sie aktiviert sind. Nimm dir einen Moment Zeit, um diese Einstellungen zu überprüfen und sicherzustellen, dass sie für deinen Anwendungsfall geeignet sind."
|
||||
privacy: Datenschutz
|
||||
privacy_hint_html: Bestimme, wie viele Informationen du für andere preisgeben möchtest. Viele Menschen entdecken interessante Profile und coole Apps, indem sie die Follower anderer Profile durchstöbern und die Apps sehen, über die Beiträge veröffentlicht wurden – möglicherweise möchtest du diese Informationen ausblenden.
|
||||
reach: Reichweite
|
||||
|
@ -1801,7 +1801,7 @@ de:
|
|||
enabled: Alte Beiträge automatisch entfernen
|
||||
enabled_hint: Löscht automatisch deine Beiträge, sobald sie die angegebene Altersgrenze erreicht haben, es sei denn, sie entsprechen einer der unten angegebenen Ausnahmen
|
||||
exceptions: Ausnahmen
|
||||
explanation: Damit der Server nicht durch das Löschen von Beiträgen ausgebremst wird, wartet die Mastodon-Software, bis wenig(er) los ist. Deshalb könnten deine Beiträge ggf. erst einige Zeit nach Erreichen der Altersgrenze gelöscht werden.
|
||||
explanation: Damit Mastodon nicht durch das Löschen von Beiträgen ausgebremst wird, wartet der Server damit, bis wenig los ist. Aus diesem Grund werden deine Beiträge ggf. erst einige Zeit nach Erreichen der Altersgrenze gelöscht.
|
||||
ignore_favs: Favoriten ignorieren
|
||||
ignore_reblogs: Geteilte Beiträge ignorieren
|
||||
interaction_exceptions: Ausnahmen basierend auf Interaktionen
|
||||
|
@ -1980,13 +1980,13 @@ de:
|
|||
seamless_external_login: Du bist über einen externen Dienst angemeldet, daher sind Passwort- und E-Mail-Einstellungen nicht verfügbar.
|
||||
signed_in_as: 'Angemeldet als:'
|
||||
verification:
|
||||
extra_instructions_html: <strong>Hinweis:</strong> Der Link auf deiner Website kann unsichtbar sein. Der wichtige Teil ist <code>rel="me"</code>. Du kannst auch den Tag <code>link</code> im <code>head</code> (statt <code>a</code> im <code>body</code>) verwenden, jedoch muss die Internetseite ohne JavaScript abrufbar sein.
|
||||
extra_instructions_html: <strong>Hinweis:</strong> Der Link auf deiner Website kann unsichtbar sein. Der wichtige Teil ist <code>rel="me"</code>, wodurch das Nachahmen von Personen auf Websites mit nutzergenerierten Inhalten verhindert wird. Du kannst auch ein <code>link</code>-Tag statt <code>a</code> im Header auf der Seite verwenden, jedoch muss der HTML-Code ohne das Ausführen von JavaScript zugänglich sein.
|
||||
here_is_how: So funktioniert’s
|
||||
hint_html: "<strong>Alle können ihre Identität auf Mastodon verifizieren.</strong> Basierend auf offenen Standards – jetzt und für immer kostenlos. Alles, was du brauchst, ist eine eigene Website. Wenn du von deinem Profil auf diese Website verlinkst, überprüfen wir, ob die Website zu deinem Profil zurückverlinkt, und zeigen einen visuellen Hinweis an."
|
||||
instructions_html: Kopiere den unten stehenden Code und füge ihn in den HTML-Code deiner Website ein. Trage anschließend die Adresse deiner Website in ein Zusatzfeld auf deinem Profil ein und speichere die Änderungen. Die Zusatzfelder befinden sich im Reiter „Profil bearbeiten“.
|
||||
verification: Verifizierung
|
||||
verified_links: Deine verifizierten Domains
|
||||
website_verification: Verifizierung einer Website
|
||||
verified_links: Deine verifizierten Links
|
||||
website_verification: Website-Verifizierung
|
||||
webauthn_credentials:
|
||||
add: Sicherheitsschlüssel hinzufügen
|
||||
create:
|
||||
|
|
|
@ -125,7 +125,7 @@ zh-CN:
|
|||
admin/reports: 举报管理
|
||||
all: 完全访问你的Mastodon账户
|
||||
blocks: 屏蔽
|
||||
bookmarks: 收藏
|
||||
bookmarks: 书签
|
||||
conversations: 会话
|
||||
crypto: 端到端加密
|
||||
favourites: 喜欢
|
||||
|
@ -172,7 +172,7 @@ zh-CN:
|
|||
read: 读取你的账户数据
|
||||
read:accounts: 查看账号信息
|
||||
read:blocks: 查看你的屏蔽列表
|
||||
read:bookmarks: 查看你的收藏夹
|
||||
read:bookmarks: 查看你的书签
|
||||
read:favourites: 查看喜欢的嘟文
|
||||
read:filters: 查看你的过滤规则
|
||||
read:follows: 查看你的关注
|
||||
|
@ -185,7 +185,7 @@ zh-CN:
|
|||
write: 修改你的账号数据
|
||||
write:accounts: 修改你的个人资料
|
||||
write:blocks: 屏蔽账号和域名
|
||||
write:bookmarks: 收藏嘟文
|
||||
write:bookmarks: 为嘟文添加书签
|
||||
write:conversations: 静音并删除会话
|
||||
write:favourites: 喜欢嘟文
|
||||
write:filters: 创建过滤规则
|
||||
|
|
|
@ -187,7 +187,7 @@ es-AR:
|
|||
create_domain_block: Crear bloqueo de dominio
|
||||
create_email_domain_block: Crear bloqueo de dominio de correo electrónico
|
||||
create_ip_block: Crear regla de dirección IP
|
||||
create_relay: Crear relé
|
||||
create_relay: Crear Relé
|
||||
create_unavailable_domain: Crear dominio no disponible
|
||||
create_user_role: Crear rol
|
||||
demote_user: Descender usuario
|
||||
|
@ -199,17 +199,17 @@ es-AR:
|
|||
destroy_email_domain_block: Eliminar bloqueo de dominio de correo electrónico
|
||||
destroy_instance: Purgar dominio
|
||||
destroy_ip_block: Eliminar regla de dirección IP
|
||||
destroy_relay: Eliminar relé
|
||||
destroy_relay: Eliminar Relé
|
||||
destroy_status: Eliminar mensaje
|
||||
destroy_unavailable_domain: Eliminar dominio no disponible
|
||||
destroy_user_role: Destruir rol
|
||||
disable_2fa_user: Deshabilitar 2FA
|
||||
disable_custom_emoji: Deshabilitar emoji personalizado
|
||||
disable_relay: Deshabilitar relé
|
||||
disable_relay: Desactivar Relé
|
||||
disable_sign_in_token_auth_user: Deshabilitar autenticación de token por correo electrónico para el usuario
|
||||
disable_user: Deshabilitar usuario
|
||||
enable_custom_emoji: Habilitar emoji personalizado
|
||||
enable_relay: Habilitar relé
|
||||
enable_relay: Activar Relé
|
||||
enable_sign_in_token_auth_user: Habilitar autenticación de token por correo electrónico para el usuario
|
||||
enable_user: Habilitar usuario
|
||||
memorialize_account: Convertir en cuenta conmemorativa
|
||||
|
@ -251,7 +251,7 @@ es-AR:
|
|||
create_domain_block_html: "%{name} bloqueó el dominio %{target}"
|
||||
create_email_domain_block_html: "%{name} bloqueó el dominio de correo electrónico %{target}"
|
||||
create_ip_block_html: "%{name} creó la regla para la dirección IP %{target}"
|
||||
create_relay_html: "%{name} creó el relé %{target}"
|
||||
create_relay_html: "%{name} creó un relé %{target}"
|
||||
create_unavailable_domain_html: "%{name} detuvo la entrega al dominio %{target}"
|
||||
create_user_role_html: "%{name} creó el rol %{target}"
|
||||
demote_user_html: "%{name} bajó de nivel al usuario %{target}"
|
||||
|
@ -269,11 +269,11 @@ es-AR:
|
|||
destroy_user_role_html: "%{name} eliminó el rol %{target}"
|
||||
disable_2fa_user_html: "%{name} deshabilitó el requerimiento de dos factores para el usuario %{target}"
|
||||
disable_custom_emoji_html: "%{name} deshabilitó el emoji %{target}"
|
||||
disable_relay_html: "%{name} deshabilitó el relé %{target}"
|
||||
disable_relay_html: "%{name} desactivó el relé %{target}"
|
||||
disable_sign_in_token_auth_user_html: "%{name} deshabilitó la autenticación de token por correo electrónico para %{target}"
|
||||
disable_user_html: "%{name} deshabilitó el inicio de sesión para el usuario %{target}"
|
||||
enable_custom_emoji_html: "%{name} habilitó el emoji %{target}"
|
||||
enable_relay_html: "%{name} eliminó el relé %{target}"
|
||||
enable_relay_html: "%{name} activó el relé %{target}"
|
||||
enable_sign_in_token_auth_user_html: "%{name} habilitó la autenticación de token por correo electrónico para %{target}"
|
||||
enable_user_html: "%{name} habilitó el inicio de sesión para el usuario %{target}"
|
||||
memorialize_account_html: "%{name} convirtió la cuenta de %{target} en una cuenta conmemorativa"
|
||||
|
|
|
@ -144,7 +144,7 @@ de:
|
|||
url: Wohin Ereignisse gesendet werden
|
||||
labels:
|
||||
account:
|
||||
attribution_domains_as_text: Websites, die auf dich verweisen dürfen
|
||||
attribution_domains_as_text: Websites, die dich anerkennen dürfen
|
||||
discoverable: Profil und Beiträge in Suchalgorithmen berücksichtigen
|
||||
fields:
|
||||
name: Beschriftung
|
||||
|
|
|
@ -1272,7 +1272,7 @@ zh-CN:
|
|||
request: 请求你的存档
|
||||
size: 大小
|
||||
blocks: 屏蔽的用户
|
||||
bookmarks: 收藏
|
||||
bookmarks: 书签
|
||||
csv: CSV
|
||||
domain_blocks: 域名屏蔽
|
||||
lists: 列表
|
||||
|
@ -1361,7 +1361,7 @@ zh-CN:
|
|||
blocking_html:
|
||||
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>账户<strong>替换你的屏蔽列表</strong>。
|
||||
bookmarks_html:
|
||||
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 条</strong>嘟文<strong>替换你的收藏列表</strong>。
|
||||
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 条</strong>嘟文<strong>替换你的书签</strong>。
|
||||
domain_blocking_html:
|
||||
other: 你即将使用来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个</strong>域名<strong>替换你的域名屏蔽列表</strong>。
|
||||
following_html:
|
||||
|
@ -1374,7 +1374,7 @@ zh-CN:
|
|||
blocking_html:
|
||||
other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个账号</strong>。
|
||||
bookmarks_html:
|
||||
other: 你即将把来自<strong> %{filename} </strong>的<strong> %{count} 篇嘟文</strong>添加到你的<strong>收藏夹</strong>中。
|
||||
other: 你即将把来自<strong> %{filename} </strong>的<strong> %{count} 篇嘟文</strong>添加到你的<strong>书签</strong>中。
|
||||
domain_blocking_html:
|
||||
other: 你即将<strong>屏蔽</strong>来自 <strong>%{filename}</strong> 的最多 <strong>%{count} 个域名</strong>。
|
||||
following_html:
|
||||
|
@ -1395,18 +1395,18 @@ zh-CN:
|
|||
time_started: 开始于
|
||||
titles:
|
||||
blocking: 正在导入被屏蔽的账户
|
||||
bookmarks: 正在导入收藏
|
||||
bookmarks: 正在导入书签
|
||||
domain_blocking: 正在导入被屏蔽的域名
|
||||
following: 正在导入关注的账户
|
||||
lists: 导入列表
|
||||
muting: 正在导入隐藏的账户
|
||||
type: 导入类型
|
||||
type_groups:
|
||||
constructive: 关注与收藏
|
||||
constructive: 关注和书签
|
||||
destructive: 屏蔽与隐藏
|
||||
types:
|
||||
blocking: 屏蔽列表
|
||||
bookmarks: 收藏
|
||||
bookmarks: 书签
|
||||
domain_blocking: 域名屏蔽列表
|
||||
following: 关注列表
|
||||
lists: 列表
|
||||
|
@ -1757,24 +1757,24 @@ zh-CN:
|
|||
unlisted_long: 对所有人可见,但不出现在公共时间线上
|
||||
statuses_cleanup:
|
||||
enabled: 自动删除旧嘟文
|
||||
enabled_hint: 自动删除你发布的超过指定期限的嘟文,除非满足下列条件之一
|
||||
enabled_hint: 达到指定过期时间后自动删除你的嘟文,除非满足下列条件之一
|
||||
exceptions: 例外
|
||||
explanation: 删除嘟文会占用大量服务器资源,所以这个操作将在服务器空闲时完成。因此,你的嘟文可能会在达到删除期限之后一段时间才会被删除。
|
||||
ignore_favs: 喜欢数阈值
|
||||
ignore_reblogs: 转嘟数阈值
|
||||
explanation: 删除嘟文是一个消耗系统资源的耗时操作,所以这个操作会在服务器空闲时完成。因此,你的嘟文可能会在达到过期阈值之后一段时间才会被删除。
|
||||
ignore_favs: 取消喜欢
|
||||
ignore_reblogs: 忽略转嘟
|
||||
interaction_exceptions: 基于互动的例外
|
||||
interaction_exceptions_explanation: 请注意,如果嘟文的转嘟数和喜欢数超过保留阈值之后,又降到阈值以下,则可能不会被删除。
|
||||
interaction_exceptions_explanation: 请注意,如果嘟文超出转嘟和喜欢的阈值之后,又降到阈值以下,则可能不会被删除。
|
||||
keep_direct: 保留私信
|
||||
keep_direct_hint: 不删除你的任何私信
|
||||
keep_direct_hint: 不会删除你的任何私信
|
||||
keep_media: 保留带媒体附件的嘟文
|
||||
keep_media_hint: 不删除任何包含媒体附件的嘟文
|
||||
keep_media_hint: 不会删除任何包含媒体附件的嘟文
|
||||
keep_pinned: 保留置顶嘟文
|
||||
keep_pinned_hint: 不删除你的任何置顶嘟文
|
||||
keep_pinned_hint: 不会删除你的任何置顶嘟文
|
||||
keep_polls: 保留投票
|
||||
keep_polls_hint: 不删除你的任何投票
|
||||
keep_self_bookmark: 保存你收藏的的嘟文
|
||||
keep_self_bookmark_hint: 不删除你收藏的嘟文
|
||||
keep_self_fav: 保留你喜欢的嘟文
|
||||
keep_polls_hint: 不会删除你的任何投票
|
||||
keep_self_bookmark: 保存被你加入书签的嘟文
|
||||
keep_self_bookmark_hint: 如果你已将自己的嘟文添加书签,就不会删除这些嘟文
|
||||
keep_self_fav: 保留你点赞的嘟文
|
||||
keep_self_fav_hint: 如果你喜欢了自己的嘟文,则不会删除这些嘟文
|
||||
min_age:
|
||||
'1209600': 2周
|
||||
|
@ -1786,8 +1786,8 @@ zh-CN:
|
|||
'63113904': 两年
|
||||
'7889238': 3个月
|
||||
min_age_label: 过期阈值
|
||||
min_favs: 保留如下嘟文:喜欢数超过
|
||||
min_favs_hint: 获得喜欢数超过该阈值的的嘟文都不会被删除。如果留空,则无论嘟文获得多少点赞,都将被删除。
|
||||
min_favs: 保留如下嘟文:点赞数超过
|
||||
min_favs_hint: 点赞数超过该阈值的的嘟文都不会被删除。如果留空,则无论嘟文获得多少点赞,都将被删除。
|
||||
min_reblogs: 保留如下嘟文:转嘟数超过
|
||||
min_reblogs_hint: 转嘟数超过该阈值的的嘟文不会被删除。如果留空,则无论嘟文获得多少转嘟,都将被删除。
|
||||
stream_entries:
|
||||
|
@ -1899,13 +1899,13 @@ zh-CN:
|
|||
edit_profile_step: 完善个人资料,提升你的互动体验。
|
||||
edit_profile_title: 个性化你的个人资料
|
||||
explanation: 下面是几个小贴士,希望它们能帮到你
|
||||
feature_action: 详细了解
|
||||
feature_action: 了解更多
|
||||
feature_audience: Mastodon 为你提供了无需中间商即可管理受众的独特可能。Mastodon 可被部署在你自己的基础设施上,允许你关注其它任何 Mastodon 在线服务器的用户,或被任何其他在线 Mastodon 服务器的用户关注,并且不受你之外的任何人控制。
|
||||
feature_audience_title: 自由吸引你的受众
|
||||
feature_control: 你最清楚你想在你自己的主页中看到什么动态。没有算法或广告浪费你的时间。你可以用一个账号关注任何 Mastodon 服务器上的任何人,并按时间顺序获得他们发布的嘟文,让你的互联网的角落更合自己的心意。
|
||||
feature_control_title: 掌控自己的时间线
|
||||
feature_creativity: Mastodon 支持音频、视频和图片、无障碍描述、投票、内容警告, 动画头像、自定义表情包、缩略图裁剪控制等功能,帮助你在网上尽情表达自己。无论你是要发布你的艺术作品、音乐还是播客,Mastodon 都能为你服务。
|
||||
feature_creativity_title: 尽情发挥创造力
|
||||
feature_creativity_title: 无与伦比的创造力
|
||||
feature_moderation: Mastodon 将决策权交还给你。每个服务器都会创建自己的规则和条例,并在站点内施行,而不是像企业社交媒体那样居高临下,这使得它可以最灵活地响应不同人群的需求。加入一个你认同其规则的服务器,或托管你自己的服务器。
|
||||
feature_moderation_title: 管理,本应如此
|
||||
follow_action: 关注
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@mastodon/mastodon",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "yarn@4.5.3",
|
||||
"packageManager": "yarn@4.5.2",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
@ -43,7 +43,7 @@
|
|||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@babel/runtime": "^7.22.3",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@formatjs/intl-pluralrules": "^5.2.2",
|
||||
"@gamestdio/websocket": "^0.3.2",
|
||||
|
|
|
@ -5,20 +5,12 @@ require 'rails_helper'
|
|||
RSpec.describe ApplicationHelper do
|
||||
describe 'body_classes' do
|
||||
context 'with a body class string from a controller' do
|
||||
before do
|
||||
user = Fabricate :user
|
||||
user.settings['web.use_system_font'] = true
|
||||
user.settings['web.reduce_motion'] = true
|
||||
user.save
|
||||
before { helper.extend controller_helpers }
|
||||
|
||||
helper.extend controller_helpers
|
||||
end
|
||||
|
||||
it 'uses the current theme and user settings classes in the result' do
|
||||
it 'uses the controller body classes in the result' do
|
||||
expect(helper.body_classes)
|
||||
.to match(/theme-default/)
|
||||
.and match(/system-font/)
|
||||
.and match(/reduce-motion/)
|
||||
.to match(/modal-layout compose-standalone/)
|
||||
.and match(/theme-default/)
|
||||
end
|
||||
|
||||
it 'includes values set via content_for' do
|
||||
|
@ -32,8 +24,10 @@ RSpec.describe ApplicationHelper do
|
|||
|
||||
def controller_helpers
|
||||
Module.new do
|
||||
def body_class_string = 'modal-layout compose-standalone'
|
||||
|
||||
def current_account
|
||||
@current_account ||= Fabricate(:account, user: User.last)
|
||||
@current_account ||= Fabricate(:account)
|
||||
end
|
||||
|
||||
def current_theme = 'default'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@mastodon/streaming",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "yarn@4.5.3",
|
||||
"packageManager": "yarn@4.5.2",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
54
yarn.lock
54
yarn.lock
|
@ -1837,7 +1837,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/postcss-random-function@npm:^1.0.1":
|
||||
"@csstools/postcss-random-function@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "@csstools/postcss-random-function@npm:1.0.1"
|
||||
dependencies:
|
||||
|
@ -1876,16 +1876,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/postcss-sign-functions@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@csstools/postcss-sign-functions@npm:1.1.0"
|
||||
"@csstools/postcss-sign-functions@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@csstools/postcss-sign-functions@npm:1.0.0"
|
||||
dependencies:
|
||||
"@csstools/css-calc": "npm:^2.1.0"
|
||||
"@csstools/css-parser-algorithms": "npm:^3.0.4"
|
||||
"@csstools/css-tokenizer": "npm:^3.0.3"
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
checksum: 10c0/503bbaa8fe1d1a619880d5d6b838f07f1898a5820889e5db3c4e02bb8b340dab18b88f439f9f1da44c6669bab2d4ba3f9543643ccc459d8a21191c5d22109c9b
|
||||
checksum: 10c0/ec745b2f1e714ffead43ade5964234dfc1750c3a71d2e29df862ab3f79ba4a1275187b270b4c226bbb1155bee8e9e63c35597b4f4cb3effaa632e5e07e422344
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -1979,41 +1979,41 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@dnd-kit/accessibility@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "@dnd-kit/accessibility@npm:3.1.1"
|
||||
"@dnd-kit/accessibility@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "@dnd-kit/accessibility@npm:3.1.0"
|
||||
dependencies:
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8
|
||||
checksum: 10c0/4f9d24e801d66d4fbb551ec389ed90424dd4c5bbdf527000a618e9abb9833cbd84d9a79e362f470ccbccfbd6d00217a9212c92f3cef66e01c951c7f79625b9d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@dnd-kit/core@npm:^6.1.0":
|
||||
version: 6.2.0
|
||||
resolution: "@dnd-kit/core@npm:6.2.0"
|
||||
version: 6.1.0
|
||||
resolution: "@dnd-kit/core@npm:6.1.0"
|
||||
dependencies:
|
||||
"@dnd-kit/accessibility": "npm:^3.1.1"
|
||||
"@dnd-kit/accessibility": "npm:^3.1.0"
|
||||
"@dnd-kit/utilities": "npm:^3.2.2"
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
react-dom: ">=16.8.0"
|
||||
checksum: 10c0/478d6bb027441b0e5fa5ecd9a4da8a5876811505147303de1a5a0783a4418c5f7f464bd3eb07b4be77ef7626364d1b905dc2a4f9055093b845cf39e1d6f13b73
|
||||
checksum: 10c0/c793eb97cb59285ca8937ebcdfcd27cff09d750ae06722e36ca5ed07925e41abc36a38cff98f9f6056f7a07810878d76909826142a2968330e7e22060e6be584
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@dnd-kit/sortable@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "@dnd-kit/sortable@npm:9.0.0"
|
||||
"@dnd-kit/sortable@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "@dnd-kit/sortable@npm:8.0.0"
|
||||
dependencies:
|
||||
"@dnd-kit/utilities": "npm:^3.2.2"
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@dnd-kit/core": ^6.2.0
|
||||
"@dnd-kit/core": ^6.1.0
|
||||
react: ">=16.8.0"
|
||||
checksum: 10c0/30566ec05371bd59729c0fb87537d78cd1760f08e4b49b5fa8298ebd3cb9f29fc258a48425c6a060b9efeca88e36a059000e770d630681986626abcc3589e97a
|
||||
checksum: 10c0/a6066c652b892c6a11320c7d8f5c18fdf723e721e8eea37f4ab657dee1ac5e7ca710ac32ce0712a57fe968bc07c13bcea5d5599d90dfdd95619e162befd4d2fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -2843,7 +2843,7 @@ __metadata:
|
|||
"@babel/preset-typescript": "npm:^7.21.5"
|
||||
"@babel/runtime": "npm:^7.22.3"
|
||||
"@dnd-kit/core": "npm:^6.1.0"
|
||||
"@dnd-kit/sortable": "npm:^9.0.0"
|
||||
"@dnd-kit/sortable": "npm:^8.0.0"
|
||||
"@dnd-kit/utilities": "npm:^3.2.2"
|
||||
"@formatjs/cli": "npm:^6.1.1"
|
||||
"@formatjs/intl-pluralrules": "npm:^5.2.2"
|
||||
|
@ -5156,13 +5156,13 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"axios@npm:^1.4.0":
|
||||
version: 1.7.8
|
||||
resolution: "axios@npm:1.7.8"
|
||||
version: 1.7.7
|
||||
resolution: "axios@npm:1.7.7"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.6"
|
||||
form-data: "npm:^4.0.0"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: 10c0/23ae2d0105aea9170c34ac9b6f30d9b2ab2fa8b1370205d2f7ce98b9f9510ab420148c13359ee837ea5a4bf2fb028ff225bd2fc92052fb0c478c6b4a836e2d5f
|
||||
checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -13850,8 +13850,8 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"postcss-preset-env@npm:^10.0.0":
|
||||
version: 10.1.1
|
||||
resolution: "postcss-preset-env@npm:10.1.1"
|
||||
version: 10.1.0
|
||||
resolution: "postcss-preset-env@npm:10.1.0"
|
||||
dependencies:
|
||||
"@csstools/postcss-cascade-layers": "npm:^5.0.1"
|
||||
"@csstools/postcss-color-function": "npm:^4.0.6"
|
||||
|
@ -13877,10 +13877,10 @@ __metadata:
|
|||
"@csstools/postcss-normalize-display-values": "npm:^4.0.0"
|
||||
"@csstools/postcss-oklab-function": "npm:^4.0.6"
|
||||
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
|
||||
"@csstools/postcss-random-function": "npm:^1.0.1"
|
||||
"@csstools/postcss-random-function": "npm:^1.0.0"
|
||||
"@csstools/postcss-relative-color-syntax": "npm:^3.0.6"
|
||||
"@csstools/postcss-scope-pseudo-class": "npm:^4.0.1"
|
||||
"@csstools/postcss-sign-functions": "npm:^1.1.0"
|
||||
"@csstools/postcss-sign-functions": "npm:^1.0.0"
|
||||
"@csstools/postcss-stepped-value-functions": "npm:^4.0.5"
|
||||
"@csstools/postcss-text-decoration-shorthand": "npm:^4.0.1"
|
||||
"@csstools/postcss-trigonometric-functions": "npm:^4.0.5"
|
||||
|
@ -13918,7 +13918,7 @@ __metadata:
|
|||
postcss-selector-not: "npm:^8.0.1"
|
||||
peerDependencies:
|
||||
postcss: ^8.4
|
||||
checksum: 10c0/99931117735a66827c7318be023ddb614990457617ccbe7fd2fdc1f10345554652df180d4842768d68d57e14fc0be4d86d0b413c65e77e02db5511e57ed07c4f
|
||||
checksum: 10c0/bd157dbed38c3c125b3bf86f5437a8094539ec5bf24428487c7bbf29da393731e48053afc695494cc9dbe4d182cfe405c398fcf0b22eb326b6db395e7315f892
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user