Merge remote-tracking branch 'upstream/master'
commit
7a1ca8b0df
|
@ -6,6 +6,7 @@ cache:
|
|||
- node_modules
|
||||
- public/assets
|
||||
- public/packs-test
|
||||
- tmp/cache/babel-loader
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
|
|
22
Dockerfile
22
Dockerfile
|
@ -7,6 +7,9 @@ ENV UID=991 GID=991 \
|
|||
RAILS_SERVE_STATIC_FILES=true \
|
||||
RAILS_ENV=production NODE_ENV=production
|
||||
|
||||
ARG LIBICONV_VERSION=1.15
|
||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
||||
|
||||
EXPOSE 3000 4000
|
||||
|
||||
WORKDIR /mastodon
|
||||
|
@ -18,8 +21,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
|
|||
build-base \
|
||||
icu-dev \
|
||||
libidn-dev \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
libtool \
|
||||
postgresql-dev \
|
||||
protobuf-dev \
|
||||
python \
|
||||
|
@ -32,8 +34,6 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
|
|||
imagemagick@edge \
|
||||
libidn \
|
||||
libpq \
|
||||
libxml2 \
|
||||
libxslt \
|
||||
nodejs-npm@edge \
|
||||
nodejs@edge \
|
||||
protobuf \
|
||||
|
@ -41,11 +41,23 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
|
|||
tini \
|
||||
yarn@edge \
|
||||
&& 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/*
|
||||
|
||||
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
|
||||
|
||||
COPY . /mastodon
|
||||
|
|
120
Gemfile.lock
120
Gemfile.lock
|
@ -1,25 +1,25 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.1.2)
|
||||
actionpack (= 5.1.2)
|
||||
actioncable (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.1.2)
|
||||
actionpack (= 5.1.2)
|
||||
actionview (= 5.1.2)
|
||||
activejob (= 5.1.2)
|
||||
actionmailer (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activejob (= 5.1.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.2)
|
||||
actionview (= 5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
actionpack (5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
actionview (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -30,16 +30,16 @@ GEM
|
|||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
|
||||
active_record_query_trace (1.5.4)
|
||||
activejob (5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
activejob (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
activerecord (5.1.2)
|
||||
activemodel (= 5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
activemodel (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
activerecord (5.1.3)
|
||||
activemodel (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
arel (~> 8.0)
|
||||
activesupport (5.1.2)
|
||||
activesupport (5.1.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
|
@ -57,14 +57,14 @@ GEM
|
|||
encryptor (~> 3.0.0)
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-sdk (2.10.6)
|
||||
aws-sdk-resources (= 2.10.6)
|
||||
aws-sdk-core (2.10.6)
|
||||
aws-sdk (2.10.21)
|
||||
aws-sdk-resources (= 2.10.21)
|
||||
aws-sdk-core (2.10.21)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.10.6)
|
||||
aws-sdk-core (= 2.10.6)
|
||||
aws-sigv4 (1.0.0)
|
||||
aws-sdk-resources (2.10.21)
|
||||
aws-sdk-core (= 2.10.21)
|
||||
aws-sigv4 (1.0.1)
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.1.1)
|
||||
coderay (>= 1.0.0)
|
||||
|
@ -72,7 +72,7 @@ GEM
|
|||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.1.1)
|
||||
bootsnap (1.1.2)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (3.6.2)
|
||||
browser (2.4.0)
|
||||
|
@ -155,7 +155,7 @@ GEM
|
|||
et-orbi (1.0.5)
|
||||
tzinfo
|
||||
execjs (2.7.0)
|
||||
fabrication (2.16.1)
|
||||
fabrication (2.16.2)
|
||||
faker (1.7.3)
|
||||
i18n (~> 0.5)
|
||||
fast_blank (1.0.0)
|
||||
|
@ -165,7 +165,7 @@ GEM
|
|||
ruby-progressbar (~> 1.4)
|
||||
globalid (0.4.0)
|
||||
activesupport (>= 4.2.0)
|
||||
goldfinger (2.0.0)
|
||||
goldfinger (2.0.1)
|
||||
addressable (~> 2.5)
|
||||
http (~> 2.2)
|
||||
nokogiri (~> 1.8)
|
||||
|
@ -179,7 +179,7 @@ GEM
|
|||
activesupport (>= 4.0.1)
|
||||
hamlit (>= 1.2.0)
|
||||
railties (>= 4.0.1)
|
||||
hashdiff (0.3.4)
|
||||
hashdiff (0.3.5)
|
||||
highline (1.7.8)
|
||||
hiredis (0.6.1)
|
||||
hkdf (0.3.0)
|
||||
|
@ -194,11 +194,11 @@ GEM
|
|||
http-form_data (1.0.3)
|
||||
http_accept_language (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
httplog (0.99.4)
|
||||
httplog (0.99.7)
|
||||
colorize
|
||||
rack
|
||||
i18n (0.8.4)
|
||||
i18n-tasks (0.9.15)
|
||||
i18n (0.8.6)
|
||||
i18n-tasks (0.9.16)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
easy_translate (>= 0.5.0)
|
||||
|
@ -211,7 +211,7 @@ GEM
|
|||
idn-ruby (0.1.0)
|
||||
jmespath (1.3.1)
|
||||
json (2.1.0)
|
||||
jsonapi-renderer (0.1.2)
|
||||
jsonapi-renderer (0.1.3)
|
||||
jwt (1.5.6)
|
||||
kaminari (1.0.1)
|
||||
activesupport (>= 4.1.0)
|
||||
|
@ -253,7 +253,7 @@ GEM
|
|||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_portile2 (2.2.0)
|
||||
minitest (5.10.2)
|
||||
minitest (5.10.3)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
net-scp (1.2.1)
|
||||
|
@ -264,7 +264,7 @@ GEM
|
|||
mini_portile2 (~> 2.2.0)
|
||||
nokogumbo (1.4.13)
|
||||
nokogiri
|
||||
oj (3.2.0)
|
||||
oj (3.3.4)
|
||||
openssl (2.0.4)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (2.0.1)
|
||||
|
@ -283,14 +283,14 @@ GEM
|
|||
av (~> 0.9.0)
|
||||
paperclip (>= 2.5.2)
|
||||
parallel (1.11.2)
|
||||
parallel_tests (2.14.1)
|
||||
parallel_tests (2.14.2)
|
||||
parallel
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
pg (0.21.0)
|
||||
pghero (1.7.0)
|
||||
activerecord
|
||||
pkg-config (1.2.3)
|
||||
pkg-config (1.2.4)
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
|
@ -313,17 +313,17 @@ GEM
|
|||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rack-timeout (0.4.2)
|
||||
rails (5.1.2)
|
||||
actioncable (= 5.1.2)
|
||||
actionmailer (= 5.1.2)
|
||||
actionpack (= 5.1.2)
|
||||
actionview (= 5.1.2)
|
||||
activejob (= 5.1.2)
|
||||
activemodel (= 5.1.2)
|
||||
activerecord (= 5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 5.1.2)
|
||||
rails (5.1.3)
|
||||
actioncable (= 5.1.3)
|
||||
actionmailer (= 5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activejob (= 5.1.3)
|
||||
activemodel (= 5.1.3)
|
||||
activerecord (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.1.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.2)
|
||||
actionpack (~> 5.x, >= 5.0.1)
|
||||
|
@ -337,11 +337,11 @@ GEM
|
|||
rails-i18n (5.0.4)
|
||||
i18n (~> 0.7)
|
||||
railties (~> 5.0)
|
||||
rails-settings-cached (0.6.5)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (5.1.2)
|
||||
actionpack (= 5.1.2)
|
||||
activesupport (= 5.1.2)
|
||||
railties (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
|
@ -353,7 +353,7 @@ GEM
|
|||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
redis-store (>= 1.1.0, < 1.4.0)
|
||||
redis-activesupport (5.0.2)
|
||||
redis-activesupport (5.0.3)
|
||||
activesupport (>= 3, < 6)
|
||||
redis-store (~> 1.3.0)
|
||||
redis-namespace (1.5.3)
|
||||
|
@ -413,7 +413,7 @@ GEM
|
|||
scss_lint (0.54.0)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.4.20)
|
||||
sidekiq (5.0.3)
|
||||
sidekiq (5.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
|
@ -421,12 +421,12 @@ GEM
|
|||
sidekiq-bulk (0.1.1)
|
||||
activesupport
|
||||
sidekiq
|
||||
sidekiq-scheduler (2.1.7)
|
||||
sidekiq-scheduler (2.1.8)
|
||||
redis (~> 3)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (5.0.8)
|
||||
sidekiq-unique-jobs (5.0.9)
|
||||
sidekiq (>= 4.0, <= 6.0)
|
||||
thor (~> 0)
|
||||
simple-navigation (4.0.5)
|
||||
|
@ -450,15 +450,15 @@ GEM
|
|||
sshkit (1.13.1)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
statsd-instrument (2.1.2)
|
||||
statsd-instrument (2.1.4)
|
||||
temple (0.8.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thor (0.19.4)
|
||||
thread (0.2.2)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.7)
|
||||
twitter-text (1.14.6)
|
||||
tilt (2.0.8)
|
||||
twitter-text (1.14.7)
|
||||
unf (~> 0.1.0)
|
||||
tzinfo (1.2.3)
|
||||
thread_safe (~> 0.1)
|
||||
|
@ -590,4 +590,4 @@ RUBY VERSION
|
|||
ruby 2.4.1p111
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.2
|
||||
1.15.3
|
||||
|
|
|
@ -43,6 +43,10 @@ class ApplicationController < ActionController::Base
|
|||
forbidden if current_user.account.suspended?
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
new_user_session_path
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def forbidden
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Auth::PasswordsController < Devise::PasswordsController
|
||||
before_action :check_validity_of_reset_password_token, only: :edit
|
||||
|
||||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AuthorizeFollowsController < ApplicationController
|
||||
layout 'public'
|
||||
layout 'modal'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoteFollowController < ApplicationController
|
||||
layout 'public'
|
||||
layout 'modal'
|
||||
|
||||
before_action :set_account
|
||||
before_action :gone, if: :suspended_account?
|
||||
|
|
|
@ -23,6 +23,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
|
|||
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
|
||||
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) {
|
||||
return {
|
||||
type: STATUS_FETCH_REQUEST,
|
||||
|
@ -215,3 +218,17 @@ export function unmuteStatusFail(id, error) {
|
|||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function setStatusHeight (id, height) {
|
||||
return {
|
||||
type: STATUS_SET_HEIGHT,
|
||||
id,
|
||||
height,
|
||||
};
|
||||
};
|
||||
|
||||
export function clearStatusesHeight () {
|
||||
return {
|
||||
type: STATUSES_CLEAR_HEIGHT,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import scrollTop from '../scroll';
|
||||
import { scrollTop } from '../scroll';
|
||||
|
||||
export default class Column extends React.PureComponent {
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import emojify from '../emoji';
|
||||
|
||||
export default class DisplayName extends React.PureComponent {
|
||||
|
||||
|
@ -10,12 +8,11 @@ export default class DisplayName extends React.PureComponent {
|
|||
};
|
||||
|
||||
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: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const displayNameHtml = { __html: this.props.account.get('display_name_html') };
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ import DisplayName from './display_name';
|
|||
import StatusContent from './status_content';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import emojify from '../emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
||||
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
|
||||
|
@ -39,6 +37,7 @@ export default class Status extends ImmutablePureComponent {
|
|||
onOpenMedia: PropTypes.func,
|
||||
onOpenVideo: PropTypes.func,
|
||||
onBlock: PropTypes.func,
|
||||
onHeightChange: PropTypes.func,
|
||||
me: PropTypes.number,
|
||||
boostModal: PropTypes.bool,
|
||||
autoPlayGif: PropTypes.bool,
|
||||
|
@ -50,7 +49,6 @@ export default class Status extends ImmutablePureComponent {
|
|||
|
||||
state = {
|
||||
isExpanded: false,
|
||||
isIntersecting: true, // assume intersecting until told otherwise
|
||||
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) {
|
||||
// save the height of the fully-rendered element
|
||||
this.height = getRectFromEntry(entry).height;
|
||||
|
||||
if (this.props.onHeightChange) {
|
||||
this.props.onHeightChange(this.props.status, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState((prevState) => {
|
||||
|
@ -182,9 +184,13 @@ export default class Status extends ImmutablePureComponent {
|
|||
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 (
|
||||
<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.get('content')}
|
||||
</article>
|
||||
|
@ -192,19 +198,13 @@ export default class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
let displayName = status.getIn(['account', 'display_name']);
|
||||
|
||||
if (displayName.length === 0) {
|
||||
displayName = status.getIn(['account', 'username']);
|
||||
}
|
||||
|
||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
return (
|
||||
<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-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>
|
||||
|
||||
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import PropTypes from 'prop-types';
|
||||
import emojify from '../emoji';
|
||||
import { isRtl } from '../rtl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
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 content = { __html: emojify(status.get('content')) };
|
||||
const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||
const directionStyle = { direction: 'ltr' };
|
||||
const classNames = classnames('status__content', {
|
||||
'status__content--with-action': this.props.onClick && this.context.router,
|
||||
|
|
|
@ -105,7 +105,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
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 = (() => {
|
||||
switch (e.key) {
|
||||
case 'PageDown':
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
blockAccount,
|
||||
muteAccount,
|
||||
} from '../actions/accounts';
|
||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
|
||||
import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
|
||||
import { initReport } from '../actions/reports';
|
||||
import { openModal } from '../actions/modal';
|
||||
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));
|
||||
|
|
|
@ -3,34 +3,28 @@ import Trie from 'substring-trie';
|
|||
|
||||
const trie = new Trie(Object.keys(unicodeMapping));
|
||||
|
||||
const excluded = ['™', '©', '®'];
|
||||
|
||||
function emojify(str) {
|
||||
// This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
|
||||
// and replacing valid unicode strings
|
||||
// that _aren't_ within tags with an <img> version.
|
||||
// The goal is to be the same as an emojione.regUnicode replacement, but faster.
|
||||
let i = -1;
|
||||
let insideTag = false;
|
||||
let match;
|
||||
while (++i < str.length) {
|
||||
const char = str.charAt(i);
|
||||
if (insideTag && char === '>') {
|
||||
insideTag = false;
|
||||
} else if (char === '<') {
|
||||
insideTag = true;
|
||||
} else if (!insideTag && (match = trie.search(str.substring(i)))) {
|
||||
const unicodeStr = match;
|
||||
if (unicodeStr in unicodeMapping && excluded.indexOf(unicodeStr) === -1) {
|
||||
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
|
||||
const emojify = str => {
|
||||
let rtn = '';
|
||||
for (;;) {
|
||||
let match, i = 0;
|
||||
while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
|
||||
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
||||
}
|
||||
if (i === str.length)
|
||||
break;
|
||||
else if (str[i] === '<') {
|
||||
let tagend = str.indexOf('>', i + 1) + 1;
|
||||
if (!tagend)
|
||||
break;
|
||||
rtn += str.slice(0, tagend);
|
||||
str = str.slice(tagend);
|
||||
} else {
|
||||
const [filename, shortCode] = unicodeMapping[match];
|
||||
rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
|
||||
str = str.slice(i + match.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return rtn + str;
|
||||
};
|
||||
|
||||
export default emojify;
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
const emojione = require('emojione');
|
||||
|
||||
const mappedUnicode = emojione.mapUnicodeToShort();
|
||||
const excluded = ['®', '©', '™'];
|
||||
|
||||
module.exports.unicodeMapping = Object.keys(emojione.jsEscapeMap)
|
||||
.filter(c => !excluded.includes(c))
|
||||
.map(unicodeStr => [unicodeStr, mappedUnicode[emojione.jsEscapeMap[unicodeStr]]])
|
||||
.map(([unicodeStr, shortCode]) => ({ [unicodeStr]: [emojione.emojioneList[shortCode].fname, shortCode.slice(1, shortCode.length - 1)] }))
|
||||
.reduce((x, y) => Object.assign(x, y), { });
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import emojify from '../../../emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Motion from 'react-motion/lib/Motion';
|
||||
|
@ -95,15 +93,10 @@ export default class Header extends ImmutablePureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
let displayName = account.get('display_name');
|
||||
let info = '';
|
||||
let actionBtn = '';
|
||||
let lockedIcon = '';
|
||||
|
||||
if (displayName.length === 0) {
|
||||
displayName = account.get('username');
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
@ -128,15 +121,15 @@ export default class Header extends ImmutablePureComponent {
|
|||
lockedIcon = <i className='fa fa-lock' />;
|
||||
}
|
||||
|
||||
const content = { __html: emojify(account.get('note')) };
|
||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const content = { __html: account.get('note_emojified') };
|
||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||
|
||||
return (
|
||||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||
<div>
|
||||
<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>
|
||||
<div className='account__header__content' dangerouslySetInnerHTML={content} />
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import emojify from '../../../emoji';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
|
@ -43,7 +42,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: emojify(status.get('content')) };
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
|
||||
return (
|
||||
<div className='reply-indicator'>
|
||||
|
|
|
@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import Permalink from '../../../components/permalink';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import emojify from '../../../emoji';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -26,7 +25,7 @@ export default class AccountAuthorize extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
const { intl, account, onAuthorize, onReject } = this.props;
|
||||
const content = { __html: emojify(account.get('note')) };
|
||||
const content = { __html: account.get('note_emojified') };
|
||||
|
||||
return (
|
||||
<div className='account-authorize__wrapper'>
|
||||
|
|
|
@ -7,8 +7,6 @@ import StatusContainer from '../../../containers/status_container';
|
|||
import AccountContainer from '../../../containers/account_container';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Permalink from '../../../components/permalink';
|
||||
import emojify from '../../../emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
export default class Notification extends ImmutablePureComponent {
|
||||
|
@ -70,9 +68,8 @@ export default class Notification extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { notification } = this.props;
|
||||
const account = notification.get('account');
|
||||
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
|
||||
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 displayNameHtml = { __html: account.get('display_name_html') };
|
||||
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')) {
|
||||
case 'follow':
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import emojify from '../../../emoji';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
export default class StatusCheckBox extends React.PureComponent {
|
||||
|
@ -15,7 +14,7 @@ export default class StatusCheckBox extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { status, checked, onToggle, disabled } = this.props;
|
||||
const content = { __html: emojify(status.get('content')) };
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
|
||||
if (status.get('reblog')) {
|
||||
return null;
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import ColumnHeader from './column_header';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'lodash';
|
||||
import scrollTop from '../../../scroll';
|
||||
import { scrollTop } from '../../../scroll';
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
|
||||
export default class Column extends React.PureComponent {
|
||||
|
|
|
@ -12,6 +12,8 @@ import ColumnLoading from './column_loading';
|
|||
import BundleColumnError from './bundle_column_error';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
||||
|
||||
import { scrollRight } from '../../../scroll';
|
||||
|
||||
const componentMap = {
|
||||
'COMPOSE': Compose,
|
||||
'HOME': HomeTimeline,
|
||||
|
@ -49,9 +51,13 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
this.setState({ shouldAnimate: true });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
componentDidUpdate(prevProps) {
|
||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||
this.setState({ shouldAnimate: true });
|
||||
|
||||
if (this.props.children !== prevProps.children && !this.props.singleColumn) {
|
||||
scrollRight(this.node);
|
||||
}
|
||||
}
|
||||
|
||||
handleSwipe = (index) => {
|
||||
|
@ -74,6 +80,10 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setRef = (node) => {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
renderView = (link, index) => {
|
||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
||||
const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
|
||||
|
@ -114,7 +124,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='columns-area'>
|
||||
<div className='columns-area' ref={this.setRef}>
|
||||
{columns.map(column => {
|
||||
const params = column.get('params', null) === null ? null : column.get('params').toJS();
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { debounce } from 'lodash';
|
|||
import { uploadCompose } from '../../actions/compose';
|
||||
import { refreshHomeTimeline } from '../../actions/timelines';
|
||||
import { refreshNotifications } from '../../actions/notifications';
|
||||
import { clearStatusesHeight } from '../../actions/statuses';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
import UploadArea from './components/upload_area';
|
||||
import ColumnsAreaContainer from './containers/columns_area_container';
|
||||
|
@ -72,6 +73,9 @@ export default class UI extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleResize = debounce(() => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.dispatch(clearStatusesHeight());
|
||||
|
||||
this.setState({ width: window.innerWidth });
|
||||
}, 500, {
|
||||
trailing: true,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"account.block": "مسدودسازی @{name}",
|
||||
"account.block_domain": "پنهانسازی همه چیز از سرور {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
|
||||
"account.edit_profile": "ویرایش نمایه",
|
||||
"account.follow": "پی بگیرید",
|
||||
"account.followers": "پیگیران",
|
||||
|
@ -13,7 +13,7 @@
|
|||
"account.posts": "نوشتهها",
|
||||
"account.report": "گزارش @{name}",
|
||||
"account.requested": "در انتظار پذیرش",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "همرسانی نمایهٔ @{name}",
|
||||
"account.unblock": "رفع انسداد @{name}",
|
||||
"account.unblock_domain": "رفع پنهانسازی از {domain}",
|
||||
"account.unfollow": "پایان پیگیری",
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"account.posts": "Statuts",
|
||||
"account.report": "Signaler",
|
||||
"account.requested": "Invitation envoyée",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "Partager le profil de @{name}",
|
||||
"account.unblock": "Débloquer",
|
||||
"account.unblock_domain": "Ne plus masquer {domain}",
|
||||
"account.unfollow": "Ne plus suivre",
|
||||
|
@ -35,11 +35,11 @@
|
|||
"column.notifications": "Notifications",
|
||||
"column.public": "Fil public global",
|
||||
"column_back_button.label": "Retour",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.hide_settings": "Masquer les paramètres",
|
||||
"column_header.moveLeft_settings": "Déplacer la colonne vers la gauche",
|
||||
"column_header.moveRight_settings": "Déplacer la colonne vers la droite",
|
||||
"column_header.pin": "Épingler",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.show_settings": "Afficher les paramètres",
|
||||
"column_header.unpin": "Retirer",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Paramètres",
|
||||
|
@ -94,8 +94,8 @@
|
|||
"home.column_settings.show_replies": "Afficher les réponses",
|
||||
"home.settings": "Paramètres de la colonne",
|
||||
"lightbox.close": "Fermer",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "Suivant",
|
||||
"lightbox.previous": "Précédent",
|
||||
"loading_indicator.label": "Chargement…",
|
||||
"media_gallery.toggle_visible": "Modifier la visibilité",
|
||||
"missing_indicator.label": "Non trouvé",
|
||||
|
@ -175,7 +175,7 @@
|
|||
"status.report": "Signaler @{name}",
|
||||
"status.sensitive_toggle": "Cliquer pour afficher",
|
||||
"status.sensitive_warning": "Contenu sensible",
|
||||
"status.share": "Share",
|
||||
"status.share": "Partager",
|
||||
"status.show_less": "Replier",
|
||||
"status.show_more": "Déplier",
|
||||
"status.unmute_conversation": "Ne plus masquer la conversation",
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
"emoji_button.search": "Szukaj...",
|
||||
"emoji_button.symbols": "Symbole",
|
||||
"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.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.",
|
||||
|
@ -159,7 +159,7 @@
|
|||
"report.target": "Zgłaszanie {target}",
|
||||
"search.placeholder": "Szukaj",
|
||||
"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.delete": "Usuń",
|
||||
"status.favourite": "Ulubione",
|
||||
|
@ -178,7 +178,7 @@
|
|||
"status.share": "Udostępnij",
|
||||
"status.show_less": "Pokaż mniej",
|
||||
"status.show_more": "Pokaż więcej",
|
||||
"status.unmute_conversation": "Cofnij wyciezenie konwersacji",
|
||||
"status.unmute_conversation": "Cofnij wyciszenie konwersacji",
|
||||
"tabs_bar.compose": "Napisz",
|
||||
"tabs_bar.federated_timeline": "Globalne",
|
||||
"tabs_bar.home": "Strona główna",
|
||||
|
|
|
@ -44,7 +44,9 @@ import {
|
|||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
} from '../actions/favourites';
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
import emojify from '../emoji';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
|
||||
const normalizeAccount = (state, account) => {
|
||||
account = { ...account };
|
||||
|
@ -53,6 +55,10 @@ const normalizeAccount = (state, account) => {
|
|||
delete account.following_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));
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
CONTEXT_FETCH_SUCCESS,
|
||||
STATUS_MUTE_SUCCESS,
|
||||
STATUS_UNMUTE_SUCCESS,
|
||||
STATUS_SET_HEIGHT,
|
||||
STATUSES_CLEAR_HEIGHT,
|
||||
} from '../actions/statuses';
|
||||
import {
|
||||
TIMELINE_REFRESH_SUCCESS,
|
||||
|
@ -33,7 +35,11 @@ import {
|
|||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
} from '../actions/favourites';
|
||||
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
|
||||
import emojify from '../emoji';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
const normalizeStatus = (state, 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');
|
||||
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)));
|
||||
};
|
||||
|
@ -82,6 +90,18 @@ const filterStatuses = (state, relationship) => {
|
|||
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();
|
||||
|
||||
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);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
return filterStatuses(state, action.relationship);
|
||||
case STATUS_SET_HEIGHT:
|
||||
return setHeight(state, action.id, action.height);
|
||||
case STATUSES_CLEAR_HEIGHT:
|
||||
return clearHeights(state);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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 offset = node.scrollTop;
|
||||
const targetY = -offset;
|
||||
const offset = node[key];
|
||||
const gap = target - offset;
|
||||
const duration = 1000;
|
||||
let interrupt = false;
|
||||
|
||||
|
@ -15,7 +15,7 @@ const scrollTop = (node) => {
|
|||
return;
|
||||
}
|
||||
|
||||
node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration);
|
||||
node[key] = easingOutQuint(0, elapsed, offset, gap, duration);
|
||||
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);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
@media screen and (max-width: 740px) {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -298,7 +298,7 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
@media screen and (max-width: 740px) {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ body {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
@media screen and (max-width: 400px) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
color: $ui-primary-color;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 10px;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@media screen and (max-width: 740px) {
|
||||
text-align: center;
|
||||
padding: 20px 10px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
|
|
@ -1835,7 +1835,6 @@
|
|||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
backface-visibility: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
@supports(display: grid) { // hack to fix Chrome <57
|
||||
contain: strict;
|
||||
|
@ -1853,8 +1852,9 @@
|
|||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
text-align: start;
|
||||
text-align: unset;
|
||||
padding: 15px;
|
||||
margin: 0;
|
||||
z-index: 3;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
margin: 0 auto;
|
||||
margin-top: 40px;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
@media screen and (max-width: 740px) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -13,8 +13,9 @@
|
|||
margin: 100px auto;
|
||||
margin-bottom: 50px;
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
@media screen and (max-width: 400px) {
|
||||
margin: 30px auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,7 +317,7 @@ code {
|
|||
}
|
||||
|
||||
.flash-message {
|
||||
background: $ui-base-color;
|
||||
background: lighten($ui-base-color, 8%);
|
||||
color: $ui-primary-color;
|
||||
border-radius: 4px;
|
||||
padding: 15px 10px;
|
||||
|
|
|
@ -104,7 +104,7 @@ class Formatter
|
|||
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
|
||||
|
||||
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])
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module AccountAvatar
|
|||
class_methods do
|
||||
def avatar_styles(file)
|
||||
styles = { original: '120x120#' }
|
||||
styles[:static] = { format: 'png' } if file.content_type == 'image/gif'
|
||||
styles[:static] = { animated: false } if file.content_type == 'image/gif'
|
||||
styles
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module AccountHeader
|
|||
class_methods do
|
||||
def header_styles(file)
|
||||
styles = { original: '700x335#' }
|
||||
styles[:static] = { format: 'png' } if file.content_type == 'image/gif'
|
||||
styles[:static] = { animated: false } if file.content_type == 'image/gif'
|
||||
styles
|
||||
end
|
||||
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
.form-container
|
||||
.follow-prompt
|
||||
%h2= t('authorize_follow.prompt_html', self: current_account.username)
|
||||
|
||||
= render 'card', account: @account
|
||||
|
||||
- unless current_account.following?(@account)
|
||||
= form_tag authorize_follow_path, method: :post, class: 'simple_form' do
|
||||
= hidden_field_tag :acct, @account.acct
|
||||
= button_tag t('authorize_follow.follow'), type: :submit
|
||||
|
|
|
@ -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'
|
|
@ -4,6 +4,7 @@
|
|||
.compact-header
|
||||
%h1<
|
||||
= link_to site_title, root_path
|
||||
%br
|
||||
%small ##{@tag.name}
|
||||
|
||||
- if @statuses.empty?
|
||||
|
|
|
@ -86,7 +86,7 @@ module Mastodon
|
|||
config.middleware.use Rack::Deflater
|
||||
|
||||
config.to_prepare do
|
||||
Doorkeeper::AuthorizationsController.layout 'public'
|
||||
Doorkeeper::AuthorizationsController.layout 'modal'
|
||||
Doorkeeper::AuthorizedApplicationsController.layout 'admin'
|
||||
Doorkeeper::Application.send :include, ApplicationExtension
|
||||
end
|
||||
|
|
|
@ -42,7 +42,9 @@ ignore_missing:
|
|||
- 'simple_form.{error_notification,required}.:'
|
||||
- 'errors.messages.*'
|
||||
- 'activerecord.errors.models.doorkeeper/*'
|
||||
|
||||
- 'sessions.{browsers,platforms}.*'
|
||||
- 'terms.body_html'
|
||||
- 'application_mailer.salutation'
|
||||
ignore_unused:
|
||||
- 'activemodel.errors.*'
|
||||
- 'activerecord.attributes.*'
|
||||
|
|
|
@ -34,6 +34,11 @@ Doorkeeper.configure do
|
|||
# https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
|
||||
# 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)
|
||||
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
|
||||
reuse_access_token
|
||||
|
|
|
@ -43,7 +43,6 @@ ar:
|
|||
authorize_follow:
|
||||
error: Unfortunately, there was an error looking up the remote account
|
||||
follow: إتبع
|
||||
prompt_html: 'You (<strong>%{self}</strong>) have requested to follow:'
|
||||
title: إتباع %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -43,7 +43,6 @@ bg:
|
|||
authorize_follow:
|
||||
error: Възникна грешка в откриването на потребителя
|
||||
follow: Последвай
|
||||
prompt_html: "(<strong>%{self}</strong>), молбата ти беше изпратена до:"
|
||||
title: Последвай %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -185,7 +185,6 @@ ca:
|
|||
authorize_follow:
|
||||
error: Malauradament, ha ocorregut un error buscant el compte remot
|
||||
follow: Seguir
|
||||
prompt_html: 'Tú (<strong>%{self}</strong>) has solicitat seguir:'
|
||||
title: Seguir %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -166,7 +166,6 @@ de:
|
|||
authorize_follow:
|
||||
error: Das Profil konnte nicht geladen werden
|
||||
follow: Folgen
|
||||
prompt_html: 'Du (<strong>%{self}</strong>) möchtest dieser Person folgen:'
|
||||
title: "%{acct} folgen"
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -12,9 +12,9 @@ pl:
|
|||
last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie.
|
||||
locked: Twoje konto zostało zablokowane.
|
||||
not_found_in_database: Nieprawidłowy %{authentication_keys} lub hasło.
|
||||
timeout: Twoja sesja wygasła. Zaloguj się ponownie aby kontynuować..
|
||||
unauthenticated: Zapisz się lub zaloguj aby kontynuować.
|
||||
unconfirmed: Zweryfikuj adres e-mail aby kontynuować.
|
||||
timeout: Twoja sesja wygasła. Zaloguj się ponownie, aby kontynuować..
|
||||
unauthenticated: Zapisz się lub zaloguj, aby kontynuować.
|
||||
unconfirmed: Zweryfikuj adres e-mail, aby kontynuować.
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail'
|
||||
|
@ -38,7 +38,7 @@ pl:
|
|||
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_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.
|
||||
updated: Konto zostało zaktualizowane.
|
||||
sessions:
|
||||
|
@ -48,7 +48,7 @@ pl:
|
|||
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_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:
|
||||
messages:
|
||||
already_confirmed: był już potwierdzony, spróbuj się zalogować
|
||||
|
|
|
@ -31,7 +31,7 @@ pl:
|
|||
help:
|
||||
native_redirect_uri: Użyj %{native_redirect_uri} do lokalnych testów
|
||||
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:
|
||||
callback_url: URL wywołania zwrotnego (callback)
|
||||
name: Nazwa
|
||||
|
|
|
@ -215,7 +215,7 @@ en:
|
|||
body: "%{reporter} has reported %{target}"
|
||||
subject: New report for %{instance} (#%{id})
|
||||
application_mailer:
|
||||
salutation: '%{name},'
|
||||
salutation: "%{name},"
|
||||
settings: 'Change e-mail preferences: %{link}'
|
||||
signature: Mastodon notifications from %{instance}
|
||||
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.
|
||||
didnt_get_confirmation: Didn't receive confirmation instructions?
|
||||
forgot_password: Forgot your password?
|
||||
invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.
|
||||
login: Log in
|
||||
logout: Logout
|
||||
register: Sign up
|
||||
|
@ -243,7 +244,6 @@ en:
|
|||
close: Or, you can just close this window.
|
||||
return: Return to the user's profile
|
||||
web: Go to web
|
||||
prompt_html: 'You (<strong>%{self}</strong>) have requested to follow:'
|
||||
title: Follow %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -523,3 +523,4 @@ en:
|
|||
users:
|
||||
invalid_email: The e-mail address is invalid
|
||||
invalid_otp_token: Invalid two-factor code
|
||||
signed_in_as: 'Signed in as:'
|
||||
|
|
|
@ -42,7 +42,6 @@ eo:
|
|||
authorize_follow:
|
||||
error: Bedaŭrinde, okazis eraro provante konsulti la foran konton
|
||||
follow: Sekvi
|
||||
prompt_html: 'Vi (<strong>%{self}</strong>) petis sekvi:'
|
||||
title: Sekvi %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -43,7 +43,6 @@ es:
|
|||
authorize_follow:
|
||||
error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
|
||||
follow: Seguir
|
||||
prompt_html: 'Tú (<strong>%{self}</strong>) has solicitado seguir:'
|
||||
title: Seguir %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
fa:
|
||||
about:
|
||||
about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی است که بر اساس پروتکلهای آزاد وب و نرمافزارهای آزاد و کدباز ساخته شده است. این شبکه مانند ایمیل غیرمتمرکز است.
|
||||
about_this: درباره.
|
||||
about_this: درباره
|
||||
closed_registrations: ثبتنام روی این سرور هماینک فعال نیست. اما شما میتوانید سرور دیگری بیابید و با حسابی که آنجا میسازید دقیقاً به همین شبکه دسترسی داشته باشید.
|
||||
contact: تماس
|
||||
contact_missing: تعیین نشده
|
||||
|
@ -20,11 +20,11 @@ fa:
|
|||
not_a_product_title: شما یک انسان هستید، نه یک محصول
|
||||
real_conversation_body: با ۵۰۰ نویسه برای هر نوشته و با پشتیبانی از هشدارهای موردی برای نوشتهها و تصاویر، میتوانید خود را همان گونه که میخواهید ابراز کنید.
|
||||
real_conversation_title: برای گفتگوهای واقعی
|
||||
within_reach_body: اپهای متنوع برای iOS، اندروید، و سیستمهای دیگر به خاطر وحود یک اکوسیستم API دوستانه برای برنامهنویسان. از همه جا با دوستان خود ارتباط داشته باشید.
|
||||
within_reach_body: اپهای متنوع برای iOS، اندروید، و سیستمهای دیگر به خاطر وجود یک اکوسیستم API دوستانه برای برنامهنویسان. از همه جا با دوستان خود ارتباط داشته باشید.
|
||||
within_reach_title: همیشه در دسترس
|
||||
find_another_instance: یافتن سرورهای دیگر
|
||||
generic_description: "%{domain} یک سرور روی شبکه است"
|
||||
hosted_on: ماستدون میزبانیشده روی %{domain}
|
||||
hosted_on: ماستدون، میزبانیشده روی %{domain}
|
||||
learn_more: بیشتر بدانید
|
||||
other_instances: فهرست سرورها
|
||||
source_code: کدهای منبع
|
||||
|
@ -212,10 +212,10 @@ fa:
|
|||
title: مدیریت
|
||||
admin_mailer:
|
||||
new_report:
|
||||
body: "کاربر %{reporter} کاربر %{target} را گزارش داد"
|
||||
body: کاربر %{reporter} کاربر %{target} را گزارش داد
|
||||
subject: گزارش تازهای برای %{instance} (#%{id})
|
||||
application_mailer:
|
||||
salutation: '%{name},'
|
||||
salutation: "%{name},"
|
||||
settings: 'تغییر تنظیمات ایمیل: %{link}'
|
||||
signature: اعلانهای ماستدون از %{instance}
|
||||
view: 'نمایش:'
|
||||
|
@ -243,7 +243,6 @@ fa:
|
|||
close: یا این پنجره را ببندید.
|
||||
return: به نمایهٔ این کاربر بازگردید
|
||||
web: رفتن به وب
|
||||
prompt_html: 'شما (<strong>%{self}</strong>) میخواهید این حساب را پی بگیرید:'
|
||||
title: پیگیری %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -433,7 +432,7 @@ fa:
|
|||
sensitive_content: محتوای حساس
|
||||
terms:
|
||||
body_html: |
|
||||
<h2>Privacy Policy</h2>
|
||||
<h2>سیاست رازداری (Privacy Policy)</h2>
|
||||
|
||||
<h3 id="collect">ما چه اطلاعاتی را گردآوری میکنیم؟</h3>
|
||||
|
||||
|
@ -451,7 +450,7 @@ fa:
|
|||
<li>برای شخصیسازی تجربهٔ کاربری شما — ما به کمک اطلاعات شما بهتر میتوانیم نیازهای شما را برآورده کنیم.</li>
|
||||
<li>برای بهتر کردن سایت — ما پیوسته میکوشیم تا خدمات این سایت را به کمک اطلاعات و بازخوردی که از شما میگیریم بهتر کنیم.</li>
|
||||
<li>برای بهتر کردن خدمات به کاربران — ما به کمک اطلاعات شما به طور مؤثرتری میتوانیم به درخواستهای پشتیبانی شما پاسخ دهیم.</li>
|
||||
<li>برای فرستادن ایمیلهای دورهای — ما گاهی به نشانی ایمیلی که وارد کردهاید نامه میفرستیم تا درخواستهای شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.</li>
|
||||
<li>برای فرستادن ایمیلهای دورهای — ما گاهی به نشانی ایمیلی که وارد کردهاید نامه میفرستیم تا به درخواستهای شما پاسخ دهیم یا شما را در جریان پاسخ دیگران به شما قرار دهیم.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="protect">ما چگونه از اطلاعات شما محافظت میکنیم؟</h3>
|
||||
|
@ -500,7 +499,7 @@ fa:
|
|||
<p>این نوشته تحت اجازهنامهٔ CC-BY-SA قرار دارد. تاریخ آخرین بهروزرسانی آن ۱۰ خرداد ۱۳۹۲ است.</p>
|
||||
|
||||
<p>این نوشته اقتباسی است از <a href="https://github.com/discourse/discourse">سیاست رازداری Discourse</a>.</p>
|
||||
title: "شرایط استفاده و سیاست رازداری %{instance}"
|
||||
title: شرایط استفاده و سیاست رازداری %{instance}
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
|
|
|
@ -42,7 +42,6 @@ fi:
|
|||
authorize_follow:
|
||||
error: Valitettavasti tapahtui virhe etätilin haussa.
|
||||
follow: Seuraa
|
||||
prompt_html: 'Sinä (<strong>%{self}</strong>) olet pyytänyt lupaa seurata:'
|
||||
title: Seuraa %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -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.
|
||||
didnt_get_confirmation: Vous n’avez pas reçu les consignes de confirmation ?
|
||||
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
|
||||
logout: Se déconnecter
|
||||
register: S’inscrire
|
||||
|
@ -235,7 +236,6 @@ fr:
|
|||
close: Ou bien, vous pouvez fermer cette fenêtre.
|
||||
return: Retour au profil de l'utilisateur⋅trice
|
||||
web: Retour à l'interface web
|
||||
prompt_html: 'Vous (<strong>%{self}</strong>) avez demandé à suivre :'
|
||||
title: Suivre %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -278,7 +278,7 @@ fr:
|
|||
blocks: Vous bloquez
|
||||
csv: CSV
|
||||
follows: Vous suivez
|
||||
mutes: Vous faites taire
|
||||
mutes: Vous masquez
|
||||
storage: Médias stockés
|
||||
followers:
|
||||
domain: Domaine
|
||||
|
@ -305,7 +305,7 @@ fr:
|
|||
types:
|
||||
blocking: Liste d’utilisateur⋅ice⋅s bloqué⋅es
|
||||
following: Liste d’utilisateur⋅ice⋅s suivi⋅es
|
||||
muting: Liste d’utilisateur⋅ice⋅s que vous faites taire
|
||||
muting: Liste d’utilisateur⋅ice⋅s que vous masquez
|
||||
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_signup_html: Si ce n’est pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>.
|
||||
|
@ -451,3 +451,4 @@ fr:
|
|||
users:
|
||||
invalid_email: L’adresse courriel est invalide
|
||||
invalid_otp_token: Le code d’authentification à deux facteurs est invalide
|
||||
signed_in_as: 'Connecté·e en tant que :'
|
||||
|
|
|
@ -177,7 +177,6 @@ he:
|
|||
authorize_follow:
|
||||
error: למרבה הצער, היתה שגיאה בחיפוש החשבון המרוחק
|
||||
follow: לעקוב
|
||||
prompt_html: 'בקשת מעקב ממך (<strong>%{self}</strong>) אחרי:'
|
||||
title: לעקוב אחרי %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -43,7 +43,6 @@ hr:
|
|||
authorize_follow:
|
||||
error: Nažalost, došlo je do greške looking up the remote račun
|
||||
follow: Slijedi
|
||||
prompt_html: 'Ti si (<strong>%{self}</strong>) poslao zahtjev za sljeđenje:'
|
||||
title: Slijedi %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -168,7 +168,6 @@ id:
|
|||
authorize_follow:
|
||||
error: Sayangnya, ada error saat melihat akun remote
|
||||
follow: Ikuti
|
||||
prompt_html: 'Anda (<strong>%{self}</strong>) telah diminta untuk mengikuti:'
|
||||
title: Mengikuti %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -166,7 +166,6 @@ io:
|
|||
authorize_follow:
|
||||
error: Regretinde, eventis eraro probante konsultar la fora konto
|
||||
follow: Sequar
|
||||
prompt_html: 'Tu (<strong>%{self}</strong>) demandis sequar:'
|
||||
title: Sequar %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -43,7 +43,6 @@ it:
|
|||
authorize_follow:
|
||||
error: Sfortunatamente c'è stato un errore nel consultare l'account remoto
|
||||
follow: Segui
|
||||
prompt_html: 'Tu, (<strong>%{self}</strong>), hai richiesto di seguire:'
|
||||
title: Segui %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -234,7 +234,7 @@ ja:
|
|||
reset_password: パスワードを再発行
|
||||
set_new_password: 新しいパスワード
|
||||
authorize_follow:
|
||||
error: 残念ながら、リモートアカウントにエラーが発生しました。
|
||||
error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました。
|
||||
follow: フォロー
|
||||
follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:'
|
||||
following: '成功! あなたは現在以下のアカウントをフォローしています:'
|
||||
|
@ -242,7 +242,6 @@ ja:
|
|||
close: またはこのウィンドウを閉じます
|
||||
return: ユーザーのプロフィールに戻る
|
||||
web: Web を開く
|
||||
prompt_html: 'あなた(<strong>%{self}</strong>)は以下のアカウントのフォローをリクエストしました:'
|
||||
title: "%{acct} をフォロー"
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -522,3 +521,4 @@ ja:
|
|||
users:
|
||||
invalid_email: メールアドレスが無効です
|
||||
invalid_otp_token: 二段階認証コードが間違っています
|
||||
signed_in_as: '下記でログイン中:'
|
||||
|
|
|
@ -189,7 +189,6 @@ ko:
|
|||
authorize_follow:
|
||||
error: 리모트 팔로우 도중 오류가 발생했습니다.
|
||||
follow: 팔로우
|
||||
prompt_html: '나(<strong>%{self}</strong>) 는 아래 계정의 팔로우를 요청했습니다:'
|
||||
title: "%{acct} 를 팔로우"
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -220,7 +220,7 @@ nl:
|
|||
applications:
|
||||
invalid_url: De opgegeven URL is ongeldig
|
||||
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
|
||||
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.
|
||||
|
@ -241,7 +241,6 @@ nl:
|
|||
close: Of je kan dit venster gewoon sluiten.
|
||||
return: Ga terug naar het profiel van de gebruiker
|
||||
web: Ga naar de webapp
|
||||
prompt_html: 'Je (<strong>%{self}</strong>) hebt toestemming gevraagd om iemand te mogen volgen:'
|
||||
title: Volg %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -307,7 +306,7 @@ nl:
|
|||
following: Volglijst
|
||||
muting: Negeerlijst
|
||||
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>.
|
||||
media_attachments:
|
||||
validations:
|
||||
|
@ -510,7 +509,7 @@ nl:
|
|||
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."
|
||||
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_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.)
|
||||
|
|
|
@ -170,7 +170,6 @@
|
|||
authorize_follow:
|
||||
error: Uheldigvis så skjedde det en feil da vi prøvde å få tak i en bruker fra en annen instans.
|
||||
follow: Følg
|
||||
prompt_html: 'Du (<strong>%{self}</strong>) har spurt om å følge:'
|
||||
title: Følg %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -214,6 +214,7 @@ oc:
|
|||
body: "%{reporter} a senhalat %{target}"
|
||||
subject: Novèl senhalament per %{instance} (#%{id})
|
||||
application_mailer:
|
||||
salutation: '%{name},'
|
||||
settings: 'Cambiar las preferéncias de corrièl : %{link}'
|
||||
signature: Notificacion de Mastodon sus %{instance}
|
||||
view: 'Veire :'
|
||||
|
@ -232,6 +233,7 @@ oc:
|
|||
resend_confirmation: Tornar mandar las instruccions de confirmacion
|
||||
reset_password: Reïnicializar lo 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:
|
||||
error: O planhèm, i a agut una error al moment de cercar lo compte
|
||||
follow: Sègre
|
||||
|
@ -241,7 +243,6 @@ oc:
|
|||
close: O podètz tampar aquesta fenèstra.
|
||||
return: Tornar al perfil
|
||||
web: Tornar a l’interfàcia Web
|
||||
prompt_html: 'Avètz (<strong>%{self}</strong>) demandat de sègre :'
|
||||
title: Sègre %{acct}
|
||||
date:
|
||||
abbr_day_names:
|
||||
|
@ -579,3 +580,4 @@ oc:
|
|||
users:
|
||||
invalid_email: L’adreça de corrièl es invalida
|
||||
invalid_otp_token: Còdi d’autentificacion en dos temps invalid
|
||||
signed_in_as: 'Session a'
|
||||
|
|
|
@ -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.
|
||||
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."
|
||||
noop: Nic nie rób
|
||||
silence: Wycisz
|
||||
suspend: Zawieś
|
||||
title: Nowa blokada domen
|
||||
reject_media: Odrzucaj pliki multimedialne
|
||||
reject_media_hint: Usuwa przechowywane lokalnie pliki multimedialne i nie pozwala na ich pobieranie. Nieprzydatne przy zawieszeniu
|
||||
severities:
|
||||
noop: Nic nie rób
|
||||
silence: Wycisz
|
||||
suspend: Zawieś
|
||||
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><a></code> i <code><em></code>.
|
||||
title: Opis instancji
|
||||
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
|
||||
title: Niestandrdowy opis strony
|
||||
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: Niestandardowy opis strony
|
||||
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
|
||||
title: Niestandardowe zasady użytkowania
|
||||
|
@ -213,6 +215,7 @@ pl:
|
|||
body: Użytkownik %{reporter} zgłosił %{target}
|
||||
subject: Nowe zgłoszenie na %{instance} (#%{id})
|
||||
application_mailer:
|
||||
salutation: "%{name},"
|
||||
settings: 'Zmień ustawienia powiadamiania: %{link}'
|
||||
signature: Powiadomienie Mastodona z instancji %{instance}
|
||||
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.
|
||||
didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji?
|
||||
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ę
|
||||
logout: Wyloguj się
|
||||
register: Rejestracja
|
||||
|
@ -237,10 +241,9 @@ pl:
|
|||
follow_request: 'Wysłano prośbę o pozwolenie na śledzenie:'
|
||||
following: 'Pomyślnie! Od teraz śledzisz:'
|
||||
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
|
||||
web: Przejdź do sieci
|
||||
prompt_html: 'Ty (<strong>%{self}</strong>) chcesz śledzić:'
|
||||
title: Śledź %{acct}
|
||||
datetime:
|
||||
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.
|
||||
proceed: Usuń konto
|
||||
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
|
||||
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.
|
||||
'410': Strona, którą próbujesz odwiedzić, już nie istnieje.
|
||||
'422':
|
||||
|
@ -336,8 +339,8 @@ pl:
|
|||
body: "%{name} poprosił o możliwość śledzenia Cię"
|
||||
subject: 'Prośba o możliwość śledzenia: %{name}'
|
||||
mention:
|
||||
body: "%{name} wspomniał Cię w:"
|
||||
subject: "%{name} Cię wspomniał"
|
||||
body: "%{name} wspomniał o Tobie w:"
|
||||
subject: "%{name} wspomniał o Tobie"
|
||||
reblog:
|
||||
body: 'Twój wpis został podbity przez %{name}:'
|
||||
subject: Twój wpis został podbity przez %{name}
|
||||
|
@ -524,3 +527,4 @@ pl:
|
|||
users:
|
||||
invalid_email: Adres e-mail jest niepoprawny
|
||||
invalid_otp_token: Kod uwierzytelniający jest niepoprawny
|
||||
signed_in_as: 'Zalogowany jako:'
|
||||
|
|
|
@ -169,7 +169,6 @@ pt-BR:
|
|||
authorize_follow:
|
||||
error: Infelizmente houve um erro olhando uma conta remota
|
||||
follow: Seguir
|
||||
prompt_html: 'Você (<strong>%{self}</strong>) pediu pra seguir:'
|
||||
title: Seguir %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -162,7 +162,6 @@ ru:
|
|||
authorize_follow:
|
||||
error: К сожалению, при поиске удаленного аккаунта возникла ошибка
|
||||
follow: Подписаться
|
||||
prompt_html: 'Вы (<strong>%{self}</strong>) запросили подписку:'
|
||||
title: Подписаться на %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
@ -269,14 +268,14 @@ ru:
|
|||
truncate: "…"
|
||||
push_notifications:
|
||||
favourite:
|
||||
title: "Ваш статус понравился %{name}"
|
||||
title: Ваш статус понравился %{name}
|
||||
follow:
|
||||
title: "%{name} теперь подписан(а) на Вас"
|
||||
mention:
|
||||
action_boost: Продвинуть
|
||||
action_expand: Развернуть
|
||||
action_favourite: Нравится
|
||||
title: "Вас упомянул(а) %{name}"
|
||||
title: Вас упомянул(а) %{name}
|
||||
reblog:
|
||||
title: "%{name} продвинул(а) Ваш статус"
|
||||
subscribed:
|
||||
|
@ -351,7 +350,7 @@ ru:
|
|||
reblogged: продвинул(а)
|
||||
sensitive_content: Чувствительный контент
|
||||
terms:
|
||||
title: "Условия обслуживания и политика конфиденциальности %{instance}"
|
||||
title: Условия обслуживания и политика конфиденциальности %{instance}
|
||||
time:
|
||||
formats:
|
||||
default: "%b %d, %Y, %H:%M"
|
||||
|
|
|
@ -40,7 +40,7 @@ fa:
|
|||
setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن
|
||||
setting_default_privacy: حریم خصوصی نوشتهها
|
||||
setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن
|
||||
setting_delete_modal: پیش از پاک کردن یک بوق پیغام تأیید نشان بده
|
||||
setting_delete_modal: پیش از پاک کردن یک نوشته پیغام تأیید نشان بده
|
||||
setting_noindex: درخواست از موتورهای جستجو برای لغو فهرستسازی
|
||||
setting_system_font_ui: بهکاربردن قلم پیشفرض سیستم
|
||||
setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران
|
||||
|
|
|
@ -170,7 +170,6 @@ th:
|
|||
authorize_follow:
|
||||
error: Unfortunately, there was an error looking up the remote account
|
||||
follow: ติดตาม
|
||||
prompt_html: 'คุณ (<strong>%{self}</strong>) ขอติดตาม:'
|
||||
title: ติดตาม %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -169,7 +169,6 @@ tr:
|
|||
authorize_follow:
|
||||
error: Uzak hesap aranırken bir hata oluştu.
|
||||
follow: Takip et
|
||||
prompt_html: 'Siz (<strong>%{self}</strong>) bu kullanıcıyı takip etmek istiyor musunuz?:'
|
||||
title: "%{acct}'i takip et"
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -160,7 +160,6 @@ uk:
|
|||
authorize_follow:
|
||||
error: На жаль, при пошуку віддаленого аккаунту виникла помилка
|
||||
follow: Підписатися
|
||||
prompt_html: 'Ви (<strong>%{self}</strong>) запитали про підписку:'
|
||||
title: Підписатися на %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -176,7 +176,6 @@ zh-CN:
|
|||
authorize_follow:
|
||||
error: 对不起,寻找这个跨站用户时出错
|
||||
follow: 关注
|
||||
prompt_html: 你 (<strong>%{self}</strong>) 正准备关注︰
|
||||
title: 关注 %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -169,7 +169,6 @@ zh-HK:
|
|||
authorize_follow:
|
||||
error: 對不起,尋找這個跨站用戶的過程發生錯誤
|
||||
follow: 關注
|
||||
prompt_html: 你 (<strong>%{self}</strong>) 正準備關注︰
|
||||
title: 關注 %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -140,7 +140,6 @@ zh-TW:
|
|||
authorize_follow:
|
||||
error: 對不起,尋找這個跨站使用者的過程發生錯誤
|
||||
follow: 關注
|
||||
prompt_html: 您 (<strong>%{self}</strong>) 正準備關注︰
|
||||
title: 關注 %{acct}
|
||||
datetime:
|
||||
distance_in_words:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const { resolve } = require('path');
|
||||
|
||||
module.exports = {
|
||||
test: /\.js$/,
|
||||
// include react-intl because transform-react-remove-prop-types needs to apply to it
|
||||
|
@ -9,5 +11,6 @@ module.exports = {
|
|||
options: {
|
||||
forceEnv: process.env.NODE_ENV || 'development',
|
||||
sourceRoot: 'app/javascript',
|
||||
cacheDirectory: resolve(__dirname, '..', '..', '..', 'tmp', 'cache', 'babel-loader'),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def patch
|
||||
0
|
||||
1
|
||||
end
|
||||
|
||||
def pre
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Auth::PasswordsController, type: :controller do
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
describe 'GET #new' do
|
||||
it 'returns http success' do
|
||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
||||
|
@ -10,4 +12,27 @@ describe Auth::PasswordsController, type: :controller do
|
|||
expect(response).to have_http_status(:success)
|
||||
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
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
|
|||
sign_in(user, scope: :user)
|
||||
delete :destroy
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
|
|||
sign_in(user, scope: :user)
|
||||
delete :destroy
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,19 +9,9 @@ describe('<DisplayName />', () => {
|
|||
const account = fromJS({
|
||||
username: 'bar',
|
||||
acct: 'bar@baz',
|
||||
display_name: 'Foo',
|
||||
display_name_html: '<p>Foo</p>',
|
||||
});
|
||||
const wrapper = render(<DisplayName account={account} />);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue