Compare commits

...

20 Commits

Author SHA1 Message Date
Yurii Izorkin
a1f9a56702
Merge 2c6658b50f into 429e08e3d2 2024-11-26 11:02:49 +00:00
Eugen Rochko
429e08e3d2
Remove old notifications route from web UI (#33038)
Some checks are pending
Bundler Audit / security (push) Waiting to run
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Haml Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / Libvips tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2024-11-26 09:59:11 +00:00
Matt Jankowski
a27bafa596
Add UserRole#bypass_block? method for notification check (#32974) 2024-11-26 09:45:47 +00:00
renovate[bot]
f0855fd41f
Update dependency axios to v1.7.8 (#33075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 08:10:19 +00:00
renovate[bot]
5c3a64dd50
Update dependency aws-sdk-s3 to v1.174.0 (#33076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 08:10:08 +00:00
renovate[bot]
7ba19ecf1e
Update dependency webauthn to v3.2.2 (#32879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 08:09:54 +00:00
renovate[bot]
08914516d9
Update dependency postcss-preset-env to v10.1.1 (#32947)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 08:09:34 +00:00
Matt Jankowski
6efa320feb
Fix Style/SafeNavigation cop (#32970) 2024-11-26 08:09:04 +00:00
renovate[bot]
b702cd74f3
Update dependency @dnd-kit/core to v6.2.0 (#33050)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 07:26:50 +00:00
renovate[bot]
72f623c391
Update dependency @dnd-kit/sortable to v9 (#33051)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 07:26:47 +00:00
github-actions[bot]
6b1dd1bf2a
New Crowdin Translations (automated) (#33074)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-11-26 07:24:28 +00:00
renovate[bot]
36496f4d73
Update Yarn to v4.5.3 (#33069)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 07:19:48 +00:00
renovate[bot]
3e901d108c
Update dependency selenium-webdriver to v4.27.0 (#33071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 07:19:35 +00:00
Matt Jankowski
0ea9d8164b
Remove body_class_string helper (#33072) 2024-11-26 07:19:20 +00:00
Izorkin
2c6658b50f
nginx: remove unused header
Headers are not used in the streaming block.
2024-11-20 23:16:14 +03:00
Izorkin
b0f97968ed
nginx: renaming @proxy block to @mastodon
If multiple sites are hosted on server, a common `@proxy` block
name may cause a conflict if another site uses a block with
same name.
2024-11-20 23:16:14 +03:00
Izorkin
8303fcf48f
nginx: move processing of remaining static files to main location block 2024-11-20 23:16:14 +03:00
Izorkin
a9e111265e
nginx: add new location block 2024-11-20 23:16:14 +03:00
Izorkin
f286abc857
nginx: remove unused location block 2024-11-20 23:16:14 +03:00
Izorkin
07f3b5b54a
nginx: remove regexp function from location block
In PR 19438, there was an error in way location
blocks was handled.
2024-11-20 23:16:14 +03:00
25 changed files with 139 additions and 597 deletions

View File

@ -93,10 +93,9 @@ 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.1012.0)
aws-sdk-core (3.213.0)
aws-partitions (1.1013.0)
aws-sdk-core (3.214.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@ -104,7 +103,7 @@ GEM
aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.173.0)
aws-sdk-s3 (1.174.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@ -349,7 +348,8 @@ GEM
json-schema (5.1.0)
addressable (~> 2.8)
jsonapi-renderer (0.2.2)
jwt (2.7.1)
jwt (2.9.3)
base64
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.26.0)
selenium-webdriver (4.27.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@ -844,9 +844,8 @@ GEM
public_suffix
warden (1.2.9)
rack (>= 2.0.9)
webauthn (3.1.0)
webauthn (3.2.2)
android_key_attestation (~> 0.3.0)
awrence (~> 1.1)
bindata (~> 2.4)
cbor (~> 0.5.9)
cose (~> 1.1)

View File

@ -22,7 +22,6 @@ 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
@ -158,10 +157,6 @@ 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] }

View File

@ -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].nil? ? 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]&.join(',')].compact.join(':')
expires_in = options.delete(:expires_in) || 3.minutes
body = Rails.cache.read(key, raw: true)

View File

@ -143,7 +143,7 @@ module ApplicationHelper
end
def body_classes
output = body_class_string.split
output = []
output << content_for(:body_classes)
output << "theme-#{current_theme.parameterize}"
output << 'system-font' if current_account&.user&.setting_system_font_ui

View File

@ -1,119 +0,0 @@
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);

View File

@ -1,17 +0,0 @@
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);

View File

@ -1,308 +0,0 @@
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)));

View File

@ -1,9 +0,0 @@
import Notifications_v2 from 'mastodon/features/notifications_v2';
export const NotificationsWrapper = (props) => {
return (
<Notifications_v2 {...props} />
);
};
export default NotificationsWrapper;

View File

@ -8,7 +8,7 @@ import { scrollRight } from '../../../scroll';
import BundleContainer from '../containers/bundle_container';
import {
Compose,
NotificationsWrapper,
Notifications,
HomeTimeline,
CommunityTimeline,
PublicTimeline,
@ -30,7 +30,7 @@ import NavigationPanel from './navigation_panel';
const componentMap = {
'COMPOSE': Compose,
'HOME': HomeTimeline,
'NOTIFICATIONS': NotificationsWrapper,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
'REMOTE': PublicTimeline,
'COMMUNITY': CommunityTimeline,

View File

@ -49,7 +49,7 @@ import {
Favourites,
DirectTimeline,
HashtagTimeline,
NotificationsWrapper,
Notifications,
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={NotificationsWrapper} content={children} exact />
<WrappedRoute path='/notifications' component={Notifications} 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} />

View File

@ -7,15 +7,7 @@ export function Compose () {
}
export function Notifications () {
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');
return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2');
}
export function HomeTimeline () {

View File

@ -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, one {# 天} other {# 天}}前",
"relative_time.full.hours": "{number, plural, one {# 小时} other {# 小时}}前",
"relative_time.full.days": "{number, plural, other {# 天}}前",
"relative_time.full.hours": "{number, plural, other {# 小时}}前",
"relative_time.full.just_now": "刚刚",
"relative_time.full.minutes": "{number, plural, one {# 分钟} other {# 分钟}}前",
"relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
"relative_time.full.minutes": "{number, plural, other {# 分钟}}前",
"relative_time.full.seconds": "{number, plural, 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, one {# 天} other {# 天}}",
"time_remaining.hours": "剩余 {number, plural, one {# 小时} other {# 小时}}",
"time_remaining.minutes": "剩余 {number, plural, one {# 分钟} other {# 分钟}}",
"time_remaining.days": "剩余 {number, plural, other {# 天}}",
"time_remaining.hours": "剩余 {number, plural, other {# 小时}}",
"time_remaining.minutes": "剩余 {number, plural, other {# 分钟}}",
"time_remaining.moments": "即将结束",
"time_remaining.seconds": "剩余 {number, plural, one {# 秒} other {# 秒}}",
"time_remaining.seconds": "剩余 {number, plural, other {# 秒}}",
"trends.counter_by_accounts": "过去 {days, plural, other {{days} 天}}有{count, plural, other { {counter} 人}}讨论",
"trends.trending_now": "当前热门",
"ui.beforeunload": "如果你现在离开 Mastodon你的草稿内容将会丢失。",

View File

@ -142,6 +142,10 @@ 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?

View File

@ -134,7 +134,7 @@ class NotifyService < BaseService
end
def from_staff?
@sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) && @sender.user_role&.highlighted? && @sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation])
@sender.local? && @sender.user.present? && @sender.user_role&.bypass_block?(@recipient.user_role)
end
def from_self?

View File

@ -226,20 +226,26 @@ 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}"

View File

@ -1180,12 +1180,12 @@ de:
use_security_key: Sicherheitsschlüssel verwenden
author_attribution:
example_title: Beispieltext
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:'
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:'
more_from_html: Mehr von %{name}
s_blog: Blog von %{name}
then_instructions: Ergänze die Domain, auf der deine Inhalte veröffentlicht werden in das unten stehende Feld.
title: Anerkennung als Autor*in
then_instructions: Ergänze anschließend im unteren Feld die Domain, auf der sich deine Inhalte befinden.
title: Verifizierung 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, 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."
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."
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 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.
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.
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>, 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.
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.
here_is_how: So funktionierts
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 Links
website_verification: Website-Verifizierung
verified_links: Deine verifizierten Domains
website_verification: Verifizierung einer Website
webauthn_credentials:
add: Sicherheitsschlüssel hinzufügen
create:

View File

@ -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: 创建过滤规则

View File

@ -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: Desactivar Relé
disable_relay: Deshabilitar 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: Activar Relé
enable_relay: Habilitar 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ó un relé %{target}"
create_relay_html: "%{name} creó el 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} desactivó el relé %{target}"
disable_relay_html: "%{name} deshabilitó 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} activó el relé %{target}"
enable_relay_html: "%{name} eliminó 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"

View File

@ -144,7 +144,7 @@ de:
url: Wohin Ereignisse gesendet werden
labels:
account:
attribution_domains_as_text: Websites, die dich anerkennen dürfen
attribution_domains_as_text: Websites, die auf dich verweisen dürfen
discoverable: Profil und Beiträge in Suchalgorithmen berücksichtigen
fields:
name: Beschriftung

View File

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

31
dist/nginx.conf vendored
View File

@ -66,60 +66,55 @@ server {
gzip_static on;
location / {
try_files $uri @proxy;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @mastodon;
}
# If Docker is used for deployment and Rails serves static files,
# then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
location = /sw.js {
add_header Cache-Control "public, max-age=604800, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/assets/ {
# then needed must replace line `try_files $uri =404;` with `try_files $uri @mastodon;`.
location ^~ /assets/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/avatars/ {
location ^~ /avatars/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/emoji/ {
location ^~ /emoji/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/headers/ {
location ^~ /headers/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/packs/ {
location ^~ /ocr/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/shortcuts/ {
location ^~ /packs/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/sounds/ {
location ^~ /sounds/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri =404;
}
location ~ ^/system/ {
location ^~ /system/ {
add_header Cache-Control "public, max-age=2419200, immutable";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
@ -141,12 +136,10 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
tcp_nodelay on;
}
location @proxy {
location @mastodon {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -1,7 +1,7 @@
{
"name": "@mastodon/mastodon",
"license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.5.2",
"packageManager": "yarn@4.5.3",
"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": "^8.0.0",
"@dnd-kit/sortable": "^9.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@formatjs/intl-pluralrules": "^5.2.2",
"@gamestdio/websocket": "^0.3.2",

View File

@ -5,12 +5,20 @@ require 'rails_helper'
RSpec.describe ApplicationHelper do
describe 'body_classes' do
context 'with a body class string from a controller' do
before { helper.extend controller_helpers }
before do
user = Fabricate :user
user.settings['web.use_system_font'] = true
user.settings['web.reduce_motion'] = true
user.save
it 'uses the controller body classes in the result' do
helper.extend controller_helpers
end
it 'uses the current theme and user settings classes in the result' do
expect(helper.body_classes)
.to match(/modal-layout compose-standalone/)
.and match(/theme-default/)
.to match(/theme-default/)
.and match(/system-font/)
.and match(/reduce-motion/)
end
it 'includes values set via content_for' do
@ -24,10 +32,8 @@ 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)
@current_account ||= Fabricate(:account, user: User.last)
end
def current_theme = 'default'

View File

@ -1,7 +1,7 @@
{
"name": "@mastodon/streaming",
"license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.5.2",
"packageManager": "yarn@4.5.3",
"engines": {
"node": ">=18"
},

View File

@ -1837,7 +1837,7 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-random-function@npm:^1.0.0":
"@csstools/postcss-random-function@npm:^1.0.1":
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.0.0":
version: 1.0.0
resolution: "@csstools/postcss-sign-functions@npm:1.0.0"
"@csstools/postcss-sign-functions@npm:^1.1.0":
version: 1.1.0
resolution: "@csstools/postcss-sign-functions@npm:1.1.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/ec745b2f1e714ffead43ade5964234dfc1750c3a71d2e29df862ab3f79ba4a1275187b270b4c226bbb1155bee8e9e63c35597b4f4cb3effaa632e5e07e422344
checksum: 10c0/503bbaa8fe1d1a619880d5d6b838f07f1898a5820889e5db3c4e02bb8b340dab18b88f439f9f1da44c6669bab2d4ba3f9543643ccc459d8a21191c5d22109c9b
languageName: node
linkType: hard
@ -1979,41 +1979,41 @@ __metadata:
languageName: node
linkType: hard
"@dnd-kit/accessibility@npm:^3.1.0":
version: 3.1.0
resolution: "@dnd-kit/accessibility@npm:3.1.0"
"@dnd-kit/accessibility@npm:^3.1.1":
version: 3.1.1
resolution: "@dnd-kit/accessibility@npm:3.1.1"
dependencies:
tslib: "npm:^2.0.0"
peerDependencies:
react: ">=16.8.0"
checksum: 10c0/4f9d24e801d66d4fbb551ec389ed90424dd4c5bbdf527000a618e9abb9833cbd84d9a79e362f470ccbccfbd6d00217a9212c92f3cef66e01c951c7f79625b9d7
checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8
languageName: node
linkType: hard
"@dnd-kit/core@npm:^6.1.0":
version: 6.1.0
resolution: "@dnd-kit/core@npm:6.1.0"
version: 6.2.0
resolution: "@dnd-kit/core@npm:6.2.0"
dependencies:
"@dnd-kit/accessibility": "npm:^3.1.0"
"@dnd-kit/accessibility": "npm:^3.1.1"
"@dnd-kit/utilities": "npm:^3.2.2"
tslib: "npm:^2.0.0"
peerDependencies:
react: ">=16.8.0"
react-dom: ">=16.8.0"
checksum: 10c0/c793eb97cb59285ca8937ebcdfcd27cff09d750ae06722e36ca5ed07925e41abc36a38cff98f9f6056f7a07810878d76909826142a2968330e7e22060e6be584
checksum: 10c0/478d6bb027441b0e5fa5ecd9a4da8a5876811505147303de1a5a0783a4418c5f7f464bd3eb07b4be77ef7626364d1b905dc2a4f9055093b845cf39e1d6f13b73
languageName: node
linkType: hard
"@dnd-kit/sortable@npm:^8.0.0":
version: 8.0.0
resolution: "@dnd-kit/sortable@npm:8.0.0"
"@dnd-kit/sortable@npm:^9.0.0":
version: 9.0.0
resolution: "@dnd-kit/sortable@npm:9.0.0"
dependencies:
"@dnd-kit/utilities": "npm:^3.2.2"
tslib: "npm:^2.0.0"
peerDependencies:
"@dnd-kit/core": ^6.1.0
"@dnd-kit/core": ^6.2.0
react: ">=16.8.0"
checksum: 10c0/a6066c652b892c6a11320c7d8f5c18fdf723e721e8eea37f4ab657dee1ac5e7ca710ac32ce0712a57fe968bc07c13bcea5d5599d90dfdd95619e162befd4d2fb
checksum: 10c0/30566ec05371bd59729c0fb87537d78cd1760f08e4b49b5fa8298ebd3cb9f29fc258a48425c6a060b9efeca88e36a059000e770d630681986626abcc3589e97a
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:^8.0.0"
"@dnd-kit/sortable": "npm:^9.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.7
resolution: "axios@npm:1.7.7"
version: 1.7.8
resolution: "axios@npm:1.7.8"
dependencies:
follow-redirects: "npm:^1.15.6"
form-data: "npm:^4.0.0"
proxy-from-env: "npm:^1.1.0"
checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7
checksum: 10c0/23ae2d0105aea9170c34ac9b6f30d9b2ab2fa8b1370205d2f7ce98b9f9510ab420148c13359ee837ea5a4bf2fb028ff225bd2fc92052fb0c478c6b4a836e2d5f
languageName: node
linkType: hard
@ -13850,8 +13850,8 @@ __metadata:
linkType: hard
"postcss-preset-env@npm:^10.0.0":
version: 10.1.0
resolution: "postcss-preset-env@npm:10.1.0"
version: 10.1.1
resolution: "postcss-preset-env@npm:10.1.1"
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.0"
"@csstools/postcss-random-function": "npm:^1.0.1"
"@csstools/postcss-relative-color-syntax": "npm:^3.0.6"
"@csstools/postcss-scope-pseudo-class": "npm:^4.0.1"
"@csstools/postcss-sign-functions": "npm:^1.0.0"
"@csstools/postcss-sign-functions": "npm:^1.1.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/bd157dbed38c3c125b3bf86f5437a8094539ec5bf24428487c7bbf29da393731e48053afc695494cc9dbe4d182cfe405c398fcf0b22eb326b6db395e7315f892
checksum: 10c0/99931117735a66827c7318be023ddb614990457617ccbe7fd2fdc1f10345554652df180d4842768d68d57e14fc0be4d86d0b413c65e77e02db5511e57ed07c4f
languageName: node
linkType: hard