Compare commits

..

1 Commits

Author SHA1 Message Date
Ondřej Hruška
0e0ff911b4 option to add title to <Button>, use for toot buttons 2017-10-24 18:29:15 +02:00
68 changed files with 280 additions and 1276 deletions

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "app/javascript/themes/mastodon-go"]
path = app/javascript/themes/mastodon-go
url = https://github.com/marrus-sh/mastodon-go

2
Vagrantfile vendored
View File

@@ -83,7 +83,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
vb.customize ["modifyvm", :id, "--memory", "4096"]
vb.customize ["modifyvm", :id, "--memory", "2048"]
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
# https://github.com/mitchellh/vagrant/issues/1172

View File

@@ -22,14 +22,6 @@ module Admin
end
end
def update
if @custom_emoji.update(resource_params)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
else
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
end
end
def destroy
@custom_emoji.destroy
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
@@ -44,7 +36,7 @@ module Admin
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page])
redirect_to admin_custom_emojis_path(params[:page])
end
def enable
@@ -64,7 +56,7 @@ module Admin
end
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
params.require(:custom_emoji).permit(:shortcode, :image)
end
def filtered_custom_emojis

View File

@@ -1,60 +0,0 @@
# frozen_string_literal: true
class Api::V1::Timelines::DirectController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }, only: [:show]
before_action :require_user!, only: [:show]
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
respond_to :json
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def load_statuses
cached_direct_statuses
end
def cached_direct_statuses
cache_collection direct_statuses, Status
end
def direct_statuses
direct_timeline_statuses.paginate_by_max_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id]
)
end
def direct_timeline_statuses
Status.as_direct_timeline(current_account)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params)
end
def next_path
api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -1,64 +0,0 @@
# frozen_string_literal: true
class Settings::KeywordMutesController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :load_keyword_mute, only: [:edit, :update, :destroy]
def index
@keyword_mutes = paginated_keyword_mutes_for_account
end
def new
@keyword_mute = keyword_mutes_for_account.build
end
def create
@keyword_mute = keyword_mutes_for_account.create(keyword_mute_params)
if @keyword_mute.persisted?
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
else
render :new
end
end
def update
if @keyword_mute.update(keyword_mute_params)
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
else
render :edit
end
end
def destroy
@keyword_mute.destroy!
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
end
def destroy_all
keyword_mutes_for_account.delete_all
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
end
private
def keyword_mutes_for_account
Glitch::KeywordMute.where(account: current_account)
end
def load_keyword_mute
@keyword_mute = keyword_mutes_for_account.find(params[:id])
end
def keyword_mute_params
params.require(:keyword_mute).permit(:keyword, :whole_word)
end
def paginated_keyword_mutes_for_account
keyword_mutes_for_account.order(:keyword).page params[:page]
end
end

View File

@@ -9,10 +9,6 @@ module JsonLdHelper
value.is_a?(Array) ? value.first : value
end
def as_array(value)
value.is_a?(Array) ? value : [value]
end
def value_or_id(value)
value.is_a?(String) || value.nil? ? value : value['id']
end

View File

@@ -1,2 +0,0 @@
module Settings::KeywordMutesHelper
end

View File

@@ -124,16 +124,6 @@ export default class LocalSettingsPage extends React.PureComponent {
>
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'reblogs']}
id='mastodon-settings--collapsed-auto-reblogs'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_reblogs' defaultMessage='Boosts' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'replies']}

View File

@@ -287,7 +287,6 @@ properly and our intersection observer is good to go.
muted,
id,
intersectionObserverWrapper,
prepend,
} = this.props;
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
@@ -300,9 +299,6 @@ properly and our intersection observer is good to go.
node.clientHeight > (
status.get('media_attachments').size && !muted ? 650 : 400
)
) || (
autoCollapseSettings.get('reblogs') &&
prepend === 'reblogged_by'
) || (
autoCollapseSettings.get('replies') &&
status.get('in_reply_to_id', null) !== null

View File

@@ -14,7 +14,6 @@
"settings.auto_collapse_lengthy": "Lengthy toots",
"settings.auto_collapse_media": "Toots with media",
"settings.auto_collapse_notifications": "Notifications",
"settings.auto_collapse_reblogs": "Boosts",
"settings.auto_collapse_replies": "Replies",
"settings.close": "Close",
"settings.collapsed_statuses": "Collapsed toots",

View File

@@ -59,7 +59,6 @@ const initialState = ImmutableMap({
all : false,
notifications : true,
lengthy : true,
reblogs : false,
replies : false,
media : false,
}),

View File

@@ -8,7 +8,6 @@ import {
refreshHomeTimeline,
refreshCommunityTimeline,
refreshPublicTimeline,
refreshDirectTimeline,
} from './timelines';
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
@@ -134,8 +133,6 @@ export function submitCompose() {
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
insertOrRefresh('community', refreshCommunityTimeline);
insertOrRefresh('public', refreshPublicTimeline);
} else if (response.data.visibility === 'direct') {
insertOrRefresh('direct', refreshDirectTimeline);
}
}).catch(function (error) {
dispatch(submitComposeFail(error));

View File

@@ -92,4 +92,3 @@ export const connectCommunityStream = () => connectTimelineStream('community', '
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
export const connectPublicStream = () => connectTimelineStream('public', 'public');
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');

View File

@@ -115,7 +115,6 @@ export function refreshTimeline(timelineId, path, params = {}) {
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct');
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
@@ -156,7 +155,6 @@ export function expandTimeline(timelineId, path, params = {}) {
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct');
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);

View File

@@ -175,9 +175,7 @@ export default class ColumnHeader extends React.PureComponent {
<div className={wrapperClassName}>
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
<span className='column-header__title'>
{title}
</span>
{title}
<div className='column-header__buttons'>
{backButton}
{ notifCleaning ? (

View File

@@ -46,7 +46,7 @@ const getFrequentlyUsedEmojis = createSelector([
const getCustomEmojis = createSelector([
state => state.get('custom_emojis'),
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
], emojis => emojis.sort((a, b) => {
const aShort = a.get('shortcode').toLowerCase();
const bShort = b.get('shortcode').toLowerCase();

View File

@@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import ColumnSettings from '../../community_timeline/components/column_settings';
import { changeSetting } from '../../../actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'direct']),
});
const mapDispatchToProps = dispatch => ({
onChange (key, checked) {
dispatch(changeSetting(['direct', ...key], checked));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);

View File

@@ -1,107 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
refreshDirectTimeline,
expandDirectTimeline,
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectDirectStream } from '../../actions/streaming';
const messages = defineMessages({
title: { id: 'column.direct', defaultMessage: 'Direct messages' },
});
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0,
});
@connect(mapStateToProps)
@injectIntl
export default class DirectTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
};
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('DIRECT', {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
componentDidMount () {
const { dispatch } = this.props;
dispatch(refreshDirectTimeline());
this.disconnect = dispatch(connectDirectStream());
}
componentWillUnmount () {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandDirectTimeline());
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return (
<Column ref={this.setRef}>
<ColumnHeader
icon='envelope'
active={hasUnread}
title={intl.formatMessage(messages.title)}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<ColumnSettingsContainer />
</ColumnHeader>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`direct_timeline-${columnId}`}
timelineId='direct'
loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
/>
</Column>
);
}
}

View File

@@ -17,7 +17,6 @@ const messages = defineMessages({
navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' },
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
@@ -79,22 +78,18 @@ export default class GettingStarted extends ImmutablePureComponent {
}
}
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
navItems.push(<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />);
}
navItems = navItems.concat([
<ColumnLink key='5' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='6' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
]);
if (me.get('locked')) {
navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
}
navItems = navItems.concat([
<ColumnLink key='8' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
<ColumnLink key='9' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
<ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
<ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
]);
return (

View File

@@ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from '../../ui/util/async-components';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
import detectPassiveEvents from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
@@ -23,7 +23,6 @@ const componentMap = {
'PUBLIC': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'DIRECT': DirectTimeline,
'FAVOURITES': FavouritedStatuses,
};

View File

@@ -29,7 +29,6 @@ import {
Following,
Reblogs,
Favourites,
DirectTimeline,
HashtagTimeline,
Notifications,
FollowRequests,
@@ -72,7 +71,6 @@ const keyMap = {
goToNotifications: 'g n',
goToLocal: 'g l',
goToFederated: 'g t',
goToDirect: 'g d',
goToStart: 'g s',
goToFavourites: 'g f',
goToPinned: 'g p',
@@ -304,10 +302,6 @@ export default class UI extends React.Component {
this.context.router.history.push('/timelines/public');
}
handleHotkeyGoToDirect = () => {
this.context.router.history.push('/timelines/direct');
}
handleHotkeyGoToStart = () => {
this.context.router.history.push('/getting-started');
}
@@ -363,7 +357,6 @@ export default class UI extends React.Component {
goToNotifications: this.handleHotkeyGoToNotifications,
goToLocal: this.handleHotkeyGoToLocal,
goToFederated: this.handleHotkeyGoToFederated,
goToDirect: this.handleHotkeyGoToDirect,
goToStart: this.handleHotkeyGoToStart,
goToFavourites: this.handleHotkeyGoToFavourites,
goToPinned: this.handleHotkeyGoToPinned,
@@ -384,7 +377,6 @@ export default class UI extends React.Component {
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />

View File

@@ -26,10 +26,6 @@ export function HashtagTimeline () {
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline');
}
export function DirectTimeline() {
return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
}
export function Status () {
return import(/* webpackChunkName: "features/status" */'../../status');
}

View File

@@ -755,19 +755,6 @@
],
"path": "app/javascript/mastodon/features/compose/index.json"
},
{
"descriptors": [
{
"defaultMessage": "Direct messages",
"id": "column.direct"
},
{
"defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"id": "empty_column.direct"
}
],
"path": "app/javascript/mastodon/features/direct_timeline/index.json"
},
{
"descriptors": [
{
@@ -829,10 +816,6 @@
"defaultMessage": "Local timeline",
"id": "navigation_bar.community_timeline"
},
{
"defaultMessage": "Direct messages",
"id": "navigation_bar.direct"
},
{
"defaultMessage": "Preferences",
"id": "navigation_bar.preferences"

View File

@@ -28,7 +28,6 @@
"bundle_modal_error.retry": "Try again",
"column.blocks": "Blocked users",
"column.community": "Local timeline",
"column.direct": "Direct messages",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
@@ -81,7 +80,6 @@
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
@@ -108,7 +106,6 @@
"missing_indicator.label": "Not found",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.direct": "Direct messages",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.follow_requests": "Follow requests",

View File

@@ -1,221 +1,221 @@
{
"account.block": "Bloki @{name}",
"account.block_domain": "Kaŝi ĉion el {domain}",
"account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.edit_profile": "Redakti la profilon",
"account.follow": "Sekvi",
"account.followers": "Sekvantoj",
"account.follows": "Sekvatoj",
"account.follows_you": "Sekvas vin",
"account.media": "Sonbildaĵoj",
"account.media": "Media",
"account.mention": "Mencii @{name}",
"account.mute": "Silentigi @{name}",
"account.mute": "Mute @{name}",
"account.posts": "Mesaĝoj",
"account.report": "Signali @{name}",
"account.report": "Report @{name}",
"account.requested": "Atendas aprobon",
"account.share": "Diskonigi la profilon de @{name}",
"account.share": "Share @{name}'s profile",
"account.unblock": "Malbloki @{name}",
"account.unblock_domain": "Malkaŝi {domain}",
"account.unfollow": "Ne plus sekvi",
"account.unmute": "Malsilentigi @{name}",
"account.view_full_profile": "Vidi plenan profilon",
"boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi",
"bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.",
"bundle_column_error.retry": "Bonvolu reprovi",
"bundle_column_error.title": "Reta eraro",
"bundle_modal_error.close": "Fermi",
"bundle_modal_error.message": "Io malfunkciis ŝargante tiun ĉi komponanton.",
"bundle_modal_error.retry": "Bonvolu reprovi",
"column.blocks": "Blokitaj uzantoj",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Malsekvi",
"account.unmute": "Unmute @{name}",
"account.view_full_profile": "View full profile",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"column.blocks": "Blocked users",
"column.community": "Loka tempolinio",
"column.favourites": "Favoritoj",
"column.follow_requests": "Abonpetoj",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Hejmo",
"column.mutes": "Silentigitaj uzantoj",
"column.mutes": "Muted users",
"column.notifications": "Sciigoj",
"column.pins": "Alpinglitaj pepoj",
"column.pins": "Pinned toot",
"column.public": "Fratara tempolinio",
"column_back_button.label": "Reveni",
"column_header.hide_settings": "Kaŝi agordojn",
"column_header.moveLeft_settings": "Movi kolumnon maldekstren",
"column_header.moveRight_settings": "Movi kolumnon dekstren",
"column_header.pin": "Alpingli",
"column_header.show_settings": "Malkaŝi agordojn",
"column_header.unpin": "Depingli",
"column_subheading.navigation": "Navigado",
"column_subheading.settings": "Agordoj",
"compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
"compose_form.lock_disclaimer.lock": "ŝlosita",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.pin": "Pin",
"column_header.show_settings": "Show settings",
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Pri kio vi pensas?",
"compose_form.publish": "Hup",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marki ke la enhavo estas tikla",
"compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
"compose_form.spoiler_placeholder": "Skribu tie vian averton",
"confirmation_modal.cancel": "Malfari",
"confirmations.block.confirm": "Bloki",
"confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?",
"confirmations.delete.confirm": "Malaperigi",
"confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?",
"confirmations.domain_block.confirm": "Kaŝi la tutan reton",
"confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas bloki {domain} tute? Plej ofte, kelkaj celitaj blokadoj aŭ silentigoj estas sufiĉaj kaj preferindaj.",
"confirmations.mute.confirm": "Silentigi",
"confirmations.mute.message": "Ĉu vi konfirmas la silentigon de {name}?",
"confirmations.unfollow.confirm": "Ne plu sekvi",
"confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?",
"embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.",
"embed.preview": "Ĝi aperos tiel:",
"emoji_button.activity": "Aktivecoj",
"emoji_button.custom": "Personaj",
"emoji_button.flags": "Flagoj",
"emoji_button.food": "Manĝi kaj trinki",
"emoji_button.label": "Enmeti mieneton",
"emoji_button.nature": "Naturo",
"emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objektoj",
"emoji_button.people": "Homoj",
"emoji_button.recent": "Ofte uzataj",
"emoji_button.search": "Serĉo…",
"emoji_button.search_results": "Rezultatoj de serĉo",
"emoji_button.symbols": "Simboloj",
"emoji_button.travel": "Vojaĝoj & lokoj",
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
"empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.",
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
"empty_column.home.public_timeline": "la publika tempolinio",
"empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.",
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion.",
"follow_request.authorize": "Akcepti",
"follow_request.reject": "Rifuzi",
"getting_started.appsshort": "Aplikaĵoj",
"getting_started.faq": "Oftaj demandoj",
"compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Flags",
"emoji_button.food": "Food & Drink",
"emoji_button.label": "Insert emoji",
"emoji_button.nature": "Nature",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objects",
"emoji_button.people": "People",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Search...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.appsshort": "Apps",
"getting_started.faq": "FAQ",
"getting_started.heading": "Por komenci",
"getting_started.open_source_notice": "Mastodono estas malfermkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
"getting_started.userguide": "Gvidilo de uzo",
"home.column_settings.advanced": "Precizaj agordoj",
"home.column_settings.basic": "Bazaj agordoj",
"home.column_settings.filter_regex": "Forfiltri per regulesprimo",
"home.column_settings.show_reblogs": "Montri diskonigojn",
"home.column_settings.show_replies": "Montri respondojn",
"home.settings": "Agordoj de la kolumno",
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}.",
"getting_started.userguide": "User Guide",
"home.column_settings.advanced": "Advanced",
"home.column_settings.basic": "Basic",
"home.column_settings.filter_regex": "Filter out by regular expressions",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.settings": "Column settings",
"lightbox.close": "Fermi",
"lightbox.next": "Malantaŭa",
"lightbox.previous": "Antaŭa",
"loading_indicator.label": "Ŝarganta",
"media_gallery.toggle_visible": "Baskuli videblecon",
"missing_indicator.label": "Ne trovita",
"navigation_bar.blocks": "Blokitaj uzantoj",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"loading_indicator.label": "Ŝarĝanta...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Loka tempolinio",
"navigation_bar.edit_profile": "Redakti la profilon",
"navigation_bar.favourites": "Favoritaj",
"navigation_bar.follow_requests": "Abonpetoj",
"navigation_bar.info": "Plia informo",
"navigation_bar.favourites": "Favourites",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "Extended information",
"navigation_bar.logout": "Elsaluti",
"navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.pins": "Alpinglitaj pepoj",
"navigation_bar.mutes": "Muted users",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.preferences": "Preferoj",
"navigation_bar.public_timeline": "Fratara tempolinio",
"notification.favourite": "{name} favoris vian mesaĝon",
"notification.follow": "{name} sekvis vin",
"notification.mention": "{name} menciis vin",
"notification.reblog": "{name} diskonigis vian mesaĝon",
"notifications.clear": "Forviŝi la sciigojn",
"notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Retumilaj atentigoj",
"notifications.column_settings.favourite": "Favoritoj:",
"notifications.column_settings.favourite": "Favoroj:",
"notifications.column_settings.follow": "Novaj sekvantoj:",
"notifications.column_settings.mention": "Mencioj:",
"notifications.column_settings.push": "Puŝsciigoj",
"notifications.column_settings.push_meta": "Tiu ĉi aparato",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push_meta": "This device",
"notifications.column_settings.reblog": "Diskonigoj:",
"notifications.column_settings.show": "Montri en kolono",
"notifications.column_settings.sound": "Eligi sonon",
"onboarding.done": "Farita",
"onboarding.next": "Malantaŭa",
"onboarding.page_five.public_timelines": "La loka tempolinio enhavas mesaĝojn de ĉiuj ĉe {domain}. La federacia tempolinio enhavas ĉiujn mesaĝojn de uzantoj, kiujn iu ĉe {domain} sekvas. Ambaŭ tre utilas por trovi novajn kunparolantojn.",
"onboarding.page_four.home": "La hejma tempolinio enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.",
"onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.",
"onboarding.page_one.federation": "Mastodono estas reto de nedependaj serviloj, unuiĝintaj por krei pligrandan socian retejon. Ni nomas tiujn servilojn instancoj.",
"onboarding.page_one.handle": "Vi estas ĉe {domain}, unu el la multaj instancoj de Mastodono. Via kompleta uznomo do estas {handle}",
"onboarding.page_one.welcome": "Bonvenon al Mastodono!",
"onboarding.page_six.admin": "Via instancestro estas {admin}.",
"onboarding.page_six.almost_done": "Estas preskaŭ finita…",
"onboarding.page_six.appetoot": "Bonan apepiton!",
"onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj. Kaj nun… bonan apepiton!",
"onboarding.page_six.github": "Mastodono estas libera, senpaga kaj malfermkoda programaro. Vi povas signali cimojn, proponi funkciojn aŭ kontribui al gîa kreskado ĉe {github}.",
"onboarding.page_six.guidelines": "komunreguloj",
"onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!",
"onboarding.page_six.various_app": "telefon-aplikaĵoj",
"onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian avataron, priskribon kaj vian nomon. Vi tie trovos ankoraŭ aliajn agordojn.",
"onboarding.page_three.search": "Uzu la serĉokampo por trovi uzantojn kaj esplori kradvortojn tiel ke {illustration} kaj {introductions}. Por trovi iun, kiu ne estas ĉe ĉi tiu instanco, uzu ĝian kompletan uznomon.",
"onboarding.page_two.compose": "Skribu pepojn en la verkkolumno. Vi povas aldoni bildojn, ŝanĝi la agordojn de privateco kaj aldoni tiklavertojn («content warning») dank' al la piktogramoj malsupre.",
"onboarding.skip": "Pasigi",
"privacy.change": "Austigi la privateco de la mesaĝo",
"privacy.direct.long": "Vidigi nur al la menciitaj personoj",
"privacy.direct.short": "Rekta",
"privacy.private.long": "Vidigi nur al viaj sekvantoj",
"privacy.private.short": "Nursekvanta",
"privacy.public.long": "Vidigi en publikaj tempolinioj",
"privacy.public.short": "Publika",
"privacy.unlisted.long": "Ne vidigi en publikaj tempolinioj",
"privacy.unlisted.short": "Nelistigita",
"relative_time.days": "{number}t",
"notifications.column_settings.sound": "Play sound",
"onboarding.done": "Done",
"onboarding.next": "Next",
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
"onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
"onboarding.page_one.welcome": "Welcome to Mastodon!",
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
"onboarding.page_six.almost_done": "Almost done...",
"onboarding.page_six.appetoot": "Bon Appetoot!",
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"onboarding.page_six.guidelines": "community guidelines",
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
"onboarding.page_six.various_app": "mobile apps",
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
"onboarding.skip": "Skip",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not show in public timelines",
"privacy.unlisted.short": "Unlisted",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "nun",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Malfari",
"report.placeholder": "Pliaj komentoj",
"report.submit": "Sendi",
"report.target": "Signalaĵo",
"reply_indicator.cancel": "Rezigni",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting",
"search.placeholder": "Serĉi",
"search_popout.search_format": "Detala serĉo",
"search_popout.tips.hashtag": "kradvorto",
"search_popout.tips.status": "statkonigo",
"search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn.",
"search_popout.tips.user": "uzanto",
"search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
"standalone.public_title": "Rigardeti…",
"status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Forigi",
"status.embed": "Enmeti",
"status.embed": "Embed",
"status.favourite": "Favori",
"status.load_more": "Ŝargi plie",
"status.media_hidden": "Sonbildaĵo kaŝita",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.mention": "Mencii @{name}",
"status.more": "Pli",
"status.mute_conversation": "Silentigi konversacion",
"status.open": "Disfaldi statkonigon",
"status.pin": "Pingli al la profilo",
"status.more": "More",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.reblog": "Diskonigi",
"status.reblogged_by": "{name} diskonigis",
"status.reblogged_by": "{name} diskonigita",
"status.reply": "Respondi",
"status.replyAll": "Respondi al la fadeno",
"status.report": "Signali @{name}",
"status.replyAll": "Reply to thread",
"status.report": "Report @{name}",
"status.sensitive_toggle": "Alklaki por vidi",
"status.sensitive_warning": "Tikla enhavo",
"status.share": "Diskonigi",
"status.show_less": "Refaldi",
"status.show_more": "Disfaldi",
"status.unmute_conversation": "Malsilentigi konversacion",
"status.unpin": "Depingli de profilo",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_more": "Show more",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Ekskribi",
"tabs_bar.federated_timeline": "Federacia tempolinio",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Hejmo",
"tabs_bar.local_timeline": "Loka tempolinio",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Sciigoj",
"upload_area.title": "Algliti por alŝuti",
"upload_button.label": "Aldoni sonbildaĵon",
"upload_form.description": "Priskribi por la misvidantaj",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Aldoni enhavaĵon",
"upload_form.description": "Describe for the visually impaired",
"upload_form.undo": "Malfari",
"upload_progress.label": "Alŝutanta…",
"video.close": "Fermi videon",
"video.exit_fullscreen": "Eliri el plenekrano",
"video.expand": "Vastigi videon",
"video.fullscreen": "Igi plenekrane",
"video.hide": "Kaŝi videon",
"video.mute": "Silentigi",
"video.pause": "Paŭzi",
"video.play": "Legi",
"video.unmute": "Malsilentigi"
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
}

View File

@@ -63,20 +63,20 @@
"confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
"confirmations.unfollow.confirm": "Отписаться",
"confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?",
"embed.instructions": "Встройте этот статус на Вашем сайте, скопировав код внизу.",
"embed.preview": "Так это будет выглядеть:",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Занятия",
"emoji_button.custom": "Собственные",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Флаги",
"emoji_button.food": "Еда и напитки",
"emoji_button.label": "Вставить эмодзи",
"emoji_button.nature": "Природа",
"emoji_button.not_found": "Нет эмодзи!! (╯°□°)╯︵ ┻━┻",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Предметы",
"emoji_button.people": "Люди",
"emoji_button.recent": "Последние",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Найти...",
"emoji_button.search_results": "Результаты поиска",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия",
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
@@ -159,34 +159,34 @@
"privacy.public.short": "Публичный",
"privacy.unlisted.long": "Не показывать в лентах",
"privacy.unlisted.short": "Скрытый",
"relative_time.days": "{number}д",
"relative_time.hours": "{number}ч",
"relative_time.just_now": "только что",
"relative_time.minutes": "{number}м",
"relative_time.seconds": "{number}с",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Отмена",
"report.placeholder": "Комментарий",
"report.submit": "Отправить",
"report.target": "Жалуемся на",
"search.placeholder": "Поиск",
"search_popout.search_format": "Продвинутый формат поиска",
"search_popout.tips.hashtag": "хэштег",
"search_popout.tips.status": "статус",
"search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
"search_popout.tips.user": "пользователь",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
"standalone.public_title": "Прямо сейчас",
"standalone.public_title": "A look inside...",
"status.cannot_reblog": "Этот статус не может быть продвинут",
"status.delete": "Удалить",
"status.embed": "Встроить",
"status.embed": "Embed",
"status.favourite": "Нравится",
"status.load_more": "Показать еще",
"status.media_hidden": "Медиаконтент скрыт",
"status.mention": "Упомянуть @{name}",
"status.more": "Больше",
"status.more": "More",
"status.mute_conversation": "Заглушить тред",
"status.open": "Развернуть статус",
"status.pin": "Закрепить в профиле",
"status.pin": "Pin on profile",
"status.reblog": "Продвинуть",
"status.reblogged_by": "{name} продвинул(а)",
"status.reply": "Ответить",
@@ -194,11 +194,11 @@
"status.report": "Пожаловаться",
"status.sensitive_toggle": "Нажмите для просмотра",
"status.sensitive_warning": "Чувствительный контент",
"status.share": "Поделиться",
"status.share": "Share",
"status.show_less": "Свернуть",
"status.show_more": "Развернуть",
"status.unmute_conversation": "Снять глушение с треда",
"status.unpin": "Открепить от профиля",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Написать",
"tabs_bar.federated_timeline": "Глобальная",
"tabs_bar.home": "Главная",
@@ -206,16 +206,16 @@
"tabs_bar.notifications": "Уведомления",
"upload_area.title": "Перетащите сюда, чтобы загрузить",
"upload_button.label": "Добавить медиаконтент",
"upload_form.description": "Описать для людей с нарушениями зрения",
"upload_form.description": "Describe for the visually impaired",
"upload_form.undo": "Отменить",
"upload_progress.label": "Загрузка...",
"video.close": "Закрыть видео",
"video.exit_fullscreen": "Покинуть полноэкранный режим",
"video.expand": "Развернуть видео",
"video.fullscreen": "Полноэкранный режим",
"video.hide": "Скрыть видео",
"video.mute": "Заглушить звук",
"video.pause": "Пауза",
"video.play": "Пуск",
"video.unmute": "Включить звук"
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
}

View File

@@ -58,12 +58,6 @@ const initialState = ImmutableMap({
body: '',
}),
}),
direct: ImmutableMap({
regex: ImmutableMap({
body: '',
}),
}),
});
const defaultColumns = fromJS([

View File

@@ -2503,7 +2503,6 @@ button.icon-button.active i.fa-retweet {
}
.column-header {
display: flex;
padding: 15px;
font-size: 16px;
background: lighten($ui-base-color, 4%);
@@ -2529,10 +2528,12 @@ button.icon-button.active i.fa-retweet {
}
.column-header__buttons {
height: 48px;
position: absolute;
right: 0;
top: 0;
height: 100%;
display: flex;
margin: -15px;
margin-left: 0;
height: 48px;
}
.column-header__button {
@@ -2691,14 +2692,6 @@ button.icon-button.active i.fa-retweet {
}
}
.column-header__title {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 1;
}
.text-btn {
display: inline-block;
padding: 0;
@@ -3472,6 +3465,7 @@ button.icon-button.active i.fa-retweet {
right: 0;
bottom: 0;
background: rgba($base-overlay-background, 0.7);
transform: translateZ(0);
}
.modal-root__container {

View File

@@ -53,9 +53,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def process_tags(status)
return if @object['tag'].nil?
return unless @object['tag'].is_a?(Array)
as_array(@object['tag']).each do |tag|
@object['tag'].each do |tag|
case tag['type']
when 'Hashtag'
process_hashtag tag, status
@@ -103,9 +103,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def process_attachments(status)
return if @object['attachment'].nil?
return unless @object['attachment'].is_a?(Array)
as_array(@object['attachment']).each do |attachment|
@object['attachment'].each do |attachment|
next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank?
href = Addressable::URI.parse(attachment['url']).normalize.to_s

View File

@@ -141,8 +141,6 @@ class FeedManager
return false if receiver_id == status.account_id
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
return true if keyword_filter?(status, Glitch::KeywordMute.matcher_for(receiver_id))
check_for_mutes = [status.account_id]
check_for_mutes.concat(status.mentions.pluck(:account_id))
check_for_mutes.concat([status.reblog.account_id]) if status.reblog?
@@ -168,18 +166,6 @@ class FeedManager
false
end
def keyword_filter?(status, matcher)
should_filter = matcher =~ status.text
should_filter ||= matcher =~ status.spoiler_text
if status.reblog?
should_filter ||= matcher =~ status.reblog.text
should_filter ||= matcher =~ status.reblog.spoiler_text
end
!!should_filter
end
def filter_from_mentions?(status, receiver_id)
return true if receiver_id == status.account_id
@@ -189,7 +175,6 @@ class FeedManager
should_filter = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked
should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them
should_filter ||= keyword_filter?(status, Glitch::KeywordMute.matcher_for(receiver_id)) # or if the mention contains a muted keyword
should_filter
end

View File

@@ -15,7 +15,6 @@
# disabled :boolean default(FALSE), not null
# uri :string
# image_remote_url :string
# visible_in_picker :boolean default(TRUE), not null
#
class CustomEmoji < ApplicationRecord

View File

@@ -1,7 +0,0 @@
# frozen_string_literal: true
module Glitch
def self.table_name_prefix
'glitch_'
end
end

View File

@@ -1,66 +0,0 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: glitch_keyword_mutes
#
# id :integer not null, primary key
# account_id :integer not null
# keyword :string not null
# whole_word :boolean default(TRUE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class Glitch::KeywordMute < ApplicationRecord
belongs_to :account, required: true
validates_presence_of :keyword
after_commit :invalidate_cached_matcher
def self.matcher_for(account_id)
Matcher.new(account_id)
end
private
def invalidate_cached_matcher
Rails.cache.delete("keyword_mutes:regex:#{account_id}")
end
class Matcher
attr_reader :account_id
attr_reader :regex
def initialize(account_id)
@account_id = account_id
regex_text = Rails.cache.fetch("keyword_mutes:regex:#{account_id}") { regex_text_for_account }
@regex = /#{regex_text}/i
end
def =~(str)
regex =~ str
end
private
def keywords
Glitch::KeywordMute.where(account_id: account_id).select(:keyword, :id, :whole_word)
end
def regex_text_for_account
kws = keywords.find_each.with_object([]) do |kw, a|
a << (kw.whole_word ? boundary_regex_for_keyword(kw.keyword) : kw.keyword)
end
Regexp.union(kws).source
end
def boundary_regex_for_keyword(keyword)
sb = keyword =~ /\A[[:word:]]/ ? '\b' : ''
eb = keyword =~ /[[:word:]]\Z/ ? '\b' : ''
/#{sb}#{Regexp.escape(keyword)}#{eb}/
end
end
end

View File

@@ -154,14 +154,6 @@ class Status < ApplicationRecord
where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
end
def as_direct_timeline(account)
query = joins("LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = #{account.id}")
.where("mentions.account_id = #{account.id} OR statuses.account_id = #{account.id}")
.where(visibility: [:direct])
apply_timeline_filters(query, account, false)
end
def as_public_timeline(account = nil, local_only = false)
query = timeline_scope(local_only).without_replies

View File

@@ -3,7 +3,7 @@
class REST::CustomEmojiSerializer < ActiveModel::Serializer
include RoutingHelper
attributes :shortcode, :url, :static_url, :visible_in_picker
attributes :shortcode, :url, :static_url
def url
full_asset_url(object.image.url)

View File

@@ -40,7 +40,6 @@ class BatchedRemoveStatusService < BaseService
# Cannot be batched
statuses.each do |status|
unpush_from_public_timelines(status)
unpush_from_direct_timelines(status) if status.direct_visibility?
batch_salmon_slaps(status) if status.local?
end
@@ -101,16 +100,6 @@ class BatchedRemoveStatusService < BaseService
end
end
def unpush_from_direct_timelines(status)
payload = @json_payloads[status.id]
redis.pipelined do
@mentions[status.id].each do |mention|
redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local?
end
redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local?
end
end
def batch_salmon_slaps(status)
return if @mentions[status.id].empty?

View File

@@ -10,17 +10,15 @@ class FanOutOnWriteService < BaseService
deliver_to_self(status) if status.account.local?
render_anonymous_payload(status)
if status.direct_visibility?
deliver_to_mentioned_followers(status)
deliver_to_direct_timelines(status)
else
deliver_to_followers(status)
end
return if status.account.silenced? || !status.public_visibility? || status.reblog?
render_anonymous_payload(status)
deliver_to_hashtags(status)
return if status.reply? && status.in_reply_to_account_id != status.account_id
@@ -75,13 +73,4 @@ class FanOutOnWriteService < BaseService
Redis.current.publish('timeline:public', @payload)
Redis.current.publish('timeline:public:local', @payload) if status.local?
end
def deliver_to_direct_timelines(status)
Rails.logger.debug "Delivering status #{status.id} to direct timelines"
status.mentions.includes(:account).each do |mention|
Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
end
Redis.current.publish("timeline:direct:#{status.account.id}", @payload) if status.account.local?
end
end

View File

@@ -18,7 +18,6 @@ class RemoveStatusService < BaseService
remove_reblogs
remove_from_hashtags
remove_from_public
remove_from_direct if status.direct_visibility?
@status.destroy!
@@ -122,13 +121,6 @@ class RemoveStatusService < BaseService
Redis.current.publish('timeline:public:local', @payload) if @status.local?
end
def remove_from_direct
@mentions.each do |mention|
Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
end
Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local?
end
def redis
Redis.current
end

View File

@@ -9,12 +9,7 @@
- else
= custom_emoji.domain
%td
- if custom_emoji.local?
- if custom_emoji.visible_in_picker
= table_link_to 'eye', t('admin.custom_emojis.listed'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: false }), method: :patch
- else
= table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }), method: :patch
- else
- unless custom_emoji.local?
= table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post
%td
- if custom_emoji.disabled?

View File

@@ -5,7 +5,6 @@
%link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
%link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
%link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
%link{ href: asset_pack_path('features/direct_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)

View File

@@ -1,11 +0,0 @@
.fields-group
= f.input :keyword
= f.check_box :whole_word
= f.label :whole_word, t('keyword_mutes.match_whole_word')
.actions
- if f.object.persisted?
= f.button :button, t('generic.save_changes'), type: :submit
= link_to t('keyword_mutes.remove'), settings_keyword_mute_path(f.object), class: 'negative button', method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
- else
= f.button :button, t('keyword_mutes.add_keyword'), type: :submit

View File

@@ -1,10 +0,0 @@
%tr
%td
= keyword_mute.keyword
%td
- if keyword_mute.whole_word
%i.fa.fa-check
%td
= table_link_to 'edit', t('keyword_mutes.edit'), edit_settings_keyword_mute_path(keyword_mute)
%td
= table_link_to 'times', t('keyword_mutes.remove'), settings_keyword_mute_path(keyword_mute), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

View File

@@ -1,6 +0,0 @@
- content_for :page_title do
= t('keyword_mutes.edit_keyword')
= simple_form_for @keyword_mute, url: settings_keyword_mute_path(@keyword_mute), as: :keyword_mute do |f|
= render 'shared/error_messages', object: @keyword_mute
= render 'fields', f: f

View File

@@ -1,18 +0,0 @@
- content_for :page_title do
= t('settings.keyword_mutes')
.table-wrapper
%table.table
%thead
%tr
%th= t('keyword_mutes.keyword')
%th= t('keyword_mutes.match_whole_word')
%th
%th
%tbody
= render partial: 'keyword_mute', collection: @keyword_mutes, as: :keyword_mute
= paginate @keyword_mutes
.simple_form
= link_to t('keyword_mutes.add_keyword'), new_settings_keyword_mute_path, class: 'button'
= link_to t('keyword_mutes.remove_all'), destroy_all_settings_keyword_mutes_path, class: 'button negative', method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }

View File

@@ -1,6 +0,0 @@
- content_for :page_title do
= t('keyword_mutes.add_keyword')
= simple_form_for @keyword_mute, url: settings_keyword_mutes_path, as: :keyword_mute do |f|
= render 'shared/error_messages', object: @keyword_mute
= render 'fields', f: f

View File

@@ -97,8 +97,6 @@ Rails.application.configure do
'X-XSS-Protection' => '1; mode=block',
'Content-Security-Policy' => "frame-ancestors 'none'; object-src 'none'; script-src 'self' https://dev-static.glitch.social 'unsafe-inline'; base-uri 'none';" ,
'Referrer-Policy' => 'no-referrer, strict-origin-when-cross-origin',
'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload',
'X-Clacks-Overhead' => 'GNU Natalie Nguyen'
'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload'
}
end

View File

@@ -130,15 +130,11 @@ en:
enable: Enable
enabled_msg: Successfully enabled that emoji
image_hint: PNG up to 50KB
listed: Listed
new:
title: Add new custom emoji
shortcode: Shortcode
shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
title: Custom emojis
unlisted: Unlisted
update_failed_msg: Could not update that emoji
updated_msg: Emoji successfully updated!
upload: Upload
domain_blocks:
add_new: Add new
@@ -377,14 +373,6 @@ en:
following: Following list
muting: Muting list
upload: Upload
keyword_mutes:
add_keyword: Add keyword
edit: Edit
edit_keyword: Edit keyword
keyword: Keyword
match_whole_word: Match whole word
remove: Remove
remove_all: Remove all
landing_strip_html: "<strong>%{name}</strong> is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse."
landing_strip_signup_html: If you don't, you can <a href="%{sign_up_path}">sign up here</a>.
media_attachments:
@@ -503,7 +491,6 @@ en:
export: Data export
followers: Authorized followers
import: Import
keyword_mutes: Muted keywords
notifications: Notifications
preferences: Preferences
settings: Settings

View File

@@ -1,77 +1,39 @@
---
ru:
about:
about_hashtag_html: Это публичные статусы, отмеченные хэштегом <strong>#%{hashtag}</strong>. Вы можете взаимодействовать с ними при наличии у Вас аккаунта в глобальной сети Mastodon.
about_mastodon_html: Mastodon - это <em>свободная</em> социальная сеть с <em>открытым исходным кодом</em>. Как <em>децентрализованная</em> альтернатива коммерческим платформам, Mastodon предотвращает риск монополизации Вашего общения одной компанией. Выберите сервер, которому Вы доверяете &mdash; что бы Вы ни выбрали, Вы сможете общаться со всеми остальными. Любой может запустить свой собственный узел Mastodon и участвовать в <em>социальной сети</em> совершенно бесшовно.
about_this: Об этом узле
closed_registrations: В данный момент регистрация на этом узле закрыта.
contact: Связаться
contact_missing: Не установлено
contact_unavailable: Недоступен
description_headline: Что такое %{domain}?
domain_count_after: другими узлами
domain_count_before: Связан с
extended_description_html: |
<h3>Хорошее место для правил</h3>
<p>Расширенное описание еще не настроено.</p>
features:
humane_approach_body: Наученный ошибками других проектов, Mastodon направлен на выбор этичных решений в борьбе со злоупотреблениями возможностями социальных сетей.
humane_approach_title: Человечный подход
not_a_product_body: Mastodon - не коммерческая сеть. Здесь нет рекламы, сбора данных, отгороженных мест. Здесь нет централизованного управления.
not_a_product_title: Вы - человек, а не продукт
real_conversation_body: С 500 символами в Вашем распоряжении и поддержкой предупреждений о содержании статусов Вы сможете выражать свои мысли так, как Вы этого хотите.
real_conversation_title: Создан для настоящего общения
within_reach_body: Различные приложения для iOS, Android и других платформ, написанные благодаря дружественной к разработчикам экосистеме API, позволят Вам держать связь с Вашими друзьями где угодно.
within_reach_title: Всегда под рукой
find_another_instance: Найти другой узел
generic_description: "%{domain} - один из серверов сети"
hosted_on: Mastodon размещен на %{domain}
learn_more: Узнать больше
other_instances: Другие узлы
source_code: Исходный код
status_count_after: статусов
status_count_before: Опубликовано
user_count_after: пользователей
user_count_before: Здесь живет
what_is_mastodon: Что такое Mastodon?
accounts:
follow: Подписаться
followers: Подписчики
following: Подписан(а)
media: Медиаконтент
nothing_here: Здесь ничего нет!
people_followed_by: Люди, на которых подписан(а) %{name}
people_who_follow: Подписчики %{name}
posts: Посты
posts_with_replies: Посты с ответами
remote_follow: Подписаться на удаленном узле
reserved_username: Имя пользователя зарезервировано
roles:
admin: Администратор
unfollow: Отписаться
admin:
account_moderation_notes:
account: Модератор
create: Создать
created_at: Дата
created_msg: Заметка модератора успешно создана!
delete: Удалить
destroyed_msg: Заметка модератора успешно удалена!
accounts:
are_you_sure: Вы уверены?
confirm: Подтвердить
confirmed: Подтверждено
disable_two_factor_authentication: Отключить 2FA
display_name: Отображаемое имя
domain: Домен
edit: Изменить
email: E-mail
feed_url: URL фида
followers: Подписчики
followers_url: URL подписчиков
follows: Подписки
inbox_url: URL входящих
ip: IP
location:
all: Все
local: Локальные
@@ -83,7 +45,6 @@ ru:
silenced: Заглушенные
suspended: Заблокированные
title: Модерация
moderation_notes: Заметки модератора
most_recent_activity: Последняя активность
most_recent_ip: Последний IP
not_subscribed: Не подписаны
@@ -91,51 +52,19 @@ ru:
alphabetic: По алфавиту
most_recent: По дате
title: Порядок
outbox_url: URL исходящих
perform_full_suspension: Полная блокировка
profile_url: URL профиля
protocol: Протокол
public: Публичный
push_subscription_expires: Подписка PuSH истекает
redownload: Обновить аватар
reset: Сбросить
reset_password: Сбросить пароль
resubscribe: Переподписаться
salmon_url: Salmon URL
search: Поиск
shared_inbox_url: URL общих входящих
show:
created_reports: Жалобы, отправленные этим аккаунтом
report: жалоба
targeted_reports: Жалобы на этот аккаунт
silence: Глушение
statuses: Статусы
subscribe: Подписаться
title: Аккаунты
undo_silenced: Снять глушение
undo_suspension: Снять блокировку
unsubscribe: Отписаться
username: Имя пользователя
web: WWW
custom_emojis:
copied_msg: Локальная копия эмодзи успешно создана
copy: Скопироват
copy_failed_msg: Не удалось создать локальную копию эмодзи
created_msg: Эмодзи успешно создано!
delete: Удалить
destroyed_msg: Эмодзи успешно удалено!
disable: Отключить
disabled_msg: Эмодзи успешно отключено
emoji: Эмодзи
enable: Включить
enabled_msg: Эмодзи успешно включено
image_hint: PNG до 50KB
new:
title: Добавить новое эмодзи
shortcode: Шорткод
shortcode_hint: Как минимум 2 символа, только алфавитно-цифровые символы и подчеркивания
title: Собственные эмодзи
upload: Загрузить
domain_blocks:
add_new: Добавить новую
created_msg: Блокировка домена обрабатывается
@@ -145,15 +74,13 @@ ru:
create: Создать блокировку
hint: Блокировка домена не предотвратит создание новых аккаунтов в базе данных, но ретроактивно и автоматически применит указанные методы модерации для этих аккаунтов.
severity:
desc_html: "<strong>Глушение</strong> сделает статусы аккаунта невидимыми для всех, кроме их подписчиков. <strong>Блокировка</strong> удалит весь контент аккаунта, включая мультимедийные вложения и данные профиля. Используйте <strong>Ничего</strong>, если хотите только запретить медиаконтент."
noop: Ничего
desc_html: "<strong>Глушение</strong> сделает статусы аккаунта невидимыми для всех, кроме их подписчиков. <strong>Блокировка</strong> удалит весь контент аккаунта, включая мультимедийные вложения и данные профиля."
silence: Глушение
suspend: Блокировка
title: Новая доменная блокировка
reject_media: Запретить медиаконтент
reject_media_hint: Удаляет локально хранимый медиаконтент и запрещает его загрузку в будущем. Не имеет значения в случае блокировки.
severities:
noop: Ничего
silence: Глушение
suspend: Блокировка
severity: Строгость
@@ -170,34 +97,13 @@ ru:
undo: Отменить
title: Доменные блокировки
undo: Отемнить
email_domain_blocks:
add_new: Добавить новую
created_msg: Доменная блокировка еmail успешно создана
delete: Удалить
destroyed_msg: Доменная блокировка еmail успешно удалена
domain: Домен
new:
create: Создать блокировку
title: Новая доменная блокировка еmail
title: Доменная блокировка email
instances:
account_count: Известных аккаунтов
domain_name: Домен
reset: Сбросить
search: Поиск
title: Известные узлы
reports:
action_taken_by: 'Действие предпринято:'
are_you_sure: Вы уверены?
comment:
label: Комментарий
none: Нет
delete: Удалить
id: ID
mark_as_resolved: Отметить как разрешенную
nsfw:
'false': Показать мультимедийные вложения
'true': Скрыть мультимедийные вложения
report: 'Жалоба #%{id}'
reported_account: Аккаунт нарушителя
reported_by: Отправитель жалобы
@@ -210,9 +116,6 @@ ru:
unresolved: Неразрешенные
view: Просмотреть
settings:
bootstrap_timeline_accounts:
desc_html: Разделяйте имена пользователей запятыми. Сработает только для локальных незакрытых аккаунтов. По умолчанию включены все локальные администраторы.
title: Подписки по умолчанию для новых пользователей
contact_information:
email: Введите публичный e-mail
username: Введите имя пользователя
@@ -220,11 +123,7 @@ ru:
closed_message:
desc_html: Отображается на титульной странице, когда закрыта регистрация<br>Можно использовать HTML-теги
title: Сообщение о закрытой регистрации
deletion:
desc_html: Позволяет всем удалять собственные аккаунты
title: Разрешить удаление аккаунтов
open:
desc_html: Позволяет любому создавать аккаунт
title: Открыть регистрацию
site_description:
desc_html: Отображается в качестве параграфа на титульной странице и используется в качестве мета-тега.<br>Можно использовать HTML-теги, в особенности <code>&lt;a&gt;</code> и <code>&lt;em&gt;</code>.
@@ -232,32 +131,8 @@ ru:
site_description_extended:
desc_html: Отображается на странице дополнительной информации<br>Можно использовать HTML-теги
title: Расширенное описание сайта
site_terms:
desc_html: Вы можете добавить сюда собственную политику конфиденциальности, пользовательское соглашение и другие документы. Можно использовать теги HTML.
title: Условия использования
site_title: Название сайта
thumbnail:
desc_html: Используется для предпросмотра с помощью OpenGraph и API. Рекомендуется разрешение 1200x630px
title: Картинка узла
timeline_preview:
desc_html: Показывать публичную ленту на целевой странице
title: Предпросмотр ленты
title: Настройки сайта
statuses:
back_to_account: Назад к странице аккаунта
batch:
delete: Удалить
nsfw_off: Выключить NSFW
nsfw_on: Включить NSFW
execute: Выполнить
failed_to_execute: Не удалось выполнить
media:
hide: Скрыть медиаконтент
show: Показать медиаконтент
title: Медиаконтент
no_media: Без медиаконтента
title: Статусы аккаунта
with_media: С медиаконтентом
subscriptions:
callback_url: Callback URL
confirmed: Подтверждено
@@ -266,31 +141,18 @@ ru:
title: WebSub
topic: Тема
title: Администрирование
admin_mailer:
new_report:
body: "%{reporter} подал(а) жалобу на %{target}"
subject: Новая жалоба, узел %{instance} (#%{id})
application_mailer:
salutation: "%{name},"
settings: 'Изменить настройки e-mail: %{link}'
signature: Уведомления Mastodon от %{instance}
view: 'Просмотр:'
applications:
created: Приложение успешно создано
destroyed: Приложение успешно удалено
invalid_url: Введенный URL неверен
regenerate_token: Повторно сгенерировать токен доступа
token_regenerated: Токен доступа успешно сгенерирован
warning: Будьте очень внимательны с этими данными. Не делитесь ими ни с кем!
your_token: Ваш токен доступа
auth:
agreement_html: Создавая аккаунт, вы соглашаетесь с <a href="%{rules_path}">нашими правилами поведения</a> и <a href="%{terms_path}">политикой конфиденциальности</a>.
change_password: Изменить пароль
delete_account: Удалить аккаунт
delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете <a href="%{path}">перейти сюда</a>. У Вас будет запрошено подтверждение.
didnt_get_confirmation: Не получили инструкцию для подтверждения?
forgot_password: Забыли пароль?
invalid_reset_password_token: Токен сброса пароля неверен или устарел. Пожалуйста, запросите новый.
login: Войти
logout: Выйти
register: Зарегистрироваться
@@ -300,12 +162,6 @@ ru:
authorize_follow:
error: К сожалению, при поиске удаленного аккаунта возникла ошибка
follow: Подписаться
follow_request: 'Вы отправили запрос на подписку:'
following: 'Ура! Теперь Вы подписаны на:'
post_follow:
close: Или просто закрыть это окно.
return: Вернуться к профилю пользователя
web: Перейти к WWW
title: Подписаться на %{acct}
datetime:
distance_in_words:
@@ -337,10 +193,7 @@ ru:
content: Проверка безопасности не удалась. Возможно, Вы блокируете cookies?
title: Проверка безопасности не удалась.
'429': Слишком много запросов
'500':
content: Приносим извинения, но на нашей стороне что-то пошло не так.
title: Страница неверна
noscript_html: Для работы с Mastodon, пожалуйста, включите JavaScript. Кроме того, вы можете использовать одно из <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">приложений</a> Mastodon для Вашей платформы.
noscript_html: Для работы с Mastodon, пожалуйста, включите JavaScript.
exports:
blocks: Список блокировки
csv: CSV
@@ -412,30 +265,23 @@ ru:
number:
human:
decimal_units:
format: "%n %u"
format: "%n%u"
units:
billion: млрд
million: млн
billion: B
million: M
quadrillion: Q
thousand: тыс
trillion: трлн
thousand: K
trillion: T
unit: ''
pagination:
next: След
prev: Пред
truncate: "&hellip;"
preferences:
languages: Языки
other: Другое
publishing: Публикация
web: WWW
push_notifications:
favourite:
title: Ваш статус понравился %{name}
follow:
title: "%{name} теперь подписан(а) на Вас"
group:
title: "%{count} уведомлений"
mention:
action_boost: Продвинуть
action_expand: Развернуть
@@ -489,24 +335,16 @@ ru:
authorized_apps: Авторизованные приложения
back: Назад в Mastodon
delete: Удаление аккаунта
development: Разработка
edit_profile: Изменить профиль
export: Экспорт данных
followers: Авторизованные подписчики
import: Импорт
notifications: Уведомления
preferences: Настройки
settings: Опции
two_factor_authentication: Двухфакторная аутентификация
your_apps: Ваши приложения
statuses:
open_in_web: Открыть в WWW
over_character_limit: превышен лимит символов (%{max})
pin_errors:
limit: Слишком много закрепленных статусов
ownership: Нельзя закрепить чужой статус
private: Нельзя закрепить непубличный статус
reblog: Нельзя закрепить продвинутый статус
show_more: Подробнее
visibilities:
private: Для подписчиков
@@ -521,8 +359,6 @@ ru:
sensitive_content: Чувствительный контент
terms:
title: Условия обслуживания и политика конфиденциальности %{instance}
themes:
default: Mastodon
time:
formats:
default: "%b %d, %Y, %H:%M"
@@ -531,13 +367,11 @@ ru:
description_html: При включении <strong>двухфакторной аутентификации</strong>, вход потребует от Вас использования Вашего телефона, который сгенерирует входные токены.
disable: Отключить
enable: Включить
enabled: Двухфакторная аутентификация включена
enabled_success: Двухфакторная аутентификация успешно включена
generate_recovery_codes: Сгенерировать коды восстановления
instructions_html: "<strong>Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне</strong>. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа."
lost_recovery_codes: Коды восстановления позволяют вернуть доступ к аккаунту в случае утери телефона. Если Вы потеряли Ваши коды восстановления, вы можете заново сгенерировать их здесь. Ваши старые коды восстановления будут аннулированы.
manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:'
recovery_codes: Коды восстановления
recovery_codes_regenerated: Коды восстановления успешно сгенерированы
recovery_instructions_html: В случае утери доступа к Вашему телефону Вы можете использовать один из кодов восстановления, указанных ниже, чтобы вернуть доступ к аккаунту. Держите коды восстановления в безопасности, например, распечатав их и храня с другими важными документами.
setup: Настроить
@@ -545,4 +379,3 @@ ru:
users:
invalid_email: Введенный e-mail неверен
invalid_otp_token: Введен неверный код
signed_in_as: 'Выполнен вход под именем:'

View File

@@ -4,7 +4,6 @@ pt-BR:
hints:
defaults:
avatar: PNG, GIF or JPG. Arquivos de até 2MB. Eles serão diminuídos para 120x120px
digest: Enviado após um longo período de inatividade com um resumo das menções que você recebeu em sua ausência.
display_name:
one: <span class="name-counter">1</span> caracter restante
other: <span class="name-counter">%{count}</span> caracteres restantes
@@ -14,7 +13,6 @@ pt-BR:
one: <span class="note-counter">1</span> caracter restante
other: <span class="note-counter">%{count}</span> caracteres restantes
setting_noindex: Afeta seu perfil público e as páginas de suas postagens
setting_theme: Afeta a aparência do Mastodon quando em sua conta em qualquer aparelho.
imports:
data: Arquivo CSV exportado de outra instância do Mastodon
sessions:
@@ -44,9 +42,7 @@ pt-BR:
setting_default_sensitive: Sempre marcar mídia como sensível
setting_delete_modal: Mostrar diálogo de confirmação antes de deletar uma postagem
setting_noindex: Não quero ser indexado por mecanismos de busca
setting_reduce_motion: Reduz movimento em animações
setting_system_font_ui: Usar a fonte padrão de seu sistema
setting_theme: Tema do site
setting_unfollow_modal: Mostrar diálogo de confirmação antes de deixar de seguir alguém
severity: Gravidade
type: Tipo de importação

View File

@@ -4,7 +4,6 @@ ru:
hints:
defaults:
avatar: PNG, GIF или JPG. Максимально 2MB. Будет уменьшено до 120x120px
digest: Отсылается после долгого периода неактивности с общей информацией упоминаний, полученных в Ваше отсутствие
display_name:
few: Осталось <span class="name-counter">%{count}</span> символа
many: Осталось <span class="name-counter">%{count}</span> символов
@@ -18,7 +17,6 @@ ru:
one: Остался <span class="name-counter">1</span> символ
other: Осталось <span class="name-counter">%{count}</span> символов
setting_noindex: Относится к Вашему публичному профилю и страницам статусов
setting_theme: Влияет на внешний вид Mastodon при выполненном входе в аккаунт.
imports:
data: Файл CSV, экспортированный с другого узла Mastodon
sessions:
@@ -48,8 +46,6 @@ ru:
setting_default_sensitive: Всегда отмечать медиаконтент как чувствительный
setting_delete_modal: Показывать диалог подтверждения перед удалением
setting_noindex: Отказаться от индексации в поисковых машинах
setting_reduce_motion: Уменьшить движение в анимации
setting_site_theme: Тема сайта
setting_system_font_ui: Использовать шрифт системы по умолчанию
setting_unfollow_modal: Показывать диалог подтверждения перед тем, как отписаться от аккаунта
severity: Строгость

View File

@@ -7,7 +7,6 @@ SimpleNavigation::Configuration.run do |navigation|
primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings|
settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url
settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
settings.item :keyword_mutes, safe_join([fa_icon('volume-off fw'), t('settings.keyword_mutes')]), settings_keyword_mutes_url
settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url
settings.item :password, safe_join([fa_icon('lock fw'), t('auth.change_password')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
settings.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_url, highlights_on: %r{/settings/two_factor_authentication}

View File

@@ -66,13 +66,6 @@ Rails.application.routes.draw do
namespace :settings do
resource :profile, only: [:show, :update]
resources :keyword_mutes do
collection do
delete :destroy_all
end
end
resource :preferences, only: [:show, :update]
resource :notifications, only: [:show, :update]
resource :import, only: [:show, :create]
@@ -147,7 +140,7 @@ Rails.application.routes.draw do
resource :two_factor_authentication, only: [:destroy]
end
resources :custom_emojis, only: [:index, :new, :create, :update, :destroy] do
resources :custom_emojis, only: [:index, :new, :create, :destroy] do
member do
post :copy
post :enable
@@ -200,7 +193,6 @@ Rails.application.routes.draw do
end
namespace :timelines do
resource :direct, only: :show, controller: :direct
resource :home, only: :show, controller: :home
resource :public, only: :show, controller: :public
resources :tag, only: :show

View File

@@ -3,62 +3,48 @@ class FixReblogsInFeeds < ActiveRecord::Migration[5.1]
redis = Redis.current
fm = FeedManager.instance
# Old scheme:
# Each user's feed zset had a series of score:value entries,
# where "regular" statuses had the same score and value (their
# ID). Reblogs had a score of the reblogging status' ID, and a
# value of the reblogged status' ID.
# New scheme:
# The feed contains only entries with the same score and value.
# Reblogs result in the reblogging status being added to the
# feed, with an entry in a reblog tracking zset (where the score
# is once again set to the reblogging status' ID, and the value
# is set to the reblogged status' ID). This is safe for Redis'
# float coersion because in this reblog tracking zset, we only
# need the rebloggging status' ID to be able to stop tracking
# entries after they have gotten too far down the feed, which
# does not require an exact value.
# This process reads all feeds and writes 3 times for each reblogs.
# So we use Lua script to avoid overhead between Ruby and Redis.
script = <<-LUA
local timeline_key = KEYS[1]
local reblog_key = KEYS[2]
-- So, first, we iterate over the user's feed to find any reblogs.
local items = redis.call('zrange', timeline_key, 0, -1, 'withscores')
for i = 1, #items, 2 do
local reblogged_id = items[i]
local reblogging_id = items[i + 1]
if (reblogged_id ~= reblogging_id) then
-- The score and value don't match, so this is a reblog.
-- (note that we're transitioning from IDs < 53 bits so we
-- don't have to worry about the loss of precision)
-- Remove the old entry
redis.call('zrem', timeline_key, reblogged_id)
-- Add a new one for the reblogging status
redis.call('zadd', timeline_key, reblogging_id, reblogging_id)
-- Track the fact that this was a reblog
redis.call('zadd', reblog_key, reblogging_id, reblogged_id)
end
end
LUA
script_hash = redis.script(:load, script)
# find_each is batched on the database side.
User.includes(:account).find_each do |user|
account = user.account
# Old scheme:
# Each user's feed zset had a series of score:value entries,
# where "regular" statuses had the same score and value (their
# ID). Reblogs had a score of the reblogging status' ID, and a
# value of the reblogged status' ID.
# New scheme:
# The feed contains only entries with the same score and value.
# Reblogs result in the reblogging status being added to the
# feed, with an entry in a reblog tracking zset (where the score
# is once again set to the reblogging status' ID, and the value
# is set to the reblogged status' ID). This is safe for Redis'
# float coersion because in this reblog tracking zset, we only
# need the rebloggging status' ID to be able to stop tracking
# entries after they have gotten too far down the feed, which
# does not require an exact value.
# So, first, we iterate over the user's feed to find any reblogs.
timeline_key = fm.key(:home, account.id)
reblog_key = fm.key(:home, account.id, 'reblogs')
redis.zrange(timeline_key, 0, -1, with_scores: true).each do |entry|
next if entry[0] == entry[1]
redis.evalsha(script_hash, [timeline_key, reblog_key])
# The score and value don't match, so this is a reblog.
# (note that we're transitioning from IDs < 53 bits so we
# don't have to worry about the loss of precision)
reblogged_id, reblogging_id = entry
# Remove the old entry
redis.zrem(timeline_key, reblogged_id)
# Add a new one for the reblogging status
redis.zadd(timeline_key, reblogging_id, reblogging_id)
# Track the fact that this was a reblog
redis.zadd(reblog_key, reblogging_id, reblogged_id)
end
end
end

View File

@@ -1,12 +0,0 @@
class CreateKeywordMutes < ActiveRecord::Migration[5.1]
def change
create_table :keyword_mutes do |t|
t.references :account, null: false
t.string :keyword, null: false
t.boolean :whole_word, null: false, default: true
t.timestamps
end
add_foreign_key :keyword_mutes, :accounts, on_delete: :cascade
end
end

View File

@@ -1,7 +0,0 @@
class AddVisibleInPickerToCustomEmoji < ActiveRecord::Migration[5.1]
def change
safety_assured {
add_column :custom_emojis, :visible_in_picker, :boolean, default: true, null: false
}
end
end

View File

@@ -1,7 +0,0 @@
class MoveKeywordMutesIntoGlitchNamespace < ActiveRecord::Migration[5.1]
def change
safety_assured do
rename_table :keyword_mutes, :glitch_keyword_mutes
end
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171021191900) do
ActiveRecord::Schema.define(version: 20171010025614) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -111,7 +111,6 @@ ActiveRecord::Schema.define(version: 20171021191900) do
t.boolean "disabled", default: false, null: false
t.string "uri"
t.string "image_remote_url"
t.boolean "visible_in_picker", default: true, null: false
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
end
@@ -156,15 +155,6 @@ ActiveRecord::Schema.define(version: 20171021191900) do
t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true
end
create_table "glitch_keyword_mutes", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "keyword", null: false
t.boolean "whole_word", default: true, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_glitch_keyword_mutes_on_account_id"
end
create_table "imports", force: :cascade do |t|
t.integer "type", null: false
t.boolean "approved", default: false, null: false
@@ -482,7 +472,6 @@ ActiveRecord::Schema.define(version: 20171021191900) do
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
add_foreign_key "glitch_keyword_mutes", "accounts", on_delete: :cascade
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
add_foreign_key "media_attachments", "accounts", name: "fk_96dd81e81b", on_delete: :nullify
add_foreign_key "media_attachments", "statuses", on_delete: :nullify

View File

@@ -10,7 +10,6 @@ module Paperclip
unless options[:style] == :original && num_frames > 1
tmp_file = Paperclip::TempfileFactory.new.generate(attachment.instance.file_file_name)
tmp_file << file.read
tmp_file.flush
return tmp_file
end

View File

@@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe Settings::KeywordMutesController, type: :controller do
end

View File

@@ -1,2 +0,0 @@
Fabricator('Glitch::KeywordMute') do
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,15 +0,0 @@
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the Settings::KeywordMutesHelper. For example:
#
# describe Settings::KeywordMutesHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# expect(helper.concat_strings("this","that")).to eq("this that")
# end
# end
# end
RSpec.describe Settings::KeywordMutesHelper, type: :helper do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@@ -119,44 +119,6 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
end
it 'returns true for a status containing a muted keyword' do
Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
status = Fabricate(:status, text: 'This is a hot take', account: bob)
expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
end
it 'returns true for a reply containing a muted keyword' do
Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
s1 = Fabricate(:status, text: 'Something', account: alice)
s2 = Fabricate(:status, text: 'This is a hot take', thread: s1, account: bob)
expect(FeedManager.instance.filter?(:home, s2, alice.id)).to be true
end
it 'returns true for a status whose spoiler text contains a muted keyword' do
Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
status = Fabricate(:status, spoiler_text: 'This is a hot take', account: bob)
expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
end
it 'returns true for a reblog containing a muted keyword' do
Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
status = Fabricate(:status, text: 'This is a hot take', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
end
it 'returns true for a reblog whose spoiler text contains a muted keyword' do
Fabricate('Glitch::KeywordMute', account: alice, keyword: 'take')
status = Fabricate(:status, spoiler_text: 'This is a hot take', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff)
expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
end
end
context 'for mentions feed' do
@@ -185,13 +147,6 @@ RSpec.describe FeedManager do
bob.follow!(alice)
expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be false
end
it 'returns true for status that contains a muted keyword' do
Fabricate('Glitch::KeywordMute', account: bob, keyword: 'take')
status = Fabricate(:status, text: 'This is a hot take', account: alice)
bob.follow!(alice)
expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be true
end
end
end

View File

@@ -1,89 +0,0 @@
require 'rails_helper'
RSpec.describe Glitch::KeywordMute, type: :model do
let(:alice) { Fabricate(:account, username: 'alice').tap(&:save!) }
let(:bob) { Fabricate(:account, username: 'bob').tap(&:save!) }
describe '.matcher_for' do
let(:matcher) { Glitch::KeywordMute.matcher_for(alice) }
describe 'with no mutes' do
before do
Glitch::KeywordMute.delete_all
end
it 'does not match' do
expect(matcher =~ 'This is a hot take').to be_falsy
end
end
describe 'with mutes' do
it 'does not match keywords set by a different account' do
Glitch::KeywordMute.create!(account: bob, keyword: 'take')
expect(matcher =~ 'This is a hot take').to be_falsy
end
it 'does not match if no keywords match the status text' do
Glitch::KeywordMute.create!(account: alice, keyword: 'cold')
expect(matcher =~ 'This is a hot take').to be_falsy
end
it 'considers word boundaries when matching' do
Glitch::KeywordMute.create!(account: alice, keyword: 'bob', whole_word: true)
expect(matcher =~ 'bobcats').to be_falsy
end
it 'matches substrings if whole_word is false' do
Glitch::KeywordMute.create!(account: alice, keyword: 'take', whole_word: false)
expect(matcher =~ 'This is a shiitake mushroom').to be_truthy
end
it 'matches keywords at the beginning of the text' do
Glitch::KeywordMute.create!(account: alice, keyword: 'take')
expect(matcher =~ 'Take this').to be_truthy
end
it 'matches keywords at the end of the text' do
Glitch::KeywordMute.create!(account: alice, keyword: 'take')
expect(matcher =~ 'This is a hot take').to be_truthy
end
it 'matches if at least one keyword case-insensitively matches the text' do
Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
expect(matcher =~ 'This is a HOT take').to be_truthy
end
it 'matches keywords surrounded by non-alphanumeric ornamentation' do
Glitch::KeywordMute.create!(account: alice, keyword: 'hot')
expect(matcher =~ '(hot take)').to be_truthy
end
it 'escapes metacharacters in keywords' do
Glitch::KeywordMute.create!(account: alice, keyword: '(hot take)')
expect(matcher =~ '(hot take)').to be_truthy
end
it 'uses case-folding rules appropriate for more than just English' do
Glitch::KeywordMute.create!(account: alice, keyword: 'großeltern')
expect(matcher =~ 'besuch der grosseltern').to be_truthy
end
it 'matches keywords that are composed of multiple words' do
Glitch::KeywordMute.create!(account: alice, keyword: 'a shiitake')
expect(matcher =~ 'This is a shiitake').to be_truthy
expect(matcher =~ 'This is shiitake').to_not be_truthy
end
end
end
end

View File

@@ -20,29 +20,20 @@ RSpec.describe MediaAttachment, type: :model do
end
describe 'non-animated gif non-conversion' do
fixtures = [
{ filename: 'attachment.gif', width: 600, height: 400, aspect: 1.5 },
{ filename: 'mini-static.gif', width: 32, height: 32, aspect: 1.0 },
]
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('attachment.gif')) }
fixtures.each do |fixture|
context fixture[:filename] do
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture(fixture[:filename])) }
it 'sets type to image' do
expect(media.type).to eq 'image'
end
it 'sets type to image' do
expect(media.type).to eq 'image'
end
it 'leaves original file as-is' do
expect(media.file_content_type).to eq 'image/gif'
end
it 'leaves original file as-is' do
expect(media.file_content_type).to eq 'image/gif'
end
it 'sets meta' do
expect(media.file.meta["original"]["width"]).to eq fixture[:width]
expect(media.file.meta["original"]["height"]).to eq fixture[:height]
expect(media.file.meta["original"]["aspect"]).to eq fixture[:aspect]
end
end
it 'sets meta' do
expect(media.file.meta["original"]["width"]).to eq 600
expect(media.file.meta["original"]["height"]).to eq 400
expect(media.file.meta["original"]["aspect"]).to eq 1.5
end
end

View File

@@ -232,55 +232,6 @@ RSpec.describe Status, type: :model do
end
end
describe '.as_direct_timeline' do
let(:account) { Fabricate(:account) }
let(:followed) { Fabricate(:account) }
let(:not_followed) { Fabricate(:account) }
before do
Fabricate(:follow, account: account, target_account: followed)
@self_public_status = Fabricate(:status, account: account, visibility: :public)
@self_direct_status = Fabricate(:status, account: account, visibility: :direct)
@followed_public_status = Fabricate(:status, account: followed, visibility: :public)
@followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
@not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct)
@results = Status.as_direct_timeline(account)
end
it 'does not include public statuses from self' do
expect(@results).to_not include(@self_public_status)
end
it 'includes direct statuses from self' do
expect(@results).to include(@self_direct_status)
end
it 'does not include public statuses from followed' do
expect(@results).to_not include(@followed_public_status)
end
it 'includes direct statuses mentioning recipient from followed' do
Fabricate(:mention, account: account, status: @followed_direct_status)
expect(@results).to include(@followed_direct_status)
end
it 'does not include direct statuses not mentioning recipient from followed' do
expect(@results).to_not include(@followed_direct_status)
end
it 'includes direct statuses mentioning recipient from non-followed' do
Fabricate(:mention, account: account, status: @not_followed_direct_status)
expect(@results).to include(@not_followed_direct_status)
end
it 'does not include direct statuses not mentioning recipient from non-followed' do
expect(@results).to_not include(@not_followed_direct_status)
end
end
describe '.as_public_timeline' do
it 'only includes statuses with public visibility' do
public_status = Fabricate(:status, visibility: :public)

View File

@@ -402,10 +402,6 @@ const startWorker = (workerId) => {
streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true);
});
app.get('/api/v1/streaming/direct', (req, res) => {
streamFrom(`timeline:direct:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req), true);
});
app.get('/api/v1/streaming/hashtag', (req, res) => {
streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}`, req, streamToHttp(req, res), streamHttpEnd(req), true);
});
@@ -441,9 +437,6 @@ const startWorker = (workerId) => {
case 'public:local':
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
break;
case 'direct':
streamFrom(`timeline:direct:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
break;
case 'hashtag':
streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
break;