Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master
commit
82236a3703
|
@ -89,7 +89,8 @@ module Admin
|
|||
:username,
|
||||
:display_name,
|
||||
:email,
|
||||
:ip
|
||||
:ip,
|
||||
:staff
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -92,7 +92,9 @@ module Admin
|
|||
def filter_params
|
||||
params.permit(
|
||||
:local,
|
||||
:remote
|
||||
:remote,
|
||||
:by_domain,
|
||||
:shortcode
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::FilterHelper
|
||||
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze
|
||||
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip staff).freeze
|
||||
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
||||
INVITE_FILTER = %i(available expired).freeze
|
||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||
|
||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER
|
||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS
|
||||
|
||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||
new_url = filtered_url_for(link_to_params)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
|
||||
|
@ -8,12 +9,12 @@ export default class Avatar extends React.PureComponent {
|
|||
account: ImmutablePropTypes.map.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
style: PropTypes.object,
|
||||
animate: PropTypes.bool,
|
||||
inline: PropTypes.bool,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: false,
|
||||
animate: autoPlayGif,
|
||||
size: 20,
|
||||
inline: false,
|
||||
};
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
export default class AvatarOverlay extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
friend: ImmutablePropTypes.map.isRequired,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: autoPlayGif,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account, friend } = this.props;
|
||||
const { account, friend, animate } = this.props;
|
||||
|
||||
const baseStyle = {
|
||||
backgroundImage: `url(${account.get('avatar_static')})`,
|
||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
const overlayStyle = {
|
||||
backgroundImage: `url(${friend.get('avatar_static')})`,
|
||||
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -156,6 +156,8 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='compose-form'>
|
||||
<WarningContainer />
|
||||
|
||||
<Collapsable isVisible={this.props.spoiler} fullHeight={50}>
|
||||
<div className='spoiler-input'>
|
||||
<label>
|
||||
|
@ -165,8 +167,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
</div>
|
||||
</Collapsable>
|
||||
|
||||
<WarningContainer />
|
||||
|
||||
<ReplyIndicatorContainer />
|
||||
|
||||
<div className='compose-form__autosuggest-wrapper'>
|
||||
|
@ -199,13 +199,13 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||
<SensitiveButtonContainer />
|
||||
<SpoilerButtonContainer />
|
||||
</div>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
</div>
|
||||
|
||||
<div className='compose-form__publish'>
|
||||
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
|
||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import IconButton from '../../../components/icon_button';
|
|||
import DisplayName from '../../../components/display_name';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { isRtl } from '../../../rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
|
@ -43,6 +44,9 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const style = {
|
||||
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='reply-indicator'>
|
||||
|
@ -55,7 +59,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
|
||||
<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { intl, media } = this.props;
|
||||
const active = this.state.hovered || this.state.focused;
|
||||
const description = this.state.dirtyDescription || media.get('description') || '';
|
||||
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
|
|
|
@ -161,7 +161,7 @@ export default class ListTimeline extends React.PureComponent {
|
|||
scrollKey={`list_timeline-${columnId}`}
|
||||
timelineId={`list:${id}`}
|
||||
loadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from '../../../actions/timelines';
|
||||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { connectHashtagStream } from '../../../actions/streaming';
|
||||
|
||||
@connect()
|
||||
export default class HashtagTimeline extends React.PureComponent {
|
||||
|
@ -29,16 +30,13 @@ export default class HashtagTimeline extends React.PureComponent {
|
|||
const { dispatch, hashtag } = this.props;
|
||||
|
||||
dispatch(refreshHashtagTimeline(hashtag));
|
||||
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(refreshHashtagTimeline(hashtag));
|
||||
}, 10000);
|
||||
this.disconnect = dispatch(connectHashtagStream(hashtag));
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (typeof this.polling !== 'undefined') {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import Column from '../../../components/column';
|
||||
import ColumnHeader from '../../../components/column_header';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connectPublicStream } from '../../../actions/streaming';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
|
||||
|
@ -35,16 +36,13 @@ export default class PublicTimeline extends React.PureComponent {
|
|||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(refreshPublicTimeline());
|
||||
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(refreshPublicTimeline());
|
||||
}, 3000);
|
||||
this.disconnect = dispatch(connectPublicStream());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (typeof this.polling !== 'undefined') {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ const componentMap = {
|
|||
'LIST': ListTimeline,
|
||||
};
|
||||
|
||||
const isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||
|
||||
@component => injectIntl(component, { withRef: true })
|
||||
export default class ColumnsArea extends ImmutablePureComponent {
|
||||
|
||||
|
@ -79,7 +81,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
handleChildrenContentChange() {
|
||||
if (!this.props.singleColumn) {
|
||||
this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
|
||||
const modifier = isRtlLayout ? -1 : 1;
|
||||
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
"column.favourites": "المفضلة",
|
||||
"column.follow_requests": "طلبات المتابعة",
|
||||
"column.home": "الرئيسية",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "القوائم",
|
||||
"column.mutes": "الحسابات المكتومة",
|
||||
"column.notifications": "الإشعارات",
|
||||
"column.notifications": "الإخطارات",
|
||||
"column.pins": "التبويقات المثبتة",
|
||||
"column.public": "الخيط العام الموحد",
|
||||
"column_back_button.label": "العودة",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "حذف",
|
||||
"confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟",
|
||||
"confirmations.domain_block.confirm": "إخفاء إسم النطاق كاملا",
|
||||
"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": "أكتم",
|
||||
|
@ -109,32 +109,32 @@
|
|||
"home.settings": "إعدادات العمود",
|
||||
"keyboard_shortcuts.back": "للعودة",
|
||||
"keyboard_shortcuts.boost": "للترقية",
|
||||
"keyboard_shortcuts.column": "to focus a status in one of the columns",
|
||||
"keyboard_shortcuts.compose": "to focus the compose textarea",
|
||||
"keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة",
|
||||
"keyboard_shortcuts.compose": "للتركيز على نافذة تحرير النصوص",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.favourite": "to favourite",
|
||||
"keyboard_shortcuts.favourite": "للإضافة إلى المفضلة",
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.hotkey": "مفتاح الإختصار",
|
||||
"keyboard_shortcuts.legend": "لعرض هذا المفتاح",
|
||||
"keyboard_shortcuts.mention": "لذِكر الناشر",
|
||||
"keyboard_shortcuts.reply": "للردّ",
|
||||
"keyboard_shortcuts.search": "to focus search",
|
||||
"keyboard_shortcuts.search": "للتركيز على البحث",
|
||||
"keyboard_shortcuts.toot": "لتحرير تبويق جديد",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة",
|
||||
"lightbox.close": "إغلاق",
|
||||
"lightbox.next": "التالي",
|
||||
"lightbox.previous": "العودة",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "أضف إلى القائمة",
|
||||
"lists.account.remove": "إحذف من القائمة",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "تعديل القائمة",
|
||||
"lists.new.create": "إنشاء قائمة",
|
||||
"lists.new.title_placeholder": "عنوان القائمة الجديدة",
|
||||
"lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
|
||||
"lists.subheading": "قوائمك",
|
||||
"loading_indicator.label": "تحميل ...",
|
||||
"media_gallery.toggle_visible": "عرض / إخفاء",
|
||||
"missing_indicator.label": "تعذر العثور عليه",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "طلبات المتابعة",
|
||||
"navigation_bar.info": "معلومات إضافية",
|
||||
"navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "القوائم",
|
||||
"navigation_bar.logout": "خروج",
|
||||
"navigation_bar.mutes": "الحسابات المكتومة",
|
||||
"navigation_bar.pins": "التبويقات المثبتة",
|
||||
|
@ -209,7 +209,7 @@
|
|||
"search_popout.search_format": "نمط البحث المتقدم",
|
||||
"search_popout.tips.hashtag": "وسم",
|
||||
"search_popout.tips.status": "حالة",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
|
||||
"search_popout.tips.user": "مستخدِم",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
|
||||
"standalone.public_title": "نظرة على ...",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favorits",
|
||||
"column.follow_requests": "Peticions per seguir-te",
|
||||
"column.home": "Inici",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Llistes",
|
||||
"column.mutes": "Usuaris silenciats",
|
||||
"column.notifications": "Notificacions",
|
||||
"column.pins": "Toot fixat",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Esborrar",
|
||||
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
|
||||
"confirmations.domain_block.confirm": "Amagar tot el domini",
|
||||
"confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Tancar",
|
||||
"lightbox.next": "Següent",
|
||||
"lightbox.previous": "Anterior",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Afegir a la llista",
|
||||
"lists.account.remove": "Treure de la llista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Editar llista",
|
||||
"lists.new.create": "Afegir llista",
|
||||
"lists.new.title_placeholder": "Nou títol de llista",
|
||||
"lists.search": "Cercar entre les persones que segueixes",
|
||||
"lists.subheading": "Les teves llistes",
|
||||
"loading_indicator.label": "Carregant...",
|
||||
"media_gallery.toggle_visible": "Alternar visibilitat",
|
||||
"missing_indicator.label": "No trobat",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
|
||||
"navigation_bar.info": "Informació addicional",
|
||||
"navigation_bar.keyboard_shortcuts": "Dreceres de teclat",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Llistes",
|
||||
"navigation_bar.logout": "Tancar sessió",
|
||||
"navigation_bar.mutes": "Usuaris silenciats",
|
||||
"navigation_bar.pins": "Toots fixats",
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
"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.list": "There is nothing in this list yet.",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||
"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",
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
"empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag",
|
||||
"empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.",
|
||||
"empty_column.home.public_timeline": "le fil public",
|
||||
"empty_column.list": "Il n'y a rien dans cette liste pour l'instant.",
|
||||
"empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette listes publierons de nouveaux statuts ils apparaîtront ici.",
|
||||
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.",
|
||||
"empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.",
|
||||
"follow_request.authorize": "Accepter",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favoritas",
|
||||
"column.follow_requests": "Peticións de seguimento",
|
||||
"column.home": "Inicio",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Usuarias acaladas",
|
||||
"column.notifications": "Notificacións",
|
||||
"column.pins": "Mensaxes fixadas",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Borrar",
|
||||
"confirmations.delete.message": "Está segura de que quere eliminar este estado?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Estás seguro de que queres eliminar permanentemente esta lista?",
|
||||
"confirmations.domain_block.confirm": "Agochar un dominio completo",
|
||||
"confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos.",
|
||||
"confirmations.mute.confirm": "Acalar",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Seguinte",
|
||||
"lightbox.previous": "Anterior",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Engadir á lista",
|
||||
"lists.account.remove": "Eliminar da lista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Editar lista",
|
||||
"lists.new.create": "Engadir lista",
|
||||
"lists.new.title_placeholder": "Novo título da lista",
|
||||
"lists.search": "Procurar entre a xente que segues",
|
||||
"lists.subheading": "As túas listas",
|
||||
"loading_indicator.label": "Cargando...",
|
||||
"media_gallery.toggle_visible": "Dar visibilidade",
|
||||
"missing_indicator.label": "Non atopado",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Peticións de seguimento",
|
||||
"navigation_bar.info": "Sobre esta instancia",
|
||||
"navigation_bar.keyboard_shortcuts": "Atallos do teclado",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Listas",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.mutes": "Usuarias acaladas",
|
||||
"navigation_bar.pins": "Mensaxes fixadas",
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
"empty_column.home.public_timeline": "連合タイムライン",
|
||||
"empty_column.list": "このリストにはまだなにもありません。",
|
||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
|
||||
"empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう",
|
||||
"follow_request.authorize": "許可",
|
||||
"follow_request.reject": "拒否",
|
||||
"getting_started.appsshort": "アプリ",
|
||||
|
@ -162,12 +162,12 @@
|
|||
"notifications.clear": "通知を消去",
|
||||
"notifications.clear_confirmation": "本当に通知を消去しますか?",
|
||||
"notifications.column_settings.alert": "デスクトップ通知",
|
||||
"notifications.column_settings.favourite": "お気に入り",
|
||||
"notifications.column_settings.follow": "新しいフォロワー",
|
||||
"notifications.column_settings.mention": "返信",
|
||||
"notifications.column_settings.favourite": "お気に入り:",
|
||||
"notifications.column_settings.follow": "新しいフォロワー:",
|
||||
"notifications.column_settings.mention": "返信:",
|
||||
"notifications.column_settings.push": "プッシュ通知",
|
||||
"notifications.column_settings.push_meta": "このデバイス",
|
||||
"notifications.column_settings.reblog": "ブースト",
|
||||
"notifications.column_settings.reblog": "ブースト:",
|
||||
"notifications.column_settings.show": "カラムに表示",
|
||||
"notifications.column_settings.sound": "通知音を再生",
|
||||
"onboarding.done": "完了",
|
||||
|
@ -176,7 +176,7 @@
|
|||
"onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
|
||||
"onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
|
||||
"onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
|
||||
"onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。",
|
||||
"onboarding.page_one.handle": "今あなたは数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です",
|
||||
"onboarding.page_one.welcome": "Mastodonへようこそ!",
|
||||
"onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
|
||||
"onboarding.page_six.almost_done": "以上です。",
|
||||
|
@ -184,7 +184,7 @@
|
|||
"onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
|
||||
"onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
|
||||
"onboarding.page_six.guidelines": "コミュニティガイドライン",
|
||||
"onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。",
|
||||
"onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください!",
|
||||
"onboarding.page_six.various_app": "様々なモバイルアプリ",
|
||||
"onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
|
||||
"onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
|
||||
|
@ -215,7 +215,7 @@
|
|||
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
|
||||
"search_popout.tips.user": "ユーザー",
|
||||
"search_results.total": "{count, number}件の結果",
|
||||
"standalone.public_title": "今こんな話をしています",
|
||||
"standalone.public_title": "今こんな話をしています...",
|
||||
"status.cannot_reblog": "この投稿はブーストできません",
|
||||
"status.delete": "削除",
|
||||
"status.embed": "埋め込み",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favorieten",
|
||||
"column.follow_requests": "Volgverzoeken",
|
||||
"column.home": "Start",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Lijsten",
|
||||
"column.mutes": "Genegeerde gebruikers",
|
||||
"column.notifications": "Meldingen",
|
||||
"column.pins": "Vastgezette toots",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Verwijderen",
|
||||
"confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Weet je zeker dat je deze lijst permanent wilt verwijderen?",
|
||||
"confirmations.domain_block.confirm": "Negeer alles van deze server",
|
||||
"confirmations.domain_block.message": "Weet je het echt, echt zeker dat je alles van {domain} wil negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en gewenst.",
|
||||
"confirmations.mute.confirm": "Negeren",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Sluiten",
|
||||
"lightbox.next": "Volgende",
|
||||
"lightbox.previous": "Vorige",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Aan lijst toevoegen",
|
||||
"lists.account.remove": "Uit lijst verwijderen",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Lijst bewerken",
|
||||
"lists.new.create": "Lijst toevoegen",
|
||||
"lists.new.title_placeholder": "Naam nieuwe lijst",
|
||||
"lists.search": "Zoek naar mensen die je volgt",
|
||||
"lists.subheading": "Jouw lijsten",
|
||||
"loading_indicator.label": "Laden…",
|
||||
"media_gallery.toggle_visible": "Media wel/niet tonen",
|
||||
"missing_indicator.label": "Niet gevonden",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Volgverzoeken",
|
||||
"navigation_bar.info": "Uitgebreide informatie",
|
||||
"navigation_bar.keyboard_shortcuts": "Toetsenbord sneltoetsen",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Lijsten",
|
||||
"navigation_bar.logout": "Afmelden",
|
||||
"navigation_bar.mutes": "Genegeerde gebruikers",
|
||||
"navigation_bar.pins": "Vastgezette toots",
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
"empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.",
|
||||
"empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||
"empty_column.home.public_timeline": "lo flux public",
|
||||
"empty_column.list": "I a pas res dins la lista pel moment.",
|
||||
"empty_column.list": "I a pas res dins la lista pel moment. Quand de membres d’aquesta lista publiquen de novèls estatuts los veiretz aquí.",
|
||||
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
|
||||
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favoritos",
|
||||
"column.follow_requests": "Seguidores pendentes",
|
||||
"column.home": "Página inicial",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Usuários silenciados",
|
||||
"column.notifications": "Notificações",
|
||||
"column.pins": "Postagens fixadas",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Excluir",
|
||||
"confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Você tem certeza que quer deletar permanentemente a lista?",
|
||||
"confirmations.domain_block.confirm": "Esconder o domínio inteiro",
|
||||
"confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
|
@ -110,7 +110,7 @@
|
|||
"keyboard_shortcuts.back": "para navegar de volta",
|
||||
"keyboard_shortcuts.boost": "para compartilhar",
|
||||
"keyboard_shortcuts.column": "Focar um status em uma das colunas",
|
||||
"keyboard_shortcuts.compose": "to focus the compose textarea",
|
||||
"keyboard_shortcuts.compose": "para focar a área de redação",
|
||||
"keyboard_shortcuts.description": "Description",
|
||||
"keyboard_shortcuts.down": "para mover para baixo na lista",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Próximo",
|
||||
"lightbox.previous": "Anterior",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Adicionar a listas",
|
||||
"lists.account.remove": "Remover da lista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Editar lista",
|
||||
"lists.new.create": "Adicionar lista",
|
||||
"lists.new.title_placeholder": "Novo título da lista",
|
||||
"lists.search": "Procurar entre as pessoas que você segue",
|
||||
"lists.subheading": "Suas listas",
|
||||
"loading_indicator.label": "Carregando...",
|
||||
"media_gallery.toggle_visible": "Esconder/Mostrar",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Seguidores pendentes",
|
||||
"navigation_bar.info": "Mais informações",
|
||||
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Listas",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.mutes": "Usuários silenciados",
|
||||
"navigation_bar.pins": "Postagens fixadas",
|
||||
|
@ -177,7 +177,7 @@
|
|||
"onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!",
|
||||
"onboarding.page_six.admin": "O administrador de sua instância é {admin}.",
|
||||
"onboarding.page_six.almost_done": "Quase acabando...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.appetoot": "Bom Apetoot!",
|
||||
"onboarding.page_six.apps_available": "Há {apps} disponíveis para iOS, Android e outras plataformas.",
|
||||
"onboarding.page_six.github": "Mastodon é um software gratuito e de código aberto. Você pode reportar bugs, prequisitar novas funções ou contribuir para o código no {github}.",
|
||||
"onboarding.page_six.guidelines": "diretrizes da comunidade",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.block_domain": "Esconder tudo do domínio {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
|
@ -19,7 +19,7 @@
|
|||
"account.share": "Partilhar o perfil @{name}",
|
||||
"account.show_reblogs": "Mostrar partilhas de @{name}",
|
||||
"account.unblock": "Não bloquear @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_domain": "Mostrar {domain}",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
"account.unmute": "Não silenciar @{name}",
|
||||
"account.unmute_notifications": "Deixar de silenciar @{name}",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"column.favourites": "Favoritos",
|
||||
"column.follow_requests": "Seguidores Pendentes",
|
||||
"column.home": "Home",
|
||||
"column.lists": "Lists",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Utilizadores silenciados",
|
||||
"column.notifications": "Notificações",
|
||||
"column.pins": "Pinned toot",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.delete.confirm": "Eliminar",
|
||||
"confirmations.delete.message": "De certeza que queres eliminar esta publicação?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.message": "Tens a certeza de que desejas apagar permanentemente esta lista?",
|
||||
"confirmations.domain_block.confirm": "Esconder tudo deste domínio",
|
||||
"confirmations.domain_block.message": "De certeza que queres bloquear por completo o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado.",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
|
@ -88,12 +88,12 @@
|
|||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viagens & Lugares",
|
||||
"empty_column.community": "Ainda não existe conteúdo local para mostrar!",
|
||||
"empty_column.hashtag": "Não foram encontradas publicações com essa hashtag",
|
||||
"empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.",
|
||||
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
|
||||
"empty_column.home.public_timeline": "global",
|
||||
"empty_column.list": "Ainda não existem publicações nesta lista.",
|
||||
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
|
||||
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
|
||||
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Rejeitar",
|
||||
"getting_started.appsshort": "Aplicações",
|
||||
|
@ -116,7 +116,7 @@
|
|||
"keyboard_shortcuts.enter": "para expandir uma publicação",
|
||||
"keyboard_shortcuts.favourite": "para adicionar aos favoritos",
|
||||
"keyboard_shortcuts.heading": "Atalhos do teclado",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.hotkey": "Atalho",
|
||||
"keyboard_shortcuts.legend": "para mostrar esta legenda",
|
||||
"keyboard_shortcuts.mention": "para mencionar o autor",
|
||||
"keyboard_shortcuts.reply": "para responder",
|
||||
|
@ -127,14 +127,14 @@
|
|||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Próximo",
|
||||
"lightbox.previous": "Anterior",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.account.add": "Adicionar à lista",
|
||||
"lists.account.remove": "Remover da lista",
|
||||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"lists.edit": "Editar lista",
|
||||
"lists.new.create": "Adicionar lista",
|
||||
"lists.new.title_placeholder": "Novo título da lista",
|
||||
"lists.search": "Pesquisa entre as pessoas que segues",
|
||||
"lists.subheading": "As tuas listas",
|
||||
"loading_indicator.label": "A carregar...",
|
||||
"media_gallery.toggle_visible": "Esconder/Mostrar",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"navigation_bar.follow_requests": "Seguidores pendentes",
|
||||
"navigation_bar.info": "Mais informações",
|
||||
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.lists": "Listas",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.mutes": "Utilizadores silenciados",
|
||||
"navigation_bar.pins": "Posts fixos",
|
||||
|
@ -209,13 +209,13 @@
|
|||
"search_popout.search_format": "Formato avançado de pesquisa",
|
||||
"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.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
|
||||
"search_popout.tips.user": "utilizador",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
|
||||
"standalone.public_title": "Espreitar lá dentro...",
|
||||
"status.cannot_reblog": "Este post não pode ser partilhado",
|
||||
"status.delete": "Eliminar",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Incorporar",
|
||||
"status.favourite": "Adicionar aos favoritos",
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media_hidden": "Media escondida",
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
"empty_column.hashtag": "这个话题标签下暂时没有内容。",
|
||||
"empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
|
||||
"empty_column.home.public_timeline": "公共时间轴",
|
||||
"empty_column.list": "这个列表中暂时没有内容。",
|
||||
"empty_column.list": "这个列表中暂时没有内容。列表中用户所发送的的新嘟文将会在这里显示。",
|
||||
"empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。",
|
||||
"empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!",
|
||||
"follow_request.authorize": "同意",
|
||||
|
|
|
@ -62,7 +62,13 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
|
|||
|
||||
|
||||
export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
|
||||
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
|
||||
const params = [ `stream=${stream}` ];
|
||||
|
||||
if (accessToken !== null) {
|
||||
params.push(`access_token=${accessToken}`);
|
||||
}
|
||||
|
||||
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`);
|
||||
|
||||
ws.onopen = connected;
|
||||
ws.onmessage = e => received(JSON.parse(e.data));
|
||||
|
|
|
@ -265,9 +265,8 @@
|
|||
|
||||
.compose-form {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.compose-form__warning {
|
||||
.compose-form__warning {
|
||||
color: darken($ui-secondary-color, 65%);
|
||||
margin-bottom: 15px;
|
||||
background: $ui-primary-color;
|
||||
|
@ -299,62 +298,136 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__modifiers {
|
||||
.compose-form__autosuggest-wrapper {
|
||||
position: relative;
|
||||
|
||||
.emoji-picker-dropdown {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-textarea,
|
||||
.spoiler-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__textarea,
|
||||
.spoiler-input__input {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: $ui-base-color;
|
||||
background: $simple-background-color;
|
||||
padding: 10px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.spoiler-input__input {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__textarea {
|
||||
min-height: 100px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding-bottom: 0;
|
||||
padding-right: 10px + 22px;
|
||||
resize: none;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
height: 100px !important; // prevent auto-resize textarea
|
||||
resize: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
|
||||
background: $ui-secondary-color;
|
||||
border-radius: 0 0 4px 4px;
|
||||
color: $ui-base-color;
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
|
||||
&.autosuggest-textarea__suggestions--visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions__item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.selected {
|
||||
background: darken($ui-secondary-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-account,
|
||||
.autosuggest-emoji {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
line-height: 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.autosuggest-account-icon,
|
||||
.autosuggest-emoji img {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.autosuggest-account .display-name__account {
|
||||
color: lighten($ui-base-color, 36%);
|
||||
}
|
||||
|
||||
.compose-form__modifiers {
|
||||
color: $ui-base-color;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
background: $simple-background-color;
|
||||
border-radius: 0 0 4px;
|
||||
}
|
||||
|
||||
.compose-form__buttons-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.compose-form__buttons {
|
||||
padding: 10px;
|
||||
background: darken($simple-background-color, 8%);
|
||||
box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05);
|
||||
border-radius: 0 0 4px 4px;
|
||||
display: flex;
|
||||
|
||||
.icon-button {
|
||||
box-sizing: content-box;
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__upload-button-icon {
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.compose-form__sensitive-button {
|
||||
display: none;
|
||||
|
||||
&.compose-form__sensitive-button--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.compose-form__sensitive-button__icon {
|
||||
line-height: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__upload-wrapper {
|
||||
.compose-form__upload-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__uploads-wrapper {
|
||||
.compose-form__uploads-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__upload {
|
||||
.compose-form__upload {
|
||||
flex: 1 1 0;
|
||||
min-width: 40%;
|
||||
margin: 5px;
|
||||
|
@ -400,63 +473,78 @@
|
|||
.icon-button {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__upload-thumbnail {
|
||||
.compose-form__upload-thumbnail {
|
||||
border-radius: 4px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.compose-form__label {
|
||||
display: block;
|
||||
line-height: 24px;
|
||||
vertical-align: middle;
|
||||
|
||||
&.with-border {
|
||||
border-top: 1px solid $ui-base-color;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__label__text {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 14px;
|
||||
margin-left: 8px;
|
||||
color: $ui-primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__textarea,
|
||||
.follow-form__input {
|
||||
background: $simple-background-color;
|
||||
|
||||
&:disabled {
|
||||
background: $ui-secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__autosuggest-wrapper {
|
||||
position: relative;
|
||||
|
||||
.emoji-picker-dropdown {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__publish {
|
||||
.compose-form__buttons-wrapper {
|
||||
padding: 10px;
|
||||
background: darken($simple-background-color, 8%);
|
||||
border-radius: 0 0 4px 4px;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
justify-content: space-between;
|
||||
|
||||
.compose-form__publish-button-wrapper {
|
||||
.compose-form__buttons {
|
||||
display: flex;
|
||||
|
||||
.compose-form__upload-button-icon {
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.compose-form__sensitive-button {
|
||||
display: none;
|
||||
|
||||
&.compose-form__sensitive-button--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.compose-form__sensitive-button__icon {
|
||||
line-height: 27px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
box-sizing: content-box;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.character-counter__wrapper {
|
||||
align-self: center;
|
||||
margin-right: 4px;
|
||||
|
||||
.character-counter {
|
||||
cursor: default;
|
||||
font-family: 'mastodon-font-sans-serif', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: lighten($ui-base-color, 12%);
|
||||
|
||||
&.character-counter--over {
|
||||
color: $warning-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__publish {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
|
||||
.compose-form__publish-button-wrapper {
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emojione {
|
||||
|
@ -1973,121 +2061,6 @@
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
.autosuggest-textarea,
|
||||
.spoiler-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__textarea,
|
||||
.spoiler-input__input {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: $ui-base-color;
|
||||
background: $simple-background-color;
|
||||
padding: 10px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.spoiler-input__input {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.autosuggest-textarea__textarea {
|
||||
min-height: 100px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding-bottom: 0;
|
||||
padding-right: 10px + 22px;
|
||||
resize: none;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
height: 100px !important; // prevent auto-resize textarea
|
||||
resize: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
|
||||
background: $ui-secondary-color;
|
||||
border-radius: 0 0 4px 4px;
|
||||
color: $ui-base-color;
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
|
||||
&.autosuggest-textarea__suggestions--visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-textarea__suggestions__item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.selected {
|
||||
background: darken($ui-secondary-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.autosuggest-account,
|
||||
.autosuggest-emoji {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
line-height: 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.autosuggest-account-icon,
|
||||
.autosuggest-emoji img {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.autosuggest-account .display-name__account {
|
||||
color: lighten($ui-base-color, 36%);
|
||||
}
|
||||
|
||||
.character-counter__wrapper {
|
||||
line-height: 36px;
|
||||
margin: 0 16px 0 8px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.character-counter {
|
||||
cursor: default;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.character-counter--over {
|
||||
color: $warning-red;
|
||||
}
|
||||
|
||||
.getting-started__wrapper {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -7,9 +7,9 @@ body.rtl {
|
|||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.character-counter__wrapper {
|
||||
margin-right: 8px;
|
||||
margin-left: 16px;
|
||||
.compose-form .compose-form__buttons-wrapper .character-counter__wrapper {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.navigation-bar__profile {
|
||||
|
@ -30,6 +30,22 @@ body.rtl {
|
|||
.column-header__buttons {
|
||||
left: 0;
|
||||
right: auto;
|
||||
margin-left: -15px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.column-inline-form .icon-button {
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.column-header__links .text-btn {
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.account__avatar-wrapper {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.column-header__back-button {
|
||||
|
@ -41,10 +57,6 @@ body.rtl {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.compose-form__modifiers {
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
.setting-toggle {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
|
|
|
@ -2,13 +2,26 @@
|
|||
|
||||
class ProviderDiscovery < OEmbed::ProviderDiscovery
|
||||
class << self
|
||||
def get(url, **options)
|
||||
provider = discover_provider(url, options)
|
||||
|
||||
options.delete(:html)
|
||||
|
||||
provider.get(url, options)
|
||||
end
|
||||
|
||||
def discover_provider(url, **options)
|
||||
res = Request.new(:get, url).perform
|
||||
format = options[:format]
|
||||
|
||||
if options[:html]
|
||||
html = Nokogiri::HTML(options[:html])
|
||||
else
|
||||
res = Request.new(:get, url).perform
|
||||
|
||||
raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
|
||||
|
||||
html = Nokogiri::HTML(res.to_s)
|
||||
end
|
||||
|
||||
if format.nil? || format == :json
|
||||
provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
|
||||
|
|
|
@ -45,6 +45,8 @@ class AccountFilter
|
|||
else
|
||||
Account.default_scoped
|
||||
end
|
||||
when 'staff'
|
||||
accounts_with_users.merge User.staff
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
|
|
|
@ -27,6 +27,8 @@ class CustomEmojiFilter
|
|||
CustomEmoji.remote
|
||||
when 'by_domain'
|
||||
CustomEmoji.where(domain: value)
|
||||
when 'shortcode'
|
||||
CustomEmoji.where(shortcode: value)
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
|
|
|
@ -40,6 +40,12 @@ class FetchLinkCardService < BaseService
|
|||
|
||||
return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html')
|
||||
|
||||
@response = Request.new(:get, @url).perform
|
||||
|
||||
return if @response.code != 200 || @response.mime_type != 'text/html'
|
||||
|
||||
@html = @response.to_s
|
||||
|
||||
attempt_oembed || attempt_opengraph
|
||||
end
|
||||
|
||||
|
@ -70,30 +76,32 @@ class FetchLinkCardService < BaseService
|
|||
end
|
||||
|
||||
def attempt_oembed
|
||||
response = OEmbed::Providers.get(@url)
|
||||
embed = OEmbed::Providers.get(@url, html: @html)
|
||||
|
||||
return false unless response.respond_to?(:type)
|
||||
return false unless embed.respond_to?(:type)
|
||||
|
||||
@card.type = response.type
|
||||
@card.title = response.respond_to?(:title) ? response.title : ''
|
||||
@card.author_name = response.respond_to?(:author_name) ? response.author_name : ''
|
||||
@card.author_url = response.respond_to?(:author_url) ? response.author_url : ''
|
||||
@card.provider_name = response.respond_to?(:provider_name) ? response.provider_name : ''
|
||||
@card.provider_url = response.respond_to?(:provider_url) ? response.provider_url : ''
|
||||
@card.type = embed.type
|
||||
@card.title = embed.respond_to?(:title) ? embed.title : ''
|
||||
@card.author_name = embed.respond_to?(:author_name) ? embed.author_name : ''
|
||||
@card.author_url = embed.respond_to?(:author_url) ? embed.author_url : ''
|
||||
@card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : ''
|
||||
@card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : ''
|
||||
@card.width = 0
|
||||
@card.height = 0
|
||||
|
||||
case @card.type
|
||||
when 'link'
|
||||
@card.image = URI.parse(response.thumbnail_url) if response.respond_to?(:thumbnail_url)
|
||||
@card.image = URI.parse(embed.thumbnail_url) if embed.respond_to?(:thumbnail_url)
|
||||
when 'photo'
|
||||
@card.embed_url = response.url
|
||||
@card.width = response.width.presence || 0
|
||||
@card.height = response.height.presence || 0
|
||||
return false unless embed.respond_to?(:url)
|
||||
@card.embed_url = embed.url
|
||||
@card.image = URI.parse(embed.url)
|
||||
@card.width = embed.width.presence || 0
|
||||
@card.height = embed.height.presence || 0
|
||||
when 'video'
|
||||
@card.width = response.width.presence || 0
|
||||
@card.height = response.height.presence || 0
|
||||
@card.html = Formatter.instance.sanitize(response.html, Sanitize::Config::MASTODON_OEMBED)
|
||||
@card.width = embed.width.presence || 0
|
||||
@card.height = embed.height.presence || 0
|
||||
@card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
|
||||
when 'rich'
|
||||
# Most providers rely on <script> tags, which is a no-no
|
||||
return false
|
||||
|
@ -105,17 +113,11 @@ class FetchLinkCardService < BaseService
|
|||
end
|
||||
|
||||
def attempt_opengraph
|
||||
response = Request.new(:get, @url).perform
|
||||
|
||||
return if response.code != 200 || response.mime_type != 'text/html'
|
||||
|
||||
html = response.to_s
|
||||
|
||||
detector = CharlockHolmes::EncodingDetector.new
|
||||
detector.strip_tags = true
|
||||
|
||||
guess = detector.detect(html, response.charset)
|
||||
page = Nokogiri::HTML(html, nil, guess&.fetch(:encoding, nil))
|
||||
guess = detector.detect(@html, @response.charset)
|
||||
page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil))
|
||||
|
||||
if meta_property(page, 'twitter:player')
|
||||
@card.type = :video
|
||||
|
@ -140,8 +142,8 @@ class FetchLinkCardService < BaseService
|
|||
@card.save_with_optional_image!
|
||||
end
|
||||
|
||||
def meta_property(html, property)
|
||||
html.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || html.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
|
||||
def meta_property(page, property)
|
||||
page.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
|
||||
end
|
||||
|
||||
def lock_options
|
||||
|
|
|
@ -40,6 +40,6 @@ class FetchRemoteStatusService < BaseService
|
|||
end
|
||||
|
||||
def confirmed_domain?(domain, account)
|
||||
account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url || account.uri).normalized_host).zero?
|
||||
account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url.presence || account.uri).normalized_host).zero?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ class FollowService < BaseService
|
|||
elsif source_account.requested?(target_account)
|
||||
# This isn't managed by a method in AccountInteractions, so we modify it
|
||||
# ourselves if necessary.
|
||||
req = follow_requests.find_by(target_account: other_account)
|
||||
req = source_account.follow_requests.find_by(target_account: target_account)
|
||||
req.update!(show_reblogs: reblogs)
|
||||
return
|
||||
end
|
||||
|
|
|
@ -4,22 +4,11 @@
|
|||
%td.domain
|
||||
- unless account.local?
|
||||
= link_to account.domain, admin_accounts_path(by_domain: account.domain)
|
||||
%td.protocol
|
||||
- unless account.local?
|
||||
%td
|
||||
- if account.local?
|
||||
= t("admin.accounts.roles.#{account.user&.role}")
|
||||
- else
|
||||
= account.protocol.humanize
|
||||
%td.confirmed
|
||||
- if account.local?
|
||||
- if account.user_confirmed?
|
||||
%i.fa.fa-check
|
||||
- else
|
||||
%i.fa.fa-times
|
||||
%td.subscribed
|
||||
- if account.local?
|
||||
= t('admin.accounts.location.local')
|
||||
- elsif account.subscribed?
|
||||
%i.fa.fa-check
|
||||
- else
|
||||
%i.fa.fa-times
|
||||
%td
|
||||
= table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
|
||||
= table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
= filter_link_to t('admin.accounts.moderation.suspended'), {suspended: nil}, {suspended: '1'}
|
||||
- else
|
||||
= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1'
|
||||
.filter-subset
|
||||
%strong= t('admin.accounts.role')
|
||||
%ul
|
||||
%li= filter_link_to t('admin.accounts.moderation.all'), staff: nil
|
||||
%li= filter_link_to t('admin.accounts.roles.staff'), staff: '1'
|
||||
.filter-subset
|
||||
%strong= t('admin.accounts.order.title')
|
||||
%ul
|
||||
|
@ -56,9 +61,7 @@
|
|||
%tr
|
||||
%th= t('admin.accounts.username')
|
||||
%th= t('admin.accounts.domain')
|
||||
%th= t('admin.accounts.protocol')
|
||||
%th= t('admin.accounts.confirmed')
|
||||
%th= fa_icon 'paper-plane-o'
|
||||
%th
|
||||
%th
|
||||
%tbody
|
||||
= render @accounts
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
- else
|
||||
= link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account)
|
||||
|
||||
- unless @account.local?
|
||||
- if !@account.local? && @account.hub_url.present?
|
||||
%hr
|
||||
%h3 OStatus
|
||||
|
||||
|
@ -132,6 +132,7 @@
|
|||
- if @account.subscribed?
|
||||
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
|
||||
|
||||
- if !@account.local? && @account.inbox_url.present?
|
||||
%hr
|
||||
%h3 ActivityPub
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
- if custom_emoji.local?
|
||||
= t('admin.accounts.location.local')
|
||||
- else
|
||||
= custom_emoji.domain
|
||||
= link_to custom_emoji.domain, admin_custom_emojis_path(by_domain: custom_emoji.domain)
|
||||
%td
|
||||
- if custom_emoji.local?
|
||||
- if custom_emoji.visible_in_picker
|
||||
|
|
|
@ -17,6 +17,20 @@
|
|||
- else
|
||||
= filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil
|
||||
|
||||
= form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do
|
||||
.fields-group
|
||||
- Admin::FilterHelper::CUSTOM_EMOJI_FILTERS.each do |key|
|
||||
- if params[key].present?
|
||||
= hidden_field_tag key, params[key]
|
||||
|
||||
- %i(shortcode by_domain).each do |key|
|
||||
.input.string.optional
|
||||
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.custom_emojis.#{key}")
|
||||
|
||||
.actions
|
||||
%button= t('admin.accounts.search')
|
||||
= link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%thead
|
||||
|
|
|
@ -17,9 +17,12 @@ Rails.application.configure do
|
|||
config.x.alternate_domains = alternate_domains.split(/\s*,\s*/)
|
||||
|
||||
config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
|
||||
config.x.streaming_api_base_url = 'ws://localhost:4000'
|
||||
|
||||
config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') do
|
||||
if Rails.env.production?
|
||||
config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') { "ws#{https ? 's' : ''}://#{web_host}" }
|
||||
"ws#{https ? 's' : ''}://#{web_host}"
|
||||
else
|
||||
"ws://#{ENV['REMOTE_DEV'] == 'true' ? host.split(':').first : 'localhost'}:4000"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,20 +57,65 @@ ar:
|
|||
order:
|
||||
title: الترتيب
|
||||
profile_url: رابط الملف الشخصي
|
||||
role: التصريحات
|
||||
roles:
|
||||
admin: مدير
|
||||
user: مستخدِم
|
||||
search: البحث
|
||||
statuses: المنشورات
|
||||
title: الحسابات
|
||||
username: إسم المستخدم
|
||||
web: الويب
|
||||
custom_emojis:
|
||||
copy: نسخ
|
||||
delete: حذف
|
||||
emoji: إيموجي
|
||||
enable: تفعيل
|
||||
upload: رفع
|
||||
domain_blocks:
|
||||
domain: النطاق
|
||||
show:
|
||||
undo: إلغاء
|
||||
undo: إلغاء
|
||||
email_domain_blocks:
|
||||
delete: حذف
|
||||
domain: النطاق
|
||||
new:
|
||||
create: إضافة نطاق
|
||||
instances:
|
||||
domain_name: النطاق
|
||||
search: البحث
|
||||
reports:
|
||||
are_you_sure: هل أنت متأكد ؟
|
||||
comment:
|
||||
label: تعليق
|
||||
delete: حذف
|
||||
report_contents: المحتويات
|
||||
reported_by: أبلغ عنه من طرف
|
||||
status: الحالة
|
||||
title: التقارير
|
||||
view: عرض
|
||||
settings:
|
||||
contact_information:
|
||||
email: البريد الإلكتروني المهني
|
||||
registrations:
|
||||
deletion:
|
||||
desc_html: السماح لأي مستخدم إغلاق حسابه
|
||||
open:
|
||||
title: فتح التسجيل
|
||||
site_terms:
|
||||
title: شروط الخدمة المخصصة
|
||||
site_title: إسم مثيل الخادم
|
||||
title: إعدادات الموقع
|
||||
statuses:
|
||||
back_to_account: العودة إلى صفحة الحساب
|
||||
batch:
|
||||
delete: حذف
|
||||
media:
|
||||
title: الوسائط
|
||||
title: الإدارة
|
||||
application_mailer:
|
||||
salutation: "%{name},"
|
||||
settings: 'تغيير تفضيلات البريد الإلكتروني : %{link}'
|
||||
signature: إشعارات ماستدون من %{instance}
|
||||
view: 'View:'
|
||||
|
@ -83,6 +128,7 @@ ar:
|
|||
forgot_password: نسيت كلمة المرور ؟
|
||||
login: تسجيل الدخول
|
||||
logout: خروج
|
||||
migrate_account: الإنتقال إلى حساب آخر
|
||||
register: إنشاء حساب
|
||||
resend_confirmation: إعادة إرسال تعليمات التأكيد
|
||||
reset_password: إعادة تعيين كلمة المرور
|
||||
|
@ -106,15 +152,20 @@ ar:
|
|||
x_months: "%{count} شه"
|
||||
x_seconds: "%{count}ث"
|
||||
deletes:
|
||||
bad_password_msg: محاولة جيدة يا هاكرز ! كلمة السر خاطئة
|
||||
proceed: حذف حساب
|
||||
success_msg: تم حذف حسابك بنجاح
|
||||
exports:
|
||||
blocks: قمت بحظر
|
||||
csv: CSV
|
||||
follows: أنت تتبع
|
||||
storage: ذاكرة التخزين
|
||||
followers:
|
||||
domain: النطاق
|
||||
followers_count: عدد المتابِعين
|
||||
generic:
|
||||
changes_saved_msg: تم حفظ التعديلات بنجاح !
|
||||
powered_by: powered by %{link}
|
||||
powered_by: مدعوم بـ %{link}
|
||||
save_changes: حفظ التغييرات
|
||||
validation_errors:
|
||||
one: Something isn't quite right yet! Please review the error below
|
||||
|
@ -128,14 +179,19 @@ ar:
|
|||
upload: تحميل
|
||||
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>.
|
||||
lists:
|
||||
errors:
|
||||
limit: لقد بلغت الحد الأقصى للقوائم
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: ليس بالإمكان إرفاق فيديو في منشور يحتوي مسبقا على صور
|
||||
too_many: لا يمكن إرفاق أكثر من 4 ملفات
|
||||
migrations:
|
||||
acct: username@domain للحساب الجديد
|
||||
notification_mailer:
|
||||
digest:
|
||||
body: 'Here is a brief summary of what you missed on %{instance} since your last visit on %{since}:'
|
||||
mention: "%{name} mentioned you in:"
|
||||
mention: "%{name} أشار إليك في :"
|
||||
new_followers_summary:
|
||||
one: لقد حصلت على متابع جديد !
|
||||
other: لقد تحصلت على %{count} متتبعين جدد ! رائع !
|
||||
|
@ -143,11 +199,11 @@ ar:
|
|||
one: "إشعار واحد منذ زيارتك الأخيرة \U0001F418"
|
||||
other: "%{count} إشعارات جديدة منذ زيارتك الأخيرة \U0001F418"
|
||||
favourite:
|
||||
body: أُعجب %{name} بمنشورك
|
||||
body: 'أُعجب %{name} بمنشورك :'
|
||||
subject: "%{name} favourited your status"
|
||||
follow:
|
||||
body: "%{name} من متتبعيك الآن !"
|
||||
subject: "%{name} من متتبعيك الآن !"
|
||||
subject: "%{name} من متتبعيك الآن"
|
||||
follow_request:
|
||||
body: "%{name} has requested to follow you"
|
||||
subject: 'Pending follower: %{name}'
|
||||
|
@ -171,16 +227,21 @@ ar:
|
|||
pagination:
|
||||
next: التالي
|
||||
prev: السابق
|
||||
preferences:
|
||||
languages: اللغات
|
||||
other: إعدادات أخرى
|
||||
publishing: النشر
|
||||
remote_follow:
|
||||
acct: Enter your username@domain you want to follow from
|
||||
acct: قم بإدخال عنوان حسابك username@domain الذي من خلاله تود المتابعة
|
||||
missing_resource: Could not find the required redirect URL for your account
|
||||
proceed: Proceed to follow
|
||||
proceed: أكمل المتابعة
|
||||
prompt: 'إنك بصدد متابعة :'
|
||||
settings:
|
||||
authorized_apps: التطبيقات المرخص لها
|
||||
back: عودة إلى ماستدون
|
||||
edit_profile: تعديل الملف الشخصي
|
||||
export: تصدير البيانات
|
||||
followers: المتابِعون المُرَخّصون
|
||||
import: إستيراد
|
||||
preferences: التفضيلات
|
||||
settings: الإعدادات
|
||||
|
|
|
@ -286,7 +286,7 @@ ca:
|
|||
desc_html: Mostra una insígnia de personal en una pàgina d'usuari
|
||||
title: Mostra insígnia de personal
|
||||
site_description:
|
||||
desc_html: Paràgraf introductori a la pàgina principal i en etiquetes meta.<br>Pots utilitzar etiquetes HTML, en particular <code><a></code> i <code><em></code>.
|
||||
desc_html: Paràgraf introductori a la pàgina principal i en etiquetes meta. Pots utilitzar etiquetes HTML, en particular <code><a></code> i <code><em></code>.
|
||||
title: Descripció del lloc
|
||||
site_description_extended:
|
||||
desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Pots utilitzar etiquetes HTML
|
||||
|
@ -410,7 +410,7 @@ ca:
|
|||
storage: Emmagatzematge
|
||||
followers:
|
||||
domain: Domini
|
||||
explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. <strong> Les publicacions privades es lliuren a totes les instàncies on tens seguidors </ strong>. És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquestes instàncies.
|
||||
explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. <strong> Les publicacions privades es lliuren a totes les instàncies on tens seguidors </strong>. És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquestes instàncies.
|
||||
followers_count: Nombre de seguidors
|
||||
lock_link: Bloca el teu compte
|
||||
purge: Elimina dels seguidors
|
||||
|
|
|
@ -8,8 +8,11 @@ ar:
|
|||
inactive: لم يتم تنشيط حسابك بعد.
|
||||
last_attempt: بإمكانك إعادة المحاولة مرة واحدة قبل أن يتم قفل حسابك.
|
||||
locked: إن حسابك مقفل.
|
||||
unauthenticated: يجب عليك تسجيل الدخول أو إنشاء حساب قبل المواصلة.
|
||||
unconfirmed: يجب عليك تأكيد عنوان بريدك الإلكتروني قبل المواصلة.
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
subject: 'ماستدون : تعليمات التأكيد لمثيل الخادوم %{instance}'
|
||||
password_change:
|
||||
subject: 'ماستدون : تم تغيير كلمة المرور'
|
||||
reset_password_instructions:
|
||||
|
|
|
@ -35,7 +35,7 @@ ja:
|
|||
updated_not_active: パスワードは正常に更新されました。
|
||||
registrations:
|
||||
destroyed: アカウントの作成はキャンセルされました。またのご利用をお待ちしています。
|
||||
signed_up: アカウントの作成が完了しました。Mastodonへようこそ!
|
||||
signed_up: アカウントの作成が完了しました。Mastodonへようこそ。
|
||||
signed_up_but_inactive: アカウントの作成が完了しました。しかし、アカウントが有効化されていないためログインできませんでした。
|
||||
signed_up_but_locked: アカウントの作成が完了しました。しかし、アカウントがロックされているためログインできませんでした。
|
||||
signed_up_but_unconfirmed: メールアドレスの確認用のリンクが入力したメールアドレスに送信されました。メール内のリンクをクリックしてアカウントを有効化してください。
|
||||
|
@ -58,4 +58,4 @@ ja:
|
|||
not_locked: ロックされていません
|
||||
not_saved:
|
||||
one: エラーが発生したため、%{resource}の保存に失敗しました。
|
||||
other: "%{count}個のエラーが発生したため、保存に失敗しました。 %{resource}"
|
||||
other: "%{count}個のエラーが発生したため、%{resource}の保存に失敗しました:"
|
||||
|
|
|
@ -10,7 +10,7 @@ pt:
|
|||
inactive: A tua conta ainda não está ativada.
|
||||
invalid: "%{authentication_keys} ou palavra-passe não válida."
|
||||
last_attempt: Tens mais uma tentativa antes de a tua conta ficar bloqueada.
|
||||
locked: A tua conta está bloqueada
|
||||
locked: A tua conta está bloqueada.
|
||||
not_found_in_database: "%{authentication_keys} ou palavra-passe não válida."
|
||||
timeout: A tua sessão expirou. Por favor, entra de novo para continuares.
|
||||
unauthenticated: Precisas de entrar na tua conta ou registares-te antes de continuar.
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
gl:
|
||||
activerecord:
|
||||
attributes:
|
||||
doorkeeper/application:
|
||||
name: Nome do aplicativo
|
||||
redirect_uri: URI a redireccionar
|
||||
website: Sitio web do aplicativo
|
||||
errors:
|
||||
models:
|
||||
doorkeeper/application:
|
||||
attributes:
|
||||
redirect_uri:
|
||||
fragment_present: non pode conter un fragmento.
|
||||
invalid_uri: debe ser un URI válido.
|
||||
relative_uri: debe ser un URI absoluto.
|
||||
secured_uri: debe ser un URI HTTPS/SSL.
|
||||
doorkeeper:
|
||||
applications:
|
||||
buttons:
|
||||
authorize: Autorizar
|
||||
cancel: Cancelar
|
||||
destroy: Destruír
|
||||
edit: Editar
|
||||
submit: Enviar
|
||||
confirmations:
|
||||
destroy: Está segura?
|
||||
edit:
|
||||
title: Editar aplicativo
|
||||
form:
|
||||
error: Eeeeepa! Comprobe os posibles erros no formulario
|
||||
help:
|
||||
native_redirect_uri: Utilice %{native_redirect_uri} para probas locais
|
||||
redirect_uri: Utilice unha liña por URI
|
|
@ -116,6 +116,7 @@ en:
|
|||
roles:
|
||||
admin: Administrator
|
||||
moderator: Moderator
|
||||
staff: Staff
|
||||
user: User
|
||||
salmon_url: Salmon URL
|
||||
search: Search
|
||||
|
@ -160,6 +161,7 @@ en:
|
|||
update_status: "%{name} updated status by %{target}"
|
||||
title: Audit log
|
||||
custom_emojis:
|
||||
by_domain: Domain
|
||||
copied_msg: Successfully created local copy of the emoji
|
||||
copy: Copy
|
||||
copy_failed_msg: Could not make a local copy of that emoji
|
||||
|
@ -599,7 +601,7 @@ en:
|
|||
notifications: Notifications
|
||||
preferences: Preferences
|
||||
settings: Settings
|
||||
two_factor_authentication: Two-factor Authentication
|
||||
two_factor_authentication: Two-factor Auth
|
||||
your_apps: Your applications
|
||||
statuses:
|
||||
open_in_web: Open in web
|
||||
|
|
|
@ -116,6 +116,7 @@ fr:
|
|||
roles:
|
||||
admin: Administrateur
|
||||
moderator: Modérateur
|
||||
staff: Personnel
|
||||
user: Utilisateur
|
||||
salmon_url: URL Salmon
|
||||
search: Rechercher
|
||||
|
@ -135,12 +136,13 @@ fr:
|
|||
web: Web
|
||||
action_logs:
|
||||
actions:
|
||||
confirm_user: "%{name} adresse e-mail confirmée de l'utilisateur %{target}"
|
||||
confirm_user: "%{name} adresse courriel confirmée de l'utilisateur %{target}"
|
||||
create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}"
|
||||
create_domain_block: "%{name} a bloqué le domaine %{target}"
|
||||
create_email_domain_block: "%{name} a blacklisté le domaine de l'e-mail %{target}"
|
||||
create_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste noire"
|
||||
demote_user: "%{name} a rétrogradé l'utilisateur %{target}"
|
||||
destroy_domain_block: "%{name} a débloqué le domaine %{target}"
|
||||
destroy_email_domain_block: "%{name} a mis le domaine de l'e-mail %{target} sur liste blanche"
|
||||
destroy_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste blanche"
|
||||
destroy_status: "%{name} a enlevé le statut de %{target}"
|
||||
disable_2fa_user: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur %{target}"
|
||||
disable_custom_emoji: "%{name} a désactivé l'emoji %{target}"
|
||||
|
@ -159,6 +161,7 @@ fr:
|
|||
update_status: "%{name} a mis à jour le statut de %{target}"
|
||||
title: Journal d'audit
|
||||
custom_emojis:
|
||||
by_domain: Domaine
|
||||
copied_msg: Copie locale de l’émoji créée avec succès !
|
||||
copy: Copier
|
||||
copy_failed_msg: Impossible de faire une copie locale de cet émoji
|
||||
|
@ -191,7 +194,7 @@ fr:
|
|||
create: Créer le blocage
|
||||
hint: Le blocage de domaine n’empêchera pas la création de comptes dans la base de données, mais il appliquera automatiquement et rétrospectivement des méthodes de modération spécifiques sur ces comptes.
|
||||
severity:
|
||||
desc_html: "<strong>Silence</strong> rendra les messages des comptes concernés invisibles à ceux qui ne les suivent pas. <strong>Suspendre</strong> supprimera tout le contenu des comptes concernés, les médias, et les données du profil."
|
||||
desc_html: "<strong>Silence</strong> rendra les messages des comptes concernés invisibles à ceux qui ne les suivent pas. <strong>Suspendre</strong> supprimera tout le contenu des comptes concernés, les médias, et les données du profil. Utilisez <strong>Aucun</strong> si vous voulez simplement rejeter les fichiers multimédia."
|
||||
noop: Aucune
|
||||
silence: Masqué
|
||||
suspend: Suspendre
|
||||
|
@ -285,7 +288,7 @@ fr:
|
|||
desc_html: Montrer un badge de responsable sur une page utilisateur
|
||||
title: Montrer un badge de responsable
|
||||
site_description:
|
||||
desc_html: Affichée sous la forme d’un paragraphe sur la page d’accueil et utilisée comme balise meta.<br/>Vous pouvez utiliser des balises HTML, en particulier <code><a></code> et <code><em></code>.
|
||||
desc_html: Paragraphe introductif sur la page d'accueil et dans les balises meta. Vous pouvez utiliser des balises HTML, en particulier <code><a></code> et <code><em></code>.
|
||||
title: Description du site
|
||||
site_description_extended:
|
||||
desc_html: Affichée sur la page d’informations complémentaires du site<br>Vous pouvez utiliser des balises HTML
|
||||
|
@ -457,6 +460,9 @@ fr:
|
|||
title: Inviter des gens
|
||||
landing_strip_html: <strong>%{name}</strong> utilise %{link_to_root_path}. Vous pouvez læ suivre et interagir si vous possédez un compte quelque part dans le "fediverse".
|
||||
landing_strip_signup_html: Si ce n’est pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>.
|
||||
lists:
|
||||
errors:
|
||||
limit: Vous avez atteint le nombre maximum de listes
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images
|
||||
|
@ -590,11 +596,12 @@ fr:
|
|||
open_in_web: Ouvrir sur le web
|
||||
over_character_limit: limite de caractères dépassée de %{max} caractères
|
||||
pin_errors:
|
||||
limit: Trop de pouets épinglés
|
||||
limit: Vous avez déjà épinglé le nombre maximum de pouets
|
||||
ownership: Vous ne pouvez pas épingler un statut ne vous appartenant pas
|
||||
private: Les statuts non-publics ne peuvent pas être épinglés
|
||||
reblog: Un partage ne peut pas être épinglé
|
||||
show_more: Afficher plus
|
||||
title: '%{name} : "%{quote}"'
|
||||
visibilities:
|
||||
private: Abonné⋅e⋅s uniquement
|
||||
private_long: Seul⋅e⋅s vos abonné⋅e⋅s verront vos statuts
|
||||
|
@ -693,7 +700,7 @@ fr:
|
|||
manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez l’entrer manuellement, voici le secret en clair :'
|
||||
recovery_codes: Codes de récupération
|
||||
recovery_codes_regenerated: Codes de récupération régénérés avec succès
|
||||
recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour récupérer l’accès à votre compte. Conservez les codes de récupération en toute sécurité, par exemple, en les imprimant et en les stockant avec vos autres documents importants.
|
||||
recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
|
||||
setup: Installer
|
||||
wrong_code: Les codes entrés sont incorrects ! L’heure du serveur et celle de votre appareil sont-elles correctes ?
|
||||
users:
|
||||
|
|
|
@ -151,7 +151,7 @@ ja:
|
|||
memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました"
|
||||
promote_user: "%{name} さんが %{target} さんを昇格しました"
|
||||
reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました"
|
||||
resolve_report: "%{name} さんがレポート %{target} を棄却しました"
|
||||
resolve_report: "%{name} さんがレポート %{target} を解決済みにしました"
|
||||
silence_account: "%{name} さんが %{target} さんをサイレンスにしました"
|
||||
suspend_account: "%{name} さんが %{target} さんを停止しました"
|
||||
unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました"
|
||||
|
@ -192,13 +192,13 @@ ja:
|
|||
create: ブロックを作成
|
||||
hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
|
||||
severity:
|
||||
desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。"
|
||||
desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。メディアファイルの拒否は<strong>なし</strong>を使います。"
|
||||
noop: なし
|
||||
silence: サイレンス
|
||||
suspend: 停止
|
||||
title: 新規ドメインブロック
|
||||
reject_media: メディアファイルを拒否
|
||||
reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
|
||||
reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です
|
||||
severities:
|
||||
noop: なし
|
||||
silence: サイレンス
|
||||
|
@ -271,7 +271,7 @@ ja:
|
|||
username: 連絡先のユーザー名
|
||||
registrations:
|
||||
closed_message:
|
||||
desc_html: 新規登録を停止しているときにフロントページに表示されます。HTMLタグが使えます。
|
||||
desc_html: 新規登録を停止しているときにフロントページに表示されます。HTMLタグが使えます
|
||||
title: 新規登録停止時のメッセージ
|
||||
deletion:
|
||||
desc_html: 誰でも自分のアカウントを削除できるようにします
|
||||
|
@ -289,14 +289,14 @@ ja:
|
|||
desc_html: フロントページへの表示と meta タグに使用される紹介文です。HTMLタグ、特に<code><a></code> と <code><em></code>が使えます。
|
||||
title: インスタンスの説明
|
||||
site_description_extended:
|
||||
desc_html: あなたのインスタンスにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます。
|
||||
desc_html: あなたのインスタンスにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます
|
||||
title: カスタム詳細説明
|
||||
site_terms:
|
||||
desc_html: あなたは独自のプライバシーポリシーや利用規約、そのほかの法的根拠を書くことができます。HTMLタグが使えます。
|
||||
desc_html: あなたは独自のプライバシーポリシーや利用規約、そのほかの法的根拠を書くことができます。HTMLタグが使えます
|
||||
title: カスタム利用規約
|
||||
site_title: インスタンスの名前
|
||||
thumbnail:
|
||||
desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です。
|
||||
desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です
|
||||
title: インスタンスのサムネイル
|
||||
timeline_preview:
|
||||
desc_html: ランディングページに公開タイムラインを表示します
|
||||
|
@ -333,7 +333,7 @@ ja:
|
|||
salutation: "%{name} さん"
|
||||
settings: 'メール設定の変更: %{link}'
|
||||
signature: Mastodon %{instance} インスタンスからの通知
|
||||
view: リンク
|
||||
view: 'リンク:'
|
||||
applications:
|
||||
created: アプリが作成されました
|
||||
destroyed: アプリが削除されました
|
||||
|
@ -359,12 +359,12 @@ ja:
|
|||
reset_password: パスワードを再発行
|
||||
set_new_password: 新しいパスワード
|
||||
authorize_follow:
|
||||
error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました。
|
||||
error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました
|
||||
follow: フォロー
|
||||
follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:'
|
||||
following: '成功! あなたは現在以下のアカウントをフォローしています:'
|
||||
post_follow:
|
||||
close: またはこのウィンドウを閉じます
|
||||
close: またはこのウィンドウを閉じます。
|
||||
return: ユーザーのプロフィールに戻る
|
||||
web: Web を開く
|
||||
title: "%{acct} をフォロー"
|
||||
|
@ -384,7 +384,7 @@ ja:
|
|||
x_seconds: "%{count}秒"
|
||||
deletes:
|
||||
bad_password_msg: パスワードが違います
|
||||
confirm_password: 本人確認のため、現在のパスワードを入力してください。
|
||||
confirm_password: 本人確認のため、現在のパスワードを入力してください
|
||||
description_html: あなたのアカウントに含まれるコンテンツは全て削除され、アカウントは無効化されます。これは恒久的なもので、<strong>取り消すことはできません</strong>。なりすましを防ぐために、同じユーザー名で再度登録することはできなくなります。
|
||||
proceed: アカウントを削除する
|
||||
success_msg: アカウントは正常に削除されました
|
||||
|
@ -397,7 +397,7 @@ ja:
|
|||
'422':
|
||||
content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか?
|
||||
title: セキュリティ認証に失敗
|
||||
'429': リクエストの制限に達しました。
|
||||
'429': リクエストの制限に達しました
|
||||
'500':
|
||||
content: もうしわけありませんが、なにかが間違っています。
|
||||
title: このページは正しくありません
|
||||
|
@ -419,24 +419,24 @@ ja:
|
|||
other: "%{count} 個のドメインからソフトブロックするフォロワーを処理中..."
|
||||
true_privacy_html: "<strong>プライバシーの保護はエンドツーエンドの暗号化でのみ実現可能</strong>であることに留意ください。"
|
||||
unlocked_warning_html: 誰でもあなたをフォローすることができ、あなたのプライベート投稿をすぐに見ることができます。フォローする人を限定したい場合は%{lock_link}に設定してください。
|
||||
unlocked_warning_title: このアカウントは非公開アカウントに設定されていません。
|
||||
unlocked_warning_title: このアカウントは非公開アカウントに設定されていません
|
||||
generic:
|
||||
changes_saved_msg: 正常に変更されました
|
||||
changes_saved_msg: 正常に変更されました!
|
||||
powered_by: powered by %{link}
|
||||
save_changes: 変更を保存
|
||||
use_this: これを使う
|
||||
validation_errors:
|
||||
one: エラーが発生しました。以下のエラーを確認してください。
|
||||
other: エラーが発生しました。以下の%{count}個のエラーを確認してください。
|
||||
one: エラーが発生しました! 以下のエラーを確認してください
|
||||
other: エラーが発生しました! 以下の%{count}個のエラーを確認してください
|
||||
imports:
|
||||
preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。
|
||||
success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください。
|
||||
success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください
|
||||
types:
|
||||
blocking: ブロックしたアカウントリスト
|
||||
following: フォロー中のアカウントリスト
|
||||
muting: ミュートしたアカウントリスト
|
||||
upload: アップロード
|
||||
in_memoriam_html: 故人を偲んで
|
||||
in_memoriam_html: 故人を偲んで。
|
||||
invites:
|
||||
delete: 無効化
|
||||
expired: 期限切れ
|
||||
|
@ -452,7 +452,7 @@ ja:
|
|||
one: '1'
|
||||
other: "%{count}"
|
||||
max_uses_prompt: 無制限
|
||||
prompt: リンクを生成・共有してこのインスタンスへの新規登録を受け付けることができます。
|
||||
prompt: リンクを生成・共有してこのインスタンスへの新規登録を受け付けることができます
|
||||
table:
|
||||
expires_at: 有効期限
|
||||
uses: 使用
|
||||
|
@ -467,15 +467,18 @@ ja:
|
|||
remove_all: すべて削除
|
||||
landing_strip_html: "<strong>%{name}</strong> さんはインスタンス %{link_to_root_path} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。"
|
||||
landing_strip_signup_html: もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。
|
||||
lists:
|
||||
errors:
|
||||
limit: リストの上限に達しました
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: 既に画像が追加されているため、動画を追加することはできません。
|
||||
too_many: 追加できるファイルは4つまでです。
|
||||
images_and_video: 既に画像が追加されているため、動画を追加することはできません
|
||||
too_many: 追加できるファイルは4つまでです
|
||||
migrations:
|
||||
acct: 引っ越し先の ユーザー名@ドメイン
|
||||
currently_redirecting: 'あなたのプロフィールは引っ越し先が設定されています:'
|
||||
proceed: 保存
|
||||
updated_msg: アカウントの引っ越し設定を更新しました
|
||||
updated_msg: アカウントの引っ越し設定を更新しました!
|
||||
moderation:
|
||||
title: モデレーション
|
||||
notification_mailer:
|
||||
|
@ -492,7 +495,7 @@ ja:
|
|||
body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:"
|
||||
subject: "%{name} さんにお気に入りに登録されました"
|
||||
follow:
|
||||
body: "%{name} さんにフォローされています"
|
||||
body: "%{name} さんにフォローされています!"
|
||||
subject: "%{name} さんにフォローされています"
|
||||
follow_request:
|
||||
body: "%{name} さんがあなたにフォローをリクエストしました"
|
||||
|
@ -601,7 +604,7 @@ ja:
|
|||
open_in_web: Webで開く
|
||||
over_character_limit: 上限は %{max}文字までです
|
||||
pin_errors:
|
||||
limit: 固定されているトゥートが多すぎます
|
||||
limit: 固定されているトゥートの上限に達しました
|
||||
ownership: 他人のトゥートを固定することはできません
|
||||
private: 非公開のトゥートを固定することはできません
|
||||
reblog: ブーストされたトゥートを固定することはできません
|
||||
|
@ -692,7 +695,7 @@ ja:
|
|||
formats:
|
||||
default: "%Y年%m月%d日 %H:%M"
|
||||
two_factor_authentication:
|
||||
code_hint: 確認するには認証アプリで表示されたコードを入力してください。
|
||||
code_hint: 確認するには認証アプリで表示されたコードを入力してください
|
||||
description_html: "<strong>二段階認証</strong>を有効にするとログイン時、電話でコードを受け取る必要があります。"
|
||||
disable: 無効
|
||||
enable: 有効
|
||||
|
@ -703,7 +706,7 @@ ja:
|
|||
lost_recovery_codes: リカバリーコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリーコードは無効になります。
|
||||
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
|
||||
recovery_codes: リカバリーコード
|
||||
recovery_codes_regenerated: リカバリーコードが再生成されました。
|
||||
recovery_codes_regenerated: リカバリーコードが再生成されました
|
||||
recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。<strong>リカバリーコードは大切に保全してください。</strong>たとえば印刷してほかの重要な書類と一緒に保管することができます。
|
||||
setup: 初期設定
|
||||
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
|
||||
|
|
|
@ -458,6 +458,9 @@ nl:
|
|||
title: Mensen uitnodigen
|
||||
landing_strip_html: "<strong>%{name}</strong> is een gebruiker op %{link_to_root_path}. Je kunt deze volgen en ermee communiceren als je op Mastodon (of ergens anders in de fediverse) een account hebt."
|
||||
landing_strip_signup_html: Als je dat niet hebt, kun je je <a href="%{sign_up_path}">hier registreren</a>.
|
||||
lists:
|
||||
errors:
|
||||
limit: Je hebt het maximaal aantal lijsten bereikt
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld
|
||||
|
@ -591,7 +594,7 @@ nl:
|
|||
open_in_web: In de webapp openen
|
||||
over_character_limit: Limiet van %{max} tekens overschreden
|
||||
pin_errors:
|
||||
limit: Te veel toots vastgezet
|
||||
limit: Je hebt het maximaal aantal toots al vastgezet
|
||||
ownership: Een toot van iemand anders kan niet worden vastgezet
|
||||
private: Alleen openbare toots kunnen worden vastgezet
|
||||
reblog: Een boost kan niet worden vastgezet
|
||||
|
|
|
@ -114,8 +114,9 @@ oc:
|
|||
resubscribe: Se tornar abonar
|
||||
role: Permissions
|
||||
roles:
|
||||
admin: Admin
|
||||
moderator: Mod
|
||||
admin: Administrator
|
||||
moderator: Moderator
|
||||
staff: Personnal
|
||||
user: Uitlizaire
|
||||
salmon_url: URL Salmon
|
||||
search: Cercar
|
||||
|
@ -160,6 +161,7 @@ oc:
|
|||
update_status: "%{name} metèt a jorn l’estatut a %{target}"
|
||||
title: Audit log
|
||||
custom_emojis:
|
||||
by_domain: Domeni
|
||||
copied_msg: Còpia locala de l’emoji ben creada
|
||||
copy: Copiar
|
||||
copy_failed_msg: Fracàs de la còpia locala de l’emoji
|
||||
|
@ -343,7 +345,7 @@ oc:
|
|||
warning: Mèfi ! Agachatz de partejar aquela donada amb degun !
|
||||
your_token: Vòstre geton d’accès
|
||||
auth:
|
||||
agreement_html: En vos marcar acceptatz <a href="%{rules_path}">nòstres tèrmes de servici</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
|
||||
agreement_html: En vos marcar acceptatz <a href="%{rules_path}">las règlas de l’instància</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
|
||||
change_password: Seguretat
|
||||
delete_account: Suprimir lo compte
|
||||
delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
|
||||
|
@ -677,6 +679,7 @@ oc:
|
|||
private: Se pòt pas penjar los tuts pas publics
|
||||
reblog: Se pòt pas penjar un tut partejat
|
||||
show_more: Ne veire mai
|
||||
title: '%{name} : "%{quote}"'
|
||||
visibilities:
|
||||
private: Seguidors solament
|
||||
private_long: Mostrar pas qu’als seguidors
|
||||
|
|
|
@ -469,6 +469,9 @@ pl:
|
|||
remove_all: Usuń wszystkie
|
||||
landing_strip_html: "<strong>%{name}</strong> ma konto na %{link_to_root_path}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersum."
|
||||
landing_strip_signup_html: Jeśli jeszcze go nie masz, możesz <a href="%{sign_up_path}">stworzyć konto</a>.
|
||||
lists:
|
||||
errors:
|
||||
limit: Przekroczyłeś maksymalną liczbę utworzonych list
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia
|
||||
|
@ -606,7 +609,7 @@ pl:
|
|||
open_in_web: Otwórz w przeglądarce
|
||||
over_character_limit: limit %{max} znaków przekroczony
|
||||
pin_errors:
|
||||
limit: Nie możesz przypiąć więcej wpisów
|
||||
limit: Przekroczyłeś maksymalną liczbę przypiętych wpisów
|
||||
ownership: Nie możesz przypiąć cudzego wpisu
|
||||
private: Nie możesz przypiąć niepublicznego wpisu
|
||||
reblog: Nie możesz przypiąć podbicia wpisu
|
||||
|
|
|
@ -282,6 +282,9 @@ pt-BR:
|
|||
open:
|
||||
desc_html: Permitir que qualquer um crie uma conta
|
||||
title: Cadastro aberto
|
||||
show_staff_badge:
|
||||
desc_html: Mostrar uma insígnia de equipe na página de usuário
|
||||
title: Mostrar insígnia de equipe
|
||||
site_description:
|
||||
desc_html: Parágrafo introdutório na página inicial e em meta tags. Você pode usar tags HTML, em especial <code><a></code> e <code><em></code>.
|
||||
title: Descrição da instância
|
||||
|
@ -350,7 +353,7 @@ pt-BR:
|
|||
login: Entrar
|
||||
logout: Sair
|
||||
migrate_account: Mudar para uma conta diferente
|
||||
migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configura isso aqui</a>.
|
||||
migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
|
||||
register: Cadastrar-se
|
||||
resend_confirmation: Reenviar instruções de confirmação
|
||||
reset_password: Redefinir senha
|
||||
|
@ -455,6 +458,9 @@ pt-BR:
|
|||
title: Convidar pessoas
|
||||
landing_strip_html: "<strong>%{name}</strong> é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso."
|
||||
landing_strip_signup_html: Se não, você pode <a href="%{sign_up_path}">se cadastrar aqui</a>.
|
||||
lists:
|
||||
errors:
|
||||
limit: Você alcançou o número máximo de listas
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
gl:
|
||||
simple_form:
|
||||
hints:
|
||||
defaults:
|
||||
avatar: PNG, GIF ou JPG. Como moito 2MB. Será reducida ate 120x120px
|
||||
digest: Enviar despois de un período longo de inactividade con un resumo das
|
||||
mencións que recibeu na súa ausencia
|
||||
display_name:
|
||||
one: <span class="name-counter">1</span> caracter restante
|
||||
other: <span class="name-counter">%{count}</span> caracteres restantes
|
||||
header: PNG, GIF ou JPG. Como moito 2MB. Será reducida a 700x335px
|
||||
locked: Require que vostede aprove as seguidoras de xeito manual
|
||||
note:
|
||||
one: <span class="note-counter">1</span> caracter restante
|
||||
other: <span class="note-counter">%{count}</span> caracteres restantes
|
||||
setting_noindex: Afecta ao seu perfil público e páxinas de estado
|
||||
setting_theme: Afecta ao aspecto de Mastodon en calquer dispositivo cando
|
||||
está conectada.
|
||||
imports:
|
||||
data: Ficheiro CSV exportado desde outra instancia Mastodon
|
||||
sessions:
|
||||
otp: Introduza o código de Doble-Factor desde o seu teléfono ou utilice un
|
||||
dos seus códigos de recuperación.
|
||||
user:
|
||||
filtered_languages: Os idiomas marcados filtraranse das liñas temporais públicas
|
||||
para vostede
|
||||
labels:
|
||||
defaults:
|
||||
avatar: Avatar
|
||||
confirm_new_password: Confirme o novo contrasinal
|
||||
confirm_password: Confirme o contrasinal
|
||||
current_password: Contrasinal actual
|
||||
data: Data
|
||||
display_name: Nome mostrado
|
||||
email: enderezo correo electrónico
|
||||
expires_in: Caducidade despois de
|
||||
filtered_languages: Idiomas filtrados
|
||||
header: Cabezallo
|
||||
locale: Idioma
|
||||
locked: Protexer conta
|
||||
max_uses: Número máximo de usos
|
||||
new_password: Novo contrasinal
|
||||
note: Sobre vostede
|
||||
otp_attempt: Código de Doble-Factor
|
||||
password: Contrasinal
|
||||
setting_auto_play_gif: Reprodución automática de GIFs animados
|
||||
setting_boost_modal: Pedir confirmación antes de promocionar
|
||||
setting_default_privacy: Intimidade da publicación
|
||||
setting_default_sensitive: Marcar sempre multimedia como sensible
|
||||
setting_delete_modal: Solicitar confirmación antes de eliminar unha mensaxe
|
||||
setting_noindex: Pedir non aparecer nas buscas dos motores de busca
|
||||
setting_reduce_motion: Reducir o movemento nas animacións
|
||||
setting_system_font_ui: Utilizar a tipografía por defecto do sistema
|
||||
setting_theme: Decorado da instancia
|
||||
setting_unfollow_modal: Solicitar confirmación antes de deixar de seguir alguén
|
||||
severity: Severidade
|
||||
type: Tipo de importación
|
||||
username: Nome de usuaria
|
||||
interactions:
|
||||
must_be_follower: Bloquear as notificacións de non-seguidoras
|
||||
must_be_following: Bloquea as notificacións de personas que non segue
|
||||
must_be_following_dm: Bloquea as mensaxes directas de personas que non segue
|
||||
notification_emails:
|
||||
digest: Enviar correos con resumos
|
||||
favourite: Enviar un correo cando alguén marca como favorita unha das súas
|
||||
publicacións
|
||||
follow: Enviar un correo cando alguén a segue
|
||||
follow_request: Enviar un correo cando alguén solicita seguila
|
||||
mention: Enviar un correo cando alguén a menciona
|
||||
reblog: Enviar un correo cando alguén promociona a súa mensaxe
|
||||
'no': Non
|
||||
required:
|
||||
mark: '*'
|
||||
text: requerido
|
||||
'yes': Si
|
|
@ -116,6 +116,7 @@ zh-CN:
|
|||
roles:
|
||||
admin: 管理员
|
||||
moderator: 协管
|
||||
staff: 工作人员
|
||||
user: 普通用户
|
||||
salmon_url: Salmon URL
|
||||
search: 搜索
|
||||
|
@ -160,6 +161,7 @@ zh-CN:
|
|||
update_status: "%{name} 刷新了 %{target} 的嘟文"
|
||||
title: 运营日志
|
||||
custom_emojis:
|
||||
by_domain: 域名
|
||||
copied_msg: 成功将表情复制到本地
|
||||
copy: 复制
|
||||
copy_failed_msg: 无法将表情复制到本地
|
||||
|
@ -281,8 +283,8 @@ zh-CN:
|
|||
desc_html: 允许任何人建立一个帐户
|
||||
title: 开放注册
|
||||
show_staff_badge:
|
||||
desc_html: 在个人资料页上显示管理员标志
|
||||
title: 显示管理员标志
|
||||
desc_html: 在个人资料页上显示工作人员标志
|
||||
title: 显示工作人员标志
|
||||
site_description:
|
||||
desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <code><a></code> 和 <code><em></code>。
|
||||
title: 本站简介
|
||||
|
@ -341,7 +343,7 @@ zh-CN:
|
|||
warning: 一定小心,千万不要把它分享给任何人!
|
||||
your_token: 你的访问令牌
|
||||
auth:
|
||||
agreement_html: 注册即表示你同意<a href="%{rules_path}">我们的使用条款</a>和<a href="%{terms_path}">隐私权政策</a>。
|
||||
agreement_html: 注册即表示你同意遵守<a href="%{rules_path}">本实例的相关规定</a>和<a href="%{terms_path}">我们的使用条款</a>。
|
||||
change_password: 帐户安全
|
||||
delete_account: 删除帐户
|
||||
delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
|
||||
|
@ -368,18 +370,18 @@ zh-CN:
|
|||
title: 关注 %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
about_x_hours: "%{count} 时"
|
||||
about_x_months: "%{count} 个月"
|
||||
about_x_years: "%{count} 年"
|
||||
almost_x_years: "%{count} 年"
|
||||
about_x_hours: "%{count}时"
|
||||
about_x_months: "%{count}个月"
|
||||
about_x_years: "%{count}年"
|
||||
almost_x_years: "%{count}年"
|
||||
half_a_minute: 刚刚
|
||||
less_than_x_minutes: "%{count} 分"
|
||||
less_than_x_minutes: "%{count}分"
|
||||
less_than_x_seconds: 刚刚
|
||||
over_x_years: "%{count} 年"
|
||||
x_days: "%{count} 天"
|
||||
x_minutes: "%{count} 分"
|
||||
x_months: "%{count} 个月"
|
||||
x_seconds: "%{count} 秒"
|
||||
over_x_years: "%{count}年"
|
||||
x_days: "%{count}天"
|
||||
x_minutes: "%{count}分"
|
||||
x_months: "%{count}个月"
|
||||
x_seconds: "%{count}秒"
|
||||
deletes:
|
||||
bad_password_msg: 想得美,黑客!密码输入错误
|
||||
confirm_password: 输入你当前的密码来验证身份
|
||||
|
@ -591,6 +593,7 @@ zh-CN:
|
|||
private: 不能置顶非公开的嘟文
|
||||
reblog: 不能置顶转嘟
|
||||
show_more: 显示更多
|
||||
title: "%{name}:“%{quote}”"
|
||||
visibilities:
|
||||
private: 仅关注者
|
||||
private_long: 只有关注你的用户能看到
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class RemoveDuplicateIndexesInLists < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
remove_index :list_accounts, name: "index_list_accounts_on_account_id"
|
||||
remove_index :list_accounts, name: "index_list_accounts_on_list_id"
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20171201000000) do
|
||||
ActiveRecord::Schema.define(version: 20171212195226) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -211,10 +211,8 @@ ActiveRecord::Schema.define(version: 20171201000000) do
|
|||
t.bigint "account_id", null: false
|
||||
t.bigint "follow_id", null: false
|
||||
t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true
|
||||
t.index ["account_id"], name: "index_list_accounts_on_account_id"
|
||||
t.index ["follow_id"], name: "index_list_accounts_on_follow_id"
|
||||
t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id"
|
||||
t.index ["list_id"], name: "index_list_accounts_on_list_id"
|
||||
end
|
||||
|
||||
create_table "lists", force: :cascade do |t|
|
||||
|
|
|
@ -17,7 +17,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def pre
|
||||
'rc3'
|
||||
'rc4'
|
||||
end
|
||||
|
||||
def flags
|
||||
|
|
|
@ -97,6 +97,8 @@ const startWorker = (workerId) => {
|
|||
};
|
||||
|
||||
const app = express();
|
||||
app.set('trusted proxy', process.env.TRUSTED_PROXY_IP || 'loopback,uniquelocal');
|
||||
|
||||
const pgPool = new pg.Pool(Object.assign(pgConfigs[env], dbUrlToConfig(process.env.DATABASE_URL)));
|
||||
const server = http.createServer(app);
|
||||
const redisNamespace = process.env.REDIS_NAMESPACE || null;
|
||||
|
@ -177,6 +179,12 @@ const startWorker = (workerId) => {
|
|||
next();
|
||||
};
|
||||
|
||||
const setRemoteAddress = (req, res, next) => {
|
||||
req.remoteAddress = req.connection.remoteAddress;
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const accountFromToken = (token, req, next) => {
|
||||
pgPool.connect((err, client, done) => {
|
||||
if (err) {
|
||||
|
@ -208,17 +216,22 @@ const startWorker = (workerId) => {
|
|||
});
|
||||
};
|
||||
|
||||
const accountFromRequest = (req, next) => {
|
||||
const accountFromRequest = (req, next, required = true) => {
|
||||
const authorization = req.headers.authorization;
|
||||
const location = url.parse(req.url, true);
|
||||
const accessToken = location.query.access_token;
|
||||
|
||||
if (!authorization && !accessToken) {
|
||||
if (required) {
|
||||
const err = new Error('Missing access token');
|
||||
err.statusCode = 401;
|
||||
|
||||
next(err);
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken;
|
||||
|
@ -226,7 +239,17 @@ const startWorker = (workerId) => {
|
|||
accountFromToken(token, req, next);
|
||||
};
|
||||
|
||||
const PUBLIC_STREAMS = [
|
||||
'public',
|
||||
'public:local',
|
||||
'hashtag',
|
||||
'hashtag:local',
|
||||
];
|
||||
|
||||
const wsVerifyClient = (info, cb) => {
|
||||
const location = url.parse(info.req.url, true);
|
||||
const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
|
||||
|
||||
accountFromRequest(info.req, err => {
|
||||
if (!err) {
|
||||
cb(true, undefined, undefined);
|
||||
|
@ -234,16 +257,24 @@ const startWorker = (workerId) => {
|
|||
log.error(info.req.requestId, err.toString());
|
||||
cb(false, 401, 'Unauthorized');
|
||||
}
|
||||
});
|
||||
}, authRequired);
|
||||
};
|
||||
|
||||
const PUBLIC_ENDPOINTS = [
|
||||
'/api/v1/streaming/public',
|
||||
'/api/v1/streaming/public/local',
|
||||
'/api/v1/streaming/hashtag',
|
||||
'/api/v1/streaming/hashtag/local',
|
||||
];
|
||||
|
||||
const authenticationMiddleware = (req, res, next) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
accountFromRequest(req, next);
|
||||
const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
|
||||
accountFromRequest(req, next, authRequired);
|
||||
};
|
||||
|
||||
const errorMiddleware = (err, req, res, {}) => {
|
||||
|
@ -275,8 +306,10 @@ const startWorker = (workerId) => {
|
|||
};
|
||||
|
||||
const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false, notificationOnly = false) => {
|
||||
const accountId = req.accountId || req.remoteAddress;
|
||||
|
||||
const streamType = notificationOnly ? ' (notification)' : '';
|
||||
log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}${streamType}`);
|
||||
log.verbose(req.requestId, `Starting stream from ${id} for ${accountId}${streamType}`);
|
||||
|
||||
const listener = message => {
|
||||
const { event, payload, queued_at } = JSON.parse(message);
|
||||
|
@ -286,7 +319,7 @@ const startWorker = (workerId) => {
|
|||
const delta = now - queued_at;
|
||||
const encodedPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
|
||||
|
||||
log.silly(req.requestId, `Transmitting for ${req.accountId}: ${event} ${encodedPayload} Delay: ${delta}ms`);
|
||||
log.silly(req.requestId, `Transmitting for ${accountId}: ${event} ${encodedPayload} Delay: ${delta}ms`);
|
||||
output(event, encodedPayload);
|
||||
};
|
||||
|
||||
|
@ -313,6 +346,7 @@ const startWorker = (workerId) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (req.accountId) {
|
||||
const queries = [
|
||||
client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, unpackedPayload.account.id].concat(targetAccountIds)),
|
||||
];
|
||||
|
@ -333,6 +367,10 @@ const startWorker = (workerId) => {
|
|||
done();
|
||||
log.error(err);
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
transmit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
transmit();
|
||||
|
@ -345,13 +383,15 @@ const startWorker = (workerId) => {
|
|||
|
||||
// Setup stream output to HTTP
|
||||
const streamToHttp = (req, res) => {
|
||||
const accountId = req.accountId || req.remoteAddress;
|
||||
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Transfer-Encoding', 'chunked');
|
||||
|
||||
const heartbeat = setInterval(() => res.write(':thump\n'), 15000);
|
||||
|
||||
req.on('close', () => {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
log.verbose(req.requestId, `Ending stream for ${accountId}`);
|
||||
clearInterval(heartbeat);
|
||||
});
|
||||
|
||||
|
@ -383,8 +423,10 @@ const startWorker = (workerId) => {
|
|||
|
||||
// Setup stream end for WebSockets
|
||||
const streamWsEnd = (req, ws, closeHandler = false) => (id, listener) => {
|
||||
const accountId = req.accountId || req.remoteAddress;
|
||||
|
||||
ws.on('close', () => {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
log.verbose(req.requestId, `Ending stream for ${accountId}`);
|
||||
unsubscribe(id, listener);
|
||||
if (closeHandler) {
|
||||
closeHandler();
|
||||
|
@ -392,7 +434,7 @@ const startWorker = (workerId) => {
|
|||
});
|
||||
|
||||
ws.on('error', () => {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
log.verbose(req.requestId, `Ending stream for ${accountId}`);
|
||||
unsubscribe(id, listener);
|
||||
if (closeHandler) {
|
||||
closeHandler();
|
||||
|
@ -401,6 +443,7 @@ const startWorker = (workerId) => {
|
|||
};
|
||||
|
||||
app.use(setRequestId);
|
||||
app.use(setRemoteAddress);
|
||||
app.use(allowCrossDomain);
|
||||
app.use(authenticationMiddleware);
|
||||
app.use(errorMiddleware);
|
||||
|
@ -455,6 +498,7 @@ const startWorker = (workerId) => {
|
|||
const req = ws.upgradeReq;
|
||||
const location = url.parse(req.url, true);
|
||||
req.requestId = uuid.v4();
|
||||
req.remoteAddress = ws._socket.remoteAddress;
|
||||
|
||||
ws.isAlive = true;
|
||||
|
||||
|
@ -527,12 +571,14 @@ const startWorker = (workerId) => {
|
|||
|
||||
const onError = (err) => {
|
||||
log.error(err);
|
||||
server.close();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', onExit);
|
||||
process.on('SIGTERM', onExit);
|
||||
process.on('exit', onExit);
|
||||
process.on('error', onError);
|
||||
process.on('uncaughtException', onError);
|
||||
};
|
||||
|
||||
throng({
|
||||
|
|
Loading…
Reference in New Issue