Merge remote-tracking branch 'upstream/master'

pull/137/head
Ondřej Hruška 2017-08-07 22:43:52 +02:00
commit 7a1ca8b0df
82 changed files with 419 additions and 260 deletions

View File

@ -6,6 +6,7 @@ cache:
- node_modules - node_modules
- public/assets - public/assets
- public/packs-test - public/packs-test
- tmp/cache/babel-loader
dist: trusty dist: trusty
sudo: required sudo: required

View File

@ -7,6 +7,9 @@ ENV UID=991 GID=991 \
RAILS_SERVE_STATIC_FILES=true \ RAILS_SERVE_STATIC_FILES=true \
RAILS_ENV=production NODE_ENV=production RAILS_ENV=production NODE_ENV=production
ARG LIBICONV_VERSION=1.15
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
EXPOSE 3000 4000 EXPOSE 3000 4000
WORKDIR /mastodon WORKDIR /mastodon
@ -18,8 +21,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
build-base \ build-base \
icu-dev \ icu-dev \
libidn-dev \ libidn-dev \
libxml2-dev \ libtool \
libxslt-dev \
postgresql-dev \ postgresql-dev \
protobuf-dev \ protobuf-dev \
python \ python \
@ -32,8 +34,6 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
imagemagick@edge \ imagemagick@edge \
libidn \ libidn \
libpq \ libpq \
libxml2 \
libxslt \
nodejs-npm@edge \ nodejs-npm@edge \
nodejs@edge \ nodejs@edge \
protobuf \ protobuf \
@ -41,11 +41,23 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
tini \ tini \
yarn@edge \ yarn@edge \
&& update-ca-certificates \ && update-ca-certificates \
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
&& mkdir -p /tmp/src \
&& tar -xzf libiconv.tar.gz -C /tmp/src \
&& rm libiconv.tar.gz \
&& cd /tmp/src/libiconv-$LIBICONV_VERSION \
&& ./configure --prefix=/usr/local \
&& make -j$(getconf _NPROCESSORS_ONLN)\
&& make install \
&& libtool --finish /usr/local/lib \
&& cd /mastodon \
&& rm -rf /tmp/* /var/cache/apk/* && rm -rf /tmp/* /var/cache/apk/*
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/ COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
RUN bundle install --deployment --without test development \ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
&& yarn --ignore-optional --pure-lockfile && yarn --ignore-optional --pure-lockfile
COPY . /mastodon COPY . /mastodon

View File

@ -1,25 +1,25 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.1.2) actioncable (5.1.3)
actionpack (= 5.1.2) actionpack (= 5.1.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (~> 0.6.1) websocket-driver (~> 0.6.1)
actionmailer (5.1.2) actionmailer (5.1.3)
actionpack (= 5.1.2) actionpack (= 5.1.3)
actionview (= 5.1.2) actionview (= 5.1.3)
activejob (= 5.1.2) activejob (= 5.1.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.1.2) actionpack (5.1.3)
actionview (= 5.1.2) actionview (= 5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
rack (~> 2.0) rack (~> 2.0)
rack-test (~> 0.6.3) rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.2) actionview (5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -30,16 +30,16 @@ GEM
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
active_record_query_trace (1.5.4) active_record_query_trace (1.5.4)
activejob (5.1.2) activejob (5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.1.2) activemodel (5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
activerecord (5.1.2) activerecord (5.1.3)
activemodel (= 5.1.2) activemodel (= 5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
arel (~> 8.0) arel (~> 8.0)
activesupport (5.1.2) activesupport (5.1.3)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7) i18n (~> 0.7)
minitest (~> 5.1) minitest (~> 5.1)
@ -57,14 +57,14 @@ GEM
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
aws-sdk (2.10.6) aws-sdk (2.10.21)
aws-sdk-resources (= 2.10.6) aws-sdk-resources (= 2.10.21)
aws-sdk-core (2.10.6) aws-sdk-core (2.10.21)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.10.6) aws-sdk-resources (2.10.21)
aws-sdk-core (= 2.10.6) aws-sdk-core (= 2.10.21)
aws-sigv4 (1.0.0) aws-sigv4 (1.0.1)
bcrypt (3.1.11) bcrypt (3.1.11)
better_errors (2.1.1) better_errors (2.1.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
@ -72,7 +72,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 (1.1.1) bootsnap (1.1.2)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (3.6.2) brakeman (3.6.2)
browser (2.4.0) browser (2.4.0)
@ -155,7 +155,7 @@ GEM
et-orbi (1.0.5) et-orbi (1.0.5)
tzinfo tzinfo
execjs (2.7.0) execjs (2.7.0)
fabrication (2.16.1) fabrication (2.16.2)
faker (1.7.3) faker (1.7.3)
i18n (~> 0.5) i18n (~> 0.5)
fast_blank (1.0.0) fast_blank (1.0.0)
@ -165,7 +165,7 @@ GEM
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
globalid (0.4.0) globalid (0.4.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
goldfinger (2.0.0) goldfinger (2.0.1)
addressable (~> 2.5) addressable (~> 2.5)
http (~> 2.2) http (~> 2.2)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -179,7 +179,7 @@ GEM
activesupport (>= 4.0.1) activesupport (>= 4.0.1)
hamlit (>= 1.2.0) hamlit (>= 1.2.0)
railties (>= 4.0.1) railties (>= 4.0.1)
hashdiff (0.3.4) hashdiff (0.3.5)
highline (1.7.8) highline (1.7.8)
hiredis (0.6.1) hiredis (0.6.1)
hkdf (0.3.0) hkdf (0.3.0)
@ -194,11 +194,11 @@ GEM
http-form_data (1.0.3) http-form_data (1.0.3)
http_accept_language (2.1.1) http_accept_language (2.1.1)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httplog (0.99.4) httplog (0.99.7)
colorize colorize
rack rack
i18n (0.8.4) i18n (0.8.6)
i18n-tasks (0.9.15) i18n-tasks (0.9.16)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
ast (>= 2.1.0) ast (>= 2.1.0)
easy_translate (>= 0.5.0) easy_translate (>= 0.5.0)
@ -211,7 +211,7 @@ GEM
idn-ruby (0.1.0) idn-ruby (0.1.0)
jmespath (1.3.1) jmespath (1.3.1)
json (2.1.0) json (2.1.0)
jsonapi-renderer (0.1.2) jsonapi-renderer (0.1.3)
jwt (1.5.6) jwt (1.5.6)
kaminari (1.0.1) kaminari (1.0.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
@ -253,7 +253,7 @@ GEM
mime-types-data (3.2016.0521) mime-types-data (3.2016.0521)
mimemagic (0.3.2) mimemagic (0.3.2)
mini_portile2 (2.2.0) mini_portile2 (2.2.0)
minitest (5.10.2) minitest (5.10.3)
msgpack (1.1.0) msgpack (1.1.0)
multi_json (1.12.1) multi_json (1.12.1)
net-scp (1.2.1) net-scp (1.2.1)
@ -264,7 +264,7 @@ GEM
mini_portile2 (~> 2.2.0) mini_portile2 (~> 2.2.0)
nokogumbo (1.4.13) nokogumbo (1.4.13)
nokogiri nokogiri
oj (3.2.0) oj (3.3.4)
openssl (2.0.4) openssl (2.0.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostatus2 (2.0.1) ostatus2 (2.0.1)
@ -283,14 +283,14 @@ GEM
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.11.2) parallel (1.11.2)
parallel_tests (2.14.1) parallel_tests (2.14.2)
parallel parallel
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
pg (0.21.0) pg (0.21.0)
pghero (1.7.0) pghero (1.7.0)
activerecord activerecord
pkg-config (1.2.3) pkg-config (1.2.4)
powerpack (0.1.1) powerpack (0.1.1)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
@ -313,17 +313,17 @@ GEM
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rack-timeout (0.4.2) rack-timeout (0.4.2)
rails (5.1.2) rails (5.1.3)
actioncable (= 5.1.2) actioncable (= 5.1.3)
actionmailer (= 5.1.2) actionmailer (= 5.1.3)
actionpack (= 5.1.2) actionpack (= 5.1.3)
actionview (= 5.1.2) actionview (= 5.1.3)
activejob (= 5.1.2) activejob (= 5.1.3)
activemodel (= 5.1.2) activemodel (= 5.1.3)
activerecord (= 5.1.2) activerecord (= 5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0)
railties (= 5.1.2) railties (= 5.1.3)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2) rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1) actionpack (~> 5.x, >= 5.0.1)
@ -337,11 +337,11 @@ GEM
rails-i18n (5.0.4) rails-i18n (5.0.4)
i18n (~> 0.7) i18n (~> 0.7)
railties (~> 5.0) railties (~> 5.0)
rails-settings-cached (0.6.5) rails-settings-cached (0.6.6)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (5.1.2) railties (5.1.3)
actionpack (= 5.1.2) actionpack (= 5.1.3)
activesupport (= 5.1.2) activesupport (= 5.1.3)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
@ -353,7 +353,7 @@ GEM
actionpack (>= 4.0, < 6) actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3) redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 1.4.0) redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.2) redis-activesupport (5.0.3)
activesupport (>= 3, < 6) activesupport (>= 3, < 6)
redis-store (~> 1.3.0) redis-store (~> 1.3.0)
redis-namespace (1.5.3) redis-namespace (1.5.3)
@ -413,7 +413,7 @@ GEM
scss_lint (0.54.0) scss_lint (0.54.0)
rake (>= 0.9, < 13) rake (>= 0.9, < 13)
sass (~> 3.4.20) sass (~> 3.4.20)
sidekiq (5.0.3) sidekiq (5.0.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
@ -421,12 +421,12 @@ GEM
sidekiq-bulk (0.1.1) sidekiq-bulk (0.1.1)
activesupport activesupport
sidekiq sidekiq
sidekiq-scheduler (2.1.7) sidekiq-scheduler (2.1.8)
redis (~> 3) redis (~> 3)
rufus-scheduler (~> 3.2) rufus-scheduler (~> 3.2)
sidekiq (>= 3) sidekiq (>= 3)
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (5.0.8) sidekiq-unique-jobs (5.0.9)
sidekiq (>= 4.0, <= 6.0) sidekiq (>= 4.0, <= 6.0)
thor (~> 0) thor (~> 0)
simple-navigation (4.0.5) simple-navigation (4.0.5)
@ -450,15 +450,15 @@ GEM
sshkit (1.13.1) sshkit (1.13.1)
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
statsd-instrument (2.1.2) statsd-instrument (2.1.4)
temple (0.8.0) temple (0.8.0)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thor (0.19.4) thor (0.19.4)
thread (0.2.2) thread (0.2.2)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.7) tilt (2.0.8)
twitter-text (1.14.6) twitter-text (1.14.7)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.3) tzinfo (1.2.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
@ -590,4 +590,4 @@ RUBY VERSION
ruby 2.4.1p111 ruby 2.4.1p111
BUNDLED WITH BUNDLED WITH
1.15.2 1.15.3

View File

@ -43,6 +43,10 @@ class ApplicationController < ActionController::Base
forbidden if current_user.account.suspended? forbidden if current_user.account.suspended?
end end
def after_sign_out_path_for(_resource_or_scope)
new_user_session_path
end
protected protected
def forbidden def forbidden

View File

@ -1,5 +1,20 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::PasswordsController < Devise::PasswordsController class Auth::PasswordsController < Devise::PasswordsController
before_action :check_validity_of_reset_password_token, only: :edit
layout 'auth' layout 'auth'
private
def check_validity_of_reset_password_token
unless reset_password_token_is_valid?
flash[:error] = I18n.t('auth.invalid_reset_password_token')
redirect_to new_password_path(resource_name)
end
end
def reset_password_token_is_valid?
resource_class.with_reset_password_token(params[:reset_password_token]).present?
end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AuthorizeFollowsController < ApplicationController class AuthorizeFollowsController < ApplicationController
layout 'public' layout 'modal'
before_action :authenticate_user! before_action :authenticate_user!

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class RemoteFollowController < ApplicationController class RemoteFollowController < ApplicationController
layout 'public' layout 'modal'
before_action :set_account before_action :set_account
before_action :gone, if: :suspended_account? before_action :gone, if: :suspended_account?

View File

@ -23,6 +23,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
export const STATUS_SET_HEIGHT = 'STATUS_SET_HEIGHT';
export const STATUSES_CLEAR_HEIGHT = 'STATUSES_CLEAR_HEIGHT';
export function fetchStatusRequest(id, skipLoading) { export function fetchStatusRequest(id, skipLoading) {
return { return {
type: STATUS_FETCH_REQUEST, type: STATUS_FETCH_REQUEST,
@ -215,3 +218,17 @@ export function unmuteStatusFail(id, error) {
error, error,
}; };
}; };
export function setStatusHeight (id, height) {
return {
type: STATUS_SET_HEIGHT,
id,
height,
};
};
export function clearStatusesHeight () {
return {
type: STATUSES_CLEAR_HEIGHT,
};
};

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import detectPassiveEvents from 'detect-passive-events'; import detectPassiveEvents from 'detect-passive-events';
import scrollTop from '../scroll'; import { scrollTop } from '../scroll';
export default class Column extends React.PureComponent { export default class Column extends React.PureComponent {

View File

@ -1,7 +1,5 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../emoji';
export default class DisplayName extends React.PureComponent { export default class DisplayName extends React.PureComponent {
@ -10,12 +8,11 @@ export default class DisplayName extends React.PureComponent {
}; };
render () { render () {
const displayName = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name'); const displayNameHtml = { __html: this.props.account.get('display_name_html') };
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
return ( return (
<span className='display-name'> <span className='display-name'>
<strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span> <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
</span> </span>
); );
} }

View File

@ -11,8 +11,6 @@ import DisplayName from './display_name';
import StatusContent from './status_content'; import StatusContent from './status_content';
import StatusActionBar from './status_action_bar'; import StatusActionBar from './status_action_bar';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import emojify from '../emoji';
import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components'; import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
@ -39,6 +37,7 @@ export default class Status extends ImmutablePureComponent {
onOpenMedia: PropTypes.func, onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func, onOpenVideo: PropTypes.func,
onBlock: PropTypes.func, onBlock: PropTypes.func,
onHeightChange: PropTypes.func,
me: PropTypes.number, me: PropTypes.number,
boostModal: PropTypes.bool, boostModal: PropTypes.bool,
autoPlayGif: PropTypes.bool, autoPlayGif: PropTypes.bool,
@ -50,7 +49,6 @@ export default class Status extends ImmutablePureComponent {
state = { state = {
isExpanded: false, isExpanded: false,
isIntersecting: true, // assume intersecting until told otherwise
isHidden: false, // set to true in requestIdleCallback to trigger un-render isHidden: false, // set to true in requestIdleCallback to trigger un-render
} }
@ -111,6 +109,10 @@ export default class Status extends ImmutablePureComponent {
if (this.node && this.node.children.length !== 0) { if (this.node && this.node.children.length !== 0) {
// save the height of the fully-rendered element // save the height of the fully-rendered element
this.height = getRectFromEntry(entry).height; this.height = getRectFromEntry(entry).height;
if (this.props.onHeightChange) {
this.props.onHeightChange(this.props.status, this.height);
}
} }
this.setState((prevState) => { this.setState((prevState) => {
@ -182,9 +184,13 @@ export default class Status extends ImmutablePureComponent {
return null; return null;
} }
if (!isIntersecting && isHidden) { const hasIntersectionObserverWrapper = !!this.props.intersectionObserverWrapper;
const isHiddenForSure = isIntersecting === false && isHidden;
const visibilityUnknownButHeightIsCached = isIntersecting === undefined && status.has('height');
if (hasIntersectionObserverWrapper && (isHiddenForSure || visibilityUnknownButHeightIsCached)) {
return ( return (
<article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}> <article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height || status.get('height')}px`, opacity: 0, overflow: 'hidden' }}>
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
{status.get('content')} {status.get('content')}
</article> </article>
@ -192,19 +198,13 @@ export default class Status extends ImmutablePureComponent {
} }
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
let displayName = status.getIn(['account', 'display_name']); const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
if (displayName.length === 0) {
displayName = status.getIn(['account', 'username']);
}
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
return ( return (
<article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'> <article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'>
<div className='status__prepend'> <div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div> <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} /> <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={display_name_html} /></a> }} />
</div> </div>
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} /> <Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />

View File

@ -3,9 +3,7 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import escapeTextContentForBrowser from 'escape-html';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import emojify from '../emoji';
import { isRtl } from '../rtl'; import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Permalink from './permalink'; import Permalink from './permalink';
@ -122,8 +120,8 @@ export default class StatusContent extends React.PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const content = { __html: emojify(status.get('content')) }; const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) }; const spoilerContent = { __html: status.get('spoilerHtml') };
const directionStyle = { direction: 'ltr' }; const directionStyle = { direction: 'ltr' };
const classNames = classnames('status__content', { const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router, 'status__content--with-action': this.props.onClick && this.context.router,

View File

@ -105,7 +105,7 @@ export default class StatusList extends ImmutablePureComponent {
} }
handleKeyDown = (e) => { handleKeyDown = (e) => {
if (['PageDown', 'PageUp', 'End', 'Home'].includes(e.key)) { if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) {
const article = (() => { const article = (() => {
switch (e.key) { switch (e.key) {
case 'PageDown': case 'PageDown':

View File

@ -19,7 +19,7 @@ import {
blockAccount, blockAccount,
muteAccount, muteAccount,
} from '../actions/accounts'; } from '../actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses'; import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
import { initReport } from '../actions/reports'; import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal'; import { openModal } from '../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@ -127,6 +127,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
} }
}, },
onHeightChange (status, height) {
dispatch(setStatusHeight(status.get('id'), height));
},
}); });
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));

View File

@ -3,34 +3,28 @@ import Trie from 'substring-trie';
const trie = new Trie(Object.keys(unicodeMapping)); const trie = new Trie(Object.keys(unicodeMapping));
const excluded = ['™', '©', '®']; const emojify = str => {
let rtn = '';
function emojify(str) { for (;;) {
// This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.) let match, i = 0;
// and replacing valid unicode strings while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
// that _aren't_ within tags with an <img> version. i += str.codePointAt(i) < 65536 ? 1 : 2;
// The goal is to be the same as an emojione.regUnicode replacement, but faster. }
let i = -1; if (i === str.length)
let insideTag = false; break;
let match; else if (str[i] === '<') {
while (++i < str.length) { let tagend = str.indexOf('>', i + 1) + 1;
const char = str.charAt(i); if (!tagend)
if (insideTag && char === '>') { break;
insideTag = false; rtn += str.slice(0, tagend);
} else if (char === '<') { str = str.slice(tagend);
insideTag = true; } else {
} else if (!insideTag && (match = trie.search(str.substring(i)))) { const [filename, shortCode] = unicodeMapping[match];
const unicodeStr = match; rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
if (unicodeStr in unicodeMapping && excluded.indexOf(unicodeStr) === -1) { str = str.slice(i + match.length);
const [filename, shortCode] = unicodeMapping[unicodeStr];
const alt = unicodeStr;
const replacement = `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
}
} }
} }
return str; return rtn + str;
} };
export default emojify; export default emojify;

View File

@ -4,8 +4,10 @@
const emojione = require('emojione'); const emojione = require('emojione');
const mappedUnicode = emojione.mapUnicodeToShort(); const mappedUnicode = emojione.mapUnicodeToShort();
const excluded = ['®', '©', '™'];
module.exports.unicodeMapping = Object.keys(emojione.jsEscapeMap) module.exports.unicodeMapping = Object.keys(emojione.jsEscapeMap)
.filter(c => !excluded.includes(c))
.map(unicodeStr => [unicodeStr, mappedUnicode[emojione.jsEscapeMap[unicodeStr]]]) .map(unicodeStr => [unicodeStr, mappedUnicode[emojione.jsEscapeMap[unicodeStr]]])
.map(([unicodeStr, shortCode]) => ({ [unicodeStr]: [emojione.emojioneList[shortCode].fname, shortCode.slice(1, shortCode.length - 1)] })) .map(([unicodeStr, shortCode]) => ({ [unicodeStr]: [emojione.emojioneList[shortCode].fname, shortCode.slice(1, shortCode.length - 1)] }))
.reduce((x, y) => Object.assign(x, y), { }); .reduce((x, y) => Object.assign(x, y), { });

View File

@ -4,8 +4,6 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'escape-html';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import Motion from 'react-motion/lib/Motion'; import Motion from 'react-motion/lib/Motion';
@ -95,15 +93,10 @@ export default class Header extends ImmutablePureComponent {
return null; return null;
} }
let displayName = account.get('display_name');
let info = ''; let info = '';
let actionBtn = ''; let actionBtn = '';
let lockedIcon = ''; let lockedIcon = '';
if (displayName.length === 0) {
displayName = account.get('username');
}
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>; info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
} }
@ -128,15 +121,15 @@ export default class Header extends ImmutablePureComponent {
lockedIcon = <i className='fa fa-lock' />; lockedIcon = <i className='fa fa-lock' />;
} }
const content = { __html: emojify(account.get('note')) }; const content = { __html: account.get('note_emojified') };
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; const displayNameHtml = { __html: account.get('display_name_html') };
return ( return (
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
<div> <div>
<Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
<div className='account__header__content' dangerouslySetInnerHTML={content} /> <div className='account__header__content' dangerouslySetInnerHTML={content} />

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import emojify from '../../../emoji';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -43,7 +42,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
return null; return null;
} }
const content = { __html: emojify(status.get('content')) }; const content = { __html: status.get('contentHtml') };
return ( return (
<div className='reply-indicator'> <div className='reply-indicator'>

View File

@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import emojify from '../../../emoji';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -26,7 +25,7 @@ export default class AccountAuthorize extends ImmutablePureComponent {
render () { render () {
const { intl, account, onAuthorize, onReject } = this.props; const { intl, account, onAuthorize, onReject } = this.props;
const content = { __html: emojify(account.get('note')) }; const content = { __html: account.get('note_emojified') };
return ( return (
<div className='account-authorize__wrapper'> <div className='account-authorize__wrapper'>

View File

@ -7,8 +7,6 @@ import StatusContainer from '../../../containers/status_container';
import AccountContainer from '../../../containers/account_container'; import AccountContainer from '../../../containers/account_container';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
export default class Notification extends ImmutablePureComponent { export default class Notification extends ImmutablePureComponent {
@ -70,9 +68,8 @@ export default class Notification extends ImmutablePureComponent {
render () { render () {
const { notification } = this.props; const { notification } = this.props;
const account = notification.get('account'); const account = notification.get('account');
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); const displayNameHtml = { __html: account.get('display_name_html') };
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHtml} />;
const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
switch(notification.get('type')) { switch(notification.get('type')) {
case 'follow': case 'follow':

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import emojify from '../../../emoji';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
export default class StatusCheckBox extends React.PureComponent { export default class StatusCheckBox extends React.PureComponent {
@ -15,7 +14,7 @@ export default class StatusCheckBox extends React.PureComponent {
render () { render () {
const { status, checked, onToggle, disabled } = this.props; const { status, checked, onToggle, disabled } = this.props;
const content = { __html: emojify(status.get('content')) }; const content = { __html: status.get('contentHtml') };
if (status.get('reblog')) { if (status.get('reblog')) {
return null; return null;

View File

@ -2,7 +2,7 @@ import React from 'react';
import ColumnHeader from './column_header'; import ColumnHeader from './column_header';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import scrollTop from '../../../scroll'; import { scrollTop } from '../../../scroll';
import { isMobile } from '../../../is_mobile'; import { isMobile } from '../../../is_mobile';
export default class Column extends React.PureComponent { export default class Column extends React.PureComponent {

View File

@ -12,6 +12,8 @@ import ColumnLoading from './column_loading';
import BundleColumnError from './bundle_column_error'; import BundleColumnError from './bundle_column_error';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components'; import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
import { scrollRight } from '../../../scroll';
const componentMap = { const componentMap = {
'COMPOSE': Compose, 'COMPOSE': Compose,
'HOME': HomeTimeline, 'HOME': HomeTimeline,
@ -49,9 +51,13 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.setState({ shouldAnimate: true }); this.setState({ shouldAnimate: true });
} }
componentDidUpdate() { componentDidUpdate(prevProps) {
this.lastIndex = getIndex(this.context.router.history.location.pathname); this.lastIndex = getIndex(this.context.router.history.location.pathname);
this.setState({ shouldAnimate: true }); this.setState({ shouldAnimate: true });
if (this.props.children !== prevProps.children && !this.props.singleColumn) {
scrollRight(this.node);
}
} }
handleSwipe = (index) => { handleSwipe = (index) => {
@ -74,6 +80,10 @@ export default class ColumnsArea extends ImmutablePureComponent {
} }
} }
setRef = (node) => {
this.node = node;
}
renderView = (link, index) => { renderView = (link, index) => {
const columnIndex = getIndex(this.context.router.history.location.pathname); const columnIndex = getIndex(this.context.router.history.location.pathname);
const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] }); const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
@ -114,7 +124,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
} }
return ( return (
<div className='columns-area'> <div className='columns-area' ref={this.setRef}>
{columns.map(column => { {columns.map(column => {
const params = column.get('params', null) === null ? null : column.get('params').toJS(); const params = column.get('params', null) === null ? null : column.get('params').toJS();

View File

@ -12,6 +12,7 @@ import { debounce } from 'lodash';
import { uploadCompose } from '../../actions/compose'; import { uploadCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines'; import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications'; import { refreshNotifications } from '../../actions/notifications';
import { clearStatusesHeight } from '../../actions/statuses';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area'; import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container'; import ColumnsAreaContainer from './containers/columns_area_container';
@ -72,6 +73,9 @@ export default class UI extends React.PureComponent {
}; };
handleResize = debounce(() => { handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.dispatch(clearStatusesHeight());
this.setState({ width: window.innerWidth }); this.setState({ width: window.innerWidth });
}, 500, { }, 500, {
trailing: true, trailing: true,

View File

@ -1,7 +1,7 @@
{ {
"account.block": "مسدودسازی @{name}", "account.block": "مسدودسازی @{name}",
"account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}", "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
"account.edit_profile": "ویرایش نمایه", "account.edit_profile": "ویرایش نمایه",
"account.follow": "پی بگیرید", "account.follow": "پی بگیرید",
"account.followers": "پیگیران", "account.followers": "پیگیران",
@ -13,7 +13,7 @@
"account.posts": "نوشته‌ها", "account.posts": "نوشته‌ها",
"account.report": "گزارش @{name}", "account.report": "گزارش @{name}",
"account.requested": "در انتظار پذیرش", "account.requested": "در انتظار پذیرش",
"account.share": "Share @{name}'s profile", "account.share": "هم‌رسانی نمایهٔ @{name}",
"account.unblock": "رفع انسداد @{name}", "account.unblock": "رفع انسداد @{name}",
"account.unblock_domain": "رفع پنهان‌سازی از {domain}", "account.unblock_domain": "رفع پنهان‌سازی از {domain}",
"account.unfollow": "پایان پیگیری", "account.unfollow": "پایان پیگیری",

View File

@ -13,7 +13,7 @@
"account.posts": "Statuts", "account.posts": "Statuts",
"account.report": "Signaler", "account.report": "Signaler",
"account.requested": "Invitation envoyée", "account.requested": "Invitation envoyée",
"account.share": "Share @{name}'s profile", "account.share": "Partager le profil de @{name}",
"account.unblock": "Débloquer", "account.unblock": "Débloquer",
"account.unblock_domain": "Ne plus masquer {domain}", "account.unblock_domain": "Ne plus masquer {domain}",
"account.unfollow": "Ne plus suivre", "account.unfollow": "Ne plus suivre",
@ -35,11 +35,11 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.public": "Fil public global", "column.public": "Fil public global",
"column_back_button.label": "Retour", "column_back_button.label": "Retour",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Masquer les paramètres",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Déplacer la colonne vers la gauche",
"column_header.moveRight_settings": "Move column to the right", "column_header.moveRight_settings": "Déplacer la colonne vers la droite",
"column_header.pin": "Épingler", "column_header.pin": "Épingler",
"column_header.show_settings": "Show settings", "column_header.show_settings": "Afficher les paramètres",
"column_header.unpin": "Retirer", "column_header.unpin": "Retirer",
"column_subheading.navigation": "Navigation", "column_subheading.navigation": "Navigation",
"column_subheading.settings": "Paramètres", "column_subheading.settings": "Paramètres",
@ -94,8 +94,8 @@
"home.column_settings.show_replies": "Afficher les réponses", "home.column_settings.show_replies": "Afficher les réponses",
"home.settings": "Paramètres de la colonne", "home.settings": "Paramètres de la colonne",
"lightbox.close": "Fermer", "lightbox.close": "Fermer",
"lightbox.next": "Next", "lightbox.next": "Suivant",
"lightbox.previous": "Previous", "lightbox.previous": "Précédent",
"loading_indicator.label": "Chargement…", "loading_indicator.label": "Chargement…",
"media_gallery.toggle_visible": "Modifier la visibilité", "media_gallery.toggle_visible": "Modifier la visibilité",
"missing_indicator.label": "Non trouvé", "missing_indicator.label": "Non trouvé",
@ -175,7 +175,7 @@
"status.report": "Signaler @{name}", "status.report": "Signaler @{name}",
"status.sensitive_toggle": "Cliquer pour afficher", "status.sensitive_toggle": "Cliquer pour afficher",
"status.sensitive_warning": "Contenu sensible", "status.sensitive_warning": "Contenu sensible",
"status.share": "Share", "status.share": "Partager",
"status.show_less": "Replier", "status.show_less": "Replier",
"status.show_more": "Déplier", "status.show_more": "Déplier",
"status.unmute_conversation": "Ne plus masquer la conversation", "status.unmute_conversation": "Ne plus masquer la conversation",

View File

@ -73,7 +73,7 @@
"emoji_button.search": "Szukaj...", "emoji_button.search": "Szukaj...",
"emoji_button.symbols": "Symbole", "emoji_button.symbols": "Symbole",
"emoji_button.travel": "Podróże i miejsca", "emoji_button.travel": "Podróże i miejsca",
"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 zagaić!",
"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 śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
"empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.", "empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.",
@ -159,7 +159,7 @@
"report.target": "Zgłaszanie {target}", "report.target": "Zgłaszanie {target}",
"search.placeholder": "Szukaj", "search.placeholder": "Szukaj",
"search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}", "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
"standalone.public_title": "Spojrzenie wgłąb…", "standalone.public_title": "Spojrzenie w głąb…",
"status.cannot_reblog": "Ten post nie może zostać podbity", "status.cannot_reblog": "Ten post nie może zostać podbity",
"status.delete": "Usuń", "status.delete": "Usuń",
"status.favourite": "Ulubione", "status.favourite": "Ulubione",
@ -178,7 +178,7 @@
"status.share": "Udostępnij", "status.share": "Udostępnij",
"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": "Cofnij wyciezenie konwersacji", "status.unmute_conversation": "Cofnij wyciszenie 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

@ -44,7 +44,9 @@ import {
FAVOURITED_STATUSES_EXPAND_SUCCESS, FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from '../actions/favourites'; } from '../actions/favourites';
import { STORE_HYDRATE } from '../actions/store'; import { STORE_HYDRATE } from '../actions/store';
import emojify from '../emoji';
import { Map as ImmutableMap, fromJS } from 'immutable'; import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
const normalizeAccount = (state, account) => { const normalizeAccount = (state, account) => {
account = { ...account }; account = { ...account };
@ -53,6 +55,10 @@ const normalizeAccount = (state, account) => {
delete account.following_count; delete account.following_count;
delete account.statuses_count; delete account.statuses_count;
const displayName = account.display_name.length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
account.note_emojified = emojify(account.note);
return state.set(account.id, fromJS(account)); return state.set(account.id, fromJS(account));
}; };

View File

@ -13,6 +13,8 @@ import {
CONTEXT_FETCH_SUCCESS, CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS, STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS, STATUS_UNMUTE_SUCCESS,
STATUS_SET_HEIGHT,
STATUSES_CLEAR_HEIGHT,
} from '../actions/statuses'; } from '../actions/statuses';
import { import {
TIMELINE_REFRESH_SUCCESS, TIMELINE_REFRESH_SUCCESS,
@ -33,7 +35,11 @@ import {
FAVOURITED_STATUSES_EXPAND_SUCCESS, FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from '../actions/favourites'; } from '../actions/favourites';
import { SEARCH_FETCH_SUCCESS } from '../actions/search'; import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import emojify from '../emoji';
import { Map as ImmutableMap, fromJS } from 'immutable'; import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
const domParser = new DOMParser();
const normalizeStatus = (state, status) => { const normalizeStatus = (state, status) => {
if (!status) { if (!status) {
@ -49,7 +55,9 @@ const normalizeStatus = (state, status) => {
} }
const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n'); const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
normalStatus.search_index = new DOMParser().parseFromString(searchContent, 'text/html').documentElement.textContent; normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(normalStatus.content);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''));
return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus))); return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
}; };
@ -82,6 +90,18 @@ const filterStatuses = (state, relationship) => {
return state; return state;
}; };
const setHeight = (state, id, height) => {
return state.update(id, ImmutableMap(), map => map.set('height', height));
};
const clearHeights = (state) => {
state.forEach(status => {
state = state.deleteIn([status.get('id'), 'height']);
});
return state;
};
const initialState = ImmutableMap(); const initialState = ImmutableMap();
export default function statuses(state = initialState, action) { export default function statuses(state = initialState, action) {
@ -120,6 +140,10 @@ export default function statuses(state = initialState, action) {
return deleteStatus(state, action.id, action.references); return deleteStatus(state, action.id, action.references);
case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_BLOCK_SUCCESS:
return filterStatuses(state, action.relationship); return filterStatuses(state, action.relationship);
case STATUS_SET_HEIGHT:
return setHeight(state, action.id, action.height);
case STATUSES_CLEAR_HEIGHT:
return clearHeights(state);
default: default:
return state; return state;
} }

View File

@ -1,9 +1,9 @@
const easingOutQuint = (x, t, b, c, d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b; const easingOutQuint = (x, t, b, c, d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b;
const scrollTop = (node) => { const scroll = (node, key, target) => {
const startTime = Date.now(); const startTime = Date.now();
const offset = node.scrollTop; const offset = node[key];
const targetY = -offset; const gap = target - offset;
const duration = 1000; const duration = 1000;
let interrupt = false; let interrupt = false;
@ -15,7 +15,7 @@ const scrollTop = (node) => {
return; return;
} }
node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration); node[key] = easingOutQuint(0, elapsed, offset, gap, duration);
requestAnimationFrame(step); requestAnimationFrame(step);
}; };
@ -26,4 +26,5 @@ const scrollTop = (node) => {
}; };
}; };
export default scrollTop; export const scrollRight = (node) => scroll(node, 'scrollLeft', node.scrollWidth);
export const scrollTop = (node) => scroll(node, 'scrollTop', 0);

View File

@ -7,7 +7,7 @@
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
overflow: hidden; overflow: hidden;
@media screen and (max-width: 700px) { @media screen and (max-width: 740px) {
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
} }
@ -298,7 +298,7 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@media screen and (max-width: 700px) { @media screen and (max-width: 740px) {
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
} }

View File

@ -47,7 +47,7 @@ body {
padding: 0; padding: 0;
} }
@media screen and (max-width: 360px) { @media screen and (max-width: 400px) {
padding-bottom: 0; padding-bottom: 0;
} }
} }

View File

@ -3,9 +3,15 @@
font-size: 24px; font-size: 24px;
line-height: 28px; line-height: 28px;
color: $ui-primary-color; color: $ui-primary-color;
overflow: hidden;
font-weight: 500; font-weight: 500;
margin-bottom: 20px; margin-bottom: 20px;
padding: 0 10px;
overflow-wrap: break-word;
@media screen and (max-width: 740px) {
text-align: center;
padding: 20px 10px 0;
}
a { a {
color: inherit; color: inherit;

View File

@ -1835,7 +1835,6 @@
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
flex: 1 1 auto; flex: 1 1 auto;
backface-visibility: hidden;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
@supports(display: grid) { // hack to fix Chrome <57 @supports(display: grid) { // hack to fix Chrome <57
contain: strict; contain: strict;
@ -1853,8 +1852,9 @@
flex: 0 0 auto; flex: 0 0 auto;
font-size: 16px; font-size: 16px;
border: 0; border: 0;
text-align: start; text-align: unset;
padding: 15px; padding: 15px;
margin: 0;
z-index: 3; z-index: 3;
&:hover { &:hover {

View File

@ -3,7 +3,7 @@
margin: 0 auto; margin: 0 auto;
margin-top: 40px; margin-top: 40px;
@media screen and (max-width: 700px) { @media screen and (max-width: 740px) {
width: 100%; width: 100%;
margin: 0; margin: 0;
} }
@ -13,8 +13,9 @@
margin: 100px auto; margin: 100px auto;
margin-bottom: 50px; margin-bottom: 50px;
@media screen and (max-width: 360px) { @media screen and (max-width: 400px) {
margin: 30px auto; margin: 30px auto;
margin-bottom: 20px;
} }
h1 { h1 {
@ -42,3 +43,54 @@
} }
} }
} }
.account-header {
width: 400px;
margin: 0 auto;
display: flex;
font-size: 13px;
line-height: 18px;
box-sizing: border-box;
padding: 20px 0;
padding-bottom: 0;
margin-bottom: -30px;
margin-top: 40px;
@media screen and (max-width: 400px) {
width: 100%;
margin: 0;
margin-bottom: 10px;
padding: 20px;
padding-bottom: 0;
}
.avatar {
width: 40px;
height: 40px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
display: block;
margin: 0;
border-radius: 4px;
}
}
.name {
flex: 1 1 auto;
color: $ui-secondary-color;
.username {
display: block;
font-weight: 500;
}
}
.logout-link {
display: block;
font-size: 32px;
line-height: 40px;
}
}

View File

@ -317,7 +317,7 @@ code {
} }
.flash-message { .flash-message {
background: $ui-base-color; background: lighten($ui-base-color, 8%);
color: $ui-primary-color; color: $ui-primary-color;
border-radius: 4px; border-radius: 4px;
padding: 15px 10px; padding: 15px 10px;

View File

@ -104,7 +104,7 @@ class Formatter
html_attrs = { target: '_blank', rel: 'nofollow noopener' } html_attrs = { target: '_blank', rel: 'nofollow noopener' }
Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), normalized_url, html_attrs) Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), normalized_url, html_attrs)
rescue Addressable::URI::InvalidURIError rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(entity[:url]) encode(entity[:url])
end end

View File

@ -8,7 +8,7 @@ module AccountAvatar
class_methods do class_methods do
def avatar_styles(file) def avatar_styles(file)
styles = { original: '120x120#' } styles = { original: '120x120#' }
styles[:static] = { format: 'png' } if file.content_type == 'image/gif' styles[:static] = { animated: false } if file.content_type == 'image/gif'
styles styles
end end

View File

@ -8,7 +8,7 @@ module AccountHeader
class_methods do class_methods do
def header_styles(file) def header_styles(file)
styles = { original: '700x335#' } styles = { original: '700x335#' }
styles[:static] = { format: 'png' } if file.content_type == 'image/gif' styles[:static] = { animated: false } if file.content_type == 'image/gif'
styles styles
end end

View File

@ -3,10 +3,9 @@
.form-container .form-container
.follow-prompt .follow-prompt
%h2= t('authorize_follow.prompt_html', self: current_account.username)
= render 'card', account: @account = render 'card', account: @account
= form_tag authorize_follow_path, method: :post, class: 'simple_form' do - unless current_account.following?(@account)
= hidden_field_tag :acct, @account.acct = form_tag authorize_follow_path, method: :post, class: 'simple_form' do
= button_tag t('authorize_follow.follow'), type: :submit = hidden_field_tag :acct, @account.acct
= button_tag t('authorize_follow.follow'), type: :submit

View File

@ -0,0 +1,16 @@
- content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do
- if user_signed_in?
.account-header
.avatar= image_tag current_account.avatar.url(:original)
.name
= t 'users.signed_in_as'
%span.username @#{current_account.local_username_and_domain}
= link_to destroy_user_session_path, method: :delete, class: 'logout-link icon-button' do
= fa_icon 'sign-out'
.container= yield
= render template: 'layouts/application'

View File

@ -4,6 +4,7 @@
.compact-header .compact-header
%h1< %h1<
= link_to site_title, root_path = link_to site_title, root_path
%br
%small ##{@tag.name} %small ##{@tag.name}
- if @statuses.empty? - if @statuses.empty?

View File

@ -86,7 +86,7 @@ module Mastodon
config.middleware.use Rack::Deflater config.middleware.use Rack::Deflater
config.to_prepare do config.to_prepare do
Doorkeeper::AuthorizationsController.layout 'public' Doorkeeper::AuthorizationsController.layout 'modal'
Doorkeeper::AuthorizedApplicationsController.layout 'admin' Doorkeeper::AuthorizedApplicationsController.layout 'admin'
Doorkeeper::Application.send :include, ApplicationExtension Doorkeeper::Application.send :include, ApplicationExtension
end end

View File

@ -42,7 +42,9 @@ ignore_missing:
- 'simple_form.{error_notification,required}.:' - 'simple_form.{error_notification,required}.:'
- 'errors.messages.*' - 'errors.messages.*'
- 'activerecord.errors.models.doorkeeper/*' - 'activerecord.errors.models.doorkeeper/*'
- 'sessions.{browsers,platforms}.*'
- 'terms.body_html'
- 'application_mailer.salutation'
ignore_unused: ignore_unused:
- 'activemodel.errors.*' - 'activemodel.errors.*'
- 'activerecord.attributes.*' - 'activerecord.attributes.*'

View File

@ -34,6 +34,11 @@ Doorkeeper.configure do
# https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
# access_token_generator "::Doorkeeper::JWT" # access_token_generator "::Doorkeeper::JWT"
# The controller Doorkeeper::ApplicationController inherits from.
# Defaults to ActionController::Base.
# https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
base_controller 'ApplicationController'
# Reuse access token for the same resource owner within an application (disabled by default) # Reuse access token for the same resource owner within an application (disabled by default)
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
reuse_access_token reuse_access_token

View File

@ -43,7 +43,6 @@ ar:
authorize_follow: authorize_follow:
error: Unfortunately, there was an error looking up the remote account error: Unfortunately, there was an error looking up the remote account
follow: إتبع follow: إتبع
prompt_html: 'You (<strong>%{self}</strong>) have requested to follow:'
title: إتباع %{acct} title: إتباع %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -43,7 +43,6 @@ bg:
authorize_follow: authorize_follow:
error: Възникна грешка в откриването на потребителя error: Възникна грешка в откриването на потребителя
follow: Последвай follow: Последвай
prompt_html: "(<strong>%{self}</strong>), молбата ти беше изпратена до:"
title: Последвай %{acct} title: Последвай %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -185,7 +185,6 @@ ca:
authorize_follow: authorize_follow:
error: Malauradament, ha ocorregut un error buscant el compte remot error: Malauradament, ha ocorregut un error buscant el compte remot
follow: Seguir follow: Seguir
prompt_html: 'Tú (<strong>%{self}</strong>) has solicitat seguir:'
title: Seguir %{acct} title: Seguir %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -166,7 +166,6 @@ de:
authorize_follow: authorize_follow:
error: Das Profil konnte nicht geladen werden error: Das Profil konnte nicht geladen werden
follow: Folgen follow: Folgen
prompt_html: 'Du (<strong>%{self}</strong>) möchtest dieser Person folgen:'
title: "%{acct} folgen" title: "%{acct} folgen"
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -12,9 +12,9 @@ pl:
last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie. last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie.
locked: Twoje konto zostało zablokowane. locked: Twoje konto zostało zablokowane.
not_found_in_database: Nieprawidłowy %{authentication_keys} lub hasło. not_found_in_database: Nieprawidłowy %{authentication_keys} lub hasło.
timeout: Twoja sesja wygasła. Zaloguj się ponownie aby kontynuować.. timeout: Twoja sesja wygasła. Zaloguj się ponownie, aby kontynuować..
unauthenticated: Zapisz się lub zaloguj aby kontynuować. unauthenticated: Zapisz się lub zaloguj, aby kontynuować.
unconfirmed: Zweryfikuj adres e-mail aby kontynuować. unconfirmed: Zweryfikuj adres e-mail, aby kontynuować.
mailer: mailer:
confirmation_instructions: confirmation_instructions:
subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail' subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail'
@ -38,7 +38,7 @@ pl:
signed_up: Twoje konto zostało utworzone. Witamy! signed_up: Twoje konto zostało utworzone. Witamy!
signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane. signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane.
signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane. signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane.
signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik aby aktywować konto. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik, aby aktywować konto. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
updated: Konto zostało zaktualizowane. updated: Konto zostało zaktualizowane.
sessions: sessions:
@ -48,7 +48,7 @@ pl:
unlocks: unlocks:
send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcjami odblokowania konta. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcjami odblokowania konta. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
send_paranoid_instructions: Jeśli Twoje konto istnieje, instrukcje odblokowania go otrzymasz w wiadomości e-mail w ciągu kilku minut. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. send_paranoid_instructions: Jeśli Twoje konto istnieje, instrukcje odblokowania go otrzymasz w wiadomości e-mail w ciągu kilku minut. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem.
unlocked: Twoje konto zostało odblokowane. Zaloguj się aby kontynuować. unlocked: Twoje konto zostało odblokowane. Zaloguj się, aby kontynuować.
errors: errors:
messages: messages:
already_confirmed: był już potwierdzony, spróbuj się zalogować already_confirmed: był już potwierdzony, spróbuj się zalogować

View File

@ -31,7 +31,7 @@ pl:
help: help:
native_redirect_uri: Użyj %{native_redirect_uri} do lokalnych testów native_redirect_uri: Użyj %{native_redirect_uri} do lokalnych testów
redirect_uri: Jeden adres na linię tekstu redirect_uri: Jeden adres na linię tekstu
scopes: Rozdziel zakresy (scopes) spacjami. Zostaw puste aby użyć domyślnych zakresów. scopes: Rozdziel zakresy (scopes) spacjami. Zostaw puste, aby użyć domyślnych zakresów.
index: index:
callback_url: URL wywołania zwrotnego (callback) callback_url: URL wywołania zwrotnego (callback)
name: Nazwa name: Nazwa

View File

@ -215,7 +215,7 @@ en:
body: "%{reporter} has reported %{target}" body: "%{reporter} has reported %{target}"
subject: New report for %{instance} (#%{id}) subject: New report for %{instance} (#%{id})
application_mailer: application_mailer:
salutation: '%{name},' salutation: "%{name},"
settings: 'Change e-mail preferences: %{link}' settings: 'Change e-mail preferences: %{link}'
signature: Mastodon notifications from %{instance} signature: Mastodon notifications from %{instance}
view: 'View:' view: 'View:'
@ -228,6 +228,7 @@ en:
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation. delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
didnt_get_confirmation: Didn't receive confirmation instructions? didnt_get_confirmation: Didn't receive confirmation instructions?
forgot_password: Forgot your password? forgot_password: Forgot your password?
invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.
login: Log in login: Log in
logout: Logout logout: Logout
register: Sign up register: Sign up
@ -243,7 +244,6 @@ en:
close: Or, you can just close this window. close: Or, you can just close this window.
return: Return to the user's profile return: Return to the user's profile
web: Go to web web: Go to web
prompt_html: 'You (<strong>%{self}</strong>) have requested to follow:'
title: Follow %{acct} title: Follow %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -523,3 +523,4 @@ en:
users: users:
invalid_email: The e-mail address is invalid invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code invalid_otp_token: Invalid two-factor code
signed_in_as: 'Signed in as:'

View File

@ -42,7 +42,6 @@ eo:
authorize_follow: authorize_follow:
error: Bedaŭrinde, okazis eraro provante konsulti la foran konton error: Bedaŭrinde, okazis eraro provante konsulti la foran konton
follow: Sekvi follow: Sekvi
prompt_html: 'Vi (<strong>%{self}</strong>) petis sekvi:'
title: Sekvi %{acct} title: Sekvi %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -43,7 +43,6 @@ es:
authorize_follow: authorize_follow:
error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
follow: Seguir follow: Seguir
prompt_html: 'Tú (<strong>%{self}</strong>) has solicitado seguir:'
title: Seguir %{acct} title: Seguir %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -2,7 +2,7 @@
fa: fa:
about: about:
about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی است که بر اساس پروتکل‌های آزاد وب و نرم‌افزارهای آزاد و کدباز ساخته شده است. این شبکه مانند ایمیل غیرمتمرکز است. about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی است که بر اساس پروتکل‌های آزاد وب و نرم‌افزارهای آزاد و کدباز ساخته شده است. این شبکه مانند ایمیل غیرمتمرکز است.
about_this: درباره. about_this: درباره
closed_registrations: ثبت‌نام روی این سرور هم‌اینک فعال نیست. اما شما می‌توانید سرور دیگری بیابید و با حسابی که آن‌جا می‌سازید دقیقاً به همین شبکه دسترسی داشته باشید. closed_registrations: ثبت‌نام روی این سرور هم‌اینک فعال نیست. اما شما می‌توانید سرور دیگری بیابید و با حسابی که آن‌جا می‌سازید دقیقاً به همین شبکه دسترسی داشته باشید.
contact: تماس contact: تماس
contact_missing: تعیین نشده contact_missing: تعیین نشده
@ -20,11 +20,11 @@ fa:
not_a_product_title: شما یک انسان هستید، نه یک محصول not_a_product_title: شما یک انسان هستید، نه یک محصول
real_conversation_body: با ۵۰۰ نویسه برای هر نوشته و با پشتیبانی از هشدارهای موردی برای نوشته‌ها و تصاویر، می‌توانید خود را همان گونه که می‌خواهید ابراز کنید. real_conversation_body: با ۵۰۰ نویسه برای هر نوشته و با پشتیبانی از هشدارهای موردی برای نوشته‌ها و تصاویر، می‌توانید خود را همان گونه که می‌خواهید ابراز کنید.
real_conversation_title: برای گفتگوهای واقعی real_conversation_title: برای گفتگوهای واقعی
within_reach_body: اپ‌های متنوع برای iOS، اندروید، و سیستم‌های دیگر به خاطر وحود یک اکوسیستم API دوستانه برای برنامه‌نویسان. از همه جا با دوستان خود ارتباط داشته باشید. within_reach_body: اپ‌های متنوع برای iOS، اندروید، و سیستم‌های دیگر به خاطر وجود یک اکوسیستم API دوستانه برای برنامه‌نویسان. از همه جا با دوستان خود ارتباط داشته باشید.
within_reach_title: همیشه در دسترس within_reach_title: همیشه در دسترس
find_another_instance: یافتن سرورهای دیگر find_another_instance: یافتن سرورهای دیگر
generic_description: "%{domain} یک سرور روی شبکه است" generic_description: "%{domain} یک سرور روی شبکه است"
hosted_on: ماستدون میزبانی‌شده روی %{domain} hosted_on: ماستدون، میزبانی‌شده روی %{domain}
learn_more: بیشتر بدانید learn_more: بیشتر بدانید
other_instances: فهرست سرورها other_instances: فهرست سرورها
source_code: کدهای منبع source_code: کدهای منبع
@ -212,10 +212,10 @@ fa:
title: مدیریت title: مدیریت
admin_mailer: admin_mailer:
new_report: new_report:
body: "کاربر %{reporter} کاربر %{target} را گزارش داد" body: کاربر %{reporter} کاربر %{target} را گزارش داد
subject: گزارش تازه‌ای برای %{instance} (#%{id}) subject: گزارش تازه‌ای برای %{instance} (#%{id})
application_mailer: application_mailer:
salutation: '%{name},' salutation: "%{name},"
settings: 'تغییر تنظیمات ایمیل: %{link}' settings: 'تغییر تنظیمات ایمیل: %{link}'
signature: اعلان‌های ماستدون از %{instance} signature: اعلان‌های ماستدون از %{instance}
view: 'نمایش:' view: 'نمایش:'
@ -243,7 +243,6 @@ fa:
close: یا این پنجره را ببندید. close: یا این پنجره را ببندید.
return: به نمایهٔ این کاربر بازگردید return: به نمایهٔ این کاربر بازگردید
web: رفتن به وب web: رفتن به وب
prompt_html: 'شما (<strong>%{self}</strong>) می‌خواهید این حساب را پی بگیرید:'
title: پیگیری %{acct} title: پیگیری %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -433,7 +432,7 @@ fa:
sensitive_content: محتوای حساس sensitive_content: محتوای حساس
terms: terms:
body_html: | body_html: |
<h2>Privacy Policy</h2> <h2>سیاست رازداری (Privacy Policy)</h2>
<h3 id="collect">ما چه اطلاعاتی را گردآوری می‌کنیم؟</h3> <h3 id="collect">ما چه اطلاعاتی را گردآوری می‌کنیم؟</h3>
@ -451,7 +450,7 @@ fa:
<li>برای شخصی‌سازی تجربهٔ کاربری شما &mdash; ما به کمک اطلاعات شما بهتر می‌توانیم نیازهای شما را برآورده کنیم.</li> <li>برای شخصی‌سازی تجربهٔ کاربری شما &mdash; ما به کمک اطلاعات شما بهتر می‌توانیم نیازهای شما را برآورده کنیم.</li>
<li>برای بهتر کردن سایت &mdash; ما پیوسته می‌کوشیم تا خدمات این سایت را به کمک اطلاعات و بازخوردی که از شما می‌گیریم بهتر کنیم.</li> <li>برای بهتر کردن سایت &mdash; ما پیوسته می‌کوشیم تا خدمات این سایت را به کمک اطلاعات و بازخوردی که از شما می‌گیریم بهتر کنیم.</li>
<li>برای بهتر کردن خدمات به کاربران &mdash; ما به کمک اطلاعات شما به طور مؤثرتری می‌توانیم به درخواست‌های پشتیبانی شما پاسخ دهیم.</li> <li>برای بهتر کردن خدمات به کاربران &mdash; ما به کمک اطلاعات شما به طور مؤثرتری می‌توانیم به درخواست‌های پشتیبانی شما پاسخ دهیم.</li>
<li>برای فرستادن ایمیل‌های دوره‌ای &mdash; ما گاهی به نشانی ایمیلی که وارد کرده‌اید نامه می‌فرستیم تا درخواست‌های شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.</li> <li>برای فرستادن ایمیل‌های دوره‌ای &mdash; ما گاهی به نشانی ایمیلی که وارد کرده‌اید نامه می‌فرستیم تا به درخواست‌های شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.</li>
</ul> </ul>
<h3 id="protect">ما چگونه از اطلاعات شما محافظت می‌کنیم؟</h3> <h3 id="protect">ما چگونه از اطلاعات شما محافظت می‌کنیم؟</h3>
@ -500,7 +499,7 @@ fa:
<p>این نوشته تحت اجازه‌نامهٔ CC-BY-SA قرار دارد. تاریخ آخرین به‌روزرسانی آن ۱۰ خرداد ۱۳۹۲ است.</p> <p>این نوشته تحت اجازه‌نامهٔ CC-BY-SA قرار دارد. تاریخ آخرین به‌روزرسانی آن ۱۰ خرداد ۱۳۹۲ است.</p>
<p>این نوشته اقتباسی است از <a href="https://github.com/discourse/discourse">سیاست رازداری Discourse</a>.</p> <p>این نوشته اقتباسی است از <a href="https://github.com/discourse/discourse">سیاست رازداری Discourse</a>.</p>
title: "شرایط استفاده و سیاست رازداری %{instance}" title: شرایط استفاده و سیاست رازداری %{instance}
time: time:
formats: formats:
default: "%d %b %Y, %H:%M" default: "%d %b %Y, %H:%M"

View File

@ -42,7 +42,6 @@ fi:
authorize_follow: authorize_follow:
error: Valitettavasti tapahtui virhe etätilin haussa. error: Valitettavasti tapahtui virhe etätilin haussa.
follow: Seuraa follow: Seuraa
prompt_html: 'Sinä (<strong>%{self}</strong>) olet pyytänyt lupaa seurata:'
title: Seuraa %{acct} title: Seuraa %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -220,6 +220,7 @@ fr:
delete_account_html: Si vous désirez supprimer votre compte, vous pouvez cliquer ici. Il vous sera demandé de confirmer cette action. delete_account_html: Si vous désirez supprimer votre compte, vous pouvez cliquer ici. Il vous sera demandé de confirmer cette action.
didnt_get_confirmation: Vous navez pas reçu les consignes de confirmation? didnt_get_confirmation: Vous navez pas reçu les consignes de confirmation?
forgot_password: Mot de passe oublié? forgot_password: Mot de passe oublié?
invalid_reset_password_token: Le lien de réinitialisation du mot de passe est invalide ou a expiré. Merci de réessayer.
login: Se connecter login: Se connecter
logout: Se déconnecter logout: Se déconnecter
register: Sinscrire register: Sinscrire
@ -235,7 +236,6 @@ fr:
close: Ou bien, vous pouvez fermer cette fenêtre. close: Ou bien, vous pouvez fermer cette fenêtre.
return: Retour au profil de l'utilisateur⋅trice return: Retour au profil de l'utilisateur⋅trice
web: Retour à l'interface web web: Retour à l'interface web
prompt_html: 'Vous (<strong>%{self}</strong>) avez demandé à suivre:'
title: Suivre %{acct} title: Suivre %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -278,7 +278,7 @@ fr:
blocks: Vous bloquez blocks: Vous bloquez
csv: CSV csv: CSV
follows: Vous suivez follows: Vous suivez
mutes: Vous faites taire mutes: Vous masquez
storage: Médias stockés storage: Médias stockés
followers: followers:
domain: Domaine domain: Domaine
@ -305,7 +305,7 @@ fr:
types: types:
blocking: Liste dutilisateur⋅ice⋅s bloqué⋅es blocking: Liste dutilisateur⋅ice⋅s bloqué⋅es
following: Liste dutilisateur⋅ice⋅s suivi⋅es following: Liste dutilisateur⋅ice⋅s suivi⋅es
muting: Liste dutilisateur⋅ice⋅s que vous faites taire muting: Liste dutilisateur⋅ice⋅s que vous masquez
upload: Importer upload: Importer
landing_strip_html: <strong>%{name}</strong> utilise %{link_to_root_path}. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse". landing_strip_html: <strong>%{name}</strong> utilise %{link_to_root_path}. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse".
landing_strip_signup_html: Si ce nest pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>. landing_strip_signup_html: Si ce nest pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>.
@ -451,3 +451,4 @@ fr:
users: users:
invalid_email: Ladresse courriel est invalide invalid_email: Ladresse courriel est invalide
invalid_otp_token: Le code dauthentification à deux facteurs est invalide invalid_otp_token: Le code dauthentification à deux facteurs est invalide
signed_in_as: 'Connecté·e en tant que :'

View File

@ -177,7 +177,6 @@ he:
authorize_follow: authorize_follow:
error: למרבה הצער, היתה שגיאה בחיפוש החשבון המרוחק error: למרבה הצער, היתה שגיאה בחיפוש החשבון המרוחק
follow: לעקוב follow: לעקוב
prompt_html: 'בקשת מעקב ממך (<strong>%{self}</strong>) אחרי:'
title: לעקוב אחרי %{acct} title: לעקוב אחרי %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -43,7 +43,6 @@ hr:
authorize_follow: authorize_follow:
error: Nažalost, došlo je do greške looking up the remote račun error: Nažalost, došlo je do greške looking up the remote račun
follow: Slijedi follow: Slijedi
prompt_html: 'Ti si (<strong>%{self}</strong>) poslao zahtjev za sljeđenje:'
title: Slijedi %{acct} title: Slijedi %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -168,7 +168,6 @@ id:
authorize_follow: authorize_follow:
error: Sayangnya, ada error saat melihat akun remote error: Sayangnya, ada error saat melihat akun remote
follow: Ikuti follow: Ikuti
prompt_html: 'Anda (<strong>%{self}</strong>) telah diminta untuk mengikuti:'
title: Mengikuti %{acct} title: Mengikuti %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -166,7 +166,6 @@ io:
authorize_follow: authorize_follow:
error: Regretinde, eventis eraro probante konsultar la fora konto error: Regretinde, eventis eraro probante konsultar la fora konto
follow: Sequar follow: Sequar
prompt_html: 'Tu (<strong>%{self}</strong>) demandis sequar:'
title: Sequar %{acct} title: Sequar %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -43,7 +43,6 @@ it:
authorize_follow: authorize_follow:
error: Sfortunatamente c'è stato un errore nel consultare l'account remoto error: Sfortunatamente c'è stato un errore nel consultare l'account remoto
follow: Segui follow: Segui
prompt_html: 'Tu, (<strong>%{self}</strong>), hai richiesto di seguire:'
title: Segui %{acct} title: Segui %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -234,7 +234,7 @@ ja:
reset_password: パスワードを再発行 reset_password: パスワードを再発行
set_new_password: 新しいパスワード set_new_password: 新しいパスワード
authorize_follow: authorize_follow:
error: 残念ながら、リモートアカウントにエラーが発生しました。 error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました。
follow: フォロー follow: フォロー
follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:' follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:'
following: '成功! あなたは現在以下のアカウントをフォローしています:' following: '成功! あなたは現在以下のアカウントをフォローしています:'
@ -242,7 +242,6 @@ ja:
close: またはこのウィンドウを閉じます close: またはこのウィンドウを閉じます
return: ユーザーのプロフィールに戻る return: ユーザーのプロフィールに戻る
web: Web を開く web: Web を開く
prompt_html: 'あなた(<strong>%{self}</strong>)は以下のアカウントのフォローをリクエストしました:'
title: "%{acct} をフォロー" title: "%{acct} をフォロー"
datetime: datetime:
distance_in_words: distance_in_words:
@ -522,3 +521,4 @@ ja:
users: users:
invalid_email: メールアドレスが無効です invalid_email: メールアドレスが無効です
invalid_otp_token: 二段階認証コードが間違っています invalid_otp_token: 二段階認証コードが間違っています
signed_in_as: '下記でログイン中:'

View File

@ -189,7 +189,6 @@ ko:
authorize_follow: authorize_follow:
error: 리모트 팔로우 도중 오류가 발생했습니다. error: 리모트 팔로우 도중 오류가 발생했습니다.
follow: 팔로우 follow: 팔로우
prompt_html: '나(<strong>%{self}</strong>) 는 아래 계정의 팔로우를 요청했습니다:'
title: "%{acct} 를 팔로우" title: "%{acct} 를 팔로우"
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -106,7 +106,7 @@ nl:
domain: Domein domain: Domein
new: new:
create: Blokkade aanmaken create: Blokkade aanmaken
hint: Een domeinblokkade voorkomt niet dat accountgegevens van dit domein aan de database worden toegevoegd, maar dat er met terugwerkende kracht en automatisch bepaalde moderatiemethoden op deze accounts worden toegepast. hint: Een domeinblokkade voorkomt niet dat accountgegevens van dit domein aan de database worden toegevoegd, maar dat er met terugwerkende kracht en automatisch bepaalde moderatiemethoden op deze accounts worden toegepast.
severity: severity:
desc_html: "<strong>Negeren</strong> zorgt ervoor dat berichten van accounts van dit domein voor iedereen onzichtbaar zijn, behalve als een account wordt gevolgd. <strong>Opschorten</strong> zorgt ervoor dat alle berichten, media en profielgegevens van accounts van dit domein worden verwijderd. Gebruik <strong>Geen</strong> wanneer je alleen mediabestanden wilt weigeren." desc_html: "<strong>Negeren</strong> zorgt ervoor dat berichten van accounts van dit domein voor iedereen onzichtbaar zijn, behalve als een account wordt gevolgd. <strong>Opschorten</strong> zorgt ervoor dat alle berichten, media en profielgegevens van accounts van dit domein worden verwijderd. Gebruik <strong>Geen</strong> wanneer je alleen mediabestanden wilt weigeren."
noop: Geen noop: Geen
@ -129,7 +129,7 @@ nl:
suspend: Alle opgeschorste accounts van dit domein niet meer opschorten suspend: Alle opgeschorste accounts van dit domein niet meer opschorten
title: Domeinblokkade voor %{domain} ongedaan maken title: Domeinblokkade voor %{domain} ongedaan maken
undo: Ongedaan maken undo: Ongedaan maken
title: Domeinblokkades title: Domeinblokkades
undo: Ongedaan maken undo: Ongedaan maken
instances: instances:
account_count: Bekende accounts account_count: Bekende accounts
@ -169,7 +169,7 @@ nl:
title: Bericht wanneer registratie is uitgeschakeld title: Bericht wanneer registratie is uitgeschakeld
deletion: deletion:
desc_html: Toestaan dat iedereen hun eigen account kan verwijderen desc_html: Toestaan dat iedereen hun eigen account kan verwijderen
title: Verwijderen account toestaan title: Verwijderen account toestaan
open: open:
desc_html: Toestaan dat iedereen een account kan registereren desc_html: Toestaan dat iedereen een account kan registereren
title: Open registratie title: Open registratie
@ -220,7 +220,7 @@ nl:
applications: applications:
invalid_url: De opgegeven URL is ongeldig invalid_url: De opgegeven URL is ongeldig
auth: auth:
agreement_html: Wanneer je op registeren klikt ga je akkoord met <a href="%{rules_path}">onze gebruikersvoorwaarden</a> en <a href="%{terms_path}">ons privacybeleid</a>. agreement_html: Wanneer je op registreren klikt ga je akkoord met <a href="%{rules_path}">onze gebruikersvoorwaarden</a> en <a href="%{terms_path}">ons privacybeleid</a>.
change_password: Beveiliging change_password: Beveiliging
delete_account: Account verwijderen delete_account: Account verwijderen
delete_account_html: Wanneer je jouw account graag wilt verwijderen, kan je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging. delete_account_html: Wanneer je jouw account graag wilt verwijderen, kan je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
@ -241,7 +241,6 @@ nl:
close: Of je kan dit venster gewoon sluiten. close: Of je kan dit venster gewoon sluiten.
return: Ga terug naar het profiel van de gebruiker return: Ga terug naar het profiel van de gebruiker
web: Ga naar de webapp web: Ga naar de webapp
prompt_html: 'Je (<strong>%{self}</strong>) hebt toestemming gevraagd om iemand te mogen volgen:'
title: Volg %{acct} title: Volg %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -307,7 +306,7 @@ nl:
following: Volglijst following: Volglijst
muting: Negeerlijst muting: Negeerlijst
upload: Uploaden upload: Uploaden
landing_strip_html: <strong>%{name}</strong> is een gebruiker op %{link_to_root_path}. Je kunt deze volgen en ermee communiceren als je ergens in deze fediverse een account hebt. landing_strip_html: "<strong>%{name}</strong> is een gebruiker op %{link_to_root_path}. Je kunt deze volgen en ermee communiceren als je ergens in deze fediverse een account hebt."
landing_strip_signup_html: Als je dat niet hebt, kun je je <a href="%{sign_up_path}">hier registreren</a>. landing_strip_signup_html: Als je dat niet hebt, kun je je <a href="%{sign_up_path}">hier registreren</a>.
media_attachments: media_attachments:
validations: validations:
@ -510,7 +509,7 @@ nl:
generate_recovery_codes: Herstelcodes genereren generate_recovery_codes: Herstelcodes genereren
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."
lost_recovery_codes: Met herstelcodes kun je toegang tot jouw account krijgen wanneer je jouw telefoon bent kwijtgeraakt. Wanneer je jouw herstelcodes bent kwijtgeraakt, kan je ze hier opnieuw genereren. Jouw oude herstelcodes zijn daarna ongeldig. lost_recovery_codes: Met herstelcodes kun je toegang tot jouw account krijgen wanneer je jouw telefoon bent kwijtgeraakt. Wanneer je jouw herstelcodes bent kwijtgeraakt, kan je ze hier opnieuw genereren. Jouw oude herstelcodes zijn daarna ongeldig.
manual_instructions: 'Hieronder vind je de geheime code in platte tekst. Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren.' manual_instructions: Hieronder vind je de geheime code in platte tekst. Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren.
recovery_codes: Herstelcodes back-uppen recovery_codes: Herstelcodes back-uppen
recovery_codes_regenerated: Opnieuw genereren herstelcodes geslaagd recovery_codes_regenerated: Opnieuw genereren herstelcodes geslaagd
recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. Zorg ervoor dat je de herstelcodes op een veilige plek bewaard. (Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.) recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. Zorg ervoor dat je de herstelcodes op een veilige plek bewaard. (Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.)

View File

@ -170,7 +170,6 @@
authorize_follow: authorize_follow:
error: Uheldigvis så skjedde det en feil da vi prøvde å få tak i en bruker fra en annen instans. error: Uheldigvis så skjedde det en feil da vi prøvde å få tak i en bruker fra en annen instans.
follow: Følg follow: Følg
prompt_html: 'Du (<strong>%{self}</strong>) har spurt om å følge:'
title: Følg %{acct} title: Følg %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -31,7 +31,7 @@ oc:
status_count_after: estatuts status_count_after: estatuts
status_count_before: quan escrich status_count_before: quan escrich
user_count_after: personas user_count_after: personas
user_count_before: Ostal de user_count_before: Ostal de
what_is_mastodon: Ques Mastodon ? what_is_mastodon: Ques Mastodon ?
accounts: accounts:
follow: Sègre follow: Sègre
@ -214,6 +214,7 @@ oc:
body: "%{reporter} a senhalat %{target}" body: "%{reporter} a senhalat %{target}"
subject: Novèl senhalament per %{instance} (#%{id}) subject: Novèl senhalament per %{instance} (#%{id})
application_mailer: application_mailer:
salutation: '%{name},'
settings: 'Cambiar las preferéncias de corrièl : %{link}' settings: 'Cambiar las preferéncias de corrièl : %{link}'
signature: Notificacion de Mastodon sus %{instance} signature: Notificacion de Mastodon sus %{instance}
view: 'Veire :' view: 'Veire :'
@ -232,6 +233,7 @@ oc:
resend_confirmation: Tornar mandar las instruccions de confirmacion resend_confirmation: Tornar mandar las instruccions de confirmacion
reset_password: Reïnicializar lo senhal reset_password: Reïnicializar lo senhal
set_new_password: Picar un nòu senhal set_new_password: Picar un nòu senhal
invalid_reset_password_token: Ligam de reïnicializacion invalid o acabat. Tornatz ensajar se vos plai.
authorize_follow: authorize_follow:
error: O planhèm, i a agut una error al moment de cercar lo compte error: O planhèm, i a agut una error al moment de cercar lo compte
follow: Sègre follow: Sègre
@ -241,7 +243,6 @@ oc:
close: O podètz tampar aquesta fenèstra. close: O podètz tampar aquesta fenèstra.
return: Tornar al perfil return: Tornar al perfil
web: Tornar a linterfàcia Web web: Tornar a linterfàcia Web
prompt_html: 'Avètz (<strong>%{self}</strong>) demandat de sègre :'
title: Sègre %{acct} title: Sègre %{acct}
date: date:
abbr_day_names: abbr_day_names:
@ -579,3 +580,4 @@ oc:
users: users:
invalid_email: Ladreça de corrièl es invalida invalid_email: Ladreça de corrièl es invalida
invalid_otp_token: Còdi dautentificacion en dos temps invalid invalid_otp_token: Còdi dautentificacion en dos temps invalid
signed_in_as: 'Session a'

View File

@ -109,12 +109,14 @@ pl:
hint: Blokada domen nie zabroni tworzenia wpisów kont w bazie danych, ale pozwoli na automatyczną moderację kont do nich należących. hint: Blokada domen nie zabroni tworzenia wpisów kont w bazie danych, ale pozwoli na automatyczną moderację kont do nich należących.
severity: severity:
desc_html: "<strong>Wyciszenie</strong> uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. <strong>Zawieszenie</strong> spowoduje usunięcie całej zawartości dodanej przez użytkownika." desc_html: "<strong>Wyciszenie</strong> uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. <strong>Zawieszenie</strong> spowoduje usunięcie całej zawartości dodanej przez użytkownika."
noop: Nic nie rób
silence: Wycisz silence: Wycisz
suspend: Zawieś suspend: Zawieś
title: Nowa blokada domen title: Nowa blokada domen
reject_media: Odrzucaj pliki multimedialne reject_media: Odrzucaj pliki multimedialne
reject_media_hint: Usuwa przechowywane lokalnie pliki multimedialne i nie pozwala na ich pobieranie. Nieprzydatne przy zawieszeniu reject_media_hint: Usuwa przechowywane lokalnie pliki multimedialne i nie pozwala na ich pobieranie. Nieprzydatne przy zawieszeniu
severities: severities:
noop: Nic nie rób
silence: Wycisz silence: Wycisz
suspend: Zawieś suspend: Zawieś
severity: Priorytet severity: Priorytet
@ -175,8 +177,8 @@ pl:
desc_html: Akapit wprowadzający, widoczny na stronie głównej i znacznikach meta. Możesz korzystać z tagów HTML, w szczególności <code>&lt;a&gt;</code> i <code>&lt;em&gt;</code>. desc_html: Akapit wprowadzający, widoczny na stronie głównej i znacznikach meta. Możesz korzystać z tagów HTML, w szczególności <code>&lt;a&gt;</code> i <code>&lt;em&gt;</code>.
title: Opis instancji title: Opis instancji
site_description_extended: site_description_extended:
desc_html: Dobre miejsce na zasady użytkowania, wprowadzenie i inne rzeczy, które wyróżniają tą instancję. Możesz korzystać z tagów HTML desc_html: Dobre miejsce na zasady użytkowania, wprowadzenie i inne rzeczy, które wyróżniają tę instancję. Możesz korzystać z tagów HTML
title: Niestandrdowy opis strony title: Niestandardowy opis strony
site_terms: site_terms:
desc_html: Miejsce na własną politykę prywatności, zasady użytkowania i inne unormowania prawne. Możesz używać tagów HTML desc_html: Miejsce na własną politykę prywatności, zasady użytkowania i inne unormowania prawne. Możesz używać tagów HTML
title: Niestandardowe zasady użytkowania title: Niestandardowe zasady użytkowania
@ -213,6 +215,7 @@ pl:
body: Użytkownik %{reporter} zgłosił %{target} body: Użytkownik %{reporter} zgłosił %{target}
subject: Nowe zgłoszenie na %{instance} (#%{id}) subject: Nowe zgłoszenie na %{instance} (#%{id})
application_mailer: application_mailer:
salutation: "%{name},"
settings: 'Zmień ustawienia powiadamiania: %{link}' settings: 'Zmień ustawienia powiadamiania: %{link}'
signature: Powiadomienie Mastodona z instancji %{instance} signature: Powiadomienie Mastodona z instancji %{instance}
view: 'Zobacz:' view: 'Zobacz:'
@ -225,6 +228,7 @@ pl:
delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie. delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie.
didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji? didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji?
forgot_password: Nie pamiętasz hasła? forgot_password: Nie pamiętasz hasła?
invalid_reset_password_token: Token do resetowania hasła jest nieprawidłowy lub utracił ważność. Spróbuj uzyskać nowy.
login: Zaloguj się login: Zaloguj się
logout: Wyloguj się logout: Wyloguj się
register: Rejestracja register: Rejestracja
@ -237,10 +241,9 @@ pl:
follow_request: 'Wysłano prośbę o pozwolenie na śledzenie:' follow_request: 'Wysłano prośbę o pozwolenie na śledzenie:'
following: 'Pomyślnie! Od teraz śledzisz:' following: 'Pomyślnie! Od teraz śledzisz:'
post_follow: post_follow:
close: Ewentualnie, możesz po prostu zamknąć tą stronę. close: Ewentualnie, możesz po prostu zamknąć tę stronę.
return: Powróć do strony użytkownika return: Powróć do strony użytkownika
web: Przejdź do sieci web: Przejdź do sieci
prompt_html: 'Ty (<strong>%{self}</strong>) chcesz śledzić:'
title: Śledź %{acct} title: Śledź %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -262,10 +265,10 @@ pl:
description_html: Ta opcja usunie <strong>bezpowrotnie i nieodwracalnie</strong> całą zawartość konta i zdezaktywuje je. Twoja nazwa użytkownika pozostanie zarezerwowana, aby zapobiec nadużyciom. description_html: Ta opcja usunie <strong>bezpowrotnie i nieodwracalnie</strong> całą zawartość konta i zdezaktywuje je. Twoja nazwa użytkownika pozostanie zarezerwowana, aby zapobiec nadużyciom.
proceed: Usuń konto proceed: Usuń konto
success_msg: Twoje konto zostało pomyślnie usunięte success_msg: Twoje konto zostało pomyślnie usunięte
warning_html: Możemy usunąć zawartość jedynie w obrębie tej instancji. Zawartość udostępniona publicznie pozostawia trwałe ślady. Serwery niepodłączone do sieci, bądź nieśledzące Twoich aktualizacji mogą zachować Twoje dane. warning_html: Możemy usunąć zawartość jedynie w obrębie tej instancji. Zawartość udostępniona publicznie pozostawia trwałe ślady. Serwery niepodłączone do sieci bądź nieśledzące Twoich aktualizacji mogą zachować Twoje dane.
warning_title: Dostępność usuniętej zawartości warning_title: Dostępność usuniętej zawartości
errors: errors:
'403': Nie masz uprawnień, aby wyświetlić tą stronę. '403': Nie masz uprawnień, aby wyświetlić tę stronę.
'404': Strona, którą próbujesz odwiedzić, nie istnieje. '404': Strona, którą próbujesz odwiedzić, nie istnieje.
'410': Strona, którą próbujesz odwiedzić, już nie istnieje. '410': Strona, którą próbujesz odwiedzić, już nie istnieje.
'422': '422':
@ -336,8 +339,8 @@ pl:
body: "%{name} poprosił o możliwość śledzenia Cię" body: "%{name} poprosił o możliwość śledzenia Cię"
subject: 'Prośba o możliwość śledzenia: %{name}' subject: 'Prośba o możliwość śledzenia: %{name}'
mention: mention:
body: "%{name} wspomniał Cię w:" body: "%{name} wspomniał o Tobie w:"
subject: "%{name} Cię wspomniał" subject: "%{name} wspomniał o Tobie"
reblog: reblog:
body: 'Twój wpis został podbity przez %{name}:' body: 'Twój wpis został podbity przez %{name}:'
subject: Twój wpis został podbity przez %{name} subject: Twój wpis został podbity przez %{name}
@ -524,3 +527,4 @@ pl:
users: users:
invalid_email: Adres e-mail jest niepoprawny invalid_email: Adres e-mail jest niepoprawny
invalid_otp_token: Kod uwierzytelniający jest niepoprawny invalid_otp_token: Kod uwierzytelniający jest niepoprawny
signed_in_as: 'Zalogowany jako:'

View File

@ -169,7 +169,6 @@ pt-BR:
authorize_follow: authorize_follow:
error: Infelizmente houve um erro olhando uma conta remota error: Infelizmente houve um erro olhando uma conta remota
follow: Seguir follow: Seguir
prompt_html: 'Você (<strong>%{self}</strong>) pediu pra seguir:'
title: Seguir %{acct} title: Seguir %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -162,7 +162,6 @@ ru:
authorize_follow: authorize_follow:
error: К сожалению, при поиске удаленного аккаунта возникла ошибка error: К сожалению, при поиске удаленного аккаунта возникла ошибка
follow: Подписаться follow: Подписаться
prompt_html: 'Вы (<strong>%{self}</strong>) запросили подписку:'
title: Подписаться на %{acct} title: Подписаться на %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
@ -269,14 +268,14 @@ ru:
truncate: "&hellip;" truncate: "&hellip;"
push_notifications: push_notifications:
favourite: favourite:
title: "Ваш статус понравился %{name}" title: Ваш статус понравился %{name}
follow: follow:
title: "%{name} теперь подписан(а) на Вас" title: "%{name} теперь подписан(а) на Вас"
mention: mention:
action_boost: Продвинуть action_boost: Продвинуть
action_expand: Развернуть action_expand: Развернуть
action_favourite: Нравится action_favourite: Нравится
title: "Вас упомянул(а) %{name}" title: Вас упомянул(а) %{name}
reblog: reblog:
title: "%{name} продвинул(а) Ваш статус" title: "%{name} продвинул(а) Ваш статус"
subscribed: subscribed:
@ -351,7 +350,7 @@ ru:
reblogged: продвинул(а) reblogged: продвинул(а)
sensitive_content: Чувствительный контент sensitive_content: Чувствительный контент
terms: terms:
title: "Условия обслуживания и политика конфиденциальности %{instance}" title: Условия обслуживания и политика конфиденциальности %{instance}
time: time:
formats: formats:
default: "%b %d, %Y, %H:%M" default: "%b %d, %Y, %H:%M"

View File

@ -40,7 +40,7 @@ fa:
setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن
setting_default_privacy: حریم خصوصی نوشته‌ها setting_default_privacy: حریم خصوصی نوشته‌ها
setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن
setting_delete_modal: پیش از پاک کردن یک بوق پیغام تأیید نشان بده setting_delete_modal: پیش از پاک کردن یک نوشته پیغام تأیید نشان بده
setting_noindex: درخواست از موتورهای جستجو برای لغو فهرست‌سازی setting_noindex: درخواست از موتورهای جستجو برای لغو فهرست‌سازی
setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم
setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران

View File

@ -170,7 +170,6 @@ th:
authorize_follow: authorize_follow:
error: Unfortunately, there was an error looking up the remote account error: Unfortunately, there was an error looking up the remote account
follow: ติดตาม follow: ติดตาม
prompt_html: 'คุณ (<strong>%{self}</strong>) ขอติดตาม:'
title: ติดตาม %{acct} title: ติดตาม %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -169,7 +169,6 @@ tr:
authorize_follow: authorize_follow:
error: Uzak hesap aranırken bir hata oluştu. error: Uzak hesap aranırken bir hata oluştu.
follow: Takip et follow: Takip et
prompt_html: 'Siz (<strong>%{self}</strong>) bu kullanıcıyı takip etmek istiyor musunuz?:'
title: "%{acct}'i takip et" title: "%{acct}'i takip et"
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -160,7 +160,6 @@ uk:
authorize_follow: authorize_follow:
error: На жаль, при пошуку віддаленого аккаунту виникла помилка error: На жаль, при пошуку віддаленого аккаунту виникла помилка
follow: Підписатися follow: Підписатися
prompt_html: 'Ви (<strong>%{self}</strong>) запитали про підписку:'
title: Підписатися на %{acct} title: Підписатися на %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -176,7 +176,6 @@ zh-CN:
authorize_follow: authorize_follow:
error: 对不起,寻找这个跨站用户时出错 error: 对不起,寻找这个跨站用户时出错
follow: 关注 follow: 关注
prompt_html: 你 (<strong>%{self}</strong>) 正准备关注︰
title: 关注 %{acct} title: 关注 %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -169,7 +169,6 @@ zh-HK:
authorize_follow: authorize_follow:
error: 對不起,尋找這個跨站用戶的過程發生錯誤 error: 對不起,尋找這個跨站用戶的過程發生錯誤
follow: 關注 follow: 關注
prompt_html: 你 (<strong>%{self}</strong>) 正準備關注︰
title: 關注 %{acct} title: 關注 %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -140,7 +140,6 @@ zh-TW:
authorize_follow: authorize_follow:
error: 對不起,尋找這個跨站使用者的過程發生錯誤 error: 對不起,尋找這個跨站使用者的過程發生錯誤
follow: 關注 follow: 關注
prompt_html: 您 (<strong>%{self}</strong>) 正準備關注︰
title: 關注 %{acct} title: 關注 %{acct}
datetime: datetime:
distance_in_words: distance_in_words:

View File

@ -1,3 +1,5 @@
const { resolve } = require('path');
module.exports = { module.exports = {
test: /\.js$/, test: /\.js$/,
// include react-intl because transform-react-remove-prop-types needs to apply to it // include react-intl because transform-react-remove-prop-types needs to apply to it
@ -9,5 +11,6 @@ module.exports = {
options: { options: {
forceEnv: process.env.NODE_ENV || 'development', forceEnv: process.env.NODE_ENV || 'development',
sourceRoot: 'app/javascript', sourceRoot: 'app/javascript',
cacheDirectory: resolve(__dirname, '..', '..', '..', 'tmp', 'cache', 'babel-loader'),
}, },
}; };

View File

@ -13,7 +13,7 @@ module Mastodon
end end
def patch def patch
0 1
end end
def pre def pre

View File

@ -3,6 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe Auth::PasswordsController, type: :controller do describe Auth::PasswordsController, type: :controller do
include Devise::Test::ControllerHelpers
describe 'GET #new' do describe 'GET #new' do
it 'returns http success' do it 'returns http success' do
@request.env['devise.mapping'] = Devise.mappings[:user] @request.env['devise.mapping'] = Devise.mappings[:user]
@ -10,4 +12,27 @@ describe Auth::PasswordsController, type: :controller do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
describe 'GET #edit' do
let(:user) { Fabricate(:user) }
before do
request.env['devise.mapping'] = Devise.mappings[:user]
@token = user.send_reset_password_instructions
end
context 'with valid reset_password_token' do
it 'returns http success' do
get :edit, params: { reset_password_token: @token }
expect(response).to have_http_status(:success)
end
end
context 'with invalid reset_password_token' do
it 'redirects to #new' do
get :edit, params: { reset_password_token: 'some_invalid_value' }
expect(response).to redirect_to subject.new_password_path(subject.send(:resource_name))
end
end
end
end end

View File

@ -28,7 +28,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
sign_in(user, scope: :user) sign_in(user, scope: :user)
delete :destroy delete :destroy
expect(response).to redirect_to(root_path) expect(response).to redirect_to(new_user_session_path)
end end
end end
@ -38,7 +38,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
sign_in(user, scope: :user) sign_in(user, scope: :user)
delete :destroy delete :destroy
expect(response).to redirect_to(root_path) expect(response).to redirect_to(new_user_session_path)
end end
end end
end end

View File

@ -9,19 +9,9 @@ describe('<DisplayName />', () => {
const account = fromJS({ const account = fromJS({
username: 'bar', username: 'bar',
acct: 'bar@baz', acct: 'bar@baz',
display_name: 'Foo', display_name_html: '<p>Foo</p>',
}); });
const wrapper = render(<DisplayName account={account} />); const wrapper = render(<DisplayName account={account} />);
expect(wrapper).to.have.text('Foo @bar@baz'); expect(wrapper).to.have.text('Foo @bar@baz');
}); });
it('renders the username + account name if display name is empty', () => {
const account = fromJS({
username: 'bar',
acct: 'bar@baz',
display_name: '',
});
const wrapper = render(<DisplayName account={account} />);
expect(wrapper).to.have.text('bar @bar@baz');
});
}); });