forked from treehouse/mastodon
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts manually resolved: - app/services/post_status_service.rb - config/locales/simple_form.pl.yml - config/routes.rb - config/webpack/loaders/sass.js - config/webpack/shared.js - package.json - yarn.locksignup-info-prompt
commit
571d219bb9
|
@ -3,7 +3,7 @@ version: 2
|
||||||
aliases:
|
aliases:
|
||||||
- &defaults
|
- &defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.5.1-stretch-node
|
- image: circleci/ruby:2.6.0-stretch-node
|
||||||
environment: &ruby_environment
|
environment: &ruby_environment
|
||||||
BUNDLE_APP_CONFIG: ./.bundle/
|
BUNDLE_APP_CONFIG: ./.bundle/
|
||||||
DB_HOST: localhost
|
DB_HOST: localhost
|
||||||
|
@ -98,21 +98,21 @@ jobs:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
<<: *install_steps
|
<<: *install_steps
|
||||||
|
|
||||||
|
install-ruby2.6:
|
||||||
|
<<: *defaults
|
||||||
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
install-ruby2.5:
|
install-ruby2.5:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
docker:
|
||||||
|
- image: circleci/ruby:2.5.3-stretch-node
|
||||||
|
environment: *ruby_environment
|
||||||
<<: *install_ruby_dependencies
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
install-ruby2.4:
|
install-ruby2.4:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.4.4-stretch-node
|
- image: circleci/ruby:2.4.5-stretch-node
|
||||||
environment: *ruby_environment
|
|
||||||
<<: *install_ruby_dependencies
|
|
||||||
|
|
||||||
install-ruby2.3:
|
|
||||||
<<: *defaults
|
|
||||||
docker:
|
|
||||||
- image: circleci/ruby:2.3.7-stretch-node
|
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
<<: *install_ruby_dependencies
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
|
@ -131,43 +131,43 @@ jobs:
|
||||||
- ./mastodon/public/assets
|
- ./mastodon/public/assets
|
||||||
- ./mastodon/public/packs-test/
|
- ./mastodon/public/packs-test/
|
||||||
|
|
||||||
|
test-ruby2.6:
|
||||||
|
<<: *defaults
|
||||||
|
docker:
|
||||||
|
- image: circleci/ruby:2.6.0-stretch-node
|
||||||
|
environment: *ruby_environment
|
||||||
|
- image: circleci/postgres:10.6-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: root
|
||||||
|
- image: circleci/redis:5.0.3-alpine3.8
|
||||||
|
<<: *test_steps
|
||||||
|
|
||||||
test-ruby2.5:
|
test-ruby2.5:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.5.1-stretch-node
|
- image: circleci/ruby:2.5.3-stretch-node
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
- image: circleci/postgres:10.3-alpine
|
- image: circleci/postgres:10.6-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: root
|
POSTGRES_USER: root
|
||||||
- image: circleci/redis:4.0.9-alpine
|
- image: circleci/redis:4.0.12-alpine
|
||||||
<<: *test_steps
|
<<: *test_steps
|
||||||
|
|
||||||
test-ruby2.4:
|
test-ruby2.4:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.4.4-stretch-node
|
- image: circleci/ruby:2.4.5-stretch-node
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
- image: circleci/postgres:10.3-alpine
|
- image: circleci/postgres:10.6-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: root
|
POSTGRES_USER: root
|
||||||
- image: circleci/redis:4.0.9-alpine
|
- image: circleci/redis:4.0.12-alpine
|
||||||
<<: *test_steps
|
|
||||||
|
|
||||||
test-ruby2.3:
|
|
||||||
<<: *defaults
|
|
||||||
docker:
|
|
||||||
- image: circleci/ruby:2.3.7-stretch-node
|
|
||||||
environment: *ruby_environment
|
|
||||||
- image: circleci/postgres:10.3-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: root
|
|
||||||
- image: circleci/redis:4.0.9-alpine
|
|
||||||
<<: *test_steps
|
<<: *test_steps
|
||||||
|
|
||||||
test-webui:
|
test-webui:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.1-stretch
|
- image: circleci/node:8.15.0-stretch
|
||||||
steps:
|
steps:
|
||||||
- *attach_workspace
|
- *attach_workspace
|
||||||
- run: ./bin/retry yarn test:jest
|
- run: ./bin/retry yarn test:jest
|
||||||
|
@ -186,6 +186,10 @@ workflows:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- install
|
- install
|
||||||
|
- install-ruby2.6:
|
||||||
|
requires:
|
||||||
|
- install
|
||||||
|
- install-ruby2.5
|
||||||
- install-ruby2.5:
|
- install-ruby2.5:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
|
@ -193,13 +197,13 @@ workflows:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
- install-ruby2.5
|
- install-ruby2.5
|
||||||
- install-ruby2.3:
|
|
||||||
requires:
|
|
||||||
- install
|
|
||||||
- install-ruby2.5
|
|
||||||
- build:
|
- build:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.5
|
- install-ruby2.5
|
||||||
|
- test-ruby2.6:
|
||||||
|
requires:
|
||||||
|
- install-ruby2.6
|
||||||
|
- build
|
||||||
- test-ruby2.5:
|
- test-ruby2.5:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.5
|
- install-ruby2.5
|
||||||
|
@ -208,10 +212,6 @@ workflows:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.4
|
- install-ruby2.4
|
||||||
- build
|
- build
|
||||||
- test-ruby2.3:
|
|
||||||
requires:
|
|
||||||
- install-ruby2.3
|
|
||||||
- build
|
|
||||||
- test-webui:
|
- test-webui:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
|
|
|
@ -27,7 +27,7 @@ plugins:
|
||||||
enabled: true
|
enabled: true
|
||||||
eslint:
|
eslint:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: eslint-4
|
channel: eslint-5
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-0-54
|
channel: rubocop-0-54
|
||||||
|
|
|
@ -26,6 +26,8 @@ parserOptions:
|
||||||
ecmaVersion: 2018
|
ecmaVersion: 2018
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
react:
|
||||||
|
version: detect
|
||||||
import/extensions:
|
import/extensions:
|
||||||
- .js
|
- .js
|
||||||
import/ignore:
|
import/ignore:
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
plugins:
|
|
||||||
postcss-smart-import: {}
|
|
||||||
precss: {}
|
|
||||||
autoprefixer:
|
|
||||||
browsers:
|
|
||||||
- last 2 versions
|
|
||||||
- IE >= 11
|
|
||||||
- iOS >= 9
|
|
||||||
postcss-object-fit-images: {}
|
|
|
@ -1 +1 @@
|
||||||
2.5.3
|
2.6.0
|
||||||
|
|
|
@ -31,6 +31,8 @@ RUN apk -U upgrade \
|
||||||
libidn-dev \
|
libidn-dev \
|
||||||
libressl \
|
libressl \
|
||||||
libtool \
|
libtool \
|
||||||
|
libxml2-dev \
|
||||||
|
libxslt-dev \
|
||||||
postgresql-dev \
|
postgresql-dev \
|
||||||
protobuf-dev \
|
protobuf-dev \
|
||||||
python \
|
python \
|
||||||
|
@ -43,6 +45,8 @@ RUN apk -U upgrade \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
libidn \
|
libidn \
|
||||||
libpq \
|
libpq \
|
||||||
|
libxml2 \
|
||||||
|
libxslt \
|
||||||
protobuf \
|
protobuf \
|
||||||
tini \
|
tini \
|
||||||
tzdata \
|
tzdata \
|
||||||
|
@ -67,7 +71,7 @@ COPY stack-fix.c /lib
|
||||||
RUN gcc -shared -fPIC /lib/stack-fix.c -o /lib/stack-fix.so
|
RUN gcc -shared -fPIC /lib/stack-fix.c -o /lib/stack-fix.so
|
||||||
RUN rm /lib/stack-fix.c
|
RUN rm /lib/stack-fix.c
|
||||||
|
|
||||||
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
RUN bundle config build.nokogiri --use-system-libraries --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
||||||
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
||||||
&& yarn install --pure-lockfile --ignore-engines \
|
&& yarn install --pure-lockfile --ignore-engines \
|
||||||
&& yarn cache clean
|
&& yarn cache clean
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.3.0', '< 2.6.0'
|
ruby '>= 2.4.0', '< 2.7.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.3'
|
gem 'pkg-config', '~> 1.3'
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.6'
|
gem 'charlock_holmes', '~> 0.7.6'
|
||||||
gem 'iso-639'
|
gem 'iso-639'
|
||||||
gem 'chewy', '~> 5.0'
|
gem 'chewy', '~> 5.0'
|
||||||
gem 'cld3', '~> 3.2.0'
|
gem 'cld3', '~> 3.2.3'
|
||||||
gem 'devise', '~> 4.5'
|
gem 'devise', '~> 4.5'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ group :test do
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec-sidekiq', '~> 3.0'
|
gem 'rspec-sidekiq', '~> 3.0'
|
||||||
gem 'simplecov', '~> 0.16', require: false
|
gem 'simplecov', '~> 0.16', require: false
|
||||||
gem 'webmock', '~> 3.4'
|
gem 'webmock', '~> 3.5'
|
||||||
gem 'parallel_tests', '~> 2.27'
|
gem 'parallel_tests', '~> 2.27'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ GEM
|
||||||
elasticsearch (>= 2.0.0)
|
elasticsearch (>= 2.0.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
chunky_png (1.3.10)
|
chunky_png (1.3.10)
|
||||||
cld3 (3.2.2)
|
cld3 (3.2.3)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.10.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
|
@ -631,7 +631,7 @@ GEM
|
||||||
uniform_notifier (1.12.1)
|
uniform_notifier (1.12.1)
|
||||||
warden (1.2.7)
|
warden (1.2.7)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
webmock (3.4.2)
|
webmock (3.5.1)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
|
@ -672,7 +672,7 @@ DEPENDENCIES
|
||||||
capybara (~> 3.12)
|
capybara (~> 3.12)
|
||||||
charlock_holmes (~> 0.7.6)
|
charlock_holmes (~> 0.7.6)
|
||||||
chewy (~> 5.0)
|
chewy (~> 5.0)
|
||||||
cld3 (~> 3.2.0)
|
cld3 (~> 3.2.3)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
devise (~> 4.5)
|
devise (~> 4.5)
|
||||||
|
@ -766,7 +766,7 @@ DEPENDENCIES
|
||||||
tty-prompt (~> 0.18)
|
tty-prompt (~> 0.18)
|
||||||
twitter-text (~> 1.14)
|
twitter-text (~> 1.14)
|
||||||
tzinfo-data (~> 1.2018)
|
tzinfo-data (~> 1.2018)
|
||||||
webmock (~> 3.4)
|
webmock (~> 3.5)
|
||||||
webpacker (~> 3.5)
|
webpacker (~> 3.5)
|
||||||
webpush
|
webpush
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ sudo apt-get install \
|
||||||
|
|
||||||
# Install rvm
|
# Install rvm
|
||||||
read RUBY_VERSION < .ruby-version
|
read RUBY_VERSION < .ruby-version
|
||||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
|
||||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||||
source /home/vagrant/.rvm/scripts/rvm
|
source /home/vagrant/.rvm/scripts/rvm
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ module Admin
|
||||||
account_action.save!
|
account_action.save!
|
||||||
|
|
||||||
if account_action.with_report?
|
if account_action.with_report?
|
||||||
redirect_to admin_report_path(account_action.report)
|
redirect_to admin_reports_path
|
||||||
else
|
else
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,9 +62,8 @@ module Admin
|
||||||
def redownload
|
def redownload
|
||||||
authorize @account, :redownload?
|
authorize @account, :redownload?
|
||||||
|
|
||||||
@account.reset_avatar!
|
@account.update!(last_webfingered_at: nil)
|
||||||
@account.reset_header!
|
ResolveAccountService.new.call(@account)
|
||||||
@account.save!
|
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class FollowersController < BaseController
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
PER_PAGE = 40
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize :account, :index?
|
||||||
|
@followers = followers.recent.page(params[:page]).per(PER_PAGE)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def followers
|
||||||
|
Follow.includes(:account).where(target_account: @account)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -68,12 +68,14 @@ class Api::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_user!
|
def require_user!
|
||||||
if current_user && !current_user.disabled?
|
if !current_user
|
||||||
set_user_activity
|
|
||||||
elsif current_user
|
|
||||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
|
||||||
else
|
|
||||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||||
|
elsif current_user.disabled?
|
||||||
|
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||||
|
elsif !current_user.confirmed?
|
||||||
|
render json: { error: 'Email confirmation is not completed' }, status: 403
|
||||||
|
else
|
||||||
|
set_user_activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
|
|
||||||
def account_statuses
|
def account_statuses
|
||||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||||
statuses = statuses.paginate_by_id(
|
statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||||
|
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||||
|
|
||||||
statuses
|
statuses
|
||||||
end
|
end
|
||||||
|
@ -65,6 +63,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
Status.without_replies
|
Status.without_replies
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def no_reblogs_scope
|
||||||
|
Status.without_reblogs
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < Api::BaseController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
|
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
|
||||||
|
|
||||||
before_action :require_user!, except: [:show]
|
before_action :require_user!, except: [:show, :create]
|
||||||
before_action :set_account
|
before_action :set_account, except: [:create]
|
||||||
before_action :check_account_suspension, only: [:show]
|
before_action :check_account_suspension, only: [:show]
|
||||||
|
before_action :check_enabled_registrations, only: [:create]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
@ -16,6 +18,16 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
token = AppSignUpService.new.call(doorkeeper_token.application, account_params)
|
||||||
|
response = Doorkeeper::OAuth::TokenResponse.new(token)
|
||||||
|
|
||||||
|
headers.merge!(response.headers)
|
||||||
|
|
||||||
|
self.response_body = Oj.dump(response.body)
|
||||||
|
self.status = response.status
|
||||||
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
||||||
|
|
||||||
|
@ -62,4 +74,12 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
gone if @account.suspended?
|
gone if @account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.permit(:username, :email, :password, :agreement)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_enabled_registrations
|
||||||
|
forbidden if single_user_mode? || !Setting.open_registrations
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||||
before_action :set_user, only: [:finish_signup]
|
before_action :set_user, only: [:finish_signup]
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
|
|
||||||
# GET/PATCH /users/:id/finish_signup
|
|
||||||
def finish_signup
|
def finish_signup
|
||||||
return unless request.patch? && params[:user]
|
return unless request.patch? && params[:user]
|
||||||
|
|
||||||
if @user.update(user_params)
|
if @user.update(user_params)
|
||||||
@user.skip_reconfirmation!
|
@user.skip_reconfirmation!
|
||||||
bypass_sign_in(@user)
|
bypass_sign_in(@user)
|
||||||
|
@ -36,4 +36,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(:email)
|
params.require(:user).permit(:email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_confirmation_path_for(_resource_name, user)
|
||||||
|
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||||
|
user.created_by_application.redirect_uri
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||||
|
|
||||||
resource.locale = I18n.locale
|
resource.locale = I18n.locale
|
||||||
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
||||||
|
resource.agreement = true
|
||||||
|
|
||||||
resource.build_account if resource.account.nil?
|
resource.build_account if resource.account.nil?
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,7 @@ module SignatureVerification
|
||||||
.with_fallback { nil }
|
.with_fallback { nil }
|
||||||
.with_threshold(1)
|
.with_threshold(1)
|
||||||
.with_cool_off_time(5.minutes.seconds)
|
.with_cool_off_time(5.minutes.seconds)
|
||||||
|
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
|
||||||
|
|
||||||
account = account_stoplight.run
|
account = account_stoplight.run
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
module Exports
|
||||||
|
class BlockedDomainsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def export_data
|
||||||
|
@export.to_blocked_domains_csv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
module Exports
|
||||||
|
class ListsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def export_data
|
||||||
|
@export.to_lists_csv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,6 +30,7 @@ module SettingsHelper
|
||||||
ja: '日本語',
|
ja: '日本語',
|
||||||
ka: 'ქართული',
|
ka: 'ქართული',
|
||||||
ko: '한국어',
|
ko: '한국어',
|
||||||
|
ml: 'മലയാളം',
|
||||||
nl: 'Nederlands',
|
nl: 'Nederlands',
|
||||||
no: 'Norsk',
|
no: 'Norsk',
|
||||||
oc: 'Occitan',
|
oc: 'Occitan',
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 27 KiB |
|
@ -130,6 +130,12 @@ export function submitCompose(routerHistory) {
|
||||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||||
},
|
},
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
|
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
||||||
|
routerHistory.push('/timelines/direct');
|
||||||
|
} else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
|
||||||
|
routerHistory.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...response.data }));
|
||||||
|
|
||||||
|
@ -142,12 +148,6 @@ export function submitCompose(routerHistory) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
|
||||||
routerHistory.push('/timelines/direct');
|
|
||||||
} else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
|
|
||||||
routerHistory.goBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data.visibility !== 'direct') {
|
if (response.data.visibility !== 'direct') {
|
||||||
insertIfOnline('home');
|
insertIfOnline('home');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import LinkHeader from 'http-link-header';
|
||||||
import ready from './ready';
|
import ready from './ready';
|
||||||
import LinkHeader from './link_header';
|
|
||||||
|
|
||||||
export const getLinks = response => {
|
export const getLinks = response => {
|
||||||
const value = response.headers.link;
|
const value = response.headers.link;
|
||||||
|
|
|
@ -148,6 +148,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
let replyIcon;
|
||||||
let replyTitle;
|
let replyTitle;
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||||
|
@ -190,8 +191,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('in_reply_to_id', null) === null) {
|
if (status.get('in_reply_to_id', null) === null) {
|
||||||
|
replyIcon = 'reply';
|
||||||
replyTitle = intl.formatMessage(messages.reply);
|
replyTitle = intl.formatMessage(messages.reply);
|
||||||
} else {
|
} else {
|
||||||
|
replyIcon = 'reply-all';
|
||||||
replyTitle = intl.formatMessage(messages.replyAll);
|
replyTitle = intl.formatMessage(messages.replyAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +204,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='status__action-bar'>
|
<div className='status__action-bar'>
|
||||||
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon='reply' onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
|
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
|
||||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||||
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
|
@ -10,8 +10,7 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, { }) => ({
|
const mapStateToProps = () => ({});
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { me, invitesEnabled, version } from '../../initial_state';
|
import { me, invitesEnabled, version, profile_directory } from '../../initial_state';
|
||||||
import { fetchFollowRequests } from '../../actions/accounts';
|
import { fetchFollowRequests } from '../../actions/accounts';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
@ -136,7 +136,7 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='getting-started__footer'>
|
<div className='getting-started__footer'>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href='/explore' target='_blank'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></a> · </li>
|
{profile_directory && <li><a href='/explore' target='_blank'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></a> · </li>}
|
||||||
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||||
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
{multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||||
|
|
|
@ -98,8 +98,8 @@ FrameInteractions.propTypes = {
|
||||||
onNext: PropTypes.func.isRequired,
|
onNext: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
|
export default @connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
|
||||||
export default class Introduction extends React.PureComponent {
|
class Introduction extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusContainer from '../../../containers/status_container';
|
import StatusContainer from '../../../containers/status_container';
|
||||||
import AccountContainer from '../../../containers/account_container';
|
import AccountContainer from '../../../containers/account_container';
|
||||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import Permalink from '../../../components/permalink';
|
import Permalink from '../../../components/permalink';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
@ -86,13 +85,12 @@ class Notification extends ImmutablePureComponent {
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<i className='fa fa-fw fa-user-plus' />
|
<i className='fa fa-fw fa-user-plus' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span title={notification.get('created_at')}>
|
<span title={notification.get('created_at')}>
|
||||||
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
|
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
|
||||||
<span className='notification__relative_time'>
|
|
||||||
<RelativeTimestamp timestamp={notification.get('created_at')} />
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
|
<AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
@ -122,9 +120,9 @@ class Notification extends ImmutablePureComponent {
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<i className='fa fa-fw fa-star star-icon' />
|
<i className='fa fa-fw fa-star star-icon' />
|
||||||
</div>
|
</div>
|
||||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
|
||||||
<span className='notification__relative_time'>
|
<span title={notification.get('created_at')}>
|
||||||
<RelativeTimestamp className='notification__relative_time' timestamp={notification.get('created_at')} />
|
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -144,9 +142,9 @@ class Notification extends ImmutablePureComponent {
|
||||||
<div className='notification__favourite-icon-wrapper'>
|
<div className='notification__favourite-icon-wrapper'>
|
||||||
<i className='fa fa-fw fa-retweet' />
|
<i className='fa fa-fw fa-retweet' />
|
||||||
</div>
|
</div>
|
||||||
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
|
||||||
<span className='notification__relative_time'>
|
<span title={notification.get('created_at')}>
|
||||||
<RelativeTimestamp className='notification__relative_time' timestamp={notification.get('created_at')} />
|
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,13 @@ class ActionBar extends React.PureComponent {
|
||||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
|
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let replyIcon;
|
||||||
|
if (status.get('in_reply_to_id', null) === null) {
|
||||||
|
replyIcon = 'reply';
|
||||||
|
} else {
|
||||||
|
replyIcon = 'reply-all';
|
||||||
|
}
|
||||||
|
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
||||||
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
|
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
|
||||||
|
@ -159,7 +166,7 @@ class ActionBar extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status__action-bar'>
|
<div className='detailed-status__action-bar'>
|
||||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
|
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||||
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
|
@ -6,4 +6,4 @@ const mapStateToProps = state => ({
|
||||||
isModalOpen: !!state.get('modal').modalType,
|
isModalOpen: !!state.get('modal').modalType,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null, null, { withRef: true })(ColumnsArea);
|
export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import LoadingBar from 'react-redux-loading-bar';
|
import LoadingBar from 'react-redux-loading-bar';
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
loading: state.get('loadingBar'),
|
loading: state.get('loadingBar')[ownProps.scope || 'default'],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(LoadingBar.WrappedComponent);
|
export default connect(mapStateToProps)(LoadingBar.WrappedComponent);
|
||||||
|
|
|
@ -134,7 +134,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRef = c => {
|
setRef = c => {
|
||||||
this.node = c.getWrappedInstance().getWrappedInstance();
|
this.node = c.getWrappedInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -16,5 +16,6 @@ export const maxChars = (initialState && initialState.max_toot_chars) || 500;
|
||||||
export const invitesEnabled = getMeta('invites_enabled');
|
export const invitesEnabled = getMeta('invites_enabled');
|
||||||
export const version = getMeta('version');
|
export const version = getMeta('version');
|
||||||
export const mascot = getMeta('mascot');
|
export const mascot = getMeta('mascot');
|
||||||
|
export const profile_directory = getMeta('profile_directory');
|
||||||
|
|
||||||
export default initialState;
|
export default initialState;
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import Link from 'http-link-header';
|
|
||||||
import querystring from 'querystring';
|
|
||||||
|
|
||||||
Link.parseAttrs = (link, parts) => {
|
|
||||||
let match = null;
|
|
||||||
let attr = '';
|
|
||||||
let value = '';
|
|
||||||
let attrs = '';
|
|
||||||
|
|
||||||
let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts);
|
|
||||||
|
|
||||||
if(uriAttrs) {
|
|
||||||
attrs = uriAttrs[2];
|
|
||||||
link = Link.parseParams(link, uriAttrs[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign
|
|
||||||
attr = match[1].toLowerCase();
|
|
||||||
value = match[4] || match[3] || match[2];
|
|
||||||
|
|
||||||
if( /\*$/.test(attr)) {
|
|
||||||
Link.setAttr(link, attr, Link.parseExtendedValue(value));
|
|
||||||
} else if(/%/.test(value)) {
|
|
||||||
Link.setAttr(link, attr, querystring.decode(value));
|
|
||||||
} else {
|
|
||||||
Link.setAttr(link, attr, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Link;
|
|
|
@ -149,22 +149,22 @@
|
||||||
"home.column_settings.basic": "أساسية",
|
"home.column_settings.basic": "أساسية",
|
||||||
"home.column_settings.show_reblogs": "عرض الترقيات",
|
"home.column_settings.show_reblogs": "عرض الترقيات",
|
||||||
"home.column_settings.show_replies": "عرض الردود",
|
"home.column_settings.show_replies": "عرض الردود",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "التالي",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "كافة المنشورات التي نُشِرت إلى العامة على الخوادم الأخرى للفديفرس سوف يتم عرضها على الخيط المُوحَّد.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "سوف تُعرَض منشورات الأشخاص الذين تُتابِعهم على الخيط الرئيسي. بإمكانك متابعة أي حساب أيا كان الخادم الذي هو عليه!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Finish tutorial!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "الإضافة إلى المفضلة",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "يمكِنك إضافة أي تبويق إلى المفضلة و إعلام صاحبه أنك أعجِبت بذاك التبويق.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "الترقية",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "يمكنكم مشاركة تبويقات الأشخاص الآخرين مع متابِعيكم عن طريق ترقيتها.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "الرد",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "هيا بنا!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "الخطوات الأولى",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
||||||
"keyboard_shortcuts.back": "للعودة",
|
"keyboard_shortcuts.back": "للعودة",
|
||||||
"keyboard_shortcuts.blocked": "لفتح قائمة المستخدمين المحظورين",
|
"keyboard_shortcuts.blocked": "لفتح قائمة المستخدمين المحظورين",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
|
"notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
|
||||||
"notifications.column_settings.alert": "إشعارات سطح المكتب",
|
"notifications.column_settings.alert": "إشعارات سطح المكتب",
|
||||||
"notifications.column_settings.favourite": "المُفَضَّلة :",
|
"notifications.column_settings.favourite": "المُفَضَّلة :",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "عرض كافة الفئات",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "شريط الفلترة السريعة",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "عرض",
|
||||||
"notifications.column_settings.follow": "متابعُون جُدُد :",
|
"notifications.column_settings.follow": "متابعُون جُدُد :",
|
||||||
"notifications.column_settings.mention": "الإشارات :",
|
"notifications.column_settings.mention": "الإشارات :",
|
||||||
"notifications.column_settings.push": "الإخطارات المدفوعة",
|
"notifications.column_settings.push": "الإخطارات المدفوعة",
|
||||||
"notifications.column_settings.reblog": "الترقيّات:",
|
"notifications.column_settings.reblog": "الترقيّات:",
|
||||||
"notifications.column_settings.show": "إعرِضها في عمود",
|
"notifications.column_settings.show": "إعرِضها في عمود",
|
||||||
"notifications.column_settings.sound": "أصدر صوتا",
|
"notifications.column_settings.sound": "أصدر صوتا",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "الكل",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "الترقيات",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "المفضلة",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "يتابِع",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "الإشارات",
|
||||||
"notifications.group": "{count} إشعارات",
|
"notifications.group": "{count} إشعارات",
|
||||||
"privacy.change": "إضبط خصوصية المنشور",
|
"privacy.change": "إضبط خصوصية المنشور",
|
||||||
"privacy.direct.long": "أنشر إلى المستخدمين المشار إليهم فقط",
|
"privacy.direct.long": "أنشر إلى المستخدمين المشار إليهم فقط",
|
||||||
|
|
|
@ -341,7 +341,7 @@
|
||||||
"upload_area.title": "Arrossega i deixa anar per carregar",
|
"upload_area.title": "Arrossega i deixa anar per carregar",
|
||||||
"upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
"upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
"upload_form.description": "Descriure els problemes visuals",
|
"upload_form.description": "Descriure els problemes visuals",
|
||||||
"upload_form.focus": "Retallar",
|
"upload_form.focus": "Modificar la previsualització",
|
||||||
"upload_form.undo": "Esborra",
|
"upload_form.undo": "Esborra",
|
||||||
"upload_progress.label": "Pujant...",
|
"upload_progress.label": "Pujant...",
|
||||||
"video.close": "Tancar el vídeo",
|
"video.close": "Tancar el vídeo",
|
||||||
|
|
|
@ -145,27 +145,27 @@
|
||||||
"hashtag.column_settings.tag_mode.all": "Tutti quessi",
|
"hashtag.column_settings.tag_mode.all": "Tutti quessi",
|
||||||
"hashtag.column_settings.tag_mode.any": "Unu di quessi",
|
"hashtag.column_settings.tag_mode.any": "Unu di quessi",
|
||||||
"hashtag.column_settings.tag_mode.none": "Nisunu di quessi",
|
"hashtag.column_settings.tag_mode.none": "Nisunu di quessi",
|
||||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
"hashtag.column_settings.tag_toggle": "Inchjude tag addiziunali per sta colonna",
|
||||||
"home.column_settings.basic": "Bàsichi",
|
"home.column_settings.basic": "Bàsichi",
|
||||||
"home.column_settings.show_reblogs": "Vede e spartere",
|
"home.column_settings.show_reblogs": "Vede e spartere",
|
||||||
"home.column_settings.show_replies": "Vede e risposte",
|
"home.column_settings.show_replies": "Vede e risposte",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Cuntinuà",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federata",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "I statuti pubblichi da l'altri servori di u fediverse saranu mustrati nant'à a linea pubblica federata.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Accolta",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "I statuti da a ghjente che vo siguitate saranu affissati nant'à a linea d'accolta. Pudete seguità qualvogliasia nant'à tutti i servori!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Lucale",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "I statuti pubblichi da quelli chì sò nant'a listessu servore chì voi ponu esse visti indè a linea pubblica lucale.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Finisce u tutoriale!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Favuritu",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Pudete salvà un statutu per ritruvallu più tardi, è fà sapè à l'autore chì v'hè piaciutu, l'aghustendu à i vostri favuriti.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Sparte",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Pudete sparte i statuti d'altre persone à i vostri abbunati cù u buttone di spartera.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Risponde",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Pudete risponde à d'altre persone o a i vostri propii statuti, cio chì i ligarà indè una cunversazione.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Andemu!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Primi passi",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Benvenutu·a indè u fediverse! In qualchi minuta, puderete diffonde missaghji è parlà à i vostri amichi nant'à una varietà maiò di servori. Mà quess'istanza, {domain}, hè speciale—ghjè induve hè uspitatu u vostru prufile, allora ricurdatevi di u so nome.",
|
||||||
"keyboard_shortcuts.back": "rivultà",
|
"keyboard_shortcuts.back": "rivultà",
|
||||||
"keyboard_shortcuts.blocked": "per apre una lista d'utilizatori bluccati",
|
"keyboard_shortcuts.blocked": "per apre una lista d'utilizatori bluccati",
|
||||||
"keyboard_shortcuts.boost": "sparte",
|
"keyboard_shortcuts.boost": "sparte",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
|
"notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
|
||||||
"notifications.column_settings.alert": "Nutificazione nant'à l'urdinatore",
|
"notifications.column_settings.alert": "Nutificazione nant'à l'urdinatore",
|
||||||
"notifications.column_settings.favourite": "Favuriti:",
|
"notifications.column_settings.favourite": "Favuriti:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Affissà tutte e categurie",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Barra di ricerca pronta",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Mustrà",
|
||||||
"notifications.column_settings.follow": "Abbunati novi:",
|
"notifications.column_settings.follow": "Abbunati novi:",
|
||||||
"notifications.column_settings.mention": "Minzione:",
|
"notifications.column_settings.mention": "Minzione:",
|
||||||
"notifications.column_settings.push": "Nutificazione Push",
|
"notifications.column_settings.push": "Nutificazione Push",
|
||||||
"notifications.column_settings.reblog": "Spartere:",
|
"notifications.column_settings.reblog": "Spartere:",
|
||||||
"notifications.column_settings.show": "Mustrà indè a colonna",
|
"notifications.column_settings.show": "Mustrà indè a colonna",
|
||||||
"notifications.column_settings.sound": "Sunà",
|
"notifications.column_settings.sound": "Sunà",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Tuttu",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Spartere",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Favuriti",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Abbunamenti",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Minzione",
|
||||||
"notifications.group": "{count} nutificazione",
|
"notifications.group": "{count} nutificazione",
|
||||||
"privacy.change": "Mudificà a cunfidenzialità di u statutu",
|
"privacy.change": "Mudificà a cunfidenzialità di u statutu",
|
||||||
"privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
|
"privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
|
||||||
|
|
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Základní",
|
"home.column_settings.basic": "Základní",
|
||||||
"home.column_settings.show_reblogs": "Zobrazit boosty",
|
"home.column_settings.show_reblogs": "Zobrazit boosty",
|
||||||
"home.column_settings.show_replies": "Zobrazit odpovědi",
|
"home.column_settings.show_replies": "Zobrazit odpovědi",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Další",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federovaná",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Domů",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Příspěvky od lidí, které sledujete, se objeví ve vašem domovském proudu. Můžete sledovat kohokoliv na jakémkoliv serveru!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Místní",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Veřejné příspěvky od lidí ze stejného serveru, jako vy, se zobrazí na místní časové ose.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Dokončit tutoriál!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Oblíbení",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Oblíbením si můžete uložit toot na později a dát jeho autorovi vědět, že se vám líbí.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Boost",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Boostnutím můžete sdílet tooty jiných lidí s vašimi sledovately.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Odpověď",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Můžete odpovídat na tooty jiných lidí i vaše vlastní, což je propojí do konverzace.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Jdeme na to!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "První kroky",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Vítejte na fediverse! Za malou chvíli budete moci posílat zprávy a povídat si se svými přátely přes širokou škálu serverů. Tento server, {domain}, je však speciální—je na něm váš profil, proto si zapamatujte jeho jméno.",
|
||||||
"keyboard_shortcuts.back": "k návratu zpět",
|
"keyboard_shortcuts.back": "k návratu zpět",
|
||||||
"keyboard_shortcuts.blocked": "k otevření seznamu blokovaných uživatelů",
|
"keyboard_shortcuts.blocked": "k otevření seznamu blokovaných uživatelů",
|
||||||
"keyboard_shortcuts.boost": "k boostnutí",
|
"keyboard_shortcuts.boost": "k boostnutí",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
|
"notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
|
||||||
"notifications.column_settings.alert": "Desktopová oznámení",
|
"notifications.column_settings.alert": "Desktopová oznámení",
|
||||||
"notifications.column_settings.favourite": "Oblíbené:",
|
"notifications.column_settings.favourite": "Oblíbené:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Zobrazit všechny kategorie",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Panel rychlého filtrování",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Zobrazit",
|
||||||
"notifications.column_settings.follow": "Noví sledovatelé:",
|
"notifications.column_settings.follow": "Noví sledovatelé:",
|
||||||
"notifications.column_settings.mention": "Zmínky:",
|
"notifications.column_settings.mention": "Zmínky:",
|
||||||
"notifications.column_settings.push": "Push oznámení",
|
"notifications.column_settings.push": "Push oznámení",
|
||||||
"notifications.column_settings.reblog": "Boosty:",
|
"notifications.column_settings.reblog": "Boosty:",
|
||||||
"notifications.column_settings.show": "Zobrazit ve sloupci",
|
"notifications.column_settings.show": "Zobrazit ve sloupci",
|
||||||
"notifications.column_settings.sound": "Přehrát zvuk",
|
"notifications.column_settings.sound": "Přehrát zvuk",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Vše",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Boosty",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Oblíbení",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Sledování",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Zmínky",
|
||||||
"notifications.group": "{count} oznámení",
|
"notifications.group": "{count} oznámení",
|
||||||
"privacy.change": "Změnit soukromí příspěvku",
|
"privacy.change": "Změnit soukromí příspěvku",
|
||||||
"privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
|
"privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"account.follows_you": "Folgt dir",
|
"account.follows_you": "Folgt dir",
|
||||||
"account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
|
"account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
|
||||||
"account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
|
"account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
|
||||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
"account.locked_info": "Der Privatsphärenstatus dieses Accounts wurde auf gesperrt gesetzt. Die Person bestimmt manuell wer ihm/ihr folgen darf.",
|
||||||
"account.media": "Medien",
|
"account.media": "Medien",
|
||||||
"account.mention": "@{name} erwähnen",
|
"account.mention": "@{name} erwähnen",
|
||||||
"account.moved_to": "{name} ist umgezogen auf:",
|
"account.moved_to": "{name} ist umgezogen auf:",
|
||||||
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Einfach",
|
"home.column_settings.basic": "Einfach",
|
||||||
"home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
|
"home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
|
||||||
"home.column_settings.show_replies": "Antworten anzeigen",
|
"home.column_settings.show_replies": "Antworten anzeigen",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Weiter",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Öffentliche Beiträge von anderen Servern im Fediverse werden in der föderierten Zeitleiste erscheinen.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Beiträge von Leuten, denen du folgst werden in deiner Startseite erscheinen. Du kannst jedem auf irgendeinen Server folgen!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Öffentliche Beiträge von Leuten auf demselben Server wie du werden in der lokalen Zeitleiste erscheinen.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Tutorial beenden!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Favorisieren",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Du kannst einen Beitrag für später speichern und dem Autor wissen lassen, dass du ihn magst, indem du ihn favorisierst.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Teilen",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Du kannst Beiträge von anderen Leuten an deine Follower teilen (oder auch \"boosten\").",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Antworten",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Du kannst auf die Beiträge von anderen Leuten antworten und die Beiträge werden dann in eine Konversation zusammengebunden.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Lasst uns loslegen!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Erste Schritte",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Willkommen im Fediverse! In wenigen Momenten wirst du in der Lage sein Nachrichten zu versenden und mit deinen Freunden über Server hinweg in Kontakt zu treten. Aber dieser Server, {domain}, ist sehr speziell — er hostet dein Profil, also merke dir den Namen.",
|
||||||
"keyboard_shortcuts.back": "zurück navigieren",
|
"keyboard_shortcuts.back": "zurück navigieren",
|
||||||
"keyboard_shortcuts.blocked": "Liste blockierter Profile öffnen",
|
"keyboard_shortcuts.blocked": "Liste blockierter Profile öffnen",
|
||||||
"keyboard_shortcuts.boost": "boosten",
|
"keyboard_shortcuts.boost": "boosten",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?",
|
"notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?",
|
||||||
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
||||||
"notifications.column_settings.favourite": "Favorisierungen:",
|
"notifications.column_settings.favourite": "Favorisierungen:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Zeige alle Kategorien an",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Schnellfilterleiste",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Anzeigen",
|
||||||
"notifications.column_settings.follow": "Neue Folgende:",
|
"notifications.column_settings.follow": "Neue Folgende:",
|
||||||
"notifications.column_settings.mention": "Erwähnungen:",
|
"notifications.column_settings.mention": "Erwähnungen:",
|
||||||
"notifications.column_settings.push": "Push-Benachrichtigungen",
|
"notifications.column_settings.push": "Push-Benachrichtigungen",
|
||||||
"notifications.column_settings.reblog": "Geteilte Beiträge:",
|
"notifications.column_settings.reblog": "Geteilte Beiträge:",
|
||||||
"notifications.column_settings.show": "In der Spalte anzeigen",
|
"notifications.column_settings.show": "In der Spalte anzeigen",
|
||||||
"notifications.column_settings.sound": "Ton abspielen",
|
"notifications.column_settings.sound": "Ton abspielen",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Alle",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Boosts",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Favoriten",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Follows",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Erwähnungen",
|
||||||
"notifications.group": "{count} Benachrichtigungen",
|
"notifications.group": "{count} Benachrichtigungen",
|
||||||
"privacy.change": "Sichtbarkeit des Beitrags anpassen",
|
"privacy.change": "Sichtbarkeit des Beitrags anpassen",
|
||||||
"privacy.direct.long": "Beitrag nur an erwähnte Profile",
|
"privacy.direct.long": "Beitrag nur an erwähnte Profile",
|
||||||
|
|
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Βασικά",
|
"home.column_settings.basic": "Βασικά",
|
||||||
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
|
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
|
||||||
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
|
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Επόμενο",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Οι δημόσιες αναρτήσεις από άλλους κόμβους του fediverse θα εμφανίζονται στην ομοσπονδιακή ροή.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Οι αναρτήσεις όσων ακολουθείς θα εμφανίζονται στην αρχική ροή. Μπορείς να ακολουθήσεις όποιον θέλεις σε οποιονδήποτε κόμβο!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Οι δημόσιες αναρτήσεις από άτομα στον ίδιο κόμβο με εσένα θα εμφανίζονται στην τοπική ροή.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Τέλος μαθήματος!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Αγαπημένο",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Φύλαξε ένα τουτ για αργότερα και να ειδοποιήσεις τον δημιουργό του ότι σου άρεσε σημειώνοντας το ως αγαπημένο.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Προώθηση",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Μοιράσου τουτ άλλων χρηστών με όσους σε ακολουθούν προωθώντας τα.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Απάντηση",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Μπορείς να απαντήσεις στα τουτ άλλων αλλά ακόμα και στα δικά σου, δένοντας τα όλα μαζί σε μια συζήτηση.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Ας ξεκινήσουμε!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Πρώτα βήματα",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Καλώς ήρθες στο fediverse! Σε πολύ λίγο θα μπορείς να στέλνεις δημοσιεύσεις και να μιλάς με τους φίλους σου σε πολλούς, διαφορετικούς κόμβους. Ο κόμβος {domain} όμως είναι ξεχωριστός — φιλοξενεί τον λογαριασμό σου, για αυτό μα θυμάσαι το όνομά του.",
|
||||||
"keyboard_shortcuts.back": "επιστροφή",
|
"keyboard_shortcuts.back": "επιστροφή",
|
||||||
"keyboard_shortcuts.blocked": "άνοιγμα λίστας αποκλεισμένων χρηστών",
|
"keyboard_shortcuts.blocked": "άνοιγμα λίστας αποκλεισμένων χρηστών",
|
||||||
"keyboard_shortcuts.boost": "προώθηση",
|
"keyboard_shortcuts.boost": "προώθηση",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
|
"notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
|
||||||
"notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
|
"notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
|
||||||
"notifications.column_settings.favourite": "Αγαπημένα:",
|
"notifications.column_settings.favourite": "Αγαπημένα:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Εμφάνιση όλων των κατηγοριών",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Εμφάνιση",
|
||||||
"notifications.column_settings.follow": "Νέοι ακόλουθοι:",
|
"notifications.column_settings.follow": "Νέοι ακόλουθοι:",
|
||||||
"notifications.column_settings.mention": "Αναφορές:",
|
"notifications.column_settings.mention": "Αναφορές:",
|
||||||
"notifications.column_settings.push": "Άμεσες ειδοποιήσεις",
|
"notifications.column_settings.push": "Άμεσες ειδοποιήσεις",
|
||||||
"notifications.column_settings.reblog": "Προωθήσεις:",
|
"notifications.column_settings.reblog": "Προωθήσεις:",
|
||||||
"notifications.column_settings.show": "Εμφάνισε σε στήλη",
|
"notifications.column_settings.show": "Εμφάνισε σε στήλη",
|
||||||
"notifications.column_settings.sound": "Ηχητική ειδοποίηση",
|
"notifications.column_settings.sound": "Ηχητική ειδοποίηση",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Όλες",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Προωθήσεις",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Αγαπημένα",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Ακόλουθοι",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Αναφορές",
|
||||||
"notifications.group": "{count} ειδοποιήσεις",
|
"notifications.group": "{count} ειδοποιήσεις",
|
||||||
"privacy.change": "Προσαρμογή ιδιωτικότητας δημοσίευσης",
|
"privacy.change": "Προσαρμογή ιδιωτικότητας δημοσίευσης",
|
||||||
"privacy.direct.long": "Δημοσίευση μόνο σε όσους και όσες αναφέρονται",
|
"privacy.direct.long": "Δημοσίευση μόνο σε όσους και όσες αναφέρονται",
|
||||||
|
|
|
@ -346,7 +346,7 @@
|
||||||
"upload_area.title": "Drag & drop to upload",
|
"upload_area.title": "Drag & drop to upload",
|
||||||
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
"upload_form.description": "Describe for the visually impaired",
|
"upload_form.description": "Describe for the visually impaired",
|
||||||
"upload_form.focus": "Crop",
|
"upload_form.focus": "Change preview",
|
||||||
"upload_form.undo": "Delete",
|
"upload_form.undo": "Delete",
|
||||||
"upload_progress.label": "Uploading...",
|
"upload_progress.label": "Uploading...",
|
||||||
"video.close": "Close video",
|
"video.close": "Close video",
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
"account.endorse": "Montri en profilo",
|
"account.endorse": "Montri en profilo",
|
||||||
"account.follow": "Sekvi",
|
"account.follow": "Sekvi",
|
||||||
"account.followers": "Sekvantoj",
|
"account.followers": "Sekvantoj",
|
||||||
"account.followers.empty": "Neniu ankoraŭ sekvas ĉi tiun uzanton.",
|
"account.followers.empty": "Ankoraŭ neniu sekvas tiun uzanton.",
|
||||||
"account.follows": "Sekvatoj",
|
"account.follows": "Sekvatoj",
|
||||||
"account.follows.empty": "Ĉi tiu uzanto ne ankoraŭ sekvas iun.",
|
"account.follows.empty": "Tiu uzanto ankoraŭ ne sekvas iun.",
|
||||||
"account.follows_you": "Sekvas vin",
|
"account.follows_you": "Sekvas vin",
|
||||||
"account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
|
"account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
|
||||||
"account.link_verified_on": "Proprieto de ĉi tiu ligilo estis kontrolita je {date}",
|
"account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
|
||||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
"account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
|
||||||
"account.media": "Aŭdovidaĵoj",
|
"account.media": "Aŭdovidaĵoj",
|
||||||
"account.mention": "Mencii @{name}",
|
"account.mention": "Mencii @{name}",
|
||||||
"account.moved_to": "{name} moviĝis al:",
|
"account.moved_to": "{name} moviĝis al:",
|
||||||
|
@ -92,9 +92,9 @@
|
||||||
"confirmations.mute.confirm": "Silentigi",
|
"confirmations.mute.confirm": "Silentigi",
|
||||||
"confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
|
"confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
|
||||||
"confirmations.redraft.confirm": "Forigi kaj reskribi",
|
"confirmations.redraft.confirm": "Forigi kaj reskribi",
|
||||||
"confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos orfigitaj.",
|
"confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos senparentaj.",
|
||||||
"confirmations.reply.confirm": "Respondi",
|
"confirmations.reply.confirm": "Respondi",
|
||||||
"confirmations.reply.message": "Respondi nun anstataŭigos la mesaĝon ke vi aktuale skribas. Ĉu vi certas ke vi volas daŭrigi?",
|
"confirmations.reply.message": "Respondi nun anstataŭigos la mesaĝon, kiun vi nun skribas. Ĉu vi certas, ke vi volas daŭrigi?",
|
||||||
"confirmations.unfollow.confirm": "Ne plu sekvi",
|
"confirmations.unfollow.confirm": "Ne plu sekvi",
|
||||||
"confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
|
"confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
|
||||||
"embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
|
"embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
|
||||||
|
@ -114,18 +114,18 @@
|
||||||
"emoji_button.symbols": "Simboloj",
|
"emoji_button.symbols": "Simboloj",
|
||||||
"emoji_button.travel": "Vojaĝoj kaj lokoj",
|
"emoji_button.travel": "Vojaĝoj kaj lokoj",
|
||||||
"empty_column.account_timeline": "Neniu mesaĝo ĉi tie!",
|
"empty_column.account_timeline": "Neniu mesaĝo ĉi tie!",
|
||||||
"empty_column.blocks": "Vi ne ankoraŭ blokis iun uzanton.",
|
"empty_column.blocks": "Vi ankoraŭ ne blokis uzanton.",
|
||||||
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
|
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
|
||||||
"empty_column.direct": "Vi ankoraŭ ne havas rektan mesaĝon. Kiam vi sendos aŭ ricevos iun, ĝi aperos ĉi tie.",
|
"empty_column.direct": "Vi ankoraŭ ne havas rektan mesaĝon. Kiam vi sendos aŭ ricevos iun, ĝi aperos ĉi tie.",
|
||||||
"empty_column.domain_blocks": "Ankoraŭ estas neniu domajno blokita.",
|
"empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
|
||||||
"empty_column.favourited_statuses": "Vi ne ankoraŭ havas iun stelumitan mesaĝon. Kiam vi stelumos iun, tiu aperos ĉi tie.",
|
"empty_column.favourited_statuses": "Vi ankoraŭ ne stelumis mesaĝon. Kiam vi stelumos iun, tiu aperos ĉi tie.",
|
||||||
"empty_column.favourites": "Neniu ankoraŭ stelumis ĉi tiun mesaĝon. Kiam iu faros ĝin, tiu aperos ĉi tie.",
|
"empty_column.favourites": "Ankoraŭ neniu stelumis tiun mesaĝon. Kiam iu faros tion, tiu aperos ĉi tie.",
|
||||||
"empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
|
"empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
|
||||||
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
|
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
|
||||||
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
||||||
"empty_column.home.public_timeline": "la publikan tempolinion",
|
"empty_column.home.public_timeline": "la publikan tempolinion",
|
||||||
"empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.",
|
"empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.",
|
||||||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
"empty_column.lists": "Vi ankoraŭ ne havas liston. Kiam vi kreos iun, ĝi aperos ĉi tie.",
|
||||||
"empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
|
"empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
|
||||||
"empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
|
"empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
|
||||||
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
|
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
|
||||||
|
@ -139,9 +139,9 @@
|
||||||
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
|
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
|
||||||
"getting_started.security": "Sekureco",
|
"getting_started.security": "Sekureco",
|
||||||
"getting_started.terms": "Uzkondiĉoj",
|
"getting_started.terms": "Uzkondiĉoj",
|
||||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
"hashtag.column_header.tag_mode.all": "kaj {additional}",
|
||||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
"hashtag.column_header.tag_mode.any": "aŭ {additional}",
|
||||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
"hashtag.column_header.tag_mode.none": "sen {additional}",
|
||||||
"hashtag.column_settings.tag_mode.all": "Ĉiuj",
|
"hashtag.column_settings.tag_mode.all": "Ĉiuj",
|
||||||
"hashtag.column_settings.tag_mode.any": "Iu ajn",
|
"hashtag.column_settings.tag_mode.any": "Iu ajn",
|
||||||
"hashtag.column_settings.tag_mode.none": "Neniu",
|
"hashtag.column_settings.tag_mode.none": "Neniu",
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
"home.column_settings.basic": "Bazaj agordoj",
|
"home.column_settings.basic": "Bazaj agordoj",
|
||||||
"home.column_settings.show_reblogs": "Montri diskonigojn",
|
"home.column_settings.show_reblogs": "Montri diskonigojn",
|
||||||
"home.column_settings.show_replies": "Montri respondojn",
|
"home.column_settings.show_replies": "Montri respondojn",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Sekva",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
"navigation_bar.apps": "Telefonaj aplikaĵoj",
|
"navigation_bar.apps": "Telefonaj aplikaĵoj",
|
||||||
"navigation_bar.blocks": "Blokitaj uzantoj",
|
"navigation_bar.blocks": "Blokitaj uzantoj",
|
||||||
"navigation_bar.community_timeline": "Loka tempolinio",
|
"navigation_bar.community_timeline": "Loka tempolinio",
|
||||||
"navigation_bar.compose": "Redakti novan mesaĝon",
|
"navigation_bar.compose": "Skribi novan mesaĝon",
|
||||||
"navigation_bar.direct": "Rektaj mesaĝoj",
|
"navigation_bar.direct": "Rektaj mesaĝoj",
|
||||||
"navigation_bar.discover": "Esplori",
|
"navigation_bar.discover": "Esplori",
|
||||||
"navigation_bar.domain_blocks": "Kaŝitaj domajnoj",
|
"navigation_bar.domain_blocks": "Kaŝitaj domajnoj",
|
||||||
|
@ -314,7 +314,7 @@
|
||||||
"status.reblog": "Diskonigi",
|
"status.reblog": "Diskonigi",
|
||||||
"status.reblog_private": "Diskonigi al la originala atentaro",
|
"status.reblog_private": "Diskonigi al la originala atentaro",
|
||||||
"status.reblogged_by": "{name} diskonigis",
|
"status.reblogged_by": "{name} diskonigis",
|
||||||
"status.reblogs.empty": "Neniu ankoraŭ diskonigis ĉi tiun mesaĝon. Kiam iu faris ĝin, tiu aperos ĉi tie.",
|
"status.reblogs.empty": "Ankoraŭ neniu diskonigis tiun mesaĝon. Kiam iu faros tion, tiu aperos ĉi tie.",
|
||||||
"status.redraft": "Forigi kaj reskribi",
|
"status.redraft": "Forigi kaj reskribi",
|
||||||
"status.reply": "Respondi",
|
"status.reply": "Respondi",
|
||||||
"status.replyAll": "Respondi al la fadeno",
|
"status.replyAll": "Respondi al la fadeno",
|
||||||
|
@ -326,8 +326,8 @@
|
||||||
"status.show_less_all": "Malgrandigi ĉiujn",
|
"status.show_less_all": "Malgrandigi ĉiujn",
|
||||||
"status.show_more": "Grandigi",
|
"status.show_more": "Grandigi",
|
||||||
"status.show_more_all": "Grandigi ĉiujn",
|
"status.show_more_all": "Grandigi ĉiujn",
|
||||||
"status.show_thread": "Montri fadenon",
|
"status.show_thread": "Montri la fadenon",
|
||||||
"status.unmute_conversation": "Malsilentigi konversacion",
|
"status.unmute_conversation": "Malsilentigi la konversacion",
|
||||||
"status.unpin": "Depingli de profilo",
|
"status.unpin": "Depingli de profilo",
|
||||||
"suggestions.dismiss": "Forigi la proponon",
|
"suggestions.dismiss": "Forigi la proponon",
|
||||||
"suggestions.header": "Vi povus interesiĝi pri…",
|
"suggestions.header": "Vi povus interesiĝi pri…",
|
||||||
|
|
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Basique",
|
"home.column_settings.basic": "Basique",
|
||||||
"home.column_settings.show_reblogs": "Afficher les partages",
|
"home.column_settings.show_reblogs": "Afficher les partages",
|
||||||
"home.column_settings.show_replies": "Afficher les réponses",
|
"home.column_settings.show_replies": "Afficher les réponses",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Suivant",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Les messages publics provenant d'autres serveurs du fediverse apparaîtront dans le fil public global.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Les messages des personnes que vous suivez apparaîtront dans votre fil d'accueil. Vous pouvez suivre n'importe qui sur n'importe quel serveur !",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Les messages publics de personnes se trouvant sur le même serveur que vous apparaîtront sur le fil public local.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Finir le tutoriel !",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Favoris",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Vous pouvez garder un pouet pour plus tard, et faire savoir à l'auteur que vous l'avez aimé, en le favorisant.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Repartager",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Vous pouvez partager les pouets d'autres personnes avec vos suiveurs en les repartageant.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Répondre",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Vous pouvez répondre aux pouets d'autres personnes et à vos propres pouets, ce qui les enchaînera dans une conversation.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Allons-y !",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Premiers pas",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Bienvenue dans le fediverse ! Dans quelques instants, vous pourrez diffuser des messages et parler à vos amis sur une grande variété de serveurs. Mais ce serveur, {domain}, est spécial - il héberge votre profil, alors souvenez-vous de son nom.",
|
||||||
"keyboard_shortcuts.back": "revenir en arrière",
|
"keyboard_shortcuts.back": "revenir en arrière",
|
||||||
"keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateurs bloqués",
|
"keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateurs bloqués",
|
||||||
"keyboard_shortcuts.boost": "partager",
|
"keyboard_shortcuts.boost": "partager",
|
||||||
|
@ -242,19 +242,19 @@
|
||||||
"notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
|
"notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
|
||||||
"notifications.column_settings.alert": "Notifications locales",
|
"notifications.column_settings.alert": "Notifications locales",
|
||||||
"notifications.column_settings.favourite": "Favoris :",
|
"notifications.column_settings.favourite": "Favoris :",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Barre de recherche rapide",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Afficher",
|
||||||
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
|
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
|
||||||
"notifications.column_settings.mention": "Mentions :",
|
"notifications.column_settings.mention": "Mentions :",
|
||||||
"notifications.column_settings.push": "Notifications",
|
"notifications.column_settings.push": "Notifications",
|
||||||
"notifications.column_settings.reblog": "Partages :",
|
"notifications.column_settings.reblog": "Partages :",
|
||||||
"notifications.column_settings.show": "Afficher dans la colonne",
|
"notifications.column_settings.show": "Afficher dans la colonne",
|
||||||
"notifications.column_settings.sound": "Émettre un son",
|
"notifications.column_settings.sound": "Émettre un son",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Tout",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Repartages",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Favoris",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Suiveurs",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Mentions",
|
||||||
"notifications.group": "{count} notifications",
|
"notifications.group": "{count} notifications",
|
||||||
"privacy.change": "Ajuster la confidentialité du message",
|
"privacy.change": "Ajuster la confidentialité du message",
|
||||||
|
@ -341,7 +341,7 @@
|
||||||
"upload_area.title": "Glissez et déposez pour envoyer",
|
"upload_area.title": "Glissez et déposez pour envoyer",
|
||||||
"upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
"upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
"upload_form.description": "Décrire pour les malvoyant·e·s",
|
"upload_form.description": "Décrire pour les malvoyant·e·s",
|
||||||
"upload_form.focus": "Recadrer",
|
"upload_form.focus": "Modifier l’aperçu",
|
||||||
"upload_form.undo": "Supprimer",
|
"upload_form.undo": "Supprimer",
|
||||||
"upload_progress.label": "Envoi en cours…",
|
"upload_progress.label": "Envoi en cours…",
|
||||||
"video.close": "Fermer la vidéo",
|
"video.close": "Fermer la vidéo",
|
||||||
|
|
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Básico",
|
"home.column_settings.basic": "Básico",
|
||||||
"home.column_settings.show_reblogs": "Mostrar repeticións",
|
"home.column_settings.show_reblogs": "Mostrar repeticións",
|
||||||
"home.column_settings.show_replies": "Mostrar respostas",
|
"home.column_settings.show_replies": "Mostrar respostas",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Seguinte",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Publicacións públicas desde outros servidores do fediverso aparecerán na liña temporal federada.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Publicacións de xente que vostede segue aparecerán no TL de Inicio. Pode seguir a calquera en calquer servidor!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Publicacións públicas de xente no seu mesmo servidor aparecerán na liña temporal local.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Rematar titorial!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Favorito",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Pode gardar un toot para máis tarde, e facerlle saber a autora que lle gustou, dándolle a Favorito.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Promocionar",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Pode compartir os toots de outra xente coas súas seguirodas promocionándoos.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Respostar",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Pode respostar aos toots de outras persoas e aos seus propios, así quedarán encadeados nunha conversa.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Imos!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Primeiros pasos",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Benvida ao fediverso! Nun intre poderá difundir mensaxes e falar cos seus amigos nun gran número de servidores. Pero este servidor (dominio) é especial—hospeda o seu perfil, así que lémbreo.",
|
||||||
"keyboard_shortcuts.back": "voltar atrás",
|
"keyboard_shortcuts.back": "voltar atrás",
|
||||||
"keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
|
"keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
|
||||||
"keyboard_shortcuts.boost": "promover",
|
"keyboard_shortcuts.boost": "promover",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?",
|
"notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?",
|
||||||
"notifications.column_settings.alert": "Notificacións de escritorio",
|
"notifications.column_settings.alert": "Notificacións de escritorio",
|
||||||
"notifications.column_settings.favourite": "Favoritas:",
|
"notifications.column_settings.favourite": "Favoritas:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorías",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Mostrar",
|
||||||
"notifications.column_settings.follow": "Novos seguidores:",
|
"notifications.column_settings.follow": "Novos seguidores:",
|
||||||
"notifications.column_settings.mention": "Mencións:",
|
"notifications.column_settings.mention": "Mencións:",
|
||||||
"notifications.column_settings.push": "Enviar notificacións",
|
"notifications.column_settings.push": "Enviar notificacións",
|
||||||
"notifications.column_settings.reblog": "Promocións:",
|
"notifications.column_settings.reblog": "Promocións:",
|
||||||
"notifications.column_settings.show": "Mostrar en columna",
|
"notifications.column_settings.show": "Mostrar en columna",
|
||||||
"notifications.column_settings.sound": "Reproducir son",
|
"notifications.column_settings.sound": "Reproducir son",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Todo",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Promocións",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Favoritos",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Seguimentos",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Mencións",
|
||||||
"notifications.group": "{count} notificacións",
|
"notifications.group": "{count} notificacións",
|
||||||
"privacy.change": "Axustar a intimidade do estado",
|
"privacy.change": "Axustar a intimidade do estado",
|
||||||
"privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas",
|
"privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas",
|
||||||
|
|
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "기본 설정",
|
"home.column_settings.basic": "기본 설정",
|
||||||
"home.column_settings.show_reblogs": "부스트 표시",
|
"home.column_settings.show_reblogs": "부스트 표시",
|
||||||
"home.column_settings.show_replies": "답글 표시",
|
"home.column_settings.show_replies": "답글 표시",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "다음",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "페디버스의 다른 서버의 공개 게시물이 연합 타임라인에 나타납니다.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "당신이 팔로우 하고 있는 사람의 게시물이 홈 타임라인에 나타납니다. 어느 서버에 있는 사람이라도 팔로우가 가능합니다!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "같은 서버에 있는 공개 게시물은 로컬 타임라인에 나타납니다.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "튜토리얼 마치기!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "즐겨찾기",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "나중을 위해 툿을 저장할 수 있습니다, 그리고 작성자에게 당신이 이 글을 마음에 들어한다는 걸 알립니다.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "부스트",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "부스트를 통해 다른 사람의 툿을 당신의 팔로워들에게 공유할 수 있습니다.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "답글",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "다른 사람이나 나의 툿에 답글을 달 수 있습니다, 이 답글은 하나의 타래글로 이어집니다.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "출발!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "첫걸음",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "페디버스에 오신 것을 환영합니다! 잠시 후, 당신은 수 많은 다양한 서버들에 존재하는 친구들에게 메시지를 보내고 대화 할 수 있게 됩니다. 하지만 이 서버, {domain}은 특별합니다. 이 서버는 당신의 프로필을 제공하니 이름을 기억하세요.",
|
||||||
"keyboard_shortcuts.back": "뒤로가기",
|
"keyboard_shortcuts.back": "뒤로가기",
|
||||||
"keyboard_shortcuts.blocked": "차단한 유저 리스트 열기",
|
"keyboard_shortcuts.blocked": "차단한 유저 리스트 열기",
|
||||||
"keyboard_shortcuts.boost": "부스트",
|
"keyboard_shortcuts.boost": "부스트",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?",
|
"notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?",
|
||||||
"notifications.column_settings.alert": "데스크탑 알림",
|
"notifications.column_settings.alert": "데스크탑 알림",
|
||||||
"notifications.column_settings.favourite": "즐겨찾기:",
|
"notifications.column_settings.favourite": "즐겨찾기:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "카테고리의 모든 종류를 표시",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "퀵 필터 바",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "표시",
|
||||||
"notifications.column_settings.follow": "새 팔로워:",
|
"notifications.column_settings.follow": "새 팔로워:",
|
||||||
"notifications.column_settings.mention": "답글:",
|
"notifications.column_settings.mention": "답글:",
|
||||||
"notifications.column_settings.push": "푸시 알림",
|
"notifications.column_settings.push": "푸시 알림",
|
||||||
"notifications.column_settings.reblog": "부스트:",
|
"notifications.column_settings.reblog": "부스트:",
|
||||||
"notifications.column_settings.show": "컬럼에 표시",
|
"notifications.column_settings.show": "컬럼에 표시",
|
||||||
"notifications.column_settings.sound": "효과음 재생",
|
"notifications.column_settings.sound": "효과음 재생",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "모두",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "부스트",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "즐겨찾기",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "팔로우",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "멘션",
|
||||||
"notifications.group": "{count} 개의 알림",
|
"notifications.group": "{count} 개의 알림",
|
||||||
"privacy.change": "포스트의 프라이버시 설정을 변경",
|
"privacy.change": "포스트의 프라이버시 설정을 변경",
|
||||||
"privacy.direct.long": "멘션한 사용자에게만 공개",
|
"privacy.direct.long": "멘션한 사용자에게만 공개",
|
||||||
|
|
|
@ -145,27 +145,27 @@
|
||||||
"hashtag.column_settings.tag_mode.all": "Allemaal",
|
"hashtag.column_settings.tag_mode.all": "Allemaal",
|
||||||
"hashtag.column_settings.tag_mode.any": "Een van deze",
|
"hashtag.column_settings.tag_mode.any": "Een van deze",
|
||||||
"hashtag.column_settings.tag_mode.none": "Geen van deze",
|
"hashtag.column_settings.tag_mode.none": "Geen van deze",
|
||||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
"hashtag.column_settings.tag_toggle": "Additionele tags aan deze kolom toevoegen",
|
||||||
"home.column_settings.basic": "Algemeen",
|
"home.column_settings.basic": "Algemeen",
|
||||||
"home.column_settings.show_reblogs": "Boosts tonen",
|
"home.column_settings.show_reblogs": "Boosts tonen",
|
||||||
"home.column_settings.show_replies": "Reacties tonen",
|
"home.column_settings.show_replies": "Reacties tonen",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Volgende",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Globaal",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Openbare toots van mensen op andere servers in de fediverse verschijnen op de globale tijdlijn.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Toots van mensen die jij volgt verschijnen onder start. Je kunt iedereen op elke server volgen!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Openbare toots van mensen die ook op jouw server zitten verschijnen op de lokale tijdlijn.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Introductie beëindigen!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Favorieten",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Je kunt door een toot als favoriet te markeren, deze voor later bewaren en de auteur laten weten dat je het leuk vond.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Boost",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Je kunt toots van andere mensen met jouw volgers delen door deze te boosten.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Reageren",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Je kunt op toots van andere mensen en op die van jezelf reageren, waardoor er een gesprek ontstaat.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Laten we beginnen!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Eerste stappen",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Welkom in de fediverse! Binnen enkele ogenblikken kun jij berichten (toots) versturen en met vrienden op veel verschillende servers praten. Maar deze server, {domain}, is speciaal—het herbergt jouw profiel, onthou dus de naam.",
|
||||||
"keyboard_shortcuts.back": "om terug te gaan",
|
"keyboard_shortcuts.back": "om terug te gaan",
|
||||||
"keyboard_shortcuts.blocked": "om de door jou geblokkeerde gebruikers te tonen",
|
"keyboard_shortcuts.blocked": "om de door jou geblokkeerde gebruikers te tonen",
|
||||||
"keyboard_shortcuts.boost": "om te boosten",
|
"keyboard_shortcuts.boost": "om te boosten",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
|
"notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
|
||||||
"notifications.column_settings.alert": "Desktopmeldingen",
|
"notifications.column_settings.alert": "Desktopmeldingen",
|
||||||
"notifications.column_settings.favourite": "Favorieten:",
|
"notifications.column_settings.favourite": "Favorieten:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Snelle filterbalk",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Tonen",
|
||||||
"notifications.column_settings.follow": "Nieuwe volgers:",
|
"notifications.column_settings.follow": "Nieuwe volgers:",
|
||||||
"notifications.column_settings.mention": "Vermeldingen:",
|
"notifications.column_settings.mention": "Vermeldingen:",
|
||||||
"notifications.column_settings.push": "Pushmeldingen",
|
"notifications.column_settings.push": "Pushmeldingen",
|
||||||
"notifications.column_settings.reblog": "Boosts:",
|
"notifications.column_settings.reblog": "Boosts:",
|
||||||
"notifications.column_settings.show": "In kolom tonen",
|
"notifications.column_settings.show": "In kolom tonen",
|
||||||
"notifications.column_settings.sound": "Geluid afspelen",
|
"notifications.column_settings.sound": "Geluid afspelen",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Alles",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Boosts",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Favorieten",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Die jij volgt",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Vermeldingen",
|
||||||
"notifications.group": "{count} meldingen",
|
"notifications.group": "{count} meldingen",
|
||||||
"privacy.change": "Zichtbaarheid toot aanpassen",
|
"privacy.change": "Zichtbaarheid toot aanpassen",
|
||||||
"privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
|
"privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
|
||||||
|
|
|
@ -341,7 +341,7 @@
|
||||||
"upload_area.title": "Lisatz e depausatz per mandar",
|
"upload_area.title": "Lisatz e depausatz per mandar",
|
||||||
"upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
"upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
"upload_form.description": "Descripcion pels mal vesents",
|
"upload_form.description": "Descripcion pels mal vesents",
|
||||||
"upload_form.focus": "Retalhar",
|
"upload_form.focus": "Modificar l’apercebut",
|
||||||
"upload_form.undo": "Suprimir",
|
"upload_form.undo": "Suprimir",
|
||||||
"upload_progress.label": "Mandadís…",
|
"upload_progress.label": "Mandadís…",
|
||||||
"video.close": "Tampar la vidèo",
|
"video.close": "Tampar la vidèo",
|
||||||
|
|
|
@ -29,9 +29,9 @@
|
||||||
"account.report": "Nahlás @{name}",
|
"account.report": "Nahlás @{name}",
|
||||||
"account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
|
"account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
|
||||||
"account.share": "Zdieľať @{name} profil",
|
"account.share": "Zdieľať @{name} profil",
|
||||||
"account.show_reblogs": "Ukáž povýšenia od @{name}",
|
"account.show_reblogs": "Ukáž vyzdvihnutia od @{name}",
|
||||||
"account.unblock": "Odblokovať @{name}",
|
"account.unblock": "Odblokuj @{name}",
|
||||||
"account.unblock_domain": "Prestať blokovať {domain}",
|
"account.unblock_domain": "Prestaň skrývať {domain}",
|
||||||
"account.unendorse": "Nezobrazuj na profile",
|
"account.unendorse": "Nezobrazuj na profile",
|
||||||
"account.unfollow": "Prestať nasledovať",
|
"account.unfollow": "Prestať nasledovať",
|
||||||
"account.unmute": "Prestať ignorovať @{name}",
|
"account.unmute": "Prestať ignorovať @{name}",
|
||||||
|
@ -92,8 +92,8 @@
|
||||||
"confirmations.mute.confirm": "Ignoruj",
|
"confirmations.mute.confirm": "Ignoruj",
|
||||||
"confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
|
"confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
|
||||||
"confirmations.redraft.confirm": "Vyčistiť a prepísať",
|
"confirmations.redraft.confirm": "Vyčistiť a prepísať",
|
||||||
"confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté odpovede, povýšenia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
|
"confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté vyzdvihnutia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
|
||||||
"confirmations.reply.confirm": "Odpovedať",
|
"confirmations.reply.confirm": "Odpovedz",
|
||||||
"confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
|
"confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
|
||||||
"confirmations.unfollow.confirm": "Nesledovať",
|
"confirmations.unfollow.confirm": "Nesledovať",
|
||||||
"confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
|
"confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
|
||||||
|
@ -149,23 +149,23 @@
|
||||||
"home.column_settings.basic": "Základné",
|
"home.column_settings.basic": "Základné",
|
||||||
"home.column_settings.show_reblogs": "Zobraziť povýšené",
|
"home.column_settings.show_reblogs": "Zobraziť povýšené",
|
||||||
"home.column_settings.show_replies": "Ukázať odpovede",
|
"home.column_settings.show_replies": "Ukázať odpovede",
|
||||||
"introduction.federation.action": "Next",
|
"introduction.federation.action": "Ďalej",
|
||||||
"introduction.federation.federated.headline": "Federated",
|
"introduction.federation.federated.headline": "Federated",
|
||||||
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
"introduction.federation.federated.text": "Verejné príspevky z ostatných serverov vo fediverse budú zobrazenie vo federovanej časovej osi.",
|
||||||
"introduction.federation.home.headline": "Home",
|
"introduction.federation.home.headline": "Home",
|
||||||
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
"introduction.federation.home.text": "Príspevky od ľudí ktorých následuješ sa zobrazia na tvojej domovskej nástenke. Môžeš následovať hocikoho na ktoromkoľvek serveri!",
|
||||||
"introduction.federation.local.headline": "Local",
|
"introduction.federation.local.headline": "Local",
|
||||||
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
"introduction.federation.local.text": "Verejné príspevky od ľudí v rámci toho istého serveru na akom si aj ty, budú zobrazované na miestnej časovej osi.",
|
||||||
"introduction.interactions.action": "Finish tutorial!",
|
"introduction.interactions.action": "Ukonči návod!",
|
||||||
"introduction.interactions.favourite.headline": "Favourite",
|
"introduction.interactions.favourite.headline": "Obľúbené",
|
||||||
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
"introduction.interactions.favourite.text": "Obľúbením si môžeš príspevok uložiť na neskôr, a zároveň dať jeho autorovi vedieť, že sa ti páčil.",
|
||||||
"introduction.interactions.reblog.headline": "Boost",
|
"introduction.interactions.reblog.headline": "Povýš",
|
||||||
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
"introduction.interactions.reblog.text": "Môžeš zdieľať príspevky iných ľudí s vašimi následovateľmi tým, že ich povýšiš.",
|
||||||
"introduction.interactions.reply.headline": "Reply",
|
"introduction.interactions.reply.headline": "Odpovedz",
|
||||||
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
"introduction.interactions.reply.text": "Odpovedať môžeš na príspevky iných ľudí, aj na svoje vlastné, čím sa sspolu prepoja do konverzácie.",
|
||||||
"introduction.welcome.action": "Let's go!",
|
"introduction.welcome.action": "Poďme do toho!",
|
||||||
"introduction.welcome.headline": "First steps",
|
"introduction.welcome.headline": "Prvé kroky",
|
||||||
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
"introduction.welcome.text": "Vitaj vo fediverse! Za malú chvíľu budeš môcť posielať správy a rozpovedať sa so svojími priateľmi cez širokú škálu rôznorodých serverov. Ale tento server, {domain}, je špeciálny v tom, že ukladá tvoj profil, takže si jeho názov zapametaj.",
|
||||||
"keyboard_shortcuts.back": "dostať sa naspäť",
|
"keyboard_shortcuts.back": "dostať sa naspäť",
|
||||||
"keyboard_shortcuts.blocked": "otvor zoznam blokovaných užívateľov",
|
"keyboard_shortcuts.blocked": "otvor zoznam blokovaných užívateľov",
|
||||||
"keyboard_shortcuts.boost": "vyzdvihnúť",
|
"keyboard_shortcuts.boost": "vyzdvihnúť",
|
||||||
|
@ -242,20 +242,20 @@
|
||||||
"notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
|
"notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
|
||||||
"notifications.column_settings.alert": "Notifikácie na ploche",
|
"notifications.column_settings.alert": "Notifikácie na ploche",
|
||||||
"notifications.column_settings.favourite": "Obľúbené:",
|
"notifications.column_settings.favourite": "Obľúbené:",
|
||||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
"notifications.column_settings.filter_bar.advanced": "Zobraz všetky kategórie",
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Rýchle triedenie",
|
||||||
"notifications.column_settings.filter_bar.show": "Show",
|
"notifications.column_settings.filter_bar.show": "Ukáž",
|
||||||
"notifications.column_settings.follow": "Noví následujúci:",
|
"notifications.column_settings.follow": "Noví následujúci:",
|
||||||
"notifications.column_settings.mention": "Zmienenia:",
|
"notifications.column_settings.mention": "Zmienenia:",
|
||||||
"notifications.column_settings.push": "Push notifikácie",
|
"notifications.column_settings.push": "Push notifikácie",
|
||||||
"notifications.column_settings.reblog": "Boosty:",
|
"notifications.column_settings.reblog": "Boosty:",
|
||||||
"notifications.column_settings.show": "Zobraziť v stĺpci",
|
"notifications.column_settings.show": "Zobraziť v stĺpci",
|
||||||
"notifications.column_settings.sound": "Prehrať zvuk",
|
"notifications.column_settings.sound": "Prehrať zvuk",
|
||||||
"notifications.filter.all": "All",
|
"notifications.filter.all": "Všetky",
|
||||||
"notifications.filter.boosts": "Boosts",
|
"notifications.filter.boosts": "Vyzdvihnutia",
|
||||||
"notifications.filter.favourites": "Favourites",
|
"notifications.filter.favourites": "Obľúbené",
|
||||||
"notifications.filter.follows": "Follows",
|
"notifications.filter.follows": "Sledovania",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Spomenutia",
|
||||||
"notifications.group": "{count} oznámenia",
|
"notifications.group": "{count} oznámenia",
|
||||||
"privacy.change": "Zmeňiť viditeľnosť statusu",
|
"privacy.change": "Zmeňiť viditeľnosť statusu",
|
||||||
"privacy.direct.long": "Poslať priamo iba spomenutým používateľom",
|
"privacy.direct.long": "Poslať priamo iba spomenutým používateľom",
|
||||||
|
@ -319,7 +319,7 @@
|
||||||
"status.reply": "Odpovedať",
|
"status.reply": "Odpovedať",
|
||||||
"status.replyAll": "Odpovedať na diskusiu",
|
"status.replyAll": "Odpovedať na diskusiu",
|
||||||
"status.report": "Nahlásiť @{name}",
|
"status.report": "Nahlásiť @{name}",
|
||||||
"status.sensitive_toggle": "Kliknite pre zobrazenie",
|
"status.sensitive_toggle": "Klikni pre zobrazenie",
|
||||||
"status.sensitive_warning": "Chúlostivý obsah",
|
"status.sensitive_warning": "Chúlostivý obsah",
|
||||||
"status.share": "Zdieľať",
|
"status.share": "Zdieľať",
|
||||||
"status.show_less": "Zobraz menej",
|
"status.show_less": "Zobraz menej",
|
||||||
|
|
|
@ -88,7 +88,7 @@ const deleteStatus = (state, id, accountId, references) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearTimeline = (state, timeline) => {
|
const clearTimeline = (state, timeline) => {
|
||||||
return state.updateIn([timeline, 'items'], list => list.clear());
|
return state.set(timeline, initialTimeline);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterTimelines = (state, relationship, statuses) => {
|
const filterTimelines = (state, relationship, statuses) => {
|
||||||
|
|
|
@ -11,5 +11,5 @@ export default function configureStore() {
|
||||||
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
|
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
|
||||||
errorsMiddleware(),
|
errorsMiddleware(),
|
||||||
soundsMiddleware()
|
soundsMiddleware()
|
||||||
), window.devToolsExtension ? window.devToolsExtension() : f => f));
|
), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,15 +30,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__num {
|
&__num,
|
||||||
|
&__text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
line-height: 21px;
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
font-family: $font-display, sans-serif;
|
font-family: $font-display, sans-serif;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
|
|
|
@ -50,6 +50,8 @@ class ActivityPub::Activity
|
||||||
ActivityPub::Activity::Add
|
ActivityPub::Activity::Add
|
||||||
when 'Remove'
|
when 'Remove'
|
||||||
ActivityPub::Activity::Remove
|
ActivityPub::Activity::Remove
|
||||||
|
when 'Move'
|
||||||
|
ActivityPub::Activity::Move
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,10 @@ class ActivityPub::Activity::Block < ActivityPub::Activity
|
||||||
def perform
|
def perform
|
||||||
target_account = account_from_uri(object_uri)
|
target_account = account_from_uri(object_uri)
|
||||||
|
|
||||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.blocking?(target_account)
|
return if target_account.nil? || !target_account.local? || @account.blocking?(target_account)
|
||||||
|
|
||||||
UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
|
UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
|
||||||
@account.block!(target_account, uri: @json['id'])
|
|
||||||
|
@account.block!(target_account, uri: @json['id']) unless delete_arrived_first?(@json['id'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -210,7 +210,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_thread(status)
|
def resolve_thread(status)
|
||||||
return unless status.reply? && status.thread.nil?
|
return unless status.reply? && status.thread.nil? && Request.valid_url?(in_reply_to_uri)
|
||||||
ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
|
ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
|
||||||
target_statuses_by_account = object_uris.map { |uri| status_from_uri(uri) }.compact.select(&:local?).group_by(&:account_id)
|
target_statuses_by_account = object_uris.map { |uri| status_from_uri(uri) }.compact.select(&:local?).group_by(&:account_id)
|
||||||
|
|
||||||
target_accounts.each do |target_account|
|
target_accounts.each do |target_account|
|
||||||
next if Report.where(account: @account, target_account: target_account).exists?
|
|
||||||
|
|
||||||
target_statuses = target_statuses_by_account[target_account.id]
|
target_statuses = target_statuses_by_account[target_account.id]
|
||||||
|
|
||||||
ReportService.new.call(
|
ReportService.new.call(
|
||||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||||
|
|
||||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
|
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
|
||||||
|
|
||||||
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain)
|
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
|
||||||
reject_follow_request!(target_account)
|
reject_follow_request!(target_account)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_follow_request!(target_account)
|
def reject_follow_request!(target_account)
|
||||||
json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(FollowRequest.new(account: @account, target_account: target_account, uri: @json['id']), serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).as_json).sign!(target_account))
|
json = ActiveModelSerializers::SerializableResource.new(FollowRequest.new(account: @account, target_account: target_account, uri: @json['id']), serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).to_json
|
||||||
ActivityPub::DeliveryWorker.perform_async(json, target_account.id, @account.inbox_url)
|
ActivityPub::DeliveryWorker.perform_async(json, target_account.id, @account.inbox_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::Activity::Move < ActivityPub::Activity
|
||||||
|
PROCESSING_COOLDOWN = 7.days.seconds
|
||||||
|
|
||||||
|
def perform
|
||||||
|
return if origin_account.uri != object_uri || processed?
|
||||||
|
|
||||||
|
mark_as_processing!
|
||||||
|
|
||||||
|
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
|
||||||
|
|
||||||
|
return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri)
|
||||||
|
|
||||||
|
# In case for some reason we didn't have a redirect for the profile already, set it
|
||||||
|
origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil?
|
||||||
|
|
||||||
|
# Initiate a re-follow for each follower
|
||||||
|
origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
|
||||||
|
UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id|
|
||||||
|
[follower_account_id, origin_account.id, target_account.id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def origin_account
|
||||||
|
@account
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_uri
|
||||||
|
value_or_id(@json['target'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def processed?
|
||||||
|
redis.exists("move_in_progress:#{@account.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_processing!
|
||||||
|
redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,6 +10,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||||
'sensitive' => 'as:sensitive',
|
'sensitive' => 'as:sensitive',
|
||||||
'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' },
|
'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' },
|
||||||
|
'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' },
|
||||||
'Hashtag' => 'as:Hashtag',
|
'Hashtag' => 'as:Hashtag',
|
||||||
'ostatus' => 'http://ostatus.org#',
|
'ostatus' => 'http://ostatus.org#',
|
||||||
'atomUri' => 'ostatus:atomUri',
|
'atomUri' => 'ostatus:atomUri',
|
||||||
|
|
|
@ -57,7 +57,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
||||||
save_emojis(status)
|
save_emojis(status)
|
||||||
end
|
end
|
||||||
|
|
||||||
if thread? && status.thread.nil?
|
if thread? && status.thread.nil? && Request.valid_url?(thread.second)
|
||||||
Rails.logger.debug "Trying to attach #{status.id} (#{id}) to #{thread.first}"
|
Rails.logger.debug "Trying to attach #{status.id} (#{id}) to #{thread.first}"
|
||||||
ThreadResolveWorker.perform_async(status.id, thread.second)
|
ThreadResolveWorker.perform_async(status.id, thread.second)
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,6 +66,18 @@ class Request
|
||||||
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
|
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def valid_url?(url)
|
||||||
|
begin
|
||||||
|
parsed_url = Addressable::URI.parse(url)
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_common_headers!
|
def set_common_headers!
|
||||||
|
|
|
@ -66,16 +66,20 @@ class NotificationMailer < ApplicationMailer
|
||||||
end
|
end
|
||||||
|
|
||||||
def digest(recipient, **opts)
|
def digest(recipient, **opts)
|
||||||
@me = recipient
|
return if recipient.user.disabled?
|
||||||
@since = opts[:since] || @me.user.last_emailed_at || @me.user.current_sign_in_at
|
|
||||||
@notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since)
|
|
||||||
@follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count
|
|
||||||
|
|
||||||
return if @me.user.disabled? || @notifications.empty?
|
@me = recipient
|
||||||
|
@since = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
|
||||||
|
@notifications_count = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since).count
|
||||||
|
|
||||||
|
return if @notifications_count.zero?
|
||||||
|
|
||||||
|
@notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since).limit(40)
|
||||||
|
@follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
mail to: @me.user.email,
|
mail to: @me.user.email,
|
||||||
subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications.size)
|
subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications_count)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
# fields :jsonb
|
# fields :jsonb
|
||||||
# actor_type :string
|
# actor_type :string
|
||||||
# discoverable :boolean
|
# discoverable :boolean
|
||||||
|
# also_known_as :string is an Array
|
||||||
#
|
#
|
||||||
|
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
|
@ -59,6 +60,7 @@ class Account < ApplicationRecord
|
||||||
include Attachmentable
|
include Attachmentable
|
||||||
include Paginable
|
include Paginable
|
||||||
include AccountCounters
|
include AccountCounters
|
||||||
|
include DomainNormalizable
|
||||||
|
|
||||||
MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
|
MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
|
||||||
MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i
|
MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i
|
||||||
|
@ -87,6 +89,7 @@ class Account < ApplicationRecord
|
||||||
scope :silenced, -> { where(silenced: true) }
|
scope :silenced, -> { where(silenced: true) }
|
||||||
scope :suspended, -> { where(suspended: true) }
|
scope :suspended, -> { where(suspended: true) }
|
||||||
scope :without_suspended, -> { where(suspended: false) }
|
scope :without_suspended, -> { where(suspended: false) }
|
||||||
|
scope :without_silenced, -> { where(silenced: false) }
|
||||||
scope :recent, -> { reorder(id: :desc) }
|
scope :recent, -> { reorder(id: :desc) }
|
||||||
scope :bots, -> { where(actor_type: %w(Application Service)) }
|
scope :bots, -> { where(actor_type: %w(Application Service)) }
|
||||||
scope :alphabetic, -> { order(domain: :asc, username: :asc) }
|
scope :alphabetic, -> { order(domain: :asc, username: :asc) }
|
||||||
|
@ -94,8 +97,8 @@ class Account < ApplicationRecord
|
||||||
scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
|
scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
|
||||||
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
|
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
|
||||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||||
scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) }
|
scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) }
|
||||||
scope :discoverable, -> { searchable.where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)).by_recent_status }
|
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)).by_recent_status }
|
||||||
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
|
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
|
||||||
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
|
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
|
||||||
scope :popular, -> { order('account_stats.followers_count desc') }
|
scope :popular, -> { order('account_stats.followers_count desc') }
|
||||||
|
@ -142,6 +145,10 @@ class Account < ApplicationRecord
|
||||||
"#{username}@#{Rails.configuration.x.local_domain}"
|
"#{username}@#{Rails.configuration.x.local_domain}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def local_followers_count
|
||||||
|
Follow.where(target_account_id: id).count
|
||||||
|
end
|
||||||
|
|
||||||
def to_webfinger_s
|
def to_webfinger_s
|
||||||
"acct:#{local_username_and_domain}"
|
"acct:#{local_username_and_domain}"
|
||||||
end
|
end
|
||||||
|
@ -226,6 +233,10 @@ class Account < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def also_known_as
|
||||||
|
self[:also_known_as] || []
|
||||||
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
(self[:fields] || []).map { |f| Field.new(self, f) }
|
(self[:fields] || []).map { |f| Field.new(self, f) }
|
||||||
end
|
end
|
||||||
|
@ -459,7 +470,6 @@ class Account < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
before_create :generate_keys
|
before_create :generate_keys
|
||||||
before_validation :normalize_domain
|
|
||||||
before_validation :prepare_contents, if: :local?
|
before_validation :prepare_contents, if: :local?
|
||||||
before_destroy :clean_feed_manager
|
before_destroy :clean_feed_manager
|
||||||
|
|
||||||
|
@ -497,7 +507,7 @@ class Account < ApplicationRecord
|
||||||
def normalize_domain
|
def normalize_domain
|
||||||
return if local?
|
return if local?
|
||||||
|
|
||||||
self.domain = TagManager.instance.normalize_domain(domain)
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def emojifiable_text
|
def emojifiable_text
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DomainNormalizable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_validation :normalize_domain
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def normalize_domain
|
||||||
|
self.domain = TagManager.instance.normalize_domain(domain)
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,6 +13,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
class DomainBlock < ApplicationRecord
|
class DomainBlock < ApplicationRecord
|
||||||
|
include DomainNormalizable
|
||||||
|
|
||||||
enum severity: [:silence, :suspend, :noop]
|
enum severity: [:silence, :suspend, :noop]
|
||||||
|
|
||||||
attr_accessor :retroactive
|
attr_accessor :retroactive
|
||||||
|
@ -25,12 +27,4 @@ class DomainBlock < ApplicationRecord
|
||||||
def self.blocked?(domain)
|
def self.blocked?(domain)
|
||||||
where(domain: domain, severity: :suspend).exists?
|
where(domain: domain, severity: :suspend).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
before_validation :normalize_domain
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def normalize_domain
|
|
||||||
self.domain = TagManager.instance.normalize_domain(domain)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
class EmailDomainBlock < ApplicationRecord
|
class EmailDomainBlock < ApplicationRecord
|
||||||
before_validation :normalize_domain
|
include DomainNormalizable
|
||||||
|
|
||||||
validates :domain, presence: true, uniqueness: true
|
validates :domain, presence: true, uniqueness: true
|
||||||
|
|
||||||
|
@ -27,10 +27,4 @@ class EmailDomainBlock < ApplicationRecord
|
||||||
|
|
||||||
where(domain: domain).exists?
|
where(domain: domain).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def normalize_domain
|
|
||||||
self.domain = TagManager.instance.normalize_domain(domain)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,15 +9,33 @@ class Export
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_blocked_accounts_csv
|
def to_blocked_accounts_csv
|
||||||
to_csv account.blocking
|
to_csv account.blocking.select(:username, :domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_muted_accounts_csv
|
def to_muted_accounts_csv
|
||||||
to_csv account.muting
|
to_csv account.muting.select(:username, :domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_following_accounts_csv
|
def to_following_accounts_csv
|
||||||
to_csv account.following
|
to_csv account.following.select(:username, :domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_lists_csv
|
||||||
|
CSV.generate do |csv|
|
||||||
|
account.owned_lists.select(:title).each do |list|
|
||||||
|
list.accounts.select(:username, :domain).each do |account|
|
||||||
|
csv << [list.title, acct(account)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_blocked_domains_csv
|
||||||
|
CSV.generate do |csv|
|
||||||
|
account.domain_blocks.pluck(:domain).each do |domain|
|
||||||
|
csv << [domain]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_storage
|
def total_storage
|
||||||
|
@ -32,6 +50,10 @@ class Export
|
||||||
account.following_count
|
account.following_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def total_lists
|
||||||
|
account.owned_lists.count
|
||||||
|
end
|
||||||
|
|
||||||
def total_followers
|
def total_followers
|
||||||
account.followers_count
|
account.followers_count
|
||||||
end
|
end
|
||||||
|
@ -44,13 +66,21 @@ class Export
|
||||||
account.muting.count
|
account.muting.count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def total_domain_blocks
|
||||||
|
account.domain_blocks.count
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def to_csv(accounts)
|
def to_csv(accounts)
|
||||||
CSV.generate do |csv|
|
CSV.generate do |csv|
|
||||||
accounts.each do |account|
|
accounts.each do |account|
|
||||||
csv << [(account.local? ? account.local_username_and_domain : account.acct)]
|
csv << [acct(account)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def acct(account)
|
||||||
|
account.local? ? account.local_username_and_domain : account.acct
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,6 @@ class Form::StatusBatch
|
||||||
|
|
||||||
attr_accessor :status_ids, :action, :current_account
|
attr_accessor :status_ids, :action, :current_account
|
||||||
|
|
||||||
ACTION_TYPE = %w(nsfw_on nsfw_off delete).freeze
|
|
||||||
|
|
||||||
def save
|
def save
|
||||||
case action
|
case action
|
||||||
when 'nsfw_on', 'nsfw_off'
|
when 'nsfw_on', 'nsfw_off'
|
||||||
|
|
|
@ -15,7 +15,7 @@ class ReportNote < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :report, inverse_of: :notes, touch: true
|
belongs_to :report, inverse_of: :notes, touch: true
|
||||||
|
|
||||||
scope :latest, -> { reorder('created_at ASC') }
|
scope :latest, -> { reorder(created_at: :desc) }
|
||||||
|
|
||||||
validates :content, presence: true, length: { maximum: 500 }
|
validates :content, presence: true, length: { maximum: 500 }
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
# invite_id :bigint(8)
|
# invite_id :bigint(8)
|
||||||
# remember_token :string
|
# remember_token :string
|
||||||
# chosen_languages :string is an Array
|
# chosen_languages :string is an Array
|
||||||
|
# created_by_application_id :bigint(8)
|
||||||
#
|
#
|
||||||
|
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
|
@ -49,7 +50,7 @@ class User < ApplicationRecord
|
||||||
# every day. Raising the duration reduces the amount of expensive
|
# every day. Raising the duration reduces the amount of expensive
|
||||||
# RegenerationWorker jobs that need to be run when those people come
|
# RegenerationWorker jobs that need to be run when those people come
|
||||||
# to check their feed
|
# to check their feed
|
||||||
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days
|
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
|
||||||
|
|
||||||
devise :two_factor_authenticatable,
|
devise :two_factor_authenticatable,
|
||||||
otp_secret_encryption_key: Rails.configuration.x.otp_secret
|
otp_secret_encryption_key: Rails.configuration.x.otp_secret
|
||||||
|
@ -66,6 +67,7 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :user
|
belongs_to :account, inverse_of: :user
|
||||||
belongs_to :invite, counter_cache: :uses, optional: true
|
belongs_to :invite, counter_cache: :uses, optional: true
|
||||||
|
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
|
||||||
accepts_nested_attributes_for :account
|
accepts_nested_attributes_for :account
|
||||||
|
|
||||||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||||
|
@ -74,15 +76,18 @@ class User < ApplicationRecord
|
||||||
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
|
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
|
||||||
validates_with BlacklistedEmailValidator, if: :email_changed?
|
validates_with BlacklistedEmailValidator, if: :email_changed?
|
||||||
validates_with EmailMxValidator, if: :validate_email_dns?
|
validates_with EmailMxValidator, if: :validate_email_dns?
|
||||||
|
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||||
|
|
||||||
scope :recent, -> { order(id: :desc) }
|
scope :recent, -> { order(id: :desc) }
|
||||||
scope :admins, -> { where(admin: true) }
|
scope :admins, -> { where(admin: true) }
|
||||||
scope :moderators, -> { where(moderator: true) }
|
scope :moderators, -> { where(moderator: true) }
|
||||||
scope :staff, -> { admins.or(moderators) }
|
scope :staff, -> { admins.or(moderators) }
|
||||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||||
|
scope :enabled, -> { where(disabled: false) }
|
||||||
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
|
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
|
||||||
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) }
|
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) }
|
||||||
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
|
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
|
||||||
|
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
|
||||||
|
|
||||||
before_validation :sanitize_languages
|
before_validation :sanitize_languages
|
||||||
|
|
||||||
|
@ -136,6 +141,10 @@ class User < ApplicationRecord
|
||||||
confirmed_at.present?
|
confirmed_at.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invited?
|
||||||
|
invite_id.present?
|
||||||
|
end
|
||||||
|
|
||||||
def staff?
|
def staff?
|
||||||
admin? || moderator?
|
admin? || moderator?
|
||||||
end
|
end
|
||||||
|
@ -294,7 +303,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
if resource.blank?
|
if resource.blank?
|
||||||
resource = new(email: attributes[:email])
|
resource = new(email: attributes[:email], agreement: true)
|
||||||
if Devise.check_at_sign && !resource[:email].index('@')
|
if Devise.check_at_sign && !resource[:email].index('@')
|
||||||
resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
|
resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
|
||||||
resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]
|
resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]
|
||||||
|
@ -307,7 +316,7 @@ class User < ApplicationRecord
|
||||||
resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
|
resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
|
||||||
|
|
||||||
if resource.blank?
|
if resource.blank?
|
||||||
resource = new(email: attributes[:mail].first, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
|
resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
|
||||||
resource.ldap_setup(attributes)
|
resource.ldap_setup(attributes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
|
||||||
has_many :virtual_attachments, key: :attachment
|
has_many :virtual_attachments, key: :attachment
|
||||||
|
|
||||||
attribute :moved_to, if: :moved?
|
attribute :moved_to, if: :moved?
|
||||||
|
attribute :also_known_as, if: :also_known_as?
|
||||||
|
|
||||||
class EndpointsSerializer < ActiveModel::Serializer
|
class EndpointsSerializer < ActiveModel::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
@ -116,6 +117,10 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
|
||||||
ActivityPub::TagManager.instance.uri_for(object.moved_to_account)
|
ActivityPub::TagManager.instance.uri_for(object.moved_to_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def also_known_as?
|
||||||
|
!object.also_known_as.empty?
|
||||||
|
end
|
||||||
|
|
||||||
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
class CustomEmojiSerializer < ActivityPub::EmojiSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
version: Mastodon::Version.to_s,
|
version: Mastodon::Version.to_s,
|
||||||
invites_enabled: Setting.min_invite_role == 'user',
|
invites_enabled: Setting.min_invite_role == 'user',
|
||||||
mascot: instance_presenter.mascot&.file&.url,
|
mascot: instance_presenter.mascot&.file&.url,
|
||||||
|
profile_directory: Setting.profile_directory,
|
||||||
}
|
}
|
||||||
|
|
||||||
if object.current_account
|
if object.current_account
|
||||||
|
|
|
@ -75,6 +75,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
@account.note = @json['summary'] || ''
|
@account.note = @json['summary'] || ''
|
||||||
@account.locked = @json['manuallyApprovesFollowers'] || false
|
@account.locked = @json['manuallyApprovesFollowers'] || false
|
||||||
@account.fields = property_values || {}
|
@account.fields = property_values || {}
|
||||||
|
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
|
||||||
@account.actor_type = actor_type
|
@account.actor_type = actor_type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class ActivityPub::ProcessCollectionService < BaseService
|
||||||
private
|
private
|
||||||
|
|
||||||
def different_actor?
|
def different_actor?
|
||||||
@json['actor'].present? && value_or_id(@json['actor']) != @account.uri && @json['signature'].present?
|
@json['actor'].present? && value_or_id(@json['actor']) != @account.uri
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_items(items)
|
def process_items(items)
|
||||||
|
|
|
@ -31,11 +31,11 @@ class AfterBlockDomainFromAccountService < BaseService
|
||||||
|
|
||||||
return unless follow.account.activitypub?
|
return unless follow.account.activitypub?
|
||||||
|
|
||||||
json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
json = ActiveModelSerializers::SerializableResource.new(
|
||||||
follow,
|
follow,
|
||||||
serializer: ActivityPub::RejectFollowSerializer,
|
serializer: ActivityPub::RejectFollowSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(@account))
|
).to_json
|
||||||
|
|
||||||
ActivityPub::DeliveryWorker.perform_async(json, @account.id, follow.account.inbox_url)
|
ActivityPub::DeliveryWorker.perform_async(json, @account.id, follow.account.inbox_url)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AppSignUpService < BaseService
|
||||||
|
def call(app, params)
|
||||||
|
return unless allowed_registrations?
|
||||||
|
|
||||||
|
user_params = params.slice(:email, :password, :agreement)
|
||||||
|
account_params = params.slice(:username)
|
||||||
|
user = User.create!(user_params.merge(created_by_application: app, password_confirmation: user_params[:password], account_attributes: account_params))
|
||||||
|
|
||||||
|
Doorkeeper::AccessToken.create!(application: app,
|
||||||
|
resource_owner_id: user.id,
|
||||||
|
scopes: app.scopes,
|
||||||
|
expires_in: Doorkeeper.configuration.access_token_expires_in,
|
||||||
|
use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def allowed_registrations?
|
||||||
|
Setting.open_registrations && !Rails.configuration.x.single_user_mode
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,11 +24,11 @@ class AuthorizeFollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(follow_request)
|
def build_json(follow_request)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
follow_request,
|
follow_request,
|
||||||
serializer: ActivityPub::AcceptFollowSerializer,
|
serializer: ActivityPub::AcceptFollowSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(follow_request.target_account))
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_xml(follow_request)
|
def build_xml(follow_request)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class BlockService < BaseService
|
class BlockService < BaseService
|
||||||
include StreamEntryRenderer
|
|
||||||
|
|
||||||
def call(account, target_account)
|
def call(account, target_account)
|
||||||
return if account.id == target_account.id
|
return if account.id == target_account.id
|
||||||
|
|
||||||
|
@ -27,11 +25,11 @@ class BlockService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(block)
|
def build_json(block)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
block,
|
block,
|
||||||
serializer: ActivityPub::BlockSerializer,
|
serializer: ActivityPub::BlockSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(block.account))
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_xml(block)
|
def build_xml(block)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class FollowService < BaseService
|
class FollowService < BaseService
|
||||||
include StreamEntryRenderer
|
|
||||||
|
|
||||||
# Follow a remote user, notify remote user about the follow
|
# Follow a remote user, notify remote user about the follow
|
||||||
# @param [Account] source_account From which to follow
|
# @param [Account] source_account From which to follow
|
||||||
# @param [String, Account] uri User URI to follow in the form of username@domain (or account record)
|
# @param [String, Account] uri User URI to follow in the form of username@domain (or account record)
|
||||||
|
@ -12,7 +10,7 @@ class FollowService < BaseService
|
||||||
target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true)
|
target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true)
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
|
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
|
||||||
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
|
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved?
|
||||||
|
|
||||||
if source_account.following?(target_account)
|
if source_account.following?(target_account)
|
||||||
# We're already following this account, but we'll call follow! again to
|
# We're already following this account, but we'll call follow! again to
|
||||||
|
@ -82,10 +80,10 @@ class FollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(follow_request)
|
def build_json(follow_request)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
follow_request,
|
follow_request,
|
||||||
serializer: ActivityPub::FollowSerializer,
|
serializer: ActivityPub::FollowSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(follow_request.account))
|
).to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,13 +26,16 @@ class PostStatusService < BaseService
|
||||||
text = media.find(&:video?) ? '📹' : '🖼' if media.size > 0
|
text = media.find(&:video?) ? '📹' : '🖼' if media.size > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
visibility = options[:visibility] || account.user&.setting_default_privacy
|
||||||
|
visibility = :unlisted if visibility == :public && account.silenced
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
status = account.statuses.create!(text: text,
|
status = account.statuses.create!(text: text,
|
||||||
media_attachments: media || [],
|
media_attachments: media || [],
|
||||||
thread: in_reply_to,
|
thread: in_reply_to,
|
||||||
sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]) || options[:spoiler_text].present?,
|
sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]) || options[:spoiler_text].present?,
|
||||||
spoiler_text: options[:spoiler_text] || '',
|
spoiler_text: options[:spoiler_text] || '',
|
||||||
visibility: options[:visibility] || account.user&.setting_default_privacy,
|
visibility: visibility,
|
||||||
language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account),
|
language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account),
|
||||||
application: options[:application])
|
application: options[:application])
|
||||||
end
|
end
|
||||||
|
@ -46,7 +49,6 @@ class PostStatusService < BaseService
|
||||||
unless status.local_only?
|
unless status.local_only?
|
||||||
Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id)
|
Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id)
|
||||||
ActivityPub::DistributionWorker.perform_async(status.id)
|
ActivityPub::DistributionWorker.perform_async(status.id)
|
||||||
ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:idempotency].present?
|
if options[:idempotency].present?
|
||||||
|
|
|
@ -60,11 +60,13 @@ class ProcessMentionsService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def activitypub_json
|
def activitypub_json
|
||||||
@activitypub_json ||= Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
return @activitypub_json if defined?(@activitypub_json)
|
||||||
|
payload = ActiveModelSerializers::SerializableResource.new(
|
||||||
@status,
|
@status,
|
||||||
serializer: ActivityPub::ActivitySerializer,
|
serializer: ActivityPub::ActivitySerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(@status.account))
|
).as_json
|
||||||
|
@activitypub_json = Oj.dump(@status.distributable? ? ActivityPub::LinkedDataSignature.new(payload).sign!(@status.account) : payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_account_service
|
def resolve_account_service
|
||||||
|
|
|
@ -19,11 +19,11 @@ class RejectFollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(follow_request)
|
def build_json(follow_request)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
follow_request,
|
follow_request,
|
||||||
serializer: ActivityPub::RejectFollowSerializer,
|
serializer: ActivityPub::RejectFollowSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(follow_request.target_account))
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_xml(follow_request)
|
def build_xml(follow_request)
|
||||||
|
|
|
@ -19,6 +19,7 @@ class ResolveAccountService < BaseService
|
||||||
@account = uri
|
@account = uri
|
||||||
@username = @account.username
|
@username = @account.username
|
||||||
@domain = @account.domain
|
@domain = @account.domain
|
||||||
|
uri = "#{@username}@#{@domain}"
|
||||||
|
|
||||||
return @account if @account.local? || !webfinger_update_due?
|
return @account if @account.local? || !webfinger_update_due?
|
||||||
else
|
else
|
||||||
|
|
|
@ -34,6 +34,8 @@ class SearchService < BaseService
|
||||||
.compact
|
.compact
|
||||||
|
|
||||||
statuses.reject { |status| StatusFilter.new(status, account).filtered? }
|
statuses.reject { |status| StatusFilter.new(status, account).filtered? }
|
||||||
|
rescue Faraday::ConnectionFailed
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_hashtags_search!
|
def perform_hashtags_search!
|
||||||
|
|
|
@ -20,11 +20,11 @@ class UnblockService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(unblock)
|
def build_json(unblock)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
unblock,
|
unblock,
|
||||||
serializer: ActivityPub::UndoBlockSerializer,
|
serializer: ActivityPub::UndoBlockSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(unblock.account))
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_xml(block)
|
def build_xml(block)
|
||||||
|
|
|
@ -43,11 +43,11 @@ class UnfollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_json(follow)
|
def build_json(follow)
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
follow,
|
follow,
|
||||||
serializer: ActivityPub::UndoFollowSerializer,
|
serializer: ActivityPub::UndoFollowSerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(follow.account))
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_xml(follow)
|
def build_xml(follow)
|
||||||
|
|
|
@ -10,7 +10,6 @@ class VerifyLinkService < BaseService
|
||||||
return unless link_back_present?
|
return unless link_back_present?
|
||||||
|
|
||||||
field.mark_verified!
|
field.mark_verified!
|
||||||
field.account.save!
|
|
||||||
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
|
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
|
||||||
Rails.logger.debug "Error fetching link #{@url}: #{e}"
|
Rails.logger.debug "Error fetching link #{@url}: #{e}"
|
||||||
nil
|
nil
|
||||||
|
|
|
@ -2,31 +2,32 @@
|
||||||
|
|
||||||
class BlacklistedEmailValidator < ActiveModel::Validator
|
class BlacklistedEmailValidator < ActiveModel::Validator
|
||||||
def validate(user)
|
def validate(user)
|
||||||
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?(user.email)
|
@email = user.email
|
||||||
|
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def blocked_email?(value)
|
def blocked_email?
|
||||||
on_blacklist?(value) || not_on_whitelist?(value)
|
on_blacklist? || not_on_whitelist?
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_blacklist?(value)
|
def on_blacklist?
|
||||||
return true if EmailDomainBlock.block?(value)
|
return true if EmailDomainBlock.block?(@email)
|
||||||
return false if Rails.configuration.x.email_domains_blacklist.blank?
|
return false if Rails.configuration.x.email_domains_blacklist.blank?
|
||||||
|
|
||||||
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
||||||
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
|
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
|
||||||
|
|
||||||
value =~ regexp
|
@email =~ regexp
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_on_whitelist?(value)
|
def not_on_whitelist?
|
||||||
return false if Rails.configuration.x.email_domains_whitelist.blank?
|
return false if Rails.configuration.x.email_domains_whitelist.blank?
|
||||||
|
|
||||||
domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
|
domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
|
||||||
regexp = Regexp.new("@(.+\\.)?(#{domains})$", true)
|
regexp = Regexp.new("@(.+\\.)?(#{domains})$", true)
|
||||||
|
|
||||||
value !~ regexp
|
@email !~ regexp
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,14 +4,19 @@ class DisallowedHashtagsValidator < ActiveModel::Validator
|
||||||
def validate(status)
|
def validate(status)
|
||||||
return unless status.local? && !status.reblog?
|
return unless status.local? && !status.reblog?
|
||||||
|
|
||||||
tags = Extractor.extract_hashtags(status.text)
|
@status = status
|
||||||
tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase }
|
tags = select_tags
|
||||||
|
|
||||||
status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty?
|
status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def select_tags
|
||||||
|
tags = Extractor.extract_hashtags(@status.text)
|
||||||
|
tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase }
|
||||||
|
end
|
||||||
|
|
||||||
def disallowed_hashtags
|
def disallowed_hashtags
|
||||||
return @disallowed_hashtags if @disallowed_hashtags
|
return @disallowed_hashtags if @disallowed_hashtags
|
||||||
|
|
||||||
|
|
|
@ -5,27 +5,29 @@ class StatusLengthValidator < ActiveModel::Validator
|
||||||
|
|
||||||
def validate(status)
|
def validate(status)
|
||||||
return unless status.local? && !status.reblog?
|
return unless status.local? && !status.reblog?
|
||||||
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
|
|
||||||
|
@status = status
|
||||||
|
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def too_long?(status)
|
def too_long?
|
||||||
countable_length(status) > MAX_CHARS
|
countable_length > MAX_CHARS
|
||||||
end
|
end
|
||||||
|
|
||||||
def countable_length(status)
|
def countable_length
|
||||||
total_text(status).mb_chars.grapheme_length
|
total_text.mb_chars.grapheme_length
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_text(status)
|
def total_text
|
||||||
[status.spoiler_text, countable_text(status)].join
|
[@status.spoiler_text, countable_text].join
|
||||||
end
|
end
|
||||||
|
|
||||||
def countable_text(status)
|
def countable_text
|
||||||
return '' if status.text.nil?
|
return '' if @status.text.nil?
|
||||||
|
|
||||||
status.text.dup.tap do |new_text|
|
@status.text.dup.tap do |new_text|
|
||||||
new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
|
new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
|
||||||
new_text.gsub!(Account::MENTION_RE, '@\2')
|
new_text.gsub!(Account::MENTION_RE, '@\2')
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,200 +1,185 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= @account.acct
|
= @account.acct
|
||||||
|
|
||||||
.table-wrapper
|
= render 'application/card', account: @account
|
||||||
%table.table.inline-table
|
|
||||||
%tbody
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.username')
|
|
||||||
%td= @account.username
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.domain')
|
|
||||||
%td= @account.domain
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.display_name')
|
|
||||||
%td= @account.display_name
|
|
||||||
|
|
||||||
%tr
|
.dashboard__counters{ style: 'margin-top: 10px' }
|
||||||
%th= t('admin.accounts.avatar')
|
%div
|
||||||
%td
|
= link_to admin_account_statuses_path(@account.id) do
|
||||||
= link_to @account.avatar.url(:original) do
|
.dashboard__counters__num= number_with_delimiter @account.statuses_count
|
||||||
= image_tag @account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
|
.dashboard__counters__label= t 'admin.accounts.statuses'
|
||||||
- if @account.local? && @account.avatar?
|
%div
|
||||||
= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account)
|
= link_to admin_account_statuses_path(@account.id, { media: true }) do
|
||||||
%tr
|
.dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
|
||||||
%th= t('admin.accounts.header')
|
.dashboard__counters__label= t 'admin.accounts.media_attachments'
|
||||||
%td
|
%div
|
||||||
= link_to @account.header.url(:original) do
|
= link_to admin_account_followers_path(@account.id) do
|
||||||
= image_tag @account.header.url(:original), alt: '', width: 128, height: 40, class: 'header'
|
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
|
||||||
- if @account.local? && @account.header?
|
.dashboard__counters__label= t 'admin.accounts.followers'
|
||||||
= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, @account)
|
%div
|
||||||
|
= link_to admin_reports_path(account_id: @account.id) do
|
||||||
- if @account.local?
|
.dashboard__counters__num= number_with_delimiter @account.reports.count
|
||||||
%tr
|
.dashboard__counters__label= t '.created_reports'
|
||||||
%th= t('admin.accounts.role')
|
%div
|
||||||
%td
|
= link_to admin_reports_path(target_account_id: @account.id) do
|
||||||
- if @account.user.nil?
|
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
|
||||||
= t("admin.accounts.moderation.suspended")
|
.dashboard__counters__label= t '.targeted_reports'
|
||||||
- else
|
%div
|
||||||
= t("admin.accounts.roles.#{@account.user&.role}")
|
%div
|
||||||
= table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user)
|
.dashboard__counters__text
|
||||||
= table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
|
- if @account.local? && @account.user.nil?
|
||||||
%tr
|
%span.neutral= t('admin.accounts.deleted')
|
||||||
%th= t('admin.accounts.email')
|
- elsif @account.suspended?
|
||||||
%td
|
%span.red= t('admin.accounts.suspended')
|
||||||
= @account.user_email
|
- elsif @account.silenced?
|
||||||
= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
|
%span.red= t('admin.accounts.silenced')
|
||||||
- if @account.user_unconfirmed_email.present?
|
- elsif @account.local? && @account.user&.disabled?
|
||||||
%th= t('admin.accounts.unconfirmed_email')
|
%span.red= t('admin.accounts.disabled')
|
||||||
%td
|
- elsif @account.local? && !@account.user&.confirmed?
|
||||||
= @account.user_unconfirmed_email
|
%span.neutral= t('admin.accounts.confirming')
|
||||||
%tr
|
- else
|
||||||
%th= t('admin.accounts.email_status')
|
%span.neutral= t('admin.accounts.no_limits_imposed')
|
||||||
%td
|
.dashboard__counters__label= t 'admin.accounts.login_status'
|
||||||
- if @account.user&.confirmed?
|
|
||||||
= t('admin.accounts.confirmed')
|
|
||||||
- else
|
|
||||||
= t('admin.accounts.confirming')
|
|
||||||
= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.login_status')
|
|
||||||
%td
|
|
||||||
- if @account.user&.disabled?
|
|
||||||
= t('admin.accounts.disabled')
|
|
||||||
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
|
|
||||||
- else
|
|
||||||
= t('admin.accounts.enabled')
|
|
||||||
= table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user)
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.most_recent_ip')
|
|
||||||
%td= @account.user_current_sign_in_ip
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.most_recent_activity')
|
|
||||||
%td
|
|
||||||
- if @account.user_current_sign_in_at
|
|
||||||
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }
|
|
||||||
= l @account.user_current_sign_in_at
|
|
||||||
- else
|
|
||||||
\-
|
|
||||||
- else
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.profile_url')
|
|
||||||
%td= link_to @account.url, @account.url
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.protocol')
|
|
||||||
%td= @account.protocol.humanize
|
|
||||||
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.follows')
|
|
||||||
%td= number_to_human @account.following_count
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.followers')
|
|
||||||
%td= number_to_human @account.followers_count
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.statuses')
|
|
||||||
%td= link_to number_to_human(@account.statuses_count), admin_account_statuses_path(@account.id)
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.media_attachments')
|
|
||||||
%td
|
|
||||||
= link_to number_to_human(@account.media_attachments.count), admin_account_statuses_path(@account.id, { media: true })
|
|
||||||
= surround '(', ')' do
|
|
||||||
= number_to_human_size @account.media_attachments.sum('file_file_size')
|
|
||||||
%tr
|
|
||||||
%th= t('.created_reports')
|
|
||||||
%td= link_to pluralize(@account.reports.count, t('.report')), admin_reports_path(account_id: @account.id)
|
|
||||||
%tr
|
|
||||||
%th= t('.targeted_reports')
|
|
||||||
%td= link_to pluralize(@account.targeted_reports.count, t('.report')), admin_reports_path(target_account_id: @account.id)
|
|
||||||
|
|
||||||
%div{ style: 'overflow: hidden' }
|
|
||||||
%div{ style: 'float: right' }
|
|
||||||
- if @account.local?
|
|
||||||
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
|
||||||
- if @account.user&.otp_required_for_login?
|
|
||||||
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
|
||||||
- unless @account.memorial?
|
|
||||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
|
||||||
- else
|
|
||||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
|
||||||
|
|
||||||
%div{ style: 'float: left' }
|
|
||||||
- if @account.silenced?
|
|
||||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
|
||||||
- else
|
|
||||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
|
|
||||||
|
|
||||||
- if @account.local?
|
|
||||||
- unless @account.user_confirmed?
|
|
||||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
|
||||||
|
|
||||||
- if @account.suspended?
|
|
||||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
|
||||||
- else
|
|
||||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
|
|
||||||
|
|
||||||
- if !@account.local? && @account.hub_url.present?
|
|
||||||
%hr.spacer/
|
|
||||||
|
|
||||||
%h3 OStatus
|
|
||||||
|
|
||||||
|
- unless @account.local? && @account.user.nil?
|
||||||
.table-wrapper
|
.table-wrapper
|
||||||
%table.table.inline-table
|
%table.table.inline-table
|
||||||
%tbody
|
%tbody
|
||||||
%tr
|
- if @account.local?
|
||||||
%th= t('admin.accounts.feed_url')
|
- if @account.avatar?
|
||||||
%td= link_to @account.remote_url, @account.remote_url
|
%tr
|
||||||
%tr
|
%th= t('admin.accounts.avatar')
|
||||||
%th= t('admin.accounts.push_subscription_expires')
|
%td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account)
|
||||||
%td
|
%td
|
||||||
- if @account.subscribed?
|
|
||||||
%time.formatted{ datetime: @account.subscription_expires_at.iso8601, title: l(@account.subscription_expires_at) }
|
- if @account.header?
|
||||||
= l @account.subscription_expires_at
|
%tr
|
||||||
- else
|
%th= t('admin.accounts.header')
|
||||||
= t('admin.accounts.not_subscribed')
|
%td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, @account)
|
||||||
%tr
|
%td
|
||||||
%th= t('admin.accounts.salmon_url')
|
|
||||||
%td= link_to @account.salmon_url, @account.salmon_url
|
%tr
|
||||||
|
%th= t('admin.accounts.role')
|
||||||
|
%td= t("admin.accounts.roles.#{@account.user&.role}")
|
||||||
|
%td
|
||||||
|
= table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user)
|
||||||
|
= table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.email')
|
||||||
|
%td= @account.user_email
|
||||||
|
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
|
||||||
|
|
||||||
|
- if @account.user_unconfirmed_email.present?
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.unconfirmed_email')
|
||||||
|
%td= @account.user_unconfirmed_email
|
||||||
|
%td
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.email_status')
|
||||||
|
%td
|
||||||
|
- if @account.user&.confirmed?
|
||||||
|
= t('admin.accounts.confirmed')
|
||||||
|
- else
|
||||||
|
= t('admin.accounts.confirming')
|
||||||
|
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.login_status')
|
||||||
|
%td
|
||||||
|
- if @account.user&.disabled?
|
||||||
|
= t('admin.accounts.disabled')
|
||||||
|
- else
|
||||||
|
= t('admin.accounts.enabled')
|
||||||
|
%td
|
||||||
|
- if @account.user&.disabled?
|
||||||
|
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
|
||||||
|
- else
|
||||||
|
= table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user)
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('simple_form.labels.defaults.locale')
|
||||||
|
%td= @account.user_locale
|
||||||
|
%td
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.joined')
|
||||||
|
%td
|
||||||
|
%time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at
|
||||||
|
%td
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.most_recent_ip')
|
||||||
|
%td= @account.user_current_sign_in_ip
|
||||||
|
%td
|
||||||
|
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.most_recent_activity')
|
||||||
|
%td
|
||||||
|
- if @account.user_current_sign_in_at
|
||||||
|
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at
|
||||||
|
|
||||||
|
- if @account.user&.invited?
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.invited_by')
|
||||||
|
%td= admin_account_link_to @account.user.invite.user.account
|
||||||
|
%td
|
||||||
|
|
||||||
|
- else
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.inbox_url')
|
||||||
|
%td
|
||||||
|
= @account.inbox_url
|
||||||
|
= fa_icon DeliveryFailureTracker.unavailable?(@account.inbox_url) ? 'times' : 'check'
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.shared_inbox_url')
|
||||||
|
%td
|
||||||
|
= @account.shared_inbox_url
|
||||||
|
= fa_icon DeliveryFailureTracker.unavailable?(@account.shared_inbox_url) ? 'times' : 'check'
|
||||||
|
|
||||||
%div{ style: 'overflow: hidden' }
|
%div{ style: 'overflow: hidden' }
|
||||||
%div{ style: 'float: right' }
|
%div{ style: 'float: right' }
|
||||||
= link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' if can?(:subscribe, @account)
|
- if @account.local?
|
||||||
- if @account.subscribed?
|
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
||||||
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
|
- if @account.user&.otp_required_for_login?
|
||||||
|
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
||||||
|
- unless @account.memorial?
|
||||||
|
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||||
|
- else
|
||||||
|
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||||
|
|
||||||
|
%div{ style: 'float: left' }
|
||||||
|
- if @account.local?
|
||||||
|
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||||
|
- if @account.silenced?
|
||||||
|
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||||
|
- else
|
||||||
|
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
|
||||||
|
|
||||||
|
- if @account.local?
|
||||||
|
- unless @account.user_confirmed?
|
||||||
|
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||||
|
|
||||||
|
- if @account.suspended?
|
||||||
|
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||||
|
- else
|
||||||
|
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
|
||||||
|
|
||||||
- if !@account.local? && @account.inbox_url.present?
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
%h3 ActivityPub
|
- unless @warnings.empty?
|
||||||
|
= render @warnings
|
||||||
|
|
||||||
.table-wrapper
|
%hr.spacer/
|
||||||
%table.table.inline-table
|
|
||||||
%tbody
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.inbox_url')
|
|
||||||
%td= link_to @account.inbox_url, @account.inbox_url
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.outbox_url')
|
|
||||||
%td= link_to @account.outbox_url, @account.outbox_url
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.shared_inbox_url')
|
|
||||||
%td= link_to @account.shared_inbox_url, @account.shared_inbox_url
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.followers_url')
|
|
||||||
%td= link_to @account.followers_url, @account.followers_url
|
|
||||||
|
|
||||||
%hr.spacer/
|
= render @moderation_notes
|
||||||
|
|
||||||
= render @warnings
|
= simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|
|
||||||
|
= render 'shared/error_messages', object: @account_moderation_note
|
||||||
|
|
||||||
%hr.spacer/
|
= f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6
|
||||||
|
= f.hidden_field :target_account_id
|
||||||
|
|
||||||
= render @moderation_notes
|
.actions
|
||||||
|
= f.button :button, t('admin.account_moderation_notes.create'), type: :submit
|
||||||
= simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f|
|
|
||||||
= render 'shared/error_messages', object: @account_moderation_note
|
|
||||||
|
|
||||||
= f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6
|
|
||||||
= f.hidden_field :target_account_id
|
|
||||||
|
|
||||||
.actions
|
|
||||||
= f.button :button, t('admin.account_moderation_notes.create'), type: :submit
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('admin.followers.title', acct: @account.acct)
|
||||||
|
|
||||||
|
.filters
|
||||||
|
.filter-subset
|
||||||
|
%strong= t('admin.accounts.location.title')
|
||||||
|
%ul
|
||||||
|
%li= link_to t('admin.accounts.location.local'), admin_account_followers_path(@account.id), class: 'selected'
|
||||||
|
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
||||||
|
= link_to admin_account_path(@account.id) do
|
||||||
|
= fa_icon 'chevron-left fw'
|
||||||
|
= t('admin.followers.back_to_account')
|
||||||
|
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
.table-wrapper
|
||||||
|
%table.table
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= t('admin.accounts.username')
|
||||||
|
%th= t('admin.accounts.role')
|
||||||
|
%th= t('admin.accounts.most_recent_ip')
|
||||||
|
%th= t('admin.accounts.most_recent_activity')
|
||||||
|
%th
|
||||||
|
%tbody
|
||||||
|
= render partial: 'admin/accounts/account', collection: @followers.map(&:account)
|
||||||
|
|
||||||
|
= paginate @followers
|
|
@ -11,7 +11,7 @@
|
||||||
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
|
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
|
||||||
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
.back-link{ style: 'flex: 1 1 auto; text-align: right' }
|
||||||
= link_to admin_account_path(@account.id) do
|
= link_to admin_account_path(@account.id) do
|
||||||
%i.fa.fa-chevron-left.fa-fw
|
= fa_icon 'chevron-left fw'
|
||||||
= t('admin.statuses.back_to_account')
|
= t('admin.statuses.back_to_account')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
%tr
|
%tr
|
||||||
%td.column-cell.text-center.padded
|
%td.column-cell.text-center.padded
|
||||||
%h1= t 'notification_mailer.digest.title'
|
%h1= t 'notification_mailer.digest.title'
|
||||||
%p.lead= t('notification_mailer.digest.body', since: l(@since.to_date, format: :short), instance: site_hostname)
|
%p.lead= t('notification_mailer.digest.body', since: l((@me.user_current_sign_in_at || @since).to_date, format: :short), instance: site_hostname)
|
||||||
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
|
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
|
||||||
%tbody
|
%tbody
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||||
|
|
||||||
<%= raw t('notification_mailer.digest.body', since: l(@since), instance: root_url) %>
|
<%= raw t('notification_mailer.digest.body', since: l(@me.user_current_sign_in_at || @since), instance: root_url) %>
|
||||||
<% @notifications.each do |notification| %>
|
<% @notifications.each do |notification| %>
|
||||||
|
|
||||||
* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.acct) %>
|
* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.acct) %>
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
%th= t('exports.follows')
|
%th= t('exports.follows')
|
||||||
%td= number_with_delimiter @export.total_follows
|
%td= number_with_delimiter @export.total_follows
|
||||||
%td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
|
%td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
|
||||||
|
%tr
|
||||||
|
%th= t('exports.lists')
|
||||||
|
%td= number_with_delimiter @export.total_lists
|
||||||
|
%td= table_link_to 'download', t('exports.csv'), settings_exports_lists_path(format: :csv)
|
||||||
%tr
|
%tr
|
||||||
%th= t('accounts.followers', count: @export.total_followers)
|
%th= t('accounts.followers', count: @export.total_followers)
|
||||||
%td= number_with_delimiter @export.total_followers
|
%td= number_with_delimiter @export.total_followers
|
||||||
|
@ -28,6 +32,10 @@
|
||||||
%th= t('exports.mutes')
|
%th= t('exports.mutes')
|
||||||
%td= number_with_delimiter @export.total_mutes
|
%td= number_with_delimiter @export.total_mutes
|
||||||
%td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
|
%td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
|
||||||
|
%tr
|
||||||
|
%th= t('exports.domain_blocks')
|
||||||
|
%td= number_with_delimiter @export.total_domain_blocks
|
||||||
|
%td= table_link_to 'download', t('exports.csv'), settings_exports_domain_blocks_path(format: :csv)
|
||||||
|
|
||||||
%p.muted-hint= t('exports.archive_takeout.hint_html')
|
%p.muted-hint= t('exports.archive_takeout.hint_html')
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,12 @@
|
||||||
%tbody
|
%tbody
|
||||||
%tr
|
%tr
|
||||||
%td.button-primary
|
%td.button-primary
|
||||||
= link_to confirmation_url(@resource, confirmation_token: @token) do
|
- if @resource.created_by_application
|
||||||
%span= t 'devise.mailer.confirmation_instructions.action'
|
= link_to confirmation_url(@resource, confirmation_token: @token, redirect_to_app: 'true') do
|
||||||
|
%span= t 'devise.mailer.confirmation_instructions.action_with_app', app: @resource.created_by_application.name
|
||||||
|
- else
|
||||||
|
= link_to confirmation_url(@resource, confirmation_token: @token) do
|
||||||
|
%span= t 'devise.mailer.confirmation_instructions.action'
|
||||||
|
|
||||||
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
||||||
%tbody
|
%tbody
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<%= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname %>
|
<%= t 'devise.mailer.confirmation_instructions.explanation', host: site_hostname %>
|
||||||
|
|
||||||
=> <%= confirmation_url(@resource, confirmation_token: @token) %>
|
=> <%= confirmation_url(@resource, confirmation_token: @token, redirect_to_app: @resource.created_by_application ? 'true' : nil) %>
|
||||||
|
|
||||||
<%= strip_tags(t('devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: terms_url)) %>
|
<%= strip_tags(t('devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: terms_url)) %>
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class ActivityPub::DistributionWorker
|
||||||
return if skip_distribution?
|
return if skip_distribution?
|
||||||
|
|
||||||
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
|
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
|
||||||
[signed_payload, @account.id, inbox_url]
|
[payload, @account.id, inbox_url]
|
||||||
end
|
end
|
||||||
|
|
||||||
relay! if relayable?
|
relay! if relayable?
|
||||||
|
@ -31,24 +31,35 @@ class ActivityPub::DistributionWorker
|
||||||
end
|
end
|
||||||
|
|
||||||
def inboxes
|
def inboxes
|
||||||
@inboxes ||= @account.followers.inboxes
|
# Deliver the status to all followers.
|
||||||
|
# If the status is a reply to another local status, also forward it to that
|
||||||
|
# status' authors' followers.
|
||||||
|
@inboxes ||= if @status.reply? && @status.thread.account.local? && @status.distributable?
|
||||||
|
@account.followers.or(@status.thread.account.followers).inboxes
|
||||||
|
else
|
||||||
|
@account.followers.inboxes
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def signed_payload
|
def signed_payload
|
||||||
@signed_payload ||= Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account))
|
Oj.dump(ActivityPub::LinkedDataSignature.new(unsigned_payload).sign!(@account))
|
||||||
end
|
end
|
||||||
|
|
||||||
def payload
|
def unsigned_payload
|
||||||
@payload ||= ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
@status,
|
@status,
|
||||||
serializer: ActivityPub::ActivitySerializer,
|
serializer: ActivityPub::ActivitySerializer,
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json
|
).as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def payload
|
||||||
|
@payload ||= @status.distributable? ? signed_payload : Oj.dump(unsigned_payload)
|
||||||
|
end
|
||||||
|
|
||||||
def relay!
|
def relay!
|
||||||
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
|
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
|
||||||
[signed_payload, @account.id, inbox_url]
|
[payload, @account.id, inbox_url]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue