Compare commits

...

30 Commits

Author SHA1 Message Date
Eugen Rochko
8963f8c3c2 Bump version to 1.4.1 2017-05-28 19:30:38 +02:00
Nolan Lawson
5e41c26203 Use immutable list in UploadButton to avoid wasteful re-render (#3394) 2017-05-28 19:15:35 +02:00
Clworld
45837c533e Re-add stream end log for WebSocket (#3397) 2017-05-28 19:14:44 +02:00
Eugen Rochko
3fa8512474 Fix video having black border on top due to regression from #2608 (#3392)
The combination of object-fit, relative position 50% from top and translating it
back upwards 50% is what allows us to crop the video properly, so it needs to
be +50%-50%
2017-05-28 19:11:47 +02:00
ster
0e20de9f89 Ukrainian translation: fix typo (#3393) 2017-05-28 19:11:29 +02:00
Nolan Lawson
24d645b7d0 Fix IntersectionObserver isIntersecting in Edge (#3365) 2017-05-28 16:45:42 +02:00
Eugen Rochko
7b23f79d41 Bump version to 1.4.0.6 2017-05-28 16:32:53 +02:00
Eugen Rochko
3b4095cf1b Update bootsnap to 0.3.0 (fix xattr.h error) (#3390) 2017-05-28 16:32:29 +02:00
Nolan Lawson
28cbfb9f10 Simplify isIntersecting in status_list.js (#3371) 2017-05-28 16:26:35 +02:00
Akihiko Odaki
189a06d2a2 Fix Webpack Bundle Analyzer output for Webpacker (#3374)
Webpacker failed to parse output of Webpack when a module requires
non-existent module or has similar errors. This commit fixes the bug.
2017-05-28 16:26:16 +02:00
vidarlee
450441fc11 i18n: Add some tags for Chinese translation (#3379)
* Enhance for the Chinese translation

* Add filtered_languages for Chinese Translation include CN/HK/TW for PR #3175

* i18n: Add some tags for Chinese translation

* i18n: Add some tags for Chinese translation
2017-05-28 16:25:54 +02:00
Masoud Abkenar
b619362a36 Persian translation fixes and updates (#3380)
* Persian translation fix

* Persian translation of new strings
2017-05-28 16:25:45 +02:00
Eugen Rochko
425d02287a Improve streaming API cluster logging (#3370)
* Improve streaming API cluster logging

* Less verbose error middleware logging (stack trace useless there)

* Fix error logging

* Prevent potential issue

* Add missing "done()" in catch of Promise.all, websocket heartbeat re-implemented like in example

* I actually forgot a done(), the absolute madman
2017-05-28 16:25:26 +02:00
Clworld
2e429c0c25 Reject revoked access_token on Streaming API. (#3367) 2017-05-27 23:27:54 +02:00
Eugen Rochko
e0e12b0fee Bump version to 1.4.0.5 2017-05-27 16:56:47 +02:00
Eugen Rochko
62ca37884a Fix #2922 - Load stylesheet from "custom.css" entrypoint when present (#3332)
* Fix #2922 - Load stylesheet from "custom.css" entrypoint when present

This is pretty much the same way it worked as before, albeit with
having to create app/javascript/packs/custom.js with
require('../styles/custom.scss') (or whatever you want really), which
will be a blank slate for you to import whatever you want

* Remove old assets directory

* Extract font-awesome into common.css and always load it
2017-05-27 16:55:09 +02:00
jeroenpraat
f9180823bc Update Dutch strings for 1.4 (#3363)
* Update nl strings for 1.4

* Update nl strings for 1.4

* Update nl strings for 1.4

* nl strings (+1)

More new OTP strings will be translated another time
2017-05-27 16:28:32 +02:00
Quent-in
4b0c667c09 i18n update for Occitan language (#3362)
* Update simple_form.oc.yml

* Added check spam folder

* Update oc.json

abonats => seguidors

* Update oc.yml

* Update oc.json

Added translations + corrections

* Update confirmation_instructions.oc.html.erb

* Update confirmation_instructions.oc.text.erb

* i18n mailer

* Update reset_password_instructions.oc.html.erb
2017-05-27 15:50:40 +02:00
m4sk1n
1b732cad61 i18n: pl: pluralized (#3344)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2017-05-27 13:04:42 +02:00
unarist
ecef03bb15 Fix empty flash message on the settings page (#3345) 2017-05-27 13:04:28 +02:00
happycoloredbanana
9642601126 Avoid comparing domains when looking for an exact match of a local account (#3336) 2017-05-27 00:55:08 +02:00
Eugen Rochko
3836d293a1 Add missing background center on public profile headers (#3340) 2017-05-27 00:54:00 +02:00
Eugen Rochko
0734e1fe33 Language filtering in streaming API (#3339) 2017-05-27 00:53:48 +02:00
Eugen Rochko
44cb08297c Fix some nil errors (#3338)
* Fix nil input not handled well in AuthorExtractor concern

* Fix hard error in ProcessFeedService when replied-to status has been deleted

* Fix nil errors in ProcessInteractionService when favourited status
cannot be found
2017-05-27 00:53:38 +02:00
Eugen Rochko
bd21afb5ed Replace onboarding elephant with friendlier graphic, shorter animation (#3337)
on status fade-in, fix buttons not having pointer cursor
2017-05-27 00:53:25 +02:00
m4sk1n
ef80ad17b3 Updated Polish translation (#3335)
* i18n: updated Polish translation

Signed-off-by: Marcin Mikołajczak <m4sk1n@vivaldi.net>

* i18n: completed Polish translation

Signed-off-by: Marcin Mikołajczak <m4sk1n@vivaldi.net>

* i18n: corrected Polish translation

Signed-off-by: Marcin Mikołajczak <m4sk1n@vivaldi.net>

* i18n: Updated Polish translation

Signed-off-by: Marcin Mikołajczak <me@m4sk.in>

* Update simple_form.pl.yml

* Update simple_form.pl.yml

* updated Polish translation

Signed-off-by: Marcin Mikołajczak <me@m4sk.in>

* Update pl.yml
2017-05-26 19:59:46 +02:00
Eugen Rochko
9ea4f37e78 Add "meta" attribute to return of POST /api/v1/media method as well (#3333) 2017-05-26 18:22:30 +02:00
Eugen Rochko
c48772fd3f Introduce react-textarea-autosize instead of using style.height side effects (#3334) 2017-05-26 18:22:23 +02:00
Akihiko Odaki
860e257a68 Remove redundant call of recent scope in AccountsController (#3330)
recent is included in paginate_by_max_id.
2017-05-26 16:35:25 +02:00
unarist
902d9e34b4 Remove status context construction in the React side (#3331)
because it may causes flicker on the conversation when it contains blocked/muted user's status.

We use `/api/v1/statuses/{id}/context` to obtain status ids in the
conversation which filters blocked/muted user, but also uses internal
cache constructed from `in_reply_to_id` by `normalizeStatus()` in
`reducers/timelines.js` on each status loading which doesn't filter.

So statuses appears in conversation if those are cached, even those
statuses are from blocked/muted user. Then context cache will be updated
with the result of the context API and those statuses will be removed.

I have left the `normalizeStatus()` function itself which is called many
functions in the file as a placeholder for now, but maybe it should be
removed completely.
2017-05-26 16:34:08 +02:00
48 changed files with 358 additions and 220 deletions

View File

@@ -19,7 +19,7 @@ gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6' gem 'paperclip-av-transcoder', '~> 0.6'
gem 'addressable', '~> 2.5' gem 'addressable', '~> 2.5'
gem 'bootsnap' gem 'bootsnap', '~> 0.3'
gem 'cld3', '~> 3.1' gem 'cld3', '~> 3.1'
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'devise-two-factor', '~> 3.0' gem 'devise-two-factor', '~> 3.0'

View File

@@ -67,7 +67,7 @@ GEM
rack (>= 0.9.0) rack (>= 0.9.0)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (0.2.14) bootsnap (0.3.0)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (3.6.1) brakeman (3.6.1)
builder (3.2.3) builder (3.2.3)
@@ -474,7 +474,7 @@ DEPENDENCIES
aws-sdk (~> 2.9) aws-sdk (~> 2.9)
better_errors (~> 2.1) better_errors (~> 2.1)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
bootsnap bootsnap (~> 0.3)
brakeman (~> 3.6) brakeman (~> 3.6)
bullet (~> 5.5) bullet (~> 5.5)
bundler-audit (~> 0.5) bundler-audit (~> 0.5)

View File

@@ -6,12 +6,12 @@ class AccountsController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html do 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) @statuses = cache_collection(@statuses, Status)
end end
format.atom do 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)) render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
end end

View File

@@ -12,13 +12,13 @@ class Auth::SessionsController < Devise::SessionsController
def create def create
super do |resource| super do |resource|
remember_me(resource) remember_me(resource)
flash[:notice] = nil flash.delete(:notice)
end end
end end
def destroy def destroy
super super
flash[:notice] = nil flash.delete(:notice)
end end
protected protected

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isRtl } from '../rtl'; import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
const textAtCursorMatchesToken = (str, caretPosition) => { const textAtCursorMatchesToken = (str, caretPosition) => {
let word; let word;
@@ -69,10 +70,6 @@ class AutosuggestTextarea extends ImmutablePureComponent {
this.props.onSuggestionsClearRequested(); this.props.onSuggestionsClearRequested();
} }
// auto-resize textarea
e.target.style.height = 'auto';
e.target.style.height = `${e.target.scrollHeight}px`;
this.props.onChange(e); this.props.onChange(e);
} }
@@ -160,10 +157,6 @@ class AutosuggestTextarea extends ImmutablePureComponent {
} }
} }
reset () {
this.textarea.style.height = 'auto';
}
render () { render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
const { suggestionsHidden, selectedSuggestion } = this.state; const { suggestionsHidden, selectedSuggestion } = this.state;
@@ -175,8 +168,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
return ( return (
<div className='autosuggest-textarea'> <div className='autosuggest-textarea'>
<textarea <Textarea
ref={this.setTextarea} inputRef={this.setTextarea}
className='autosuggest-textarea__textarea' className='autosuggest-textarea__textarea'
disabled={disabled} disabled={disabled}
placeholder={placeholder} placeholder={placeholder}

View File

@@ -27,7 +27,8 @@ class StatusList extends ImmutablePureComponent {
}; };
state = { state = {
isIntersecting: [{ }], isIntersecting: {},
intersectionCount: 0,
} }
statusRefQueue = [] statusRefQueue = []
@@ -65,15 +66,33 @@ class StatusList extends ImmutablePureComponent {
attachIntersectionObserver () { attachIntersectionObserver () {
const onIntersection = (entries) => { const onIntersection = (entries) => {
this.setState(state => { this.setState(state => {
const isIntersecting = { };
entries.forEach(entry => { entries.forEach(entry => {
const statusId = entry.target.getAttribute('data-id'); 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 () { render () {
const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props; 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 loadMore = null;
let scrollableArea = null; let scrollableArea = null;

View File

@@ -67,7 +67,6 @@ class ComposeForm extends ImmutablePureComponent {
} }
handleSubmit = () => { handleSubmit = () => {
this.autosuggestTextarea.reset();
this.props.onSubmit(); this.props.onSubmit();
} }

View File

@@ -3,6 +3,8 @@ import IconButton from '../../../components/icon_button';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
const messages = defineMessages({ const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media' }, upload: { id: 'upload_button.label', defaultMessage: 'Add media' },
@@ -10,7 +12,7 @@ const messages = defineMessages({
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray(), acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
}); });
return mapStateToProps; return mapStateToProps;
@@ -21,14 +23,14 @@ const iconStyle = {
lineHeight: '27px', lineHeight: '27px',
}; };
class UploadButton extends React.PureComponent { class UploadButton extends ImmutablePureComponent {
static propTypes = { static propTypes = {
disabled: PropTypes.bool, disabled: PropTypes.bool,
onSelectFile: PropTypes.func.isRequired, onSelectFile: PropTypes.func.isRequired,
style: PropTypes.object, style: PropTypes.object,
resetFileKey: PropTypes.number, resetFileKey: PropTypes.number,
acceptContentTypes: PropTypes.arrayOf(PropTypes.string).isRequired, acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@@ -58,7 +60,7 @@ class UploadButton extends React.PureComponent {
ref={this.setRef} ref={this.setRef}
type='file' type='file'
multiple={false} multiple={false}
accept={ acceptContentTypes.join(',')} accept={ acceptContentTypes.toArray().join(',')}
onChange={this.handleChange} onChange={this.handleChange}
disabled={disabled} disabled={disabled}
style={{ display: 'none' }} style={{ display: 'none' }}

View File

@@ -7,7 +7,7 @@
"account.followers": "پیگیران", "account.followers": "پیگیران",
"account.follows": "پی می‌گیرد", "account.follows": "پی می‌گیرد",
"account.follows_you": "پیگیر شماست", "account.follows_you": "پیگیر شماست",
"account.media": "Media", "account.media": "رسانه",
"account.mention": "نام‌بردن از @{name}", "account.mention": "نام‌بردن از @{name}",
"account.mute": "بی‌صدا کردن @{name}", "account.mute": "بی‌صدا کردن @{name}",
"account.posts": "نوشته‌ها", "account.posts": "نوشته‌ها",
@@ -92,10 +92,10 @@
"navigation_bar.mutes": "کاربران بی‌صداشده", "navigation_bar.mutes": "کاربران بی‌صداشده",
"navigation_bar.preferences": "ترجیحات", "navigation_bar.preferences": "ترجیحات",
"navigation_bar.public_timeline": "نوشته‌های همه‌جا", "navigation_bar.public_timeline": "نوشته‌های همه‌جا",
"notification.favourite": "{name} نوشتهٔ شما را پسندید", "notification.favourite": "{name} نوشتهٔ شما را پسندید",
"notification.follow": "{name} پیگیر شما شد", "notification.follow": "{name} پیگیر شما شد",
"notification.mention": "{name} از شما نام برد", "notification.mention": "{name} از شما نام برد",
"notification.reblog": "{name} نوشتهٔ شما را بازبوقید", "notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
"notifications.clear": "پاک‌کردن اعلان‌ها", "notifications.clear": "پاک‌کردن اعلان‌ها",
"notifications.clear_confirmation": "واقعاً می‌خواهید همهٔ اعلان‌هایتان را برای همیشه پاک کنید؟", "notifications.clear_confirmation": "واقعاً می‌خواهید همهٔ اعلان‌هایتان را برای همیشه پاک کنید؟",
"notifications.column_settings.alert": "اعلان در کامپیوتر", "notifications.column_settings.alert": "اعلان در کامپیوتر",
@@ -123,7 +123,7 @@
"onboarding.page_six.read_guidelines": "لطفاً {guidelines} {domain} را بخوانید!", "onboarding.page_six.read_guidelines": "لطفاً {guidelines} {domain} را بخوانید!",
"onboarding.page_six.various_app": "اپ‌های موبایل", "onboarding.page_six.various_app": "اپ‌های موبایل",
"onboarding.page_three.profile": "با ویرایش نمایه می‌توانید تصویر نمایه، نوشتهٔ معرفی، و نام نمایشی خود را تغییر دهید. ترجیحات دیگر شما هم آن‌جاست.", "onboarding.page_three.profile": "با ویرایش نمایه می‌توانید تصویر نمایه، نوشتهٔ معرفی، و نام نمایشی خود را تغییر دهید. ترجیحات دیگر شما هم آن‌جاست.",
"onboarding.page_three.search": "در نوار جستجو می‌توانید کاربران دیگر را بیابید یا هشتگ‌ها را ببینید، مانند {نقاشی} یا {معرفی}. برای یافتن افرادی که روی سرورهای دیگر هستند، شناسهٔ کامل آن‌ها را بنویسید.", "onboarding.page_three.search": "در نوار جستجو می‌توانید کاربران دیگر را بیابید یا هشتگ‌ها را ببینید، مانند {illustration} یا {introductions}. برای یافتن افرادی که روی سرورهای دیگر هستند، شناسهٔ کامل آن‌ها را بنویسید.",
"onboarding.page_two.compose": "در ستون «نوشتن» می‌توانید نوشته‌های تازه بنویسید. همچنین با دکمه‌های زیرش می‌توانید تصویر اضافه کنید، حریم خصوصی نوشته را تنظیم کنید، و هشدار محتوا بگذارید.", "onboarding.page_two.compose": "در ستون «نوشتن» می‌توانید نوشته‌های تازه بنویسید. همچنین با دکمه‌های زیرش می‌توانید تصویر اضافه کنید، حریم خصوصی نوشته را تنظیم کنید، و هشدار محتوا بگذارید.",
"onboarding.skip": "رد کن", "onboarding.skip": "رد کن",
"privacy.change": "تنظیم حریم خصوصی نوشته‌ها", "privacy.change": "تنظیم حریم خصوصی نوشته‌ها",
@@ -151,7 +151,7 @@
"status.mute_conversation": "بی‌صداکردن گفتگو", "status.mute_conversation": "بی‌صداکردن گفتگو",
"status.open": "این نوشته را باز کن", "status.open": "این نوشته را باز کن",
"status.reblog": "بازبوقیدن", "status.reblog": "بازبوقیدن",
"status.reblogged_by": "{name} بازبوقید", "status.reblogged_by": "{name} بازبوقید",
"status.reply": "پاسخ", "status.reply": "پاسخ",
"status.replyAll": "به نوشته پاسخ دهید", "status.replyAll": "به نوشته پاسخ دهید",
"status.report": "گزارش دادن @{name}", "status.report": "گزارش دادن @{name}",

View File

@@ -1,6 +1,6 @@
{ {
"account.block": "Blokkeer @{name}", "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.disclaimer": "Deze gebruiker zit op een andere server. Dit getal kan hoger zijn.",
"account.edit_profile": "Profiel bewerken", "account.edit_profile": "Profiel bewerken",
"account.follow": "Volgen", "account.follow": "Volgen",
@@ -14,9 +14,9 @@
"account.report": "Rapporteer @{name}", "account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring", "account.requested": "Wacht op goedkeuring",
"account.unblock": "Deblokkeer @{name}", "account.unblock": "Deblokkeer @{name}",
"account.unblock_domain": "Unhide {domain}", "account.unblock_domain": "{domain} niet meer negeren",
"account.unfollow": "Ontvolgen", "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", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
"column.blocks": "Geblokkeerde gebruikers", "column.blocks": "Geblokkeerde gebruikers",
"column.community": "Lokale tijdlijn", "column.community": "Lokale tijdlijn",
@@ -43,8 +43,8 @@
"confirmations.block.message": "Weet je zeker dat je {name} wilt blokkeren?", "confirmations.block.message": "Weet je zeker dat je {name} wilt blokkeren?",
"confirmations.delete.confirm": "Verwijderen", "confirmations.delete.confirm": "Verwijderen",
"confirmations.delete.message": "Weet je zeker dat je deze toot wilt verwijderen?", "confirmations.delete.message": "Weet je zeker dat je deze toot wilt verwijderen?",
"confirmations.domain_block.confirm": "Hide entire domain", "confirmations.domain_block.confirm": "Negeer alles van deze server",
"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.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.confirm": "Negeren",
"confirmations.mute.message": "Weet je zeker dat je {name} wilt negeren?", "confirmations.mute.message": "Weet je zeker dat je {name} wilt negeren?",
"emoji_button.activity": "Activiteiten", "emoji_button.activity": "Activiteiten",
@@ -148,7 +148,7 @@
"status.load_more": "Meer laden", "status.load_more": "Meer laden",
"status.media_hidden": "Media verborgen", "status.media_hidden": "Media verborgen",
"status.mention": "Vermeld @{name}", "status.mention": "Vermeld @{name}",
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Negeer conversatie",
"status.open": "Toot volledig tonen", "status.open": "Toot volledig tonen",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblogged_by": "{name} boostte", "status.reblogged_by": "{name} boostte",
@@ -159,7 +159,7 @@
"status.sensitive_warning": "Gevoelige inhoud", "status.sensitive_warning": "Gevoelige inhoud",
"status.show_less": "Minder tonen", "status.show_less": "Minder tonen",
"status.show_more": "Meer tonen", "status.show_more": "Meer tonen",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Conversatie niet meer negeren",
"tabs_bar.compose": "Schrijven", "tabs_bar.compose": "Schrijven",
"tabs_bar.federated_timeline": "Globaal", "tabs_bar.federated_timeline": "Globaal",
"tabs_bar.home": "Jouw tijdlijn", "tabs_bar.home": "Jouw tijdlijn",

View File

@@ -1,25 +1,25 @@
{ {
"account.block": "Blocar", "account.block": "Blocar @{name}",
"account.block_domain": "Hide everything from {domain}", "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.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
"account.edit_profile": "Modificar lo perfil", "account.edit_profile": "Modificar lo perfil",
"account.follow": "Sègre", "account.follow": "Sègre",
"account.followers": "Abonats", "account.followers": "Seguidors",
"account.follows": "Abonaments", "account.follows": "Abonaments",
"account.follows_you": "Vos sèc", "account.follows_you": "Vos sèc",
"account.media": "Media", "account.media": "Mèdias",
"account.mention": "Mencionar", "account.mention": "Mencionar @{name}",
"account.mute": "Rescondre", "account.mute": "Rescondre @{name}",
"account.posts": "Estatuts", "account.posts": "Estatuts",
"account.report": "Senhalar", "account.report": "Senhalar @{name}",
"account.requested": "Invitacion mandada", "account.requested": "Invitacion mandada",
"account.unblock": "Desblocar", "account.unblock": "Desblocar @{name}",
"account.unblock_domain": "Unhide {domain}", "account.unblock_domain": "Desblocar {domain}",
"account.unfollow": "Quitar de sègre", "account.unfollow": "Quitar de sègre",
"account.unmute": "Quitar de rescondre", "account.unmute": "Quitar de rescondre @{name}",
"boost_modal.combo": "Podètz butar {combo} per passar aquò lo còp que ven", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
"column.blocks": "Personas blocadas", "column.blocks": "Personas blocadas",
"column.community": "Fil public local", "column.community": "Flux dactualitat public local",
"column.favourites": "Favorits", "column.favourites": "Favorits",
"column.follow_requests": "Demandas dabonament", "column.follow_requests": "Demandas dabonament",
"column.home": "Acuèlh", "column.home": "Acuèlh",
@@ -29,7 +29,7 @@
"column_back_button.label": "Tornar", "column_back_button.label": "Tornar",
"column_subheading.navigation": "Navigacion", "column_subheading.navigation": "Navigacion",
"column_subheading.settings": "Paramètres", "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.lock_disclaimer.lock": "clavat",
"compose_form.placeholder": "A de qué pensatz ?", "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 daqueste{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 dindicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists", "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz daqueste{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 dindicacion 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", "compose_form.spoiler_placeholder": "Avertiment",
"confirmation_modal.cancel": "Anullar", "confirmation_modal.cancel": "Anullar",
"confirmations.block.confirm": "Blocar", "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.confirm": "Suprimir",
"confirmations.delete.message": "Sètz segur de voler suprimir lestatut ?", "confirmations.delete.message": "Sètz segur de voler suprimir lestatut ?",
"confirmations.domain_block.confirm": "Hide entire domain", "confirmations.domain_block.confirm": "Amagar tot lo domeni",
"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.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.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.activity": "Activitat",
"emoji_button.flags": "Drapèus", "emoji_button.flags": "Drapèus",
"emoji_button.food": "Manjar e beure", "emoji_button.food": "Manjar e beure",
@@ -57,13 +57,13 @@
"emoji_button.search": "Cercar...", "emoji_button.search": "Cercar...",
"emoji_button.symbols": "Simbòls", "emoji_button.symbols": "Simbòls",
"emoji_button.travel": "Viatges & lòcs", "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.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 dautras personas.", "empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a dautras personas.",
"empty_column.home.inactivity": "Vòstra pagina dacuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.", "empty_column.home.inactivity": "Vòstra pagina dacuè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 qualquun per començar una conversacion.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualquun per començar una conversacion.",
"empty_column.public": "I a pas res aquí ! Escribètz quicòm de public, o seguètz de personas dautras 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 dautras instàncias per garnir lo flux public.",
"follow_request.authorize": "Autorizar", "follow_request.authorize": "Autorizar",
"follow_request.reject": "Regetar", "follow_request.reject": "Regetar",
"getting_started.appsshort": "Apps", "getting_started.appsshort": "Apps",
@@ -83,16 +83,16 @@
"media_gallery.toggle_visible": "Modificar la visibilitat", "media_gallery.toggle_visible": "Modificar la visibilitat",
"missing_indicator.label": "Pas trobat", "missing_indicator.label": "Pas trobat",
"navigation_bar.blocks": "Personas blocadas", "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.edit_profile": "Modificar lo perfil",
"navigation_bar.favourites": "Favorits", "navigation_bar.favourites": "Favorits",
"navigation_bar.follow_requests": "Demandas d'abonament", "navigation_bar.follow_requests": "Demandas d'abonament",
"navigation_bar.info": "Mai informacions", "navigation_bar.info": "Mai informacions",
"navigation_bar.logout": "Desconnexion", "navigation_bar.logout": "Desconnexion",
"navigation_bar.mutes": "Muted users", "navigation_bar.mutes": "Personas rescondudas",
"navigation_bar.preferences": "Preferéncias", "navigation_bar.preferences": "Preferéncias",
"navigation_bar.public_timeline": "Fil public global", "navigation_bar.public_timeline": "Flux public global",
"notification.favourite": "{name} a apondut a sos favorits :", "notification.favourite": "{name} a ajustat a sos favorits :",
"notification.follow": "{name} vos sèc.", "notification.follow": "{name} vos sèc.",
"notification.mention": "{name} vos a mencionat :", "notification.mention": "{name} vos a mencionat :",
"notification.reblog": "{name} a partejat vòstre estatut :", "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.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
"notifications.column_settings.alert": "Notificacions localas", "notifications.column_settings.alert": "Notificacions localas",
"notifications.column_settings.favourite": "Favorits :", "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.mention": "Mencions :",
"notifications.column_settings.reblog": "Partatges :", "notifications.column_settings.reblog": "Partatges :",
"notifications.column_settings.show": "Mostrar dins la colomna", "notifications.column_settings.show": "Mostrar dins la colomna",
"notifications.column_settings.sound": "Emetre un son", "notifications.column_settings.sound": "Emetre un son",
"notifications.settings": "Paramètres de la colomna", "notifications.settings": "Paramètres de la colomna",
"onboarding.done": "Done", "onboarding.done": "Fach",
"onboarding.next": "Next", "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_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 dacuèlh mòstra los estatuts del mond que seguètz.", "onboarding.page_four.home": "Lo flux dacuèlh mòstra los estatuts del mond que seguètz.",
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualquun enteragís amb vos", "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualquun enteragís amb vos",
@@ -129,11 +129,11 @@
"privacy.change": "Ajustar la confidencialitat del messatge", "privacy.change": "Ajustar la confidencialitat del messatge",
"privacy.direct.long": "Mostrar pas qua las personas mencionadas", "privacy.direct.long": "Mostrar pas qua las personas mencionadas",
"privacy.direct.short": "Dirècte", "privacy.direct.short": "Dirècte",
"privacy.private.long": "Mostrar pas qua vòstres abonats", "privacy.private.long": "Mostrar pas qua vòstres seguidors",
"privacy.private.short": "Privat", "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.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", "privacy.unlisted.short": "Pas-listat",
"reply_indicator.cancel": "Anullar", "reply_indicator.cancel": "Anullar",
"report.heading": "Nòu senhalament", "report.heading": "Nòu senhalament",
@@ -153,10 +153,10 @@
"status.reblog": "Partejar", "status.reblog": "Partejar",
"status.reblogged_by": "{name} a partejat :", "status.reblogged_by": "{name} a partejat :",
"status.reply": "Respondre", "status.reply": "Respondre",
"status.replyAll": "Reply to thread", "status.replyAll": "Respondre a la conversacion",
"status.report": "Senhalar @{name}", "status.report": "Senhalar @{name}",
"status.sensitive_toggle": "Clicar per mostrar", "status.sensitive_toggle": "Clicar per mostrar",
"status.sensitive_warning": "Contengut embarrassant", "status.sensitive_warning": "Contengut sensible",
"status.show_less": "Tornar plegar", "status.show_less": "Tornar plegar",
"status.show_more": "Desplegar", "status.show_more": "Desplegar",
"status.unmute_conversation": "Conversacions amb silenci levat", "status.unmute_conversation": "Conversacions amb silenci levat",
@@ -166,11 +166,11 @@
"tabs_bar.local_timeline": "Flux public local", "tabs_bar.local_timeline": "Flux public local",
"tabs_bar.notifications": "Notificacions", "tabs_bar.notifications": "Notificacions",
"upload_area.title": "Lisatz e depausatz per mandar", "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_form.undo": "Anullar",
"upload_progress.label": "Mandadís…", "upload_progress.label": "Mandadís…",
"video_player.expand": "Mostrar la vidèo", "video_player.expand": "Mostrar la vidèo",
"video_player.toggle_sound": "Activar/Desactivar lo son", "video_player.toggle_sound": "Activar/Desactivar lo son",
"video_player.toggle_visible": "Mostrar/Rescondre la vidèo", "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"
} }

View File

@@ -1,6 +1,6 @@
{ {
"account.block": "Blokuj @{name}", "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.disclaimer": "Ten użytkownik pochodzi z innej instancji. Ta liczba może być większa.",
"account.edit_profile": "Edytuj profil", "account.edit_profile": "Edytuj profil",
"account.follow": "Obserwuj", "account.follow": "Obserwuj",
@@ -14,7 +14,7 @@
"account.report": "Zgłoś @{name}", "account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba", "account.requested": "Oczekująca prośba",
"account.unblock": "Odblokuj @{name}", "account.unblock": "Odblokuj @{name}",
"account.unblock_domain": "Unhide {domain}", "account.unblock_domain": "Odblokuj domenę {domain}",
"account.unfollow": "Przestań obserwować", "account.unfollow": "Przestań obserwować",
"account.unmute": "Cofnij wyciszenie @{name}", "account.unmute": "Cofnij wyciszenie @{name}",
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", "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.block.message": "Czy na pewno chcesz zablokować {name}?",
"confirmations.delete.confirm": "Usuń", "confirmations.delete.confirm": "Usuń",
"confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?", "confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?",
"confirmations.domain_block.confirm": "Hide entire domain", "confirmations.domain_block.confirm": "Ukryj wszysyko z domeny",
"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.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.confirm": "Wycisz",
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?", "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
"emoji_button.activity": "Aktywność", "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.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.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": "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.home.public_timeline": "publiczna oś czasu",
"empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "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ć.", "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.authorize": "Autoryzuj",
"follow_request.reject": "Odrzuć", "follow_request.reject": "Odrzuć",
"getting_started.appsshort": "Apps", "getting_started.appsshort": "Aplikacje",
"getting_started.faq": "FAQ", "getting_started.faq": "FAQ",
"getting_started.heading": "Dowiedz się", "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.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.support": "{faq} • {userguide} • {apps}",
"getting_started.userguide": "User Guide", "getting_started.userguide": "Podręcznik użytkownika",
"home.column_settings.advanced": "Zaawansowane", "home.column_settings.advanced": "Zaawansowane",
"home.column_settings.basic": "Podstawowe", "home.column_settings.basic": "Podstawowe",
"home.column_settings.filter_regex": "Filtruj z użyciem wyrażeń regularnych", "home.column_settings.filter_regex": "Filtruj z użyciem wyrażeń regularnych",
@@ -148,7 +148,7 @@
"status.load_more": "Załaduj więcej", "status.load_more": "Załaduj więcej",
"status.media_hidden": "Zawartość multimedialna ukryta", "status.media_hidden": "Zawartość multimedialna ukryta",
"status.mention": "Wspomnij o @{name}", "status.mention": "Wspomnij o @{name}",
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Wycisz konwersację",
"status.open": "Rozszerz ten status", "status.open": "Rozszerz ten status",
"status.reblog": "Podbij", "status.reblog": "Podbij",
"status.reblogged_by": "{name} podbił", "status.reblogged_by": "{name} podbił",
@@ -159,7 +159,7 @@
"status.sensitive_warning": "Wrażliwa zawartość", "status.sensitive_warning": "Wrażliwa zawartość",
"status.show_less": "Pokaż mniej", "status.show_less": "Pokaż mniej",
"status.show_more": "Pokaż więcej", "status.show_more": "Pokaż więcej",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Cofnij wyciezenie konwersacji",
"tabs_bar.compose": "Napisz", "tabs_bar.compose": "Napisz",
"tabs_bar.federated_timeline": "Globalne", "tabs_bar.federated_timeline": "Globalne",
"tabs_bar.home": "Strona główna", "tabs_bar.home": "Strona główna",

View File

@@ -29,7 +29,7 @@
"column_back_button.label": "Назад", "column_back_button.label": "Назад",
"column_subheading.navigation": "Навігація", "column_subheading.navigation": "Навігація",
"column_subheading.settings": "Налаштування", "column_subheading.settings": "Налаштування",
"compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні посты.", "compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.",
"compose_form.lock_disclaimer.lock": "приватний", "compose_form.lock_disclaimer.lock": "приватний",
"compose_form.placeholder": "Що у Вас на думці?", "compose_form.placeholder": "Що у Вас на думці?",
"compose_form.privacy_disclaimer": "Ваш приватний допис буде доставлено до згаданих користувачів на доменах {domains}. Ви довіряєте {domainsCount, plural, one {цьому серверу} other {цим серверам}}? Приватність постів працює тільки на інстанціях Mastodon. Якщо {domains} {domainsCount, plural, one {не є інстанцією Mastodon} other {не є інстанціями Mastodon}}, приватність поста не буде активована, та він може бути передмухнутий або іншим чином показаний не позначеним Вами користувачам.", "compose_form.privacy_disclaimer": "Ваш приватний допис буде доставлено до згаданих користувачів на доменах {domains}. Ви довіряєте {domainsCount, plural, one {цьому серверу} other {цим серверам}}? Приватність постів працює тільки на інстанціях Mastodon. Якщо {domains} {domainsCount, plural, one {не є інстанцією Mastodon} other {не є інстанціями Mastodon}}, приватність поста не буде активована, та він може бути передмухнутий або іншим чином показаний не позначеним Вами користувачам.",

View File

@@ -1,8 +1,5 @@
const perf = require('./performance'); const perf = require('./performance');
// allow override variables here
require.context('../../assets/stylesheets/', false, /variables.*\.scss$/);
// import default stylesheet with variables // import default stylesheet with variables
require('font-awesome/css/font-awesome.css'); require('font-awesome/css/font-awesome.css');
require('../styles/application.scss'); require('../styles/application.scss');
@@ -23,9 +20,6 @@ function main() {
require.context('../images/', true); require.context('../images/', true);
// import customization styles
require.context('../../assets/stylesheets/', false, /custom.*\.scss$/);
onDomContentLoaded(() => { onDomContentLoaded(() => {
const mountNode = document.getElementById('mastodon'); const mountNode = document.getElementById('mastodon');
const props = JSON.parse(mountNode.getAttribute('data-props')); const props = JSON.parse(mountNode.getAttribute('data-props'));

View File

@@ -91,19 +91,6 @@ const initialState = Immutable.Map({
}); });
const normalizeStatus = (state, status) => { 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; return state;
}; };

View File

@@ -1,6 +1,7 @@
.card { .card {
background: $ui-base-color; background: $ui-base-color;
background-size: cover; background-size: cover;
background-position: center;
padding: 60px 0; padding: 60px 0;
padding-bottom: 0; padding-bottom: 0;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;

View File

@@ -47,10 +47,11 @@ body {
button { button {
font-family: inherit; font-family: inherit;
} cursor: pointer;
button:focus { &:focus {
outline: none; outline: none;
}
} }
.app-holder { .app-holder {

View File

@@ -561,7 +561,7 @@
} }
opacity: 1; opacity: 1;
animation: fade 0.3s linear; animation: fade 150ms linear;
&.status-direct { &.status-direct {
background: lighten($ui-base-color, 8%); background: lighten($ui-base-color, 8%);
@@ -3013,13 +3013,14 @@ button.icon-button.active i.fa-retweet {
.onboarding-modal__page-one { .onboarding-modal__page-one {
display: flex; display: flex;
align-items: center;
} }
.onboarding-modal__page-one__elephant-friend { .onboarding-modal__page-one__elephant-friend {
background: url('../images/elephant-friend.png') no-repeat center center / contain; background: url('../images/elephant-friend-1.png') no-repeat center center / contain;
width: 147px; width: 155px;
height: 160px; height: 193px;
margin-right: 10px; margin-right: 15px;
} }
@media screen and (max-width: 400px) { @media screen and (max-width: 400px) {
@@ -3394,7 +3395,7 @@ button.icon-button.active i.fa-retweet {
object-fit: cover; object-fit: cover;
position: relative; position: relative;
top: 50%; top: 50%;
transform: translateY(-35%); transform: translateY(-50%);
width: 100%; width: 100%;
z-index: 1; z-index: 1;
} }

View File

@@ -59,7 +59,13 @@ class AccountSearchService < BaseService
end end
def exact_match 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 end
def search_results def search_results

View File

@@ -2,6 +2,8 @@
module AuthorExtractor module AuthorExtractor
def author_from_xml(xml) def author_from_xml(xml)
return nil if xml.nil?
# Try <email> for acct # Try <email> for acct
acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: TagManager::XMLNS)&.content acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: TagManager::XMLNS)&.content

View File

@@ -189,7 +189,7 @@ class ProcessFeedService < BaseService
def find_status(uri) def find_status(uri)
if TagManager.instance.local_id?(uri) if TagManager.instance.local_id?(uri)
local_id = TagManager.instance.unique_tag_to_local_id(uri, 'Status') local_id = TagManager.instance.unique_tag_to_local_id(uri, 'Status')
return Status.find(local_id) return Status.find_by(id: local_id)
end end
Status.find_by(uri: uri) Status.find_by(uri: uri)

View File

@@ -108,12 +108,18 @@ class ProcessInteractionService < BaseService
def favourite!(xml, from_account) def favourite!(xml, from_account)
current_status = status(xml) current_status = status(xml)
return if current_status.nil?
favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account) favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
NotifyService.new.call(current_status.account, favourite) NotifyService.new.call(current_status.account, favourite)
end end
def unfavourite!(xml, from_account) def unfavourite!(xml, from_account)
current_status = status(xml) current_status = status(xml)
return if current_status.nil?
favourite = current_status.favourites.where(account: from_account).first favourite = current_status.favourites.where(account: from_account).first
favourite&.destroy favourite&.destroy
end end

View File

@@ -1,5 +1,7 @@
object @media object @media
attribute :id, :type attribute :id, :type
node(:url) { |media| full_asset_url(media.file.url(:original)) }
node(:url) { |media| full_asset_url(media.file.url(:original)) }
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } node(:preview_url) { |media| full_asset_url(media.file.url(:small)) }
node(:text_url) { |media| medium_url(media) } node(:text_url) { |media| medium_url(media) }
node(:meta) { |media| media.file.meta }

View File

@@ -3,4 +3,4 @@ attributes :id, :remote_url, :type
node(:url) { |media| full_asset_url(media.file.url(:original)) } node(:url) { |media| full_asset_url(media.file.url(:original)) }
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } node(:preview_url) { |media| full_asset_url(media.file.url(:small)) }
node(:text_url) { |media| media.local? ? medium_url(media) : nil } node(:text_url) { |media| media.local? ? medium_url(media) : nil }
node(:meta) { |media| media.file.meta } node(:meta) { |media| media.file.meta }

View File

@@ -18,7 +18,8 @@
= ' - ' = ' - '
= title = 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 'common', integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags = csrf_meta_tags

View File

@@ -7,7 +7,7 @@
<p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de laisina.</p> <p>Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de laisina.</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> <p>Amistosament,</p>

View File

@@ -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 laisina. Aprèp vòstra primièra connexion, poiretz accedir a la documentacion de laisina.
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, Amistosament,

View File

@@ -1,6 +1,6 @@
<p>Bonjorn <%= @resource.email %>&nbsp;!</p> <p>Bonjorn <%= @resource.email %>&nbsp;!</p>
<p>Qualquun 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>Qualquun 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> <p><%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %></p>

View File

@@ -1,6 +1,6 @@
Bonjorn <%= @resource.email %>&nbsp;! Bonjorn <%= @resource.email %>&nbsp;!
Qualquun a demandat una reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion ne clicant sul ligam çai-jos.</p> Qualquun 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) %> <%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %>

View File

@@ -3,8 +3,8 @@ oc:
devise: devise:
confirmations: confirmations:
confirmed: Vòstra adreça de corrièl es ben estada validada. 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_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. 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: failure:
already_authenticated: Sètz ja connectat. already_authenticated: Sètz ja connectat.
inactive: Vòstre compte es pas encara activat. inactive: Vòstre compte es pas encara activat.
@@ -29,8 +29,8 @@ oc:
success: Sètz ben autentificat dempuèi lo compte %{kind}. success: Sètz ben autentificat dempuèi lo compte %{kind}.
passwords: passwords:
no_token: Podètz pas accedir a aquesta pagina sens venir dun corriel de reïnicializacion de senhal. Ses lo cas, mercés de verificar quavètz ben utilizat lURL donada de manièra complèta. no_token: Podètz pas accedir a aquesta pagina sens venir dun corriel de reïnicializacion de senhal. Ses lo cas, mercés de verificar quavètz ben utilizat lURL 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_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. 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: Vòstre senhal es ben estat cambiat. Sètz ara connectat.
updated_not_active: Vòstre senhal es ben estat cambiat. updated_not_active: Vòstre senhal es ben estat cambiat.
registrations: registrations:
@@ -38,16 +38,16 @@ oc:
signed_up: Benvengut ! Sètz ben marcat al malhum. 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_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_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. 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. 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. updated: Vòstre compte es estat mes a jorn amb succès.
sessions: sessions:
already_signed_out: Desconnectat amb succès. already_signed_out: Desconnectat amb succès.
signed_in: Connectat amb succès. signed_in: Connectat amb succès.
signed_out: Desconnectat amb succès. signed_out: Desconnectat amb succès.
unlocks: unlocks:
send_instructions: 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. 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. unlocked: Vòstre compte es estat desblocat amb succès. Mercés de vos connectar per contunhar.
errors: errors:
messages: messages:

View File

@@ -145,7 +145,7 @@ fa:
id: شناسه id: شناسه
mark_as_resolved: علامت‌گذاری به عنوان حل‌شده mark_as_resolved: علامت‌گذاری به عنوان حل‌شده
report: 'گزارش #%{id}' report: 'گزارش #%{id}'
report_contents: Contents report_contents: محتوا
reported_account: حساب گزارش‌شده reported_account: حساب گزارش‌شده
reported_by: گزارش از طرف reported_by: گزارش از طرف
resolved: حل‌شده resolved: حل‌شده
@@ -322,7 +322,7 @@ fa:
sensitive_content: محتوای حساس sensitive_content: محتوای حساس
time: time:
formats: formats:
default: "%b %d, %Y, %H:%M" default: "%d %b %Y, %H:%M"
two_factor_authentication: two_factor_authentication:
code_hint: برای تأیید، کدی را که برنامهٔ تأییدکننده ساخته است وارد کنید code_hint: برای تأیید، کدی را که برنامهٔ تأییدکننده ساخته است وارد کنید
description_html: اگر <strong>ورود دومرحله‌ای</strong> را فعال کنید، برای ورود به سیستم به تلفن خود نیاز خواهید داشت تا برایتان یک کد موقتی بسازد description_html: اگر <strong>ورود دومرحله‌ای</strong> را فعال کنید، برای ورود به سیستم به تلفن خود نیاز خواهید داشت تا برایتان یک کد موقتی بسازد

View File

@@ -222,6 +222,7 @@ nl:
disable: Uitschakelen disable: Uitschakelen
enable: Inschakelen 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." 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: users:
invalid_email: E-mailadres is ongeldig invalid_email: E-mailadres is ongeldig
invalid_otp_token: Ongeldige tweestaps-aanmeldcode invalid_otp_token: Ongeldige tweestaps-aanmeldcode

View File

@@ -18,7 +18,7 @@ oc:
ethics: 'Ethical design: pas cap de reclama o traçador' ethics: 'Ethical design: pas cap de reclama o traçador'
gifv: Partatge de GIFs e vidèos cortas gifv: Partatge de GIFs e vidèos cortas
privacy: Nivèl de confidencialitat configurable per cada publicacion privacy: Nivèl de confidencialitat configurable per cada publicacion
public: Fluxes dactualitat publicsPublic timelines public: Fluxes dactualitat publics
features_headline: Çò que fa que Mastodon es diferent features_headline: Çò que fa que Mastodon es diferent
get_started: Venètz al malhum get_started: Venètz al malhum
links: Ligams links: Ligams
@@ -32,7 +32,7 @@ oc:
version: Version version: Version
accounts: accounts:
follow: Sègre follow: Sègre
followers: Abonats followers: Seguidors
following: Abonaments following: Abonaments
nothing_here: I a pas res aquí ! nothing_here: I a pas res aquí !
people_followed_by: Lo mond que %{name} sèc people_followed_by: Lo mond que %{name} sèc
@@ -60,7 +60,7 @@ oc:
edit: Modificar edit: Modificar
email: Corrièl email: Corrièl
feed_url: Flux URL feed_url: Flux URL
followers: Abonats followers: Seguidors
follows: Abonaments follows: Abonaments
ip: IP ip: IP
location: location:
@@ -76,7 +76,7 @@ oc:
title: Moderacion title: Moderacion
most_recent_activity: Activitat mai recenta most_recent_activity: Activitat mai recenta
most_recent_ip: IP mai recenta most_recent_ip: IP mai recenta
not_subscribed: Pas abonat not_subscribed: Pas seguidor
order: order:
alphabetic: Alfabetic alphabetic: Alfabetic
most_recent: Mai recent most_recent: Mai recent
@@ -87,7 +87,7 @@ oc:
push_subscription_expires: Fin de labonament PuSH push_subscription_expires: Fin de labonament PuSH
reset: Reïnicializar reset: Reïnicializar
reset_password: Reïnicializar lo senhal reset_password: Reïnicializar lo senhal
salmon_url: Salmon URL salmon_url: URL Salmon
search: Cercar search: Cercar
show: show:
created_reports: Rapòrts creat per aqueste compte created_reports: Rapòrts creat per aqueste compte
@@ -109,7 +109,7 @@ oc:
create: Crear blocatge create: Crear blocatge
hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes. hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
severity: 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 silence: Silenci
suspend: Suspendre suspend: Suspendre
title: Nòu blocatge domeni title: Nòu blocatge domeni
@@ -160,6 +160,10 @@ oc:
title: Senhalament title: Senhalament
unresolved: Pas resolguts unresolved: Pas resolguts
view: Veire view: Veire
nsfw:
'true': Contengut sensible activat
'false': Sens contengut sensible
are_you_sure: Es segur ?
settings: settings:
contact_information: contact_information:
email: Picatz una adreça de corrièl email: Picatz una adreça de corrièl
@@ -240,15 +244,15 @@ oc:
storage: Mèdias gardats storage: Mèdias gardats
followers: followers:
domain: Domeni 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 quan de mond que vos sègon.</strong>. Benlèu que volètz repassar vòstra lista e tirar los abonats savètz de dubtes tocant las politica de confidencialitat de lor instàncias. 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 quan de mond que vos sègon.</strong>. Benlèu que volètz repassar vòstra lista e tirar los seguidors savètz de dubtes tocant las politica de confidencialitat de lor instàncias.
followers_count: Nombre dabonats followers_count: Nombre de seguidors
lock_link: Clavar vòstre compte lock_link: Clavar vòstre compte
purge: Tirar dels abonats purge: Tirar dels seguidors
success: success:
one: Soi a blocar los abonats dun domeni... one: Soi a blocar los seguidors dun domeni...
other: Soi a blocar los abonats de %{count} domenis... 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>. 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 unlocked_warning_title: Vòstre compte es pas clavat
generic: generic:
changes_saved_msg: Cambiaments ben realizats ! changes_saved_msg: Cambiaments ben realizats !
@@ -269,15 +273,15 @@ oc:
landing_strip_signup_html: Ses pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>. landing_strip_signup_html: Ses pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
media_attachments: media_attachments:
validations: validations:
images_and_video: Se pòt ajustar una vidèo a un estatut que ten ja dimatges images_and_video: Se pòt pas ajustar una vidèo a un estatut que ten ja dimatges
too_many: Se pòt pas ajustar mai de 4 fichièrs too_many: Se pòt pas ajustar mai de 4 fichièrs
notification_mailer: notification_mailer:
digest: digest:
body: 'Trobatz aquí un resumit de çò quavètz mancat dempuèi vòstra darrièra visita lo %{since}:' body: 'Trobatz aquí un resumit de çò quavètz mancat dempuèi vòstra darrièra visita lo %{since}:'
mention: "%{name} vos a mencionat dins :" mention: "%{name} vos a mencionat dins :"
new_followers_summary: new_followers_summary:
one: Avètz un nòu abonat ! Ouà ! one: Avètz un nòu seguidor ! Ouà !
other: Avètz %{count} nòus abonats ! Qué crane ! other: Avètz %{count} nòus seguidors ! Qué crane !
subject: subject:
one: "Una nòva notificacion dempuèi vòstra darrièra visita \U0001F418" 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" other: "%{count} nòvas notificacions dempuèi vòstra darrièra visita \U0001F418"
@@ -310,7 +314,7 @@ oc:
back: Tornar a Mastodon back: Tornar a Mastodon
edit_profile: Modificar lo perfil edit_profile: Modificar lo perfil
export: Export donadas export: Export donadas
followers: Abonats autorizats followers: Seguidors autorizats
import: Import import: Import
preferences: Preferéncias preferences: Preferéncias
settings: Paramètres settings: Paramètres
@@ -320,8 +324,8 @@ oc:
over_character_limit: limit de %{max} caractèrs passat over_character_limit: limit de %{max} caractèrs passat
show_more: Ne veire mai show_more: Ne veire mai
visibilities: visibilities:
private: Abonats solament private: Seguidors solament
private_long: Mostrar pas quals abonats private_long: Mostrar pas quals seguidors
public: Public public: Public
public_long: Tot lo mond pòt veire public_long: Tot lo mond pòt veire
unlisted: Pas listat unlisted: Pas listat
@@ -332,7 +336,7 @@ oc:
sensitive_content: Contengut sensible sensitive_content: Contengut sensible
time: time:
formats: formats:
default: "%b %d %Y a %H o %M" default: "%b %d %Y a %Ho%M"
two_factor_authentication: two_factor_authentication:
code_hint: Picatz lo còdi generat per vòstra aplicacion dautentificacion per confirmar code_hint: Picatz lo còdi generat per vòstra aplicacion dautentificacion per confirmar
description_html: Sactivatz <strong> lautentificacion two-factor</strong>, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar. description_html: Sactivatz <strong> lautentificacion two-factor</strong>, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar.

View File

@@ -160,6 +160,10 @@ pl:
title: Zgłoszenia title: Zgłoszenia
unresolved: Nierozwiązane unresolved: Nierozwiązane
view: Wyświetl view: Wyświetl
nsfw:
'true': NSFW będzie wyświetlane
'false': NSFW nie będzie wyświetlane
are_you_sure: Czy na pewno?
settings: settings:
contact_information: contact_information:
email: Wprowadź publiczny adres e-mail email: Wprowadź publiczny adres e-mail
@@ -239,8 +243,8 @@ pl:
lock_link: Zablokuj swoje konto lock_link: Zablokuj swoje konto
purge: Usuń z obserwujących purge: Usuń z obserwujących
success: success:
one: W procesie usuwania obserwujcych z jednej domeny… one: W trakcie usuwania obserwujcych z jednej domeny…
other: W procesie usuwania obserwujących z %{count} domen… 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>. 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_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 unlocked_warning_title: Twoje konto nie jest zablokowane
@@ -271,9 +275,13 @@ pl:
mention: "%{name} wspomniał o Tobie w:" mention: "%{name} wspomniał o Tobie w:"
new_followers_summary: new_followers_summary:
one: Śledzi Cię nowa osoba! Gratulacje! 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: subject:
one: "1 nowe powiadomienie od Twojej ostatniej wizyty \U0001F418" 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" other: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
favourite: favourite:
body: 'Twój wpis został polubiony przez %{name}:' body: 'Twój wpis został polubiony przez %{name}:'

View File

@@ -10,6 +10,10 @@ nl:
note: Maximaal 160 tekens note: Maximaal 160 tekens
imports: imports:
data: CSV-bestand dat op een andere Mastodon-server werd geëxporteerd 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: labels:
defaults: defaults:
avatar: Avatar avatar: Avatar
@@ -19,6 +23,7 @@ nl:
data: Gegevens data: Gegevens
display_name: Weergavenaam display_name: Weergavenaam
email: E-mailadres email: E-mailadres
filtered_languages: Talen filteren
header: Omslagfoto header: Omslagfoto
locale: Taal locale: Taal
locked: Maak account besloten locked: Maak account besloten

View File

@@ -4,14 +4,20 @@ oc:
hints: hints:
defaults: defaults:
avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 120x120px avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 120x120px
display_name: Maximum 30 caractèrs display_name:
header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 700x335px one: 'Demòra encara <span class="name-counter">1</span> caractèr'
locked: Demanda quacceptatz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres abonats solament other: 'Demòran encara <span class="name-counter">%{count}</span> caractèrs'
note: Maximum 160 caractèrs header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhada en 700x335px
locked: Demanda quacceptetz 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: imports:
data: Fichièr CSV exportat duna autra instància Mastodon data: Fichièr CSV exportat duna autra instància Mastodon
sessions: sessions:
otp: Picatz lo còdi dautentificacion en dos temps (Two factor code) de vòstre mobil o utilizatz un de vòstres còdis de recuperacion. otp: Picatz lo còdi dautentificacion 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 dactualitat
labels: labels:
defaults: defaults:
avatar: Avatar avatar: Avatar

View File

@@ -6,11 +6,15 @@ pl:
avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px
display_name: display_name:
one: 'Pozostał <span class="name-counter">1</span> znak.' 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' other: 'Pozostało <span class="name-counter">%{count}</span> znaków'
header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px 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 locked: Musisz akceptować obserwacje; Twoje wpisy są domyślnie widoczne tylko dla Twoich obserwujących
note: note:
one: 'Pozostał <span class="name-counter">1</span> znak.' 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' other: 'Pozostało <span class="name-counter">%{count}</span> znaków'
imports: imports:
data: Plik CSV wyeksportowany z innej instancji Mastodona data: Plik CSV wyeksportowany z innej instancji Mastodona

View File

@@ -54,6 +54,7 @@ zh-CN:
are_you_sure: 你确定吗? are_you_sure: 你确定吗?
confirm: 确认 confirm: 确认
confirmed: 已确认 confirmed: 已确认
disable_two_factor_authentication: 两步认证无效
display_name: 显示名称 display_name: 显示名称
domain: 域名 domain: 域名
edit: 编辑 edit: 编辑
@@ -61,6 +62,7 @@ zh-CN:
feed_url: 订阅 URL feed_url: 订阅 URL
followers: 关注者 followers: 关注者
follows: 正在关注 follows: 正在关注
ip: IP地址
location: location:
all: 全部 all: 全部
local: 本地 local: 本地
@@ -83,7 +85,10 @@ zh-CN:
profile_url: 个人文件 URL profile_url: 个人文件 URL
public: 公共 public: 公共
push_subscription_expires: 推送订阅过期 push_subscription_expires: 推送订阅过期
reset: 重置
reset_password: 重置密码
salmon_url: Salmon 反馈 URL salmon_url: Salmon 反馈 URL
search: 搜索
show: show:
created_reports: 这个账户创建的报告 created_reports: 这个账户创建的报告
report: 报告 report: 报告
@@ -116,6 +121,7 @@ zh-CN:
severity: 阻隔程度 severity: 阻隔程度
show: show:
affected_accounts: affected_accounts:
one: 数据库中有1个账户受影响
other: 数据库中有%{count}个账户受影响 other: 数据库中有%{count}个账户受影响
retroactive: retroactive:
silence: 对此域名的所有账户取消静音 silence: 对此域名的所有账户取消静音
@@ -153,6 +159,10 @@ zh-CN:
title: 举报 title: 举报
unresolved: 未处理 unresolved: 未处理
view: 查看 view: 查看
nsfw:
'true': NSFW有效
'false': NSFW无效
are_you_sure: 你确定吗?
settings: settings:
contact_information: contact_information:
email: 输入一个公开的电邮地址 email: 输入一个公开的电邮地址
@@ -218,6 +228,7 @@ zh-CN:
'422': '422':
content: 无法确认登录信息。你是不是屏蔽了 Cookie content: 无法确认登录信息。你是不是屏蔽了 Cookie
title: 无法确认登录信息 title: 无法确认登录信息
'429': 被限制
exports: exports:
blocks: 被你封锁的用户 blocks: 被你封锁的用户
csv: CSV csv: CSV
@@ -239,6 +250,7 @@ zh-CN:
powered_by: 基于 %{link} 构建 powered_by: 基于 %{link} 构建
save_changes: 保存 save_changes: 保存
validation_errors: validation_errors:
one: 出错啦!请确认以下出错的地方,修改之后再来一次:
other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次: other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次:
imports: imports:
preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。 preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。
@@ -250,6 +262,10 @@ zh-CN:
upload: 上载 upload: 上载
landing_strip_html: <strong>%{name}</strong> 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里Mastodon、GNU social任一服务器实例的用户便可以跨站关注此站用户并与其沟通。 landing_strip_html: <strong>%{name}</strong> 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里Mastodon、GNU social任一服务器实例的用户便可以跨站关注此站用户并与其沟通。
landing_strip_signup_html: 如果你没有这类账户,欢迎在<a href="%{sign_up_path}">此处登记</a>。 landing_strip_signup_html: 如果你没有这类账户,欢迎在<a href="%{sign_up_path}">此处登记</a>。
media_attachments:
validations:
images_and_video: 无法添加视频到一个已经包含图片的嘟文中
too_many: 最多只能添加4张图片
notification_mailer: notification_mailer:
digest: digest:
body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴: body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴:
@@ -318,10 +334,12 @@ zh-CN:
disable: 停用 disable: 停用
enable: 启用 enable: 启用
enabled_success: 已成功启用两步认证 enabled_success: 已成功启用两步认证
generate_recovery_codes: 生成恢复代码
instructions_html: "<strong>请用你手机的认证器应用(如 Google Authenticator、Authy扫描这里的 QR 二维码</strong>。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。" instructions_html: "<strong>请用你手机的认证器应用(如 Google Authenticator、Authy扫描这里的 QR 二维码</strong>。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。"
lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的账户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。<del>(废话)</del> lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的账户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。<del>(废话)</del>
manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰ manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰
recovery_codes_regenerated: 已成功重新生成恢复代码 recovery_codes_regenerated: 已成功重新生成恢复代码
recovery_instructions: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的账号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。
setup: 设置 setup: 设置
wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。 wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。
users: users:

View File

@@ -34,6 +34,7 @@ module.exports = merge(sharedConfig, {
analyzerMode: 'static', analyzerMode: 'static',
generateStatsFile: true, generateStatsFile: true,
openAnalyzer: false, openAnalyzer: false,
logLevel: 'silent', // do not bother Webpacker, who runs with --json and parses stdout
}), }),
], ],
}); });

View File

@@ -53,6 +53,12 @@ module.exports = {
// be loaded together // be loaded together
return false; return false;
} }
if (module.resource && /node_modules\/font-awesome/.test(module.resource)) {
// extract vendor css into common module
return true;
}
return count >= 2; return count >= 2;
}, },
}), }),

View File

@@ -13,11 +13,11 @@ module Mastodon
end end
def patch def patch
0 1
end end
def pre def pre
4 nil
end end
def to_a def to_a

View File

@@ -90,6 +90,7 @@
"react-router": "^2.8.0", "react-router": "^2.8.0",
"react-router-scroll": "^0.3.2", "react-router-scroll": "^0.3.2",
"react-simple-dropdown": "^1.1.4", "react-simple-dropdown": "^1.1.4",
"react-textarea-autosize": "^5.0.6",
"react-toggle": "^2.1.1", "react-toggle": "^2.1.1",
"redis": "^2.6.5", "redis": "^2.6.5",
"redux": "^3.6.0", "redux": "^3.6.0",
@@ -101,6 +102,7 @@
"sass-loader": "^6.0.3", "sass-loader": "^6.0.3",
"stringz": "^0.1.2", "stringz": "^0.1.2",
"style-loader": "^0.16.1", "style-loader": "^0.16.1",
"throng": "^4.0.0",
"uuid": "^3.0.1", "uuid": "^3.0.1",
"uws": "^0.14.5", "uws": "^0.14.5",
"webpack": "^2.4.1", "webpack": "^2.4.1",

View File

@@ -33,25 +33,25 @@ describe AccountSearchService do
describe 'searching local and remote users' do describe 'searching local and remote users' do
describe "when only '@'" do describe "when only '@'" do
before do before do
allow(Account).to receive(:find_remote) allow(Account).to receive(:find_local)
allow(Account).to receive(:search_for) allow(Account).to receive(:search_for)
subject.call('@', 10) subject.call('@', 10)
end end
it 'uses find_remote with empty query to look for local accounts' do it 'uses find_local with empty query to look for local accounts' do
expect(Account).to have_received(:find_remote).with('', nil) expect(Account).to have_received(:find_local).with('')
end end
end end
describe 'when no domain' do describe 'when no domain' do
before do before do
allow(Account).to receive(:find_remote) allow(Account).to receive(:find_local)
allow(Account).to receive(:search_for) allow(Account).to receive(:search_for)
subject.call('one', 10) subject.call('one', 10)
end end
it 'uses find_remote with nil domain to look for local accounts' do it 'uses find_local to look for local accounts' do
expect(Account).to have_received(:find_remote).with('one', nil) expect(Account).to have_received(:find_local).with('one')
end end
it 'uses search_for to find matches' do it 'uses search_for to find matches' do
@@ -101,6 +101,25 @@ describe AccountSearchService do
end end
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 describe 'when there is a domain but no exact match' do
it 'follows the remote account when resolve is true' do it 'follows the remote account when resolve is true' do
service = double(call: nil) service = double(call: nil)

View File

@@ -1,5 +1,5 @@
import os from 'os'; import os from 'os';
import cluster from 'cluster'; import throng from 'throng';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import express from 'express'; import express from 'express';
import http from 'http'; import http from 'http';
@@ -16,6 +16,8 @@ dotenv.config({
path: env === 'production' ? '.env.production' : '.env', path: env === 'production' ? '.env.production' : '.env',
}); });
log.level = process.env.LOG_LEVEL || 'verbose';
const dbUrlToConfig = (dbUrl) => { const dbUrlToConfig = (dbUrl) => {
if (!dbUrl) { if (!dbUrl) {
return {}; return {};
@@ -65,24 +67,15 @@ const redisUrlToClient = (defaultConfig, redisUrl) => {
})); }));
}; };
if (cluster.isMaster) { const numWorkers = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1));
// Cluster master
const core = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1));
const fork = () => { const startMaster = () => {
const worker = cluster.fork(); log.info(`Starting streaming API server master with ${numWorkers} workers`);
};
worker.on('exit', (code, signal) => { const startWorker = (workerId) => {
log.error(`Worker died with exit code ${code}, signal ${signal} received.`); log.info(`Starting worker ${workerId}`);
setTimeout(() => fork(), 0);
});
};
for (let i = 0; i < core; i++) fork();
log.info(`Starting streaming API server master with ${core} workers`);
} else {
// Cluster worker
const pgConfigs = { const pgConfigs = {
development: { development: {
database: 'mastodon_development', database: 'mastodon_development',
@@ -130,6 +123,7 @@ if (cluster.isMaster) {
if (!callbacks) { if (!callbacks) {
return; return;
} }
callbacks.forEach(callback => callback(message)); callbacks.forEach(callback => callback(message));
}); });
@@ -168,7 +162,7 @@ if (cluster.isMaster) {
return; 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(); done();
if (err) { if (err) {
@@ -185,6 +179,7 @@ if (cluster.isMaster) {
} }
req.accountId = result.rows[0].account_id; req.accountId = result.rows[0].account_id;
req.filteredLanguages = result.rows[0].filtered_languages;
next(); next();
}); });
@@ -214,9 +209,9 @@ if (cluster.isMaster) {
}; };
const errorMiddleware = (err, req, res, next) => { 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.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(', '); 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 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]; 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 = [ 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)), 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(); transmit();
}).catch(err => { }).catch(err => {
done();
log.error(err); log.error(err);
}); });
}); });
@@ -303,35 +305,24 @@ if (cluster.isMaster) {
}; };
// Setup stream output to WebSockets // Setup stream output to WebSockets
const streamToWs = (req, ws) => { const streamToWs = (req, ws) => (event, payload) => {
const heartbeat = setInterval(() => { if (ws.readyState !== ws.OPEN) {
// TODO: Can't add multiple listeners, due to the limitation of uws. log.error(req.requestId, 'Tried writing to closed socket');
if (ws.readyState !== ws.OPEN) { return;
log.verbose(req.requestId, `Ending stream for ${req.accountId}`); }
clearInterval(heartbeat);
return;
}
ws.ping(); ws.send(JSON.stringify({ event, payload }));
}, 15000);
return (event, payload) => {
if (ws.readyState !== ws.OPEN) {
log.error(req.requestId, 'Tried writing to closed socket');
return;
}
ws.send(JSON.stringify({ event, payload }));
};
}; };
// Setup stream end for WebSockets // Setup stream end for WebSockets
const streamWsEnd = ws => (id, listener) => { const streamWsEnd = (req, ws) => (id, listener) => {
ws.on('close', () => { ws.on('close', () => {
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
unsubscribe(id, listener); unsubscribe(id, listener);
}); });
ws.on('error', e => { ws.on('error', e => {
log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
unsubscribe(id, listener); unsubscribe(id, listener);
}); });
}; };
@@ -366,6 +357,12 @@ if (cluster.isMaster) {
const token = location.query.access_token; const token = location.query.access_token;
const req = { requestId: uuid.v4() }; const req = { requestId: uuid.v4() };
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
accountFromToken(token, req, err => { accountFromToken(token, req, err => {
if (err) { if (err) {
log.error(req.requestId, err); log.error(req.requestId, err);
@@ -375,19 +372,19 @@ if (cluster.isMaster) {
switch(location.query.stream) { switch(location.query.stream) {
case 'user': case 'user':
streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(ws)); streamFrom(`timeline:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws));
break; break;
case 'public': case 'public':
streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(ws), true); streamFrom('timeline:public', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
break; break;
case 'public:local': 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; break;
case 'hashtag': 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; break;
case 'hashtag:local': 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; break;
default: default:
ws.close(); 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, () => { server.listen(process.env.PORT || 4000, () => {
log.level = process.env.LOG_LEVEL || 'verbose'; log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`);
log.info(`Starting streaming API server worker on ${server.address().address}:${server.address().port}`);
}); });
process.on('SIGINT', exit); const onExit = () => {
process.on('SIGTERM', exit); log.info(`Worker ${workerId} exiting, bye bye`);
process.on('exit', exit);
function exit() {
server.close(); 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,
});

View File

@@ -5590,6 +5590,12 @@ react-test-renderer@^15.5.4:
fbjs "^0.8.9" fbjs "^0.8.9"
object-assign "^4.1.0" 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: react-toggle@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-2.1.1.tgz#80600a64417a1acc8aaa4c1477f7fbdb88b988fb" 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" version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 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: through@2, through@^2.3.6:
version "2.3.8" version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"