Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8963f8c3c2 | ||
|
5e41c26203 | ||
|
45837c533e | ||
|
3fa8512474 | ||
|
0e20de9f89 | ||
|
24d645b7d0 | ||
|
7b23f79d41 | ||
|
3b4095cf1b | ||
|
28cbfb9f10 | ||
|
189a06d2a2 | ||
|
450441fc11 | ||
|
b619362a36 | ||
|
425d02287a | ||
|
2e429c0c25 | ||
|
e0e12b0fee | ||
|
62ca37884a | ||
|
f9180823bc | ||
|
4b0c667c09 | ||
|
1b732cad61 | ||
|
ecef03bb15 | ||
|
9642601126 | ||
|
3836d293a1 | ||
|
0734e1fe33 | ||
|
44cb08297c | ||
|
bd21afb5ed | ||
|
ef80ad17b3 | ||
|
9ea4f37e78 | ||
|
c48772fd3f | ||
|
860e257a68 | ||
|
902d9e34b4 |
2
Gemfile
2
Gemfile
@@ -19,7 +19,7 @@ gem 'paperclip', '~> 5.1'
|
||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||
|
||||
gem 'addressable', '~> 2.5'
|
||||
gem 'bootsnap'
|
||||
gem 'bootsnap', '~> 0.3'
|
||||
gem 'cld3', '~> 3.1'
|
||||
gem 'devise', '~> 4.2'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
|
@@ -67,7 +67,7 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (0.2.14)
|
||||
bootsnap (0.3.0)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (3.6.1)
|
||||
builder (3.2.3)
|
||||
@@ -474,7 +474,7 @@ DEPENDENCIES
|
||||
aws-sdk (~> 2.9)
|
||||
better_errors (~> 2.1)
|
||||
binding_of_caller (~> 0.7)
|
||||
bootsnap
|
||||
bootsnap (~> 0.3)
|
||||
brakeman (~> 3.6)
|
||||
bullet (~> 5.5)
|
||||
bundler-audit (~> 0.5)
|
||||
|
@@ -6,12 +6,12 @@ class AccountsController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@statuses = @account.statuses.permitted_for(@account, current_account).recent.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
end
|
||||
|
||||
format.atom do
|
||||
@entries = @account.stream_entries.recent.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
|
||||
end
|
||||
|
||||
|
@@ -12,13 +12,13 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
def create
|
||||
super do |resource|
|
||||
remember_me(resource)
|
||||
flash[:notice] = nil
|
||||
flash.delete(:notice)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
super
|
||||
flash[:notice] = nil
|
||||
flash.delete(:notice)
|
||||
end
|
||||
|
||||
protected
|
||||
|
17
app/helpers/style_helper.rb
Normal file
17
app/helpers/style_helper.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StyleHelper
|
||||
def stylesheet_for_layout
|
||||
if asset_exist? 'custom.css'
|
||||
'custom'
|
||||
else
|
||||
'application'
|
||||
end
|
||||
end
|
||||
|
||||
def asset_exist?(path)
|
||||
true if Webpacker::Manifest.lookup(path)
|
||||
rescue Webpacker::FileLoader::NotFoundError
|
||||
false
|
||||
end
|
||||
end
|
BIN
app/javascript/images/elephant-friend-1.png
Normal file
BIN
app/javascript/images/elephant-friend-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
@@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isRtl } from '../rtl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
let word;
|
||||
@@ -69,10 +70,6 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
this.props.onSuggestionsClearRequested();
|
||||
}
|
||||
|
||||
// auto-resize textarea
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
|
||||
this.props.onChange(e);
|
||||
}
|
||||
|
||||
@@ -160,10 +157,6 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.textarea.style.height = 'auto';
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
|
||||
const { suggestionsHidden, selectedSuggestion } = this.state;
|
||||
@@ -175,8 +168,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div className='autosuggest-textarea'>
|
||||
<textarea
|
||||
ref={this.setTextarea}
|
||||
<Textarea
|
||||
inputRef={this.setTextarea}
|
||||
className='autosuggest-textarea__textarea'
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
|
@@ -27,7 +27,8 @@ class StatusList extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
state = {
|
||||
isIntersecting: [{ }],
|
||||
isIntersecting: {},
|
||||
intersectionCount: 0,
|
||||
}
|
||||
|
||||
statusRefQueue = []
|
||||
@@ -65,15 +66,33 @@ class StatusList extends ImmutablePureComponent {
|
||||
attachIntersectionObserver () {
|
||||
const onIntersection = (entries) => {
|
||||
this.setState(state => {
|
||||
const isIntersecting = { };
|
||||
|
||||
entries.forEach(entry => {
|
||||
const statusId = entry.target.getAttribute('data-id');
|
||||
|
||||
state.isIntersecting[0][statusId] = entry.isIntersecting;
|
||||
// Edge 15 doesn't support isIntersecting, but we can infer it from intersectionRatio
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/
|
||||
state.isIntersecting[statusId] = entry.intersectionRatio > 0;
|
||||
});
|
||||
|
||||
return { isIntersecting: [state.isIntersecting[0]] };
|
||||
// isIntersecting is a map of DOM data-id's to booleans (true for
|
||||
// intersecting, false for non-intersecting).
|
||||
//
|
||||
// We always want to return true in shouldComponentUpdate() if
|
||||
// this object changes, because onIntersection() is only called if
|
||||
// something has changed.
|
||||
//
|
||||
// Now, we *could* use an immutable map or some other structure to
|
||||
// diff the full map, but that would be pointless because the browser
|
||||
// has already informed us that something has changed. So we can just
|
||||
// use a regular object, which will be diffed by ImmutablePureComponent
|
||||
// based on reference equality (i.e. it's always "unchanged") and
|
||||
// then we just increment intersectionCount to force a change.
|
||||
|
||||
return {
|
||||
isIntersecting: state.isIntersecting,
|
||||
intersectionCount: state.intersectionCount + 1,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -122,7 +141,7 @@ class StatusList extends ImmutablePureComponent {
|
||||
|
||||
render () {
|
||||
const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
|
||||
const isIntersecting = this.state.isIntersecting[0];
|
||||
const { isIntersecting } = this.state;
|
||||
|
||||
let loadMore = null;
|
||||
let scrollableArea = null;
|
||||
|
@@ -67,7 +67,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.autosuggestTextarea.reset();
|
||||
this.props.onSubmit();
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,8 @@ import IconButton from '../../../components/icon_button';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const messages = defineMessages({
|
||||
upload: { id: 'upload_button.label', defaultMessage: 'Add media' },
|
||||
@@ -10,7 +12,7 @@ const messages = defineMessages({
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const mapStateToProps = (state, props) => ({
|
||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray(),
|
||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
@@ -21,14 +23,14 @@ const iconStyle = {
|
||||
lineHeight: '27px',
|
||||
};
|
||||
|
||||
class UploadButton extends React.PureComponent {
|
||||
class UploadButton extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
onSelectFile: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
resetFileKey: PropTypes.number,
|
||||
acceptContentTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -58,7 +60,7 @@ class UploadButton extends React.PureComponent {
|
||||
ref={this.setRef}
|
||||
type='file'
|
||||
multiple={false}
|
||||
accept={ acceptContentTypes.join(',')}
|
||||
accept={ acceptContentTypes.toArray().join(',')}
|
||||
onChange={this.handleChange}
|
||||
disabled={disabled}
|
||||
style={{ display: 'none' }}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"account.followers": "پیگیران",
|
||||
"account.follows": "پی میگیرد",
|
||||
"account.follows_you": "پیگیر شماست",
|
||||
"account.media": "Media",
|
||||
"account.media": "رسانه",
|
||||
"account.mention": "نامبردن از @{name}",
|
||||
"account.mute": "بیصدا کردن @{name}",
|
||||
"account.posts": "نوشتهها",
|
||||
@@ -92,10 +92,10 @@
|
||||
"navigation_bar.mutes": "کاربران بیصداشده",
|
||||
"navigation_bar.preferences": "ترجیحات",
|
||||
"navigation_bar.public_timeline": "نوشتههای همهجا",
|
||||
"notification.favourite": "{name} نوشتهٔ شما را پسندید",
|
||||
"notification.follow": "{name} پیگیر شما شد",
|
||||
"notification.mention": "{name} از شما نام برد",
|
||||
"notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
|
||||
"notification.favourite": "{name} نوشتهٔ شما را پسندید",
|
||||
"notification.follow": "{name} پیگیر شما شد",
|
||||
"notification.mention": "{name} از شما نام برد",
|
||||
"notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
|
||||
"notifications.clear": "پاککردن اعلانها",
|
||||
"notifications.clear_confirmation": "واقعاً میخواهید همهٔ اعلانهایتان را برای همیشه پاک کنید؟",
|
||||
"notifications.column_settings.alert": "اعلان در کامپیوتر",
|
||||
@@ -123,7 +123,7 @@
|
||||
"onboarding.page_six.read_guidelines": "لطفاً {guidelines} {domain} را بخوانید!",
|
||||
"onboarding.page_six.various_app": "اپهای موبایل",
|
||||
"onboarding.page_three.profile": "با ویرایش نمایه میتوانید تصویر نمایه، نوشتهٔ معرفی، و نام نمایشی خود را تغییر دهید. ترجیحات دیگر شما هم آنجاست.",
|
||||
"onboarding.page_three.search": "در نوار جستجو میتوانید کاربران دیگر را بیابید یا هشتگها را ببینید، مانند {نقاشی} یا {معرفی}. برای یافتن افرادی که روی سرورهای دیگر هستند، شناسهٔ کامل آنها را بنویسید.",
|
||||
"onboarding.page_three.search": "در نوار جستجو میتوانید کاربران دیگر را بیابید یا هشتگها را ببینید، مانند {illustration} یا {introductions}. برای یافتن افرادی که روی سرورهای دیگر هستند، شناسهٔ کامل آنها را بنویسید.",
|
||||
"onboarding.page_two.compose": "در ستون «نوشتن» میتوانید نوشتههای تازه بنویسید. همچنین با دکمههای زیرش میتوانید تصویر اضافه کنید، حریم خصوصی نوشته را تنظیم کنید، و هشدار محتوا بگذارید.",
|
||||
"onboarding.skip": "رد کن",
|
||||
"privacy.change": "تنظیم حریم خصوصی نوشتهها",
|
||||
@@ -151,7 +151,7 @@
|
||||
"status.mute_conversation": "بیصداکردن گفتگو",
|
||||
"status.open": "این نوشته را باز کن",
|
||||
"status.reblog": "بازبوقیدن",
|
||||
"status.reblogged_by": "{name} بازبوقید",
|
||||
"status.reblogged_by": "{name} بازبوقید",
|
||||
"status.reply": "پاسخ",
|
||||
"status.replyAll": "به نوشته پاسخ دهید",
|
||||
"status.report": "گزارش دادن @{name}",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"account.block": "Blokkeer @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.block_domain": "Negeer alles van {domain}",
|
||||
"account.disclaimer": "Deze gebruiker zit op een andere server. Dit getal kan hoger zijn.",
|
||||
"account.edit_profile": "Profiel bewerken",
|
||||
"account.follow": "Volgen",
|
||||
@@ -14,9 +14,9 @@
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wacht op goedkeuring",
|
||||
"account.unblock": "Deblokkeer @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_domain": "{domain} niet meer negeren",
|
||||
"account.unfollow": "Ontvolgen",
|
||||
"account.unmute": "Negeer @{name} niet meer",
|
||||
"account.unmute": "@{name} niet meer negeren",
|
||||
"boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
|
||||
"column.blocks": "Geblokkeerde gebruikers",
|
||||
"column.community": "Lokale tijdlijn",
|
||||
@@ -43,8 +43,8 @@
|
||||
"confirmations.block.message": "Weet je zeker dat je {name} wilt blokkeren?",
|
||||
"confirmations.delete.confirm": "Verwijderen",
|
||||
"confirmations.delete.message": "Weet je zeker dat je deze toot wilt verwijderen?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.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",
|
||||
"confirmations.mute.message": "Weet je zeker dat je {name} wilt negeren?",
|
||||
"emoji_button.activity": "Activiteiten",
|
||||
@@ -148,7 +148,7 @@
|
||||
"status.load_more": "Meer laden",
|
||||
"status.media_hidden": "Media verborgen",
|
||||
"status.mention": "Vermeld @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "Negeer conversatie",
|
||||
"status.open": "Toot volledig tonen",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boostte",
|
||||
@@ -159,7 +159,7 @@
|
||||
"status.sensitive_warning": "Gevoelige inhoud",
|
||||
"status.show_less": "Minder tonen",
|
||||
"status.show_more": "Meer tonen",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unmute_conversation": "Conversatie niet meer negeren",
|
||||
"tabs_bar.compose": "Schrijven",
|
||||
"tabs_bar.federated_timeline": "Globaal",
|
||||
"tabs_bar.home": "Jouw tijdlijn",
|
||||
|
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"account.block": "Blocar",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.block": "Blocar @{name}",
|
||||
"account.block_domain": "Tot amagar del domeni {domain}",
|
||||
"account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
|
||||
"account.edit_profile": "Modificar lo perfil",
|
||||
"account.follow": "Sègre",
|
||||
"account.followers": "Abonats",
|
||||
"account.followers": "Seguidors",
|
||||
"account.follows": "Abonaments",
|
||||
"account.follows_you": "Vos sèc",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mencionar",
|
||||
"account.mute": "Rescondre",
|
||||
"account.media": "Mèdias",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mute": "Rescondre @{name}",
|
||||
"account.posts": "Estatuts",
|
||||
"account.report": "Senhalar",
|
||||
"account.report": "Senhalar @{name}",
|
||||
"account.requested": "Invitacion mandada",
|
||||
"account.unblock": "Desblocar",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock": "Desblocar @{name}",
|
||||
"account.unblock_domain": "Desblocar {domain}",
|
||||
"account.unfollow": "Quitar de sègre",
|
||||
"account.unmute": "Quitar de rescondre",
|
||||
"boost_modal.combo": "Podètz butar {combo} per passar aquò lo còp que ven",
|
||||
"account.unmute": "Quitar de rescondre @{name}",
|
||||
"boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
|
||||
"column.blocks": "Personas blocadas",
|
||||
"column.community": "Fil public local",
|
||||
"column.community": "Flux d’actualitat public local",
|
||||
"column.favourites": "Favorits",
|
||||
"column.follow_requests": "Demandas d’abonament",
|
||||
"column.home": "Acuèlh",
|
||||
@@ -29,7 +29,7 @@
|
||||
"column_back_button.label": "Tornar",
|
||||
"column_subheading.navigation": "Navigacion",
|
||||
"column_subheading.settings": "Paramètres",
|
||||
"compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als abonats.",
|
||||
"compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.",
|
||||
"compose_form.lock_disclaimer.lock": "clavat",
|
||||
"compose_form.placeholder": "A de qué pensatz ?",
|
||||
"compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
|
||||
@@ -40,13 +40,13 @@
|
||||
"compose_form.spoiler_placeholder": "Avertiment",
|
||||
"confirmation_modal.cancel": "Anullar",
|
||||
"confirmations.block.confirm": "Blocar",
|
||||
"confirmations.block.message": "Sètz segur de voler blocar {name}?",
|
||||
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
||||
"confirmations.delete.confirm": "Suprimir",
|
||||
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.domain_block.confirm": "Amagar tot lo domeni",
|
||||
"confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
|
||||
"confirmations.mute.confirm": "Metre en silenci",
|
||||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name}?",
|
||||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
||||
"emoji_button.activity": "Activitat",
|
||||
"emoji_button.flags": "Drapèus",
|
||||
"emoji_button.food": "Manjar e beure",
|
||||
@@ -57,13 +57,13 @@
|
||||
"emoji_button.search": "Cercar...",
|
||||
"emoji_button.symbols": "Simbòls",
|
||||
"emoji_button.travel": "Viatges & lòcs",
|
||||
"empty_column.community": "Lo fil public local es void. Escribètz quicòm per lo garnir !",
|
||||
"empty_column.community": "Lo flux public local es void. Escribètz quicòm per lo garnir !",
|
||||
"empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
|
||||
"empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||
"empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
|
||||
"empty_column.home.public_timeline": "lo fil public",
|
||||
"empty_column.home.public_timeline": "lo flux public",
|
||||
"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í ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo fil public.",
|
||||
"empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Regetar",
|
||||
"getting_started.appsshort": "Apps",
|
||||
@@ -83,16 +83,16 @@
|
||||
"media_gallery.toggle_visible": "Modificar la visibilitat",
|
||||
"missing_indicator.label": "Pas trobat",
|
||||
"navigation_bar.blocks": "Personas blocadas",
|
||||
"navigation_bar.community_timeline": "Fil public local",
|
||||
"navigation_bar.community_timeline": "Flux public local",
|
||||
"navigation_bar.edit_profile": "Modificar lo perfil",
|
||||
"navigation_bar.favourites": "Favorits",
|
||||
"navigation_bar.follow_requests": "Demandas d'abonament",
|
||||
"navigation_bar.info": "Mai informacions",
|
||||
"navigation_bar.logout": "Desconnexion",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.mutes": "Personas rescondudas",
|
||||
"navigation_bar.preferences": "Preferéncias",
|
||||
"navigation_bar.public_timeline": "Fil public global",
|
||||
"notification.favourite": "{name} a apondut a sos favorits :",
|
||||
"navigation_bar.public_timeline": "Flux public global",
|
||||
"notification.favourite": "{name} a ajustat a sos favorits :",
|
||||
"notification.follow": "{name} vos sèc.",
|
||||
"notification.mention": "{name} vos a mencionat :",
|
||||
"notification.reblog": "{name} a partejat vòstre estatut :",
|
||||
@@ -100,14 +100,14 @@
|
||||
"notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
|
||||
"notifications.column_settings.alert": "Notificacions localas",
|
||||
"notifications.column_settings.favourite": "Favorits :",
|
||||
"notifications.column_settings.follow": "Nòus abonats :",
|
||||
"notifications.column_settings.follow": "Nòus seguidors :",
|
||||
"notifications.column_settings.mention": "Mencions :",
|
||||
"notifications.column_settings.reblog": "Partatges :",
|
||||
"notifications.column_settings.show": "Mostrar dins la colomna",
|
||||
"notifications.column_settings.sound": "Emetre un son",
|
||||
"notifications.settings": "Paramètres de la colomna",
|
||||
"onboarding.done": "Done",
|
||||
"onboarding.next": "Next",
|
||||
"onboarding.done": "Fach",
|
||||
"onboarding.next": "Seguent",
|
||||
"onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra intància, aquí {domain}. Lo flux federat mòstra los estatuts publics de tot lo mond sus {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
|
||||
"onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.",
|
||||
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un enteragís amb vos",
|
||||
@@ -129,11 +129,11 @@
|
||||
"privacy.change": "Ajustar la confidencialitat del messatge",
|
||||
"privacy.direct.long": "Mostrar pas qu’a las personas mencionadas",
|
||||
"privacy.direct.short": "Dirècte",
|
||||
"privacy.private.long": "Mostrar pas qu’a vòstres abonats",
|
||||
"privacy.private.long": "Mostrar pas qu’a vòstres seguidors",
|
||||
"privacy.private.short": "Privat",
|
||||
"privacy.public.long": "Mostrar dins los fils publics",
|
||||
"privacy.public.long": "Mostrar dins los fluxes publics",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Mostrar pas dins los fils publics",
|
||||
"privacy.unlisted.long": "Mostrar pas dins los fluxes publics",
|
||||
"privacy.unlisted.short": "Pas-listat",
|
||||
"reply_indicator.cancel": "Anullar",
|
||||
"report.heading": "Nòu senhalament",
|
||||
@@ -153,10 +153,10 @@
|
||||
"status.reblog": "Partejar",
|
||||
"status.reblogged_by": "{name} a partejat :",
|
||||
"status.reply": "Respondre",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.replyAll": "Respondre a la conversacion",
|
||||
"status.report": "Senhalar @{name}",
|
||||
"status.sensitive_toggle": "Clicar per mostrar",
|
||||
"status.sensitive_warning": "Contengut embarrassant",
|
||||
"status.sensitive_warning": "Contengut sensible",
|
||||
"status.show_less": "Tornar plegar",
|
||||
"status.show_more": "Desplegar",
|
||||
"status.unmute_conversation": "Conversacions amb silenci levat",
|
||||
@@ -166,11 +166,11 @@
|
||||
"tabs_bar.local_timeline": "Flux public local",
|
||||
"tabs_bar.notifications": "Notificacions",
|
||||
"upload_area.title": "Lisatz e depausatz per mandar",
|
||||
"upload_button.label": "Apondre un mèdia",
|
||||
"upload_button.label": "Ajustar un mèdia",
|
||||
"upload_form.undo": "Anullar",
|
||||
"upload_progress.label": "Mandadís…",
|
||||
"video_player.expand": "Mostrar la vidèo",
|
||||
"video_player.toggle_sound": "Activar/Desactivar lo son",
|
||||
"video_player.toggle_visible": "Mostrar/Rescondre la vidèo",
|
||||
"video_player.video_error": "Video could not be played"
|
||||
"video_player.video_error": "Fracàs de la lectura de la vidèo"
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"account.block": "Blokuj @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.block_domain": "Blokuj wszystko z {domain}",
|
||||
"account.disclaimer": "Ten użytkownik pochodzi z innej instancji. Ta liczba może być większa.",
|
||||
"account.edit_profile": "Edytuj profil",
|
||||
"account.follow": "Obserwuj",
|
||||
@@ -14,7 +14,7 @@
|
||||
"account.report": "Zgłoś @{name}",
|
||||
"account.requested": "Oczekująca prośba",
|
||||
"account.unblock": "Odblokuj @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_domain": "Odblokuj domenę {domain}",
|
||||
"account.unfollow": "Przestań obserwować",
|
||||
"account.unmute": "Cofnij wyciszenie @{name}",
|
||||
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
|
||||
@@ -43,8 +43,8 @@
|
||||
"confirmations.block.message": "Czy na pewno chcesz zablokować {name}?",
|
||||
"confirmations.delete.confirm": "Usuń",
|
||||
"confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.domain_block.confirm": "Ukryj wszysyko z domeny",
|
||||
"confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.",
|
||||
"confirmations.mute.confirm": "Wycisz",
|
||||
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
|
||||
"emoji_button.activity": "Aktywność",
|
||||
@@ -60,18 +60,18 @@
|
||||
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby odbić piłeczkę!",
|
||||
"empty_column.hashtag": "Nie ma postów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
|
||||
"empty_column.home": "Nie obserwujesz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć ciekawych ludzi.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.",
|
||||
"empty_column.home.public_timeline": "publiczna oś czasu",
|
||||
"empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
|
||||
"empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
|
||||
"follow_request.authorize": "Autoryzuj",
|
||||
"follow_request.reject": "Odrzuć",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"getting_started.appsshort": "Aplikacje",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Dowiedz się",
|
||||
"getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj {github}.",
|
||||
"getting_started.support": "{faq} • {userguide} • {apps}",
|
||||
"getting_started.userguide": "User Guide",
|
||||
"getting_started.userguide": "Podręcznik użytkownika",
|
||||
"home.column_settings.advanced": "Zaawansowane",
|
||||
"home.column_settings.basic": "Podstawowe",
|
||||
"home.column_settings.filter_regex": "Filtruj z użyciem wyrażeń regularnych",
|
||||
@@ -148,7 +148,7 @@
|
||||
"status.load_more": "Załaduj więcej",
|
||||
"status.media_hidden": "Zawartość multimedialna ukryta",
|
||||
"status.mention": "Wspomnij o @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "Wycisz konwersację",
|
||||
"status.open": "Rozszerz ten status",
|
||||
"status.reblog": "Podbij",
|
||||
"status.reblogged_by": "{name} podbił",
|
||||
@@ -159,7 +159,7 @@
|
||||
"status.sensitive_warning": "Wrażliwa zawartość",
|
||||
"status.show_less": "Pokaż mniej",
|
||||
"status.show_more": "Pokaż więcej",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unmute_conversation": "Cofnij wyciezenie konwersacji",
|
||||
"tabs_bar.compose": "Napisz",
|
||||
"tabs_bar.federated_timeline": "Globalne",
|
||||
"tabs_bar.home": "Strona główna",
|
||||
|
@@ -29,7 +29,7 @@
|
||||
"column_back_button.label": "Назад",
|
||||
"column_subheading.navigation": "Навігація",
|
||||
"column_subheading.settings": "Налаштування",
|
||||
"compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні посты.",
|
||||
"compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.",
|
||||
"compose_form.lock_disclaimer.lock": "приватний",
|
||||
"compose_form.placeholder": "Що у Вас на думці?",
|
||||
"compose_form.privacy_disclaimer": "Ваш приватний допис буде доставлено до згаданих користувачів на доменах {domains}. Ви довіряєте {domainsCount, plural, one {цьому серверу} other {цим серверам}}? Приватність постів працює тільки на інстанціях Mastodon. Якщо {domains} {domainsCount, plural, one {не є інстанцією Mastodon} other {не є інстанціями Mastodon}}, приватність поста не буде активована, та він може бути передмухнутий або іншим чином показаний не позначеним Вами користувачам.",
|
||||
|
@@ -1,8 +1,5 @@
|
||||
const perf = require('./performance');
|
||||
|
||||
// allow override variables here
|
||||
require.context('../../assets/stylesheets/', false, /variables.*\.scss$/);
|
||||
|
||||
// import default stylesheet with variables
|
||||
require('font-awesome/css/font-awesome.css');
|
||||
require('../styles/application.scss');
|
||||
@@ -23,9 +20,6 @@ function main() {
|
||||
|
||||
require.context('../images/', true);
|
||||
|
||||
// import customization styles
|
||||
require.context('../../assets/stylesheets/', false, /custom.*\.scss$/);
|
||||
|
||||
onDomContentLoaded(() => {
|
||||
const mountNode = document.getElementById('mastodon');
|
||||
const props = JSON.parse(mountNode.getAttribute('data-props'));
|
||||
|
@@ -91,19 +91,6 @@ const initialState = Immutable.Map({
|
||||
});
|
||||
|
||||
const normalizeStatus = (state, status) => {
|
||||
const replyToId = status.get('in_reply_to_id');
|
||||
const id = status.get('id');
|
||||
|
||||
if (replyToId) {
|
||||
if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
|
||||
state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
|
||||
}
|
||||
|
||||
if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
|
||||
state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
.card {
|
||||
background: $ui-base-color;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
padding: 60px 0;
|
||||
padding-bottom: 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
@@ -47,10 +47,11 @@ body {
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
}
|
||||
cursor: pointer;
|
||||
|
||||
button:focus {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.app-holder {
|
||||
|
@@ -561,7 +561,7 @@
|
||||
}
|
||||
|
||||
opacity: 1;
|
||||
animation: fade 0.3s linear;
|
||||
animation: fade 150ms linear;
|
||||
|
||||
&.status-direct {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
@@ -3013,13 +3013,14 @@ button.icon-button.active i.fa-retweet {
|
||||
|
||||
.onboarding-modal__page-one {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.onboarding-modal__page-one__elephant-friend {
|
||||
background: url('../images/elephant-friend.png') no-repeat center center / contain;
|
||||
width: 147px;
|
||||
height: 160px;
|
||||
margin-right: 10px;
|
||||
background: url('../images/elephant-friend-1.png') no-repeat center center / contain;
|
||||
width: 155px;
|
||||
height: 193px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
@@ -3394,7 +3395,7 @@ button.icon-button.active i.fa-retweet {
|
||||
object-fit: cover;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-35%);
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
@@ -59,7 +59,13 @@ class AccountSearchService < BaseService
|
||||
end
|
||||
|
||||
def exact_match
|
||||
@_exact_match ||= Account.find_remote(query_username, query_domain)
|
||||
@_exact_match ||= begin
|
||||
if domain_is_local?
|
||||
Account.find_local(query_username)
|
||||
else
|
||||
Account.find_remote(query_username, query_domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def search_results
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
module AuthorExtractor
|
||||
def author_from_xml(xml)
|
||||
return nil if xml.nil?
|
||||
|
||||
# Try <email> for acct
|
||||
acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: TagManager::XMLNS)&.content
|
||||
|
||||
|
@@ -189,7 +189,7 @@ class ProcessFeedService < BaseService
|
||||
def find_status(uri)
|
||||
if TagManager.instance.local_id?(uri)
|
||||
local_id = TagManager.instance.unique_tag_to_local_id(uri, 'Status')
|
||||
return Status.find(local_id)
|
||||
return Status.find_by(id: local_id)
|
||||
end
|
||||
|
||||
Status.find_by(uri: uri)
|
||||
|
@@ -108,12 +108,18 @@ class ProcessInteractionService < BaseService
|
||||
|
||||
def favourite!(xml, from_account)
|
||||
current_status = status(xml)
|
||||
|
||||
return if current_status.nil?
|
||||
|
||||
favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
|
||||
NotifyService.new.call(current_status.account, favourite)
|
||||
end
|
||||
|
||||
def unfavourite!(xml, from_account)
|
||||
current_status = status(xml)
|
||||
|
||||
return if current_status.nil?
|
||||
|
||||
favourite = current_status.favourites.where(account: from_account).first
|
||||
favourite&.destroy
|
||||
end
|
||||
|
@@ -1,5 +1,7 @@
|
||||
object @media
|
||||
attribute :id, :type
|
||||
|
||||
node(:url) { |media| full_asset_url(media.file.url(:original)) }
|
||||
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) }
|
||||
node(:text_url) { |media| medium_url(media) }
|
||||
node(:meta) { |media| media.file.meta }
|
||||
|
@@ -18,7 +18,8 @@
|
||||
= ' - '
|
||||
= title
|
||||
|
||||
= stylesheet_pack_tag 'application', media: 'all'
|
||||
= stylesheet_pack_tag 'common', media: 'all'
|
||||
= stylesheet_pack_tag stylesheet_for_layout, media: 'all'
|
||||
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
|
||||
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
|
||||
= csrf_meta_tags
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
<p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.</p>
|
||||
|
||||
<p>Pensatz tanben a gaitar a nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.</p>
|
||||
<p>Pensatz tanben a gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.</p>
|
||||
|
||||
<p>Amistosament,</p>
|
||||
|
||||
|
@@ -7,7 +7,7 @@ er confirmar vòstre inscripcion, mercés de clicar sul ligam seguent :
|
||||
|
||||
Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de l’aisina.
|
||||
|
||||
Pensatz tanben a gaitar a nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.
|
||||
Pensatz tanben a gaitar nòstras <%= link_to 'conditions d\'utilisation', terms_url %>.
|
||||
|
||||
Amistosament,
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<p>Bonjorn <%= @resource.email %> !</p>
|
||||
|
||||
<p>Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion ne clicant sul ligam çai-jos.</p>
|
||||
<p>Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
|
||||
|
||||
<p><%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %></p>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
Bonjorn <%= @resource.email %> !
|
||||
|
||||
Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion ne clicant sul ligam çai-jos.</p>
|
||||
Qualqu’un a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
|
||||
|
||||
<%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %>
|
||||
|
||||
|
@@ -3,8 +3,8 @@ oc:
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Vòstra adreça de corrièl es ben estada validada.
|
||||
send_instructions: Recebretz un corrièl per vos indicar çò que cal far per confirmar vòstra adreça de corrièl dins una estona.
|
||||
send_paranoid_instructions: Se vòstra adreça existís dins nòstra basa de donadas, recebretz un corrièl per vos indicar çò que cal far per confirmar vòstra adreça de corrièl dins una estona.
|
||||
send_instructions: Recebretz un corrièl per vos indicar çò que cal far per confirmar vòstra adreça de corrièl dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
send_paranoid_instructions: Se vòstra adreça existís dins nòstra basa de donadas, recebretz un corrièl per vos indicar çò que cal far per confirmar vòstra adreça de corrièl dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
failure:
|
||||
already_authenticated: Sètz ja connectat.
|
||||
inactive: Vòstre compte es pas encara activat.
|
||||
@@ -29,8 +29,8 @@ oc:
|
||||
success: Sètz ben autentificat dempuèi lo compte %{kind}.
|
||||
passwords:
|
||||
no_token: Podètz pas accedir a aquesta pagina sens venir d’un corriel de reïnicializacion de senhal. S’es lo cas, mercés de verificar qu’avètz ben utilizat l’URL donada de manièra complèta.
|
||||
send_instructions: Recebretz un corrièl amb las instruccions per reĩnicializar vòstre senhal dins una estona.
|
||||
send_paranoid_instructions: Se vòstra adreça de corrièl existís dins nòstra basa de donadas, recebretz un ligam per reĩnicializar vòstre senhal dins una estona.
|
||||
send_instructions: Recebretz un corrièl amb las instruccions per reĩnicializar vòstre senhal dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
send_paranoid_instructions: Se vòstra adreça de corrièl existís dins nòstra basa de donadas, recebretz un ligam per reĩnicializar vòstre senhal dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
updated: Vòstre senhal es ben estat cambiat. Sètz ara connectat.
|
||||
updated_not_active: Vòstre senhal es ben estat cambiat.
|
||||
registrations:
|
||||
@@ -38,16 +38,16 @@ oc:
|
||||
signed_up: Benvengut ! Sètz ben marcat al malhum.
|
||||
signed_up_but_inactive: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara validat.
|
||||
signed_up_but_locked: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara blocat.
|
||||
signed_up_but_unconfirmed: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça de corrièl. Clicatz sul ligam per activar vòstre compte.
|
||||
update_needs_confirmation: Avètz ben mes a jorn vòstre compte, mai nos cal verificar vòstra nòva adreça de corrièl. Mercés de verificar vòstres messatges e clicar sul ligam de confirmacion per confirmar vòstra nòva adreça de corrièl.
|
||||
signed_up_but_unconfirmed: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça de corrièl. Clicatz sul ligam per activar vòstre compte. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
update_needs_confirmation: Avètz ben mes a jorn vòstre compte, mai nos cal verificar vòstra nòva adreça de corrièl. Mercés de verificar vòstres messatges e clicar sul ligam de confirmacion per confirmar vòstra nòva adreça de corrièl. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
updated: Vòstre compte es estat mes a jorn amb succès.
|
||||
sessions:
|
||||
already_signed_out: Desconnectat amb succès.
|
||||
signed_in: Connectat amb succès.
|
||||
signed_out: Desconnectat amb succès.
|
||||
unlocks:
|
||||
send_instructions: Recebretz un corrièl amb las instruccions per o desblocar dins una estona.
|
||||
send_paranoid_instructions: Se vòstre compte existís recebretz un corrièl amb las instruccions per o desblocar dins una estona.
|
||||
send_instructions: Recebretz un corrièl amb las instruccions per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
send_paranoid_instructions: Se vòstre compte existís recebretz un corrièl amb las instruccions per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
|
||||
unlocked: Vòstre compte es estat desblocat amb succès. Mercés de vos connectar per contunhar.
|
||||
errors:
|
||||
messages:
|
||||
|
@@ -145,7 +145,7 @@ fa:
|
||||
id: شناسه
|
||||
mark_as_resolved: علامتگذاری به عنوان حلشده
|
||||
report: 'گزارش #%{id}'
|
||||
report_contents: Contents
|
||||
report_contents: محتوا
|
||||
reported_account: حساب گزارششده
|
||||
reported_by: گزارش از طرف
|
||||
resolved: حلشده
|
||||
@@ -322,7 +322,7 @@ fa:
|
||||
sensitive_content: محتوای حساس
|
||||
time:
|
||||
formats:
|
||||
default: "%b %d, %Y, %H:%M"
|
||||
default: "%d %b %Y, %H:%M"
|
||||
two_factor_authentication:
|
||||
code_hint: برای تأیید، کدی را که برنامهٔ تأییدکننده ساخته است وارد کنید
|
||||
description_html: اگر <strong>ورود دومرحلهای</strong> را فعال کنید، برای ورود به سیستم به تلفن خود نیاز خواهید داشت تا برایتان یک کد موقتی بسازد
|
||||
|
@@ -222,6 +222,7 @@ nl:
|
||||
disable: Uitschakelen
|
||||
enable: Inschakelen
|
||||
instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Van nu af aan genereert deze app aanmeldcodes die je bij het aanmelden moet invoeren."
|
||||
setup: Instellen
|
||||
users:
|
||||
invalid_email: E-mailadres is ongeldig
|
||||
invalid_otp_token: Ongeldige tweestaps-aanmeldcode
|
||||
|
@@ -18,7 +18,7 @@ oc:
|
||||
ethics: 'Ethical design: pas cap de reclama o traçador'
|
||||
gifv: Partatge de GIFs e vidèos cortas
|
||||
privacy: Nivèl de confidencialitat configurable per cada publicacion
|
||||
public: Fluxes d’actualitat publicsPublic timelines
|
||||
public: Fluxes d’actualitat publics
|
||||
features_headline: Çò que fa que Mastodon es diferent
|
||||
get_started: Venètz al malhum
|
||||
links: Ligams
|
||||
@@ -32,7 +32,7 @@ oc:
|
||||
version: Version
|
||||
accounts:
|
||||
follow: Sègre
|
||||
followers: Abonats
|
||||
followers: Seguidors
|
||||
following: Abonaments
|
||||
nothing_here: I a pas res aquí !
|
||||
people_followed_by: Lo mond que %{name} sèc
|
||||
@@ -60,7 +60,7 @@ oc:
|
||||
edit: Modificar
|
||||
email: Corrièl
|
||||
feed_url: Flux URL
|
||||
followers: Abonats
|
||||
followers: Seguidors
|
||||
follows: Abonaments
|
||||
ip: IP
|
||||
location:
|
||||
@@ -76,7 +76,7 @@ oc:
|
||||
title: Moderacion
|
||||
most_recent_activity: Activitat mai recenta
|
||||
most_recent_ip: IP mai recenta
|
||||
not_subscribed: Pas abonat
|
||||
not_subscribed: Pas seguidor
|
||||
order:
|
||||
alphabetic: Alfabetic
|
||||
most_recent: Mai recent
|
||||
@@ -87,7 +87,7 @@ oc:
|
||||
push_subscription_expires: Fin de l’abonament PuSH
|
||||
reset: Reïnicializar
|
||||
reset_password: Reïnicializar lo senhal
|
||||
salmon_url: Salmon URL
|
||||
salmon_url: URL Salmon
|
||||
search: Cercar
|
||||
show:
|
||||
created_reports: Rapòrts creat per aqueste compte
|
||||
@@ -109,7 +109,7 @@ oc:
|
||||
create: Crear blocatge
|
||||
hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
|
||||
severity:
|
||||
desc_html: "<strong>Silenci</strong> farà venir invisibles los estatuts del compte al mond que son pas abonats. <strong>Suspendre</strong> levarà tot lo contengut del compte, los mèdias e las donadas de perfil."
|
||||
desc_html: "<strong>Silenci</strong> farà venir invisibles los estatuts del compte al mond que son pas de seguidors. <strong>Suspendre</strong> levarà tot lo contengut del compte, los mèdias e las donadas de perfil."
|
||||
silence: Silenci
|
||||
suspend: Suspendre
|
||||
title: Nòu blocatge domeni
|
||||
@@ -160,6 +160,10 @@ oc:
|
||||
title: Senhalament
|
||||
unresolved: Pas resolguts
|
||||
view: Veire
|
||||
nsfw:
|
||||
'true': Contengut sensible activat
|
||||
'false': Sens contengut sensible
|
||||
are_you_sure: Es segur ?
|
||||
settings:
|
||||
contact_information:
|
||||
email: Picatz una adreça de corrièl
|
||||
@@ -240,15 +244,15 @@ oc:
|
||||
storage: Mèdias gardats
|
||||
followers:
|
||||
domain: Domeni
|
||||
explanation_html: Se volètz vos assegurar de la confidencialitat de vòstres estatuts, vos cal saber qual es abonat a vòstre compte. <strong>Vòstres estatuts privats son enviats a totas las instàncias qu’an de mond que vos sègon.</strong>. Benlèu que volètz repassar vòstra lista e tirar los abonats s’avètz de dubtes tocant las politica de confidencialitat de lor instàncias.
|
||||
followers_count: Nombre d’abonats
|
||||
explanation_html: Se volètz vos assegurar de la confidencialitat de vòstres estatuts, vos cal saber qual sèc vòstre compte. <strong>Vòstres estatuts privats son enviats a totas las instàncias qu’an de mond que vos sègon.</strong>. Benlèu que volètz repassar vòstra lista e tirar los seguidors s’avètz de dubtes tocant las politica de confidencialitat de lor instàncias.
|
||||
followers_count: Nombre de seguidors
|
||||
lock_link: Clavar vòstre compte
|
||||
purge: Tirar dels abonats
|
||||
purge: Tirar dels seguidors
|
||||
success:
|
||||
one: Soi a blocar los abonats d’un domeni...
|
||||
other: Soi a blocar los abonats de %{count} domenis...
|
||||
one: Soi a blocar los seguidors d’un domeni...
|
||||
other: Soi a blocar los seguidors de %{count} domenis...
|
||||
true_privacy_html: Mèfi que la <strong>vertadièra confidencialitat pòt solament èsser amb un chiframent del cap a la fin (end-to-end)</strong>.
|
||||
unlocked_warning_html: Tot lo mond pòt vos sègre e veire sulpic vòstres estatuts privats. %{lock_link} per poder repassar e regetar los abonats.
|
||||
unlocked_warning_html: Tot lo mond pòt vos sègre e veire sulpic vòstres estatuts privats. %{lock_link} per poder repassar e regetar los seguidors.
|
||||
unlocked_warning_title: Vòstre compte es pas clavat
|
||||
generic:
|
||||
changes_saved_msg: Cambiaments ben realizats !
|
||||
@@ -269,15 +273,15 @@ oc:
|
||||
landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Se pòt ajustar una vidèo a un estatut que ten ja d’imatges
|
||||
images_and_video: Se pòt pas ajustar una vidèo a un estatut que ten ja d’imatges
|
||||
too_many: Se pòt pas ajustar mai de 4 fichièrs
|
||||
notification_mailer:
|
||||
digest:
|
||||
body: 'Trobatz aquí un resumit de çò qu’avètz mancat dempuèi vòstra darrièra visita lo %{since}:'
|
||||
mention: "%{name} vos a mencionat dins :"
|
||||
new_followers_summary:
|
||||
one: Avètz un nòu abonat ! Ouà !
|
||||
other: Avètz %{count} nòus abonats ! Qué crane !
|
||||
one: Avètz un nòu seguidor ! Ouà !
|
||||
other: Avètz %{count} nòus seguidors ! Qué crane !
|
||||
subject:
|
||||
one: "Una nòva notificacion dempuèi vòstra darrièra visita \U0001F418"
|
||||
other: "%{count} nòvas notificacions dempuèi vòstra darrièra visita \U0001F418"
|
||||
@@ -310,7 +314,7 @@ oc:
|
||||
back: Tornar a Mastodon
|
||||
edit_profile: Modificar lo perfil
|
||||
export: Export donadas
|
||||
followers: Abonats autorizats
|
||||
followers: Seguidors autorizats
|
||||
import: Import
|
||||
preferences: Preferéncias
|
||||
settings: Paramètres
|
||||
@@ -320,8 +324,8 @@ oc:
|
||||
over_character_limit: limit de %{max} caractèrs passat
|
||||
show_more: Ne veire mai
|
||||
visibilities:
|
||||
private: Abonats solament
|
||||
private_long: Mostrar pas qu’als abonats
|
||||
private: Seguidors solament
|
||||
private_long: Mostrar pas qu’als seguidors
|
||||
public: Public
|
||||
public_long: Tot lo mond pòt veire
|
||||
unlisted: Pas listat
|
||||
@@ -332,7 +336,7 @@ oc:
|
||||
sensitive_content: Contengut sensible
|
||||
time:
|
||||
formats:
|
||||
default: "%b %d %Y a %H o %M"
|
||||
default: "%b %d %Y a %Ho%M"
|
||||
two_factor_authentication:
|
||||
code_hint: Picatz lo còdi generat per vòstra aplicacion d’autentificacion per confirmar
|
||||
description_html: S’activatz <strong> l’autentificacion two-factor</strong>, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar.
|
||||
|
@@ -160,6 +160,10 @@ pl:
|
||||
title: Zgłoszenia
|
||||
unresolved: Nierozwiązane
|
||||
view: Wyświetl
|
||||
nsfw:
|
||||
'true': NSFW będzie wyświetlane
|
||||
'false': NSFW nie będzie wyświetlane
|
||||
are_you_sure: Czy na pewno?
|
||||
settings:
|
||||
contact_information:
|
||||
email: Wprowadź publiczny adres e-mail
|
||||
@@ -239,8 +243,8 @@ pl:
|
||||
lock_link: Zablokuj swoje konto
|
||||
purge: Usuń z obserwujących
|
||||
success:
|
||||
one: W procesie usuwania obserwujcych z jednej domeny…
|
||||
other: W procesie usuwania obserwujących z %{count} domen…
|
||||
one: W trakcie usuwania obserwujcych z jednej domeny…
|
||||
other: W trakcie usuwania obserwujących z %{count} domen…
|
||||
true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>.
|
||||
unlocked_warning_html: Każdy może cię zaobserwować, aby natychmiastowo zobaczyć twoje statusy. %{lock_link} aby móc kontrolować obserwujących.
|
||||
unlocked_warning_title: Twoje konto nie jest zablokowane
|
||||
@@ -271,9 +275,13 @@ pl:
|
||||
mention: "%{name} wspomniał o Tobie w:"
|
||||
new_followers_summary:
|
||||
one: Śledzi Cię nowa osoba! Gratulacje!
|
||||
other: Kilka (%{count}) nowych osób Cię śledzi! Wspaniale!
|
||||
few: (%{count}) nowe osoby śledzą Cię!
|
||||
many: (%{count}) nowych osób Cię śledzi! Wspaniale!
|
||||
other: (%{count}) nowych osób Cię śledzi! Wspaniale!
|
||||
subject:
|
||||
one: "1 nowe powiadomienie od Twojej ostatniej wizyty \U0001F418"
|
||||
few: "%{count} nowe powiadomienia od Twojej ostatniej wizyty \U0001F418"
|
||||
many: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
|
||||
other: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
|
||||
favourite:
|
||||
body: 'Twój wpis został polubiony przez %{name}:'
|
||||
|
@@ -10,6 +10,10 @@ nl:
|
||||
note: Maximaal 160 tekens
|
||||
imports:
|
||||
data: CSV-bestand dat op een andere Mastodon-server werd geëxporteerd
|
||||
sessions:
|
||||
otp: Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcode's.
|
||||
user:
|
||||
filtered_languages: De geselecteerde talen worden uit de lokale en globale tijdlijn verwijderd.
|
||||
labels:
|
||||
defaults:
|
||||
avatar: Avatar
|
||||
@@ -19,6 +23,7 @@ nl:
|
||||
data: Gegevens
|
||||
display_name: Weergavenaam
|
||||
email: E-mailadres
|
||||
filtered_languages: Talen filteren
|
||||
header: Omslagfoto
|
||||
locale: Taal
|
||||
locked: Maak account besloten
|
||||
|
@@ -4,14 +4,20 @@ oc:
|
||||
hints:
|
||||
defaults:
|
||||
avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 120x120px
|
||||
display_name: Maximum 30 caractèrs
|
||||
header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 700x335px
|
||||
locked: Demanda qu’acceptatz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres abonats solament
|
||||
note: Maximum 160 caractèrs
|
||||
display_name:
|
||||
one: 'Demòra encara <span class="name-counter">1</span> caractèr'
|
||||
other: 'Demòran encara <span class="name-counter">%{count}</span> caractèrs'
|
||||
header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhada en 700x335px
|
||||
locked: Demanda qu’acceptetz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres seguidors solament
|
||||
note:
|
||||
one: 'Demòra encara <span class="name-counter">1</span> caractèr'
|
||||
other: 'Demòran encara <span class="name-counter">%{count}</span> caractèrs'
|
||||
imports:
|
||||
data: Fichièr CSV exportat d’una autra instància Mastodon
|
||||
sessions:
|
||||
otp: Picatz lo còdi d’autentificacion en dos temps (Two factor code) de vòstre mobil o utilizatz un de vòstres còdis de recuperacion.
|
||||
user:
|
||||
filtered_languages: Las lengas seleccionadas seràn levadas de vòstre flux d’actualitat
|
||||
labels:
|
||||
defaults:
|
||||
avatar: Avatar
|
||||
|
@@ -6,11 +6,15 @@ pl:
|
||||
avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px
|
||||
display_name:
|
||||
one: 'Pozostał <span class="name-counter">1</span> znak.'
|
||||
few: 'Pozostały <span class="name-counter">%{count}</span> znaki.'
|
||||
many: 'Pozostało <span class="name-counter">%{count}</span> znaków'
|
||||
other: 'Pozostało <span class="name-counter">%{count}</span> znaków'
|
||||
header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px
|
||||
locked: Musisz akceptować obserwacje; Twoje wpisy są domyślnie widoczne tylko dla Twoich obserwujących
|
||||
note:
|
||||
one: 'Pozostał <span class="name-counter">1</span> znak.'
|
||||
few: 'Pozostały <span class="name-counter">%{count}</span> znaki.'
|
||||
many: 'Pozostało <span class="name-counter">%{count}</span> znaków'
|
||||
other: 'Pozostało <span class="name-counter">%{count}</span> znaków'
|
||||
imports:
|
||||
data: Plik CSV wyeksportowany z innej instancji Mastodona
|
||||
|
@@ -54,6 +54,7 @@ zh-CN:
|
||||
are_you_sure: 你确定吗?
|
||||
confirm: 确认
|
||||
confirmed: 已确认
|
||||
disable_two_factor_authentication: 两步认证无效
|
||||
display_name: 显示名称
|
||||
domain: 域名
|
||||
edit: 编辑
|
||||
@@ -61,6 +62,7 @@ zh-CN:
|
||||
feed_url: 订阅 URL
|
||||
followers: 关注者
|
||||
follows: 正在关注
|
||||
ip: IP地址
|
||||
location:
|
||||
all: 全部
|
||||
local: 本地
|
||||
@@ -83,7 +85,10 @@ zh-CN:
|
||||
profile_url: 个人文件 URL
|
||||
public: 公共
|
||||
push_subscription_expires: 推送订阅过期
|
||||
reset: 重置
|
||||
reset_password: 重置密码
|
||||
salmon_url: Salmon 反馈 URL
|
||||
search: 搜索
|
||||
show:
|
||||
created_reports: 这个账户创建的报告
|
||||
report: 报告
|
||||
@@ -116,6 +121,7 @@ zh-CN:
|
||||
severity: 阻隔程度
|
||||
show:
|
||||
affected_accounts:
|
||||
one: 数据库中有1个账户受影响
|
||||
other: 数据库中有%{count}个账户受影响
|
||||
retroactive:
|
||||
silence: 对此域名的所有账户取消静音
|
||||
@@ -153,6 +159,10 @@ zh-CN:
|
||||
title: 举报
|
||||
unresolved: 未处理
|
||||
view: 查看
|
||||
nsfw:
|
||||
'true': NSFW有效
|
||||
'false': NSFW无效
|
||||
are_you_sure: 你确定吗?
|
||||
settings:
|
||||
contact_information:
|
||||
email: 输入一个公开的电邮地址
|
||||
@@ -218,6 +228,7 @@ zh-CN:
|
||||
'422':
|
||||
content: 无法确认登录信息。你是不是屏蔽了 Cookie?
|
||||
title: 无法确认登录信息
|
||||
'429': 被限制
|
||||
exports:
|
||||
blocks: 被你封锁的用户
|
||||
csv: CSV
|
||||
@@ -239,6 +250,7 @@ zh-CN:
|
||||
powered_by: 基于 %{link} 构建
|
||||
save_changes: 保存
|
||||
validation_errors:
|
||||
one: 出错啦!请确认以下出错的地方,修改之后再来一次:
|
||||
other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次:
|
||||
imports:
|
||||
preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。
|
||||
@@ -250,6 +262,10 @@ zh-CN:
|
||||
upload: 上载
|
||||
landing_strip_html: <strong>%{name}</strong> 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里(Mastodon、GNU social)任一服务器实例的用户,便可以跨站关注此站用户并与其沟通。
|
||||
landing_strip_signup_html: 如果你没有这类账户,欢迎在<a href="%{sign_up_path}">此处登记</a>。
|
||||
media_attachments:
|
||||
validations:
|
||||
images_and_video: 无法添加视频到一个已经包含图片的嘟文中
|
||||
too_many: 最多只能添加4张图片
|
||||
notification_mailer:
|
||||
digest:
|
||||
body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴:
|
||||
@@ -318,10 +334,12 @@ zh-CN:
|
||||
disable: 停用
|
||||
enable: 启用
|
||||
enabled_success: 已成功启用两步认证
|
||||
generate_recovery_codes: 生成恢复代码
|
||||
instructions_html: "<strong>请用你手机的认证器应用(如 Google Authenticator、Authy),扫描这里的 QR 二维码</strong>。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。"
|
||||
lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的账户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。<del>(废话)</del>
|
||||
manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰
|
||||
recovery_codes_regenerated: 已成功重新生成恢复代码
|
||||
recovery_instructions: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的账号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。
|
||||
setup: 设置
|
||||
wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。
|
||||
users:
|
||||
|
@@ -34,6 +34,7 @@ module.exports = merge(sharedConfig, {
|
||||
analyzerMode: 'static',
|
||||
generateStatsFile: true,
|
||||
openAnalyzer: false,
|
||||
logLevel: 'silent', // do not bother Webpacker, who runs with --json and parses stdout
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@@ -53,6 +53,12 @@ module.exports = {
|
||||
// be loaded together
|
||||
return false;
|
||||
}
|
||||
|
||||
if (module.resource && /node_modules\/font-awesome/.test(module.resource)) {
|
||||
// extract vendor css into common module
|
||||
return true;
|
||||
}
|
||||
|
||||
return count >= 2;
|
||||
},
|
||||
}),
|
||||
|
@@ -13,11 +13,11 @@ module Mastodon
|
||||
end
|
||||
|
||||
def patch
|
||||
0
|
||||
1
|
||||
end
|
||||
|
||||
def pre
|
||||
4
|
||||
nil
|
||||
end
|
||||
|
||||
def to_a
|
||||
|
@@ -90,6 +90,7 @@
|
||||
"react-router": "^2.8.0",
|
||||
"react-router-scroll": "^0.3.2",
|
||||
"react-simple-dropdown": "^1.1.4",
|
||||
"react-textarea-autosize": "^5.0.6",
|
||||
"react-toggle": "^2.1.1",
|
||||
"redis": "^2.6.5",
|
||||
"redux": "^3.6.0",
|
||||
@@ -101,6 +102,7 @@
|
||||
"sass-loader": "^6.0.3",
|
||||
"stringz": "^0.1.2",
|
||||
"style-loader": "^0.16.1",
|
||||
"throng": "^4.0.0",
|
||||
"uuid": "^3.0.1",
|
||||
"uws": "^0.14.5",
|
||||
"webpack": "^2.4.1",
|
||||
|
@@ -33,25 +33,25 @@ describe AccountSearchService do
|
||||
describe 'searching local and remote users' do
|
||||
describe "when only '@'" do
|
||||
before do
|
||||
allow(Account).to receive(:find_remote)
|
||||
allow(Account).to receive(:find_local)
|
||||
allow(Account).to receive(:search_for)
|
||||
subject.call('@', 10)
|
||||
end
|
||||
|
||||
it 'uses find_remote with empty query to look for local accounts' do
|
||||
expect(Account).to have_received(:find_remote).with('', nil)
|
||||
it 'uses find_local with empty query to look for local accounts' do
|
||||
expect(Account).to have_received(:find_local).with('')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when no domain' do
|
||||
before do
|
||||
allow(Account).to receive(:find_remote)
|
||||
allow(Account).to receive(:find_local)
|
||||
allow(Account).to receive(:search_for)
|
||||
subject.call('one', 10)
|
||||
end
|
||||
|
||||
it 'uses find_remote with nil domain to look for local accounts' do
|
||||
expect(Account).to have_received(:find_remote).with('one', nil)
|
||||
it 'uses find_local to look for local accounts' do
|
||||
expect(Account).to have_received(:find_local).with('one')
|
||||
end
|
||||
|
||||
it 'uses search_for to find matches' do
|
||||
@@ -101,6 +101,25 @@ describe AccountSearchService do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when there is a local domain' do
|
||||
around do |example|
|
||||
before = Rails.configuration.x.local_domain
|
||||
example.run
|
||||
Rails.configuration.x.local_domain = before
|
||||
end
|
||||
|
||||
it 'returns exact match first' do
|
||||
remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e')
|
||||
remote_too = Fabricate(:account, username: 'b', domain: 'remote', display_name: 'e')
|
||||
exact = Fabricate(:account, username: 'e')
|
||||
Rails.configuration.x.local_domain = 'example.com'
|
||||
|
||||
results = subject.call('e@example.com', 2)
|
||||
expect(results.size).to eq 2
|
||||
expect(results).to eq([exact, remote]).or eq([exact, remote_too])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when there is a domain but no exact match' do
|
||||
it 'follows the remote account when resolve is true' do
|
||||
service = double(call: nil)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import os from 'os';
|
||||
import cluster from 'cluster';
|
||||
import throng from 'throng';
|
||||
import dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
@@ -16,6 +16,8 @@ dotenv.config({
|
||||
path: env === 'production' ? '.env.production' : '.env',
|
||||
});
|
||||
|
||||
log.level = process.env.LOG_LEVEL || 'verbose';
|
||||
|
||||
const dbUrlToConfig = (dbUrl) => {
|
||||
if (!dbUrl) {
|
||||
return {};
|
||||
@@ -65,24 +67,15 @@ const redisUrlToClient = (defaultConfig, redisUrl) => {
|
||||
}));
|
||||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
// Cluster master
|
||||
const core = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1));
|
||||
const numWorkers = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1));
|
||||
|
||||
const fork = () => {
|
||||
const worker = cluster.fork();
|
||||
const startMaster = () => {
|
||||
log.info(`Starting streaming API server master with ${numWorkers} workers`);
|
||||
};
|
||||
|
||||
worker.on('exit', (code, signal) => {
|
||||
log.error(`Worker died with exit code ${code}, signal ${signal} received.`);
|
||||
setTimeout(() => fork(), 0);
|
||||
});
|
||||
};
|
||||
const startWorker = (workerId) => {
|
||||
log.info(`Starting worker ${workerId}`);
|
||||
|
||||
for (let i = 0; i < core; i++) fork();
|
||||
|
||||
log.info(`Starting streaming API server master with ${core} workers`);
|
||||
} else {
|
||||
// Cluster worker
|
||||
const pgConfigs = {
|
||||
development: {
|
||||
database: 'mastodon_development',
|
||||
@@ -130,6 +123,7 @@ if (cluster.isMaster) {
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks.forEach(callback => callback(message));
|
||||
});
|
||||
|
||||
@@ -168,7 +162,7 @@ if (cluster.isMaster) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 LIMIT 1', [token], (err, result) => {
|
||||
client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id, users.filtered_languages FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 AND oauth_access_tokens.revoked_at IS NULL LIMIT 1', [token], (err, result) => {
|
||||
done();
|
||||
|
||||
if (err) {
|
||||
@@ -185,6 +179,7 @@ if (cluster.isMaster) {
|
||||
}
|
||||
|
||||
req.accountId = result.rows[0].account_id;
|
||||
req.filteredLanguages = result.rows[0].filtered_languages;
|
||||
|
||||
next();
|
||||
});
|
||||
@@ -214,9 +209,9 @@ if (cluster.isMaster) {
|
||||
};
|
||||
|
||||
const errorMiddleware = (err, req, res, next) => {
|
||||
log.error(req.requestId, err);
|
||||
log.error(req.requestId, err.toString());
|
||||
res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: err.statusCode ? `${err}` : 'An unexpected error occurred' }));
|
||||
res.end(JSON.stringify({ error: err.statusCode ? err.toString() : 'An unexpected error occurred' }));
|
||||
};
|
||||
|
||||
const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', ');
|
||||
@@ -248,6 +243,12 @@ if (cluster.isMaster) {
|
||||
const targetAccountIds = [unpackedPayload.account.id].concat(unpackedPayload.mentions.map(item => item.id)).concat(unpackedPayload.reblog ? [unpackedPayload.reblog.account.id] : []);
|
||||
const accountDomain = unpackedPayload.account.acct.split('@')[1];
|
||||
|
||||
if (Array.isArray(req.filteredLanguages) && req.filteredLanguages.includes(unpackedPayload.language)) {
|
||||
log.silly(req.requestId, `Message ${unpackedPayload.id} filtered by language (${unpackedPayload.language})`);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
const queries = [
|
||||
client.query(`SELECT 1 FROM blocks WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)}) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 1)})`, [req.accountId].concat(targetAccountIds)),
|
||||
];
|
||||
@@ -265,6 +266,7 @@ if (cluster.isMaster) {
|
||||
|
||||
transmit();
|
||||
}).catch(err => {
|
||||
done();
|
||||
log.error(err);
|
||||
});
|
||||
});
|
||||
@@ -303,19 +305,7 @@ if (cluster.isMaster) {
|
||||
};
|
||||
|
||||
// Setup stream output to WebSockets
|
||||
const streamToWs = (req, ws) => {
|
||||
const heartbeat = setInterval(() => {
|
||||
// TODO: Can't add multiple listeners, due to the limitation of uws.
|
||||
if (ws.readyState !== ws.OPEN) {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
clearInterval(heartbeat);
|
||||
return;
|
||||
}
|
||||
|
||||
ws.ping();
|
||||
}, 15000);
|
||||
|
||||
return (event, payload) => {
|
||||
const streamToWs = (req, ws) => (event, payload) => {
|
||||
if (ws.readyState !== ws.OPEN) {
|
||||
log.error(req.requestId, 'Tried writing to closed socket');
|
||||
return;
|
||||
@@ -323,15 +313,16 @@ if (cluster.isMaster) {
|
||||
|
||||
ws.send(JSON.stringify({ event, payload }));
|
||||
};
|
||||
};
|
||||
|
||||
// Setup stream end for WebSockets
|
||||
const streamWsEnd = ws => (id, listener) => {
|
||||
const streamWsEnd = (req, ws) => (id, listener) => {
|
||||
ws.on('close', () => {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
unsubscribe(id, listener);
|
||||
});
|
||||
|
||||
ws.on('error', e => {
|
||||
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
|
||||
unsubscribe(id, listener);
|
||||
});
|
||||
};
|
||||
@@ -366,6 +357,12 @@ if (cluster.isMaster) {
|
||||
const token = location.query.access_token;
|
||||
const req = { requestId: uuid.v4() };
|
||||
|
||||
ws.isAlive = true;
|
||||
|
||||
ws.on('pong', () => {
|
||||
ws.isAlive = true;
|
||||
});
|
||||
|
||||
accountFromToken(token, req, err => {
|
||||
if (err) {
|
||||
log.error(req.requestId, err);
|
||||
@@ -375,19 +372,19 @@ if (cluster.isMaster) {
|
||||
|
||||
switch(location.query.stream) {
|
||||
case 'user':
|
||||
streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(ws));
|
||||
streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws));
|
||||
break;
|
||||
case 'public':
|
||||
streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(ws), true);
|
||||
streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||
break;
|
||||
case 'public:local':
|
||||
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(ws), true);
|
||||
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||
break;
|
||||
case 'hashtag':
|
||||
streamFrom(`timeline:hashtag:${location.query.tag}`, req, streamToWs(req, ws), streamWsEnd(ws), true);
|
||||
streamFrom(`timeline:hashtag:${location.query.tag}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||
break;
|
||||
case 'hashtag:local':
|
||||
streamFrom(`timeline:hashtag:${location.query.tag}:local`, req, streamToWs(req, ws), streamWsEnd(ws), true);
|
||||
streamFrom(`timeline:hashtag:${location.query.tag}:local`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
|
||||
break;
|
||||
default:
|
||||
ws.close();
|
||||
@@ -395,16 +392,40 @@ if (cluster.isMaster) {
|
||||
});
|
||||
});
|
||||
|
||||
const wsInterval = setInterval(() => {
|
||||
wss.clients.forEach(ws => {
|
||||
if (ws.isAlive === false) {
|
||||
ws.terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.isAlive = false;
|
||||
ws.ping('', false, true);
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
server.listen(process.env.PORT || 4000, () => {
|
||||
log.level = process.env.LOG_LEVEL || 'verbose';
|
||||
log.info(`Starting streaming API server worker on ${server.address().address}:${server.address().port}`);
|
||||
log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`);
|
||||
});
|
||||
|
||||
process.on('SIGINT', exit);
|
||||
process.on('SIGTERM', exit);
|
||||
process.on('exit', exit);
|
||||
|
||||
function exit() {
|
||||
const onExit = () => {
|
||||
log.info(`Worker ${workerId} exiting, bye bye`);
|
||||
server.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
log.error(err);
|
||||
};
|
||||
|
||||
process.on('SIGINT', onExit);
|
||||
process.on('SIGTERM', onExit);
|
||||
process.on('exit', onExit);
|
||||
process.on('error', onError);
|
||||
};
|
||||
|
||||
throng({
|
||||
workers: numWorkers,
|
||||
lifetime: Infinity,
|
||||
start: startWorker,
|
||||
master: startMaster,
|
||||
});
|
||||
|
12
yarn.lock
12
yarn.lock
@@ -5590,6 +5590,12 @@ react-test-renderer@^15.5.4:
|
||||
fbjs "^0.8.9"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react-textarea-autosize@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-5.0.6.tgz#a3742e0a319484021b4dbfa1519df287768f2133"
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-toggle@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-2.1.1.tgz#80600a64417a1acc8aaa4c1477f7fbdb88b988fb"
|
||||
@@ -6492,6 +6498,12 @@ text-table@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
|
||||
throng@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/throng/-/throng-4.0.0.tgz#983c6ba1993b58eae859998aa687ffe88df84c17"
|
||||
dependencies:
|
||||
lodash.defaults "^4.0.1"
|
||||
|
||||
through@2, through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
Reference in New Issue
Block a user