mirror of https://framagit.org/tykayn/mastodon
Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/controllers/home_controller.rb`: Upstream made it so `/web` is available to non-logged-in users and `/` redirects to `/web` instead of `/about`. Kept our version since glitch-soc's WebUI doesn't have what's needed yet and I think /about is still a much better landing page anyway. - `app/models/form/admin_settings.rb`: Upstream added new settings, and glitch-soc had an extra setting. Not really a conflict. Added upstream's new settings. - `app/serializers/initial_state_serializer.rb`: Upstream added a new `server` initial state object. Not really a conflict. Merged upstream's changes. - `app/views/admin/settings/edit.html.haml`: Upstream added new settings. Not really a conflict. Merged upstream's changes. - `app/workers/scheduler/feed_cleanup_scheduler.rb`: Upstream refactored that part and removed the file. Ported our relevant changes into `app/lib/vacuum/feeds_vacuum.rb` - `config/settings.yml`: Upstream added new settings. Not a real conflict. Added upstream's new settings.master
commit
221580a3af
|
@ -20,7 +20,7 @@
|
|||
"forwardPorts": [3000, 4000],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
|
||||
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
|
|
|
@ -27,6 +27,7 @@ services:
|
|||
ES_ENABLED: 'true'
|
||||
ES_HOST: es
|
||||
ES_PORT: '9200'
|
||||
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
networks:
|
||||
|
@ -72,6 +73,12 @@ services:
|
|||
soft: -1
|
||||
hard: -1
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:v1.2.9
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal_network
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
|
|
|
@ -10,6 +10,9 @@ on:
|
|||
paths:
|
||||
- .github/workflows/build-image.yml
|
||||
- Dockerfile
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -9,6 +9,9 @@ on:
|
|||
env:
|
||||
RAILS_ENV: test
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-i18n:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -55,7 +55,7 @@ jobs:
|
|||
with:
|
||||
node-version: 16.x
|
||||
cache: yarn
|
||||
- name: Intall dependencies
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Set-up RuboCop Problem Mathcher
|
||||
uses: r7kamura/rubocop-problem-matchers-action@v1
|
||||
|
|
|
@ -5,7 +5,7 @@ SHELL ["/bin/bash", "-c"]
|
|||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
|
||||
# Install Node v16 (LTS)
|
||||
ENV NODE_VER="16.16.0"
|
||||
ENV NODE_VER="16.17.1"
|
||||
RUN ARCH= && \
|
||||
dpkgArch="$(dpkg --print-architecture)" && \
|
||||
case "${dpkgArch##*-}" in \
|
||||
|
@ -19,7 +19,7 @@ RUN ARCH= && \
|
|||
esac && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends ca-certificates wget python apt-utils && \
|
||||
apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \
|
||||
cd ~ && \
|
||||
wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
|
|
8
Gemfile
8
Gemfile
|
@ -7,7 +7,7 @@ gem 'pkg-config', '~> 1.4'
|
|||
gem 'rexml', '~> 3.2'
|
||||
|
||||
gem 'puma', '~> 5.6'
|
||||
gem 'rails', '~> 6.1.6'
|
||||
gem 'rails', '~> 6.1.7'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.2'
|
||||
gem 'rack', '~> 2.2.4'
|
||||
|
@ -46,7 +46,7 @@ gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
|||
|
||||
gem 'color_diff', '~> 0.1'
|
||||
gem 'discard', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 5.5'
|
||||
gem 'doorkeeper', '~> 5.6'
|
||||
gem 'ed25519', '~> 1.3'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
|
@ -55,7 +55,7 @@ gem 'redis-namespace', '~> 1.9'
|
|||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 5.1'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.5.0'
|
||||
gem 'httplog', '~> 1.6.0'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.2'
|
||||
gem 'link_header', '~> 0.0'
|
||||
|
@ -116,7 +116,7 @@ end
|
|||
group :test do
|
||||
gem 'capybara', '~> 3.37'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.22'
|
||||
gem 'faker', '~> 2.23'
|
||||
gem 'microformats', '~> 4.4'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.1'
|
||||
|
|
154
Gemfile.lock
154
Gemfile.lock
|
@ -10,40 +10,40 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actioncable (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actionmailbox (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
activejob (= 6.1.7)
|
||||
activerecord (= 6.1.7)
|
||||
activestorage (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actionmailer (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
actionview (= 6.1.7)
|
||||
activejob (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actionpack (6.1.7)
|
||||
actionview (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actiontext (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
activerecord (= 6.1.7)
|
||||
activestorage (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
actionview (6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -54,22 +54,22 @@ GEM
|
|||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activejob (6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activerecord (6.1.6)
|
||||
activemodel (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activestorage (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
activemodel (6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
activerecord (6.1.7)
|
||||
activemodel (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
activestorage (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
activejob (= 6.1.7)
|
||||
activerecord (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.6)
|
||||
activesupport (6.1.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -110,12 +110,11 @@ GEM
|
|||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
better_html (1.0.16)
|
||||
actionview (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
better_html (2.0.1)
|
||||
actionview (>= 6.0)
|
||||
activesupport (>= 6.0)
|
||||
ast (~> 2.0)
|
||||
erubi (~> 1.4)
|
||||
html_tokenizer (~> 0.0.6)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bindata (2.4.10)
|
||||
|
@ -176,7 +175,7 @@ GEM
|
|||
coderay (1.1.3)
|
||||
color_diff (0.1)
|
||||
concurrent-ruby (1.1.10)
|
||||
connection_pool (2.2.5)
|
||||
connection_pool (2.3.0)
|
||||
cose (1.2.1)
|
||||
cbor (~> 0.5.9)
|
||||
openssl-signature_algorithm (~> 1.0)
|
||||
|
@ -207,7 +206,7 @@ GEM
|
|||
docile (1.3.4)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.5.4)
|
||||
doorkeeper (5.6.0)
|
||||
railties (>= 5)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
|
@ -224,12 +223,12 @@ GEM
|
|||
faraday (~> 1)
|
||||
multi_json
|
||||
encryptor (3.0.0)
|
||||
erubi (1.10.0)
|
||||
erubi (1.11.0)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
excon (0.76.0)
|
||||
fabrication (2.30.0)
|
||||
faker (2.22.0)
|
||||
faker (2.23.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.9.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
|
@ -301,7 +300,6 @@ GEM
|
|||
highline (2.0.3)
|
||||
hiredis (0.6.3)
|
||||
hkdf (0.3.0)
|
||||
html_tokenizer (0.0.7)
|
||||
htmlentities (4.3.4)
|
||||
http (5.1.0)
|
||||
addressable (~> 2.8)
|
||||
|
@ -313,15 +311,15 @@ GEM
|
|||
http-form_data (2.3.0)
|
||||
http_accept_language (2.1.1)
|
||||
httpclient (2.8.3)
|
||||
httplog (1.5.0)
|
||||
rack (>= 1.0)
|
||||
httplog (1.6.0)
|
||||
rack (>= 2.0)
|
||||
rainbow (>= 2.0.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (1.0.11)
|
||||
i18n-tasks (1.0.12)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
better_html (~> 1.0)
|
||||
better_html (>= 1.0, < 3.0)
|
||||
erubi
|
||||
highline (>= 2.0.0)
|
||||
i18n
|
||||
|
@ -386,7 +384,7 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.18.0)
|
||||
loofah (2.19.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -407,7 +405,7 @@ GEM
|
|||
mime-types-data (3.2022.0105)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.16.2)
|
||||
minitest (5.16.3)
|
||||
msgpack (1.5.4)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.1.1)
|
||||
|
@ -454,7 +452,7 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
ox (2.14.11)
|
||||
parallel (1.22.1)
|
||||
parser (3.1.2.0)
|
||||
parser (3.1.2.1)
|
||||
ast (~> 2.4.1)
|
||||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
|
@ -502,20 +500,20 @@ GEM
|
|||
rack
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
rails (6.1.6)
|
||||
actioncable (= 6.1.6)
|
||||
actionmailbox (= 6.1.6)
|
||||
actionmailer (= 6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
actiontext (= 6.1.6)
|
||||
actionview (= 6.1.6)
|
||||
activejob (= 6.1.6)
|
||||
activemodel (= 6.1.6)
|
||||
activerecord (= 6.1.6)
|
||||
activestorage (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
rails (6.1.7)
|
||||
actioncable (= 6.1.7)
|
||||
actionmailbox (= 6.1.7)
|
||||
actionmailer (= 6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
actiontext (= 6.1.7)
|
||||
actionview (= 6.1.7)
|
||||
activejob (= 6.1.7)
|
||||
activemodel (= 6.1.7)
|
||||
activerecord (= 6.1.7)
|
||||
activestorage (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.6)
|
||||
railties (= 6.1.7)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -531,9 +529,9 @@ GEM
|
|||
railties (>= 6.0.0, < 7)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (6.1.6)
|
||||
actionpack (= 6.1.6)
|
||||
activesupport (= 6.1.6)
|
||||
railties (6.1.7)
|
||||
actionpack (= 6.1.7)
|
||||
activesupport (= 6.1.7)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
@ -613,10 +611,10 @@ GEM
|
|||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.5.5)
|
||||
connection_pool (>= 2.2.2)
|
||||
sidekiq (6.5.7)
|
||||
connection_pool (>= 2.2.5)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (4.0.2)
|
||||
|
@ -686,12 +684,12 @@ GEM
|
|||
unf (~> 0.1.0)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.3)
|
||||
tzinfo-data (1.2022.4)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.1.0)
|
||||
unicode-display_width (2.3.0)
|
||||
uniform_notifier (1.16.0)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
|
@ -764,11 +762,11 @@ DEPENDENCIES
|
|||
devise-two-factor (~> 4.0)
|
||||
devise_pam_authenticatable2 (~> 9.2)
|
||||
discard (~> 1.2)
|
||||
doorkeeper (~> 5.5)
|
||||
doorkeeper (~> 5.6)
|
||||
dotenv-rails (~> 2.8)
|
||||
ed25519 (~> 1.3)
|
||||
fabrication (~> 2.30)
|
||||
faker (~> 2.22)
|
||||
faker (~> 2.23)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
|
@ -781,7 +779,7 @@ DEPENDENCIES
|
|||
htmlentities (~> 4.3)
|
||||
http (~> 5.1)
|
||||
http_accept_language (~> 2.1)
|
||||
httplog (~> 1.5.0)
|
||||
httplog (~> 1.6.0)
|
||||
i18n-tasks (~> 1.0)
|
||||
idn-ruby
|
||||
json-ld
|
||||
|
@ -820,7 +818,7 @@ DEPENDENCIES
|
|||
rack (~> 2.2.4)
|
||||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 1.1)
|
||||
rails (~> 6.1.6)
|
||||
rails (~> 6.1.7)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
|
|
|
@ -10,10 +10,10 @@ class AboutController < ApplicationController
|
|||
before_action :require_open_federation!, only: [:show, :more]
|
||||
before_action :set_body_classes, only: :show
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in, only: [:more, :terms]
|
||||
before_action :set_expires_in, only: [:more]
|
||||
before_action :set_registration_form_time, only: :show
|
||||
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
skip_before_action :require_functional!, only: [:more]
|
||||
|
||||
def show; end
|
||||
|
||||
|
@ -28,8 +28,6 @@ class AboutController < ApplicationController
|
|||
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
||||
end
|
||||
|
||||
def terms; end
|
||||
|
||||
helper_method :display_blocks?
|
||||
helper_method :display_blocks_rationale?
|
||||
helper_method :public_fetch_mode?
|
||||
|
|
|
@ -7,7 +7,7 @@ class AccountsController < ApplicationController
|
|||
include AccountControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController
|
|||
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
before_action :require_signature!
|
||||
before_action :require_account_signature!
|
||||
before_action :set_claim_result
|
||||
|
||||
def create
|
||||
|
|
|
@ -4,7 +4,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_items
|
||||
before_action :set_size
|
||||
before_action :set_type
|
||||
|
|
|
@ -4,7 +4,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!
|
||||
before_action :require_account_signature!
|
||||
before_action :set_items
|
||||
before_action :set_cache_headers
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
include AccountOwnedConcern
|
||||
|
||||
before_action :skip_unknown_actor_activity
|
||||
before_action :require_signature!
|
||||
before_action :require_actor_signature!
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
|
@ -49,17 +49,17 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
end
|
||||
|
||||
def upgrade_account
|
||||
if signed_request_account.ostatus?
|
||||
if signed_request_account&.ostatus?
|
||||
signed_request_account.update(last_webfingered_at: nil)
|
||||
ResolveAccountWorker.perform_async(signed_request_account.acct)
|
||||
end
|
||||
|
||||
DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
|
||||
DeliveryFailureTracker.reset!(signed_request_actor.inbox_url)
|
||||
end
|
||||
|
||||
def process_collection_synchronization
|
||||
raw_params = request.headers['Collection-Synchronization']
|
||||
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true'
|
||||
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil?
|
||||
|
||||
# Re-using the syntax for signature parameters
|
||||
tree = SignatureParamsParser.new.parse(raw_params)
|
||||
|
@ -71,6 +71,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
end
|
||||
|
||||
def process_payload
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
|||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
|||
|
||||
DESCENDANTS_LIMIT = 60
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies
|
||||
|
|
|
@ -131,4 +131,10 @@ class Api::BaseController < ApplicationController
|
|||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def respond_with_error(code)
|
||||
render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
|
|
|
@ -30,12 +30,12 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
self.response_body = Oj.dump(response.body)
|
||||
self.status = response.status
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
|
||||
render json: ValidationErrorFormatter.new(e, 'account.username': :username, 'invite_request.text': :reason).as_json, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def follow
|
||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
|
||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
|
||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
|
||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
|
||||
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||
before_action :set_status
|
||||
before_action :set_translation
|
||||
|
||||
rescue_from TranslationService::NotConfiguredError, with: :not_found
|
||||
rescue_from TranslationService::UnexpectedResponseError, TranslationService::QuotaExceededError, TranslationService::TooManyRequestsError, with: :service_unavailable
|
||||
|
||||
def create
|
||||
render json: @translation, serializer: REST::TranslationSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def set_translation
|
||||
@translation = TranslateStatusService.new.call(@status, content_locale)
|
||||
end
|
||||
end
|
|
@ -5,8 +5,7 @@ class Api::V2::SearchController < Api::BaseController
|
|||
|
||||
RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||
before_action :require_user!
|
||||
before_action -> { authorize_if_got_token! :read, :'read:search' }
|
||||
|
||||
def index
|
||||
@search = Search.new(search_results)
|
||||
|
@ -24,7 +23,7 @@ class Api::V2::SearchController < Api::BaseController
|
|||
params[:q],
|
||||
current_account,
|
||||
limit_param(RESULTS_LIMIT),
|
||||
search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed))
|
||||
search_params.merge(resolve: user_signed_in? ? truthy_param?(:resolve) : false, exclude_unreviewed: truthy_param?(:exclude_unreviewed))
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -45,10 +45,14 @@ module SignatureVerification
|
|||
end
|
||||
end
|
||||
|
||||
def require_signature!
|
||||
def require_account_signature!
|
||||
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
|
||||
end
|
||||
|
||||
def require_actor_signature!
|
||||
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
|
||||
end
|
||||
|
||||
def signed_request?
|
||||
request.headers['Signature'].present?
|
||||
end
|
||||
|
@ -68,7 +72,11 @@ module SignatureVerification
|
|||
end
|
||||
|
||||
def signed_request_account
|
||||
return @signed_request_account if defined?(@signed_request_account)
|
||||
signed_request_actor.is_a?(Account) ? signed_request_actor : nil
|
||||
end
|
||||
|
||||
def signed_request_actor
|
||||
return @signed_request_actor if defined?(@signed_request_actor)
|
||||
|
||||
raise SignatureVerificationError, 'Request not signed' unless signed_request?
|
||||
raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
|
||||
|
@ -78,26 +86,30 @@ module SignatureVerification
|
|||
verify_signature_strength!
|
||||
verify_body_digest!
|
||||
|
||||
account = account_from_key_id(signature_params['keyId'])
|
||||
actor = actor_from_key_id(signature_params['keyId'])
|
||||
|
||||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil?
|
||||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
|
||||
|
||||
signature = Base64.decode64(signature_params['signature'])
|
||||
compare_signed_string = build_signed_string
|
||||
|
||||
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
|
||||
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
|
||||
|
||||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil?
|
||||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
|
||||
|
||||
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||
|
||||
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
|
||||
@signed_request_account = nil
|
||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
|
||||
rescue SignatureVerificationError => e
|
||||
@signature_verification_failure_reason = e.message
|
||||
@signed_request_account = nil
|
||||
fail_with! e.message
|
||||
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
|
||||
fail_with! "Failed to fetch remote data: #{e.message}"
|
||||
rescue Mastodon::UnexpectedResponseError
|
||||
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
|
||||
rescue Stoplight::Error::RedLight
|
||||
fail_with! 'Fetching attempt skipped because of recent connection failure'
|
||||
end
|
||||
|
||||
def request_body
|
||||
|
@ -106,6 +118,11 @@ module SignatureVerification
|
|||
|
||||
private
|
||||
|
||||
def fail_with!(message)
|
||||
@signature_verification_failure_reason = message
|
||||
@signed_request_actor = nil
|
||||
end
|
||||
|
||||
def signature_params
|
||||
@signature_params ||= begin
|
||||
raw_signature = request.headers['Signature']
|
||||
|
@ -138,13 +155,23 @@ module SignatureVerification
|
|||
digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
|
||||
sha256 = digests.assoc('sha-256')
|
||||
raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
|
||||
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1]
|
||||
|
||||
return if body_digest == sha256[1]
|
||||
|
||||
digest_size = begin
|
||||
Base64.strict_decode64(sha256[1].strip).length
|
||||
rescue ArgumentError
|
||||
raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}"
|
||||
end
|
||||
|
||||
raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32
|
||||
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
|
||||
end
|
||||
|
||||
def verify_signature(account, signature, compare_signed_string)
|
||||
if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
|
||||
@signed_request_account = account
|
||||
@signed_request_account
|
||||
def verify_signature(actor, signature, compare_signed_string)
|
||||
if actor.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
|
||||
@signed_request_actor = actor
|
||||
@signed_request_actor
|
||||
end
|
||||
rescue OpenSSL::PKey::RSAError
|
||||
nil
|
||||
|
@ -207,7 +234,7 @@ module SignatureVerification
|
|||
signature_params['keyId'].blank? || signature_params['signature'].blank?
|
||||
end
|
||||
|
||||
def account_from_key_id(key_id)
|
||||
def actor_from_key_id(key_id)
|
||||
domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
|
||||
|
||||
if domain_not_allowed?(domain)
|
||||
|
@ -216,27 +243,34 @@ module SignatureVerification
|
|||
end
|
||||
|
||||
if key_id.start_with?('acct:')
|
||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
|
||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) }
|
||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
|
||||
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) }
|
||||
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
|
||||
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
|
||||
account
|
||||
end
|
||||
rescue Mastodon::HostValidationError
|
||||
nil
|
||||
rescue Mastodon::PrivateNetworkAddressError => e
|
||||
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
|
||||
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e
|
||||
raise SignatureVerificationError, e.message
|
||||
end
|
||||
|
||||
def stoplight_wrap_request(&block)
|
||||
Stoplight("source:#{request.remote_ip}", &block)
|
||||
.with_fallback { nil }
|
||||
.with_threshold(1)
|
||||
.with_cool_off_time(5.minutes.seconds)
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
|
||||
.run
|
||||
end
|
||||
|
||||
def account_refresh_key(account)
|
||||
return if account.local? || !account.activitypub?
|
||||
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
|
||||
def actor_refresh_key!(actor)
|
||||
return if actor.local? || !actor.activitypub?
|
||||
return actor.refresh! if actor.respond_to?(:refresh!) && actor.possibly_stale?
|
||||
|
||||
ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false)
|
||||
rescue Mastodon::PrivateNetworkAddressError => e
|
||||
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
|
||||
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
|
||||
raise SignatureVerificationError, e.message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ class FollowerAccountsController < ApplicationController
|
|||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
|
|
|
@ -4,7 +4,7 @@ class FollowingAccountsController < ApplicationController
|
|||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PrivacyController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show; end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ class StatusesController < ApplicationController
|
|||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
|
|
|
@ -8,7 +8,7 @@ class TagsController < ApplicationController
|
|||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_local
|
||||
before_action :set_tag
|
||||
|
|
|
@ -536,10 +536,12 @@ export function expandFollowingFail(id, error) {
|
|||
|
||||
export function fetchRelationships(accountIds) {
|
||||
return (dispatch, getState) => {
|
||||
const loadedRelationships = getState().get('relationships');
|
||||
const state = getState();
|
||||
const loadedRelationships = state.get('relationships');
|
||||
const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null);
|
||||
const signedIn = !!state.getIn(['meta', 'me']);
|
||||
|
||||
if (newAccountIds.length === 0) {
|
||||
if (!signedIn || newAccountIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import api from '../api';
|
||||
import { debounce } from 'lodash';
|
||||
import compareId from '../compare_id';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
|
||||
export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
|
||||
|
@ -11,7 +12,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
|
|||
const accessToken = getState().getIn(['meta', 'access_token'], '');
|
||||
const params = _buildParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0) {
|
||||
if (Object.keys(params).length === 0 || accessToken === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,7 +64,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
|
|||
const _buildParams = (state) => {
|
||||
const params = {};
|
||||
|
||||
const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null);
|
||||
const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null);
|
||||
const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
|
||||
|
||||
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
|
||||
|
@ -82,9 +83,10 @@ const _buildParams = (state) => {
|
|||
};
|
||||
|
||||
const debouncedSubmitMarkers = debounce((dispatch, getState) => {
|
||||
const params = _buildParams(getState());
|
||||
const accessToken = getState().getIn(['meta', 'access_token'], '');
|
||||
const params = _buildParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0) {
|
||||
if (Object.keys(params).length === 0 || accessToken === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,11 @@ export const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST';
|
|||
export const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS';
|
||||
export const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL';
|
||||
|
||||
export const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST';
|
||||
export const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS';
|
||||
export const STATUS_TRANSLATE_FAIL = 'STATUS_TRANSLATE_FAIL';
|
||||
export const STATUS_TRANSLATE_UNDO = 'STATUS_TRANSLATE_UNDO';
|
||||
|
||||
export function fetchStatusRequest(id, skipLoading) {
|
||||
return {
|
||||
type: STATUS_FETCH_REQUEST,
|
||||
|
@ -309,4 +314,36 @@ export function toggleStatusCollapse(id, isCollapsed) {
|
|||
id,
|
||||
isCollapsed,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const translateStatus = id => (dispatch, getState) => {
|
||||
dispatch(translateStatusRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/statuses/${id}/translate`).then(response => {
|
||||
dispatch(translateStatusSuccess(id, response.data));
|
||||
}).catch(error => {
|
||||
dispatch(translateStatusFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
export const translateStatusRequest = id => ({
|
||||
type: STATUS_TRANSLATE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const translateStatusSuccess = (id, translation) => ({
|
||||
type: STATUS_TRANSLATE_SUCCESS,
|
||||
id,
|
||||
translation,
|
||||
});
|
||||
|
||||
export const translateStatusFail = (id, error) => ({
|
||||
type: STATUS_TRANSLATE_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
export const undoStatusTranslation = id => ({
|
||||
type: STATUS_TRANSLATE_UNDO,
|
||||
id,
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
|
|||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundImage": "url(/animated/alice.gif)",
|
||||
"backgroundSize": "100px 100px",
|
||||
"height": "100px",
|
||||
|
@ -22,7 +22,7 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
|
|||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundImage": "url(/static/alice.jpg)",
|
||||
"backgroundSize": "100px 100px",
|
||||
"height": "100px",
|
||||
|
|
|
@ -7,7 +7,7 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
|||
<div
|
||||
className="account__avatar-overlay-base"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundImage": "url(/static/alice.jpg)",
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
|||
<div
|
||||
className="account__avatar-overlay-overlay"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundImage": "url(/static/eve.jpg)",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ exports[`<DisplayName /> renders display name + account name 1`] = `
|
|||
<strong
|
||||
className="display-name__html"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
{
|
||||
"__html": "<p>Foo</p>",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ class ColumnHeader extends React.PureComponent {
|
|||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
|
@ -145,7 +146,7 @@ class ColumnHeader extends React.PureComponent {
|
|||
collapsedContent.push(moveButtons);
|
||||
}
|
||||
|
||||
if (children || (multiColumn && this.props.onPin)) {
|
||||
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
|
||||
collapseButton = (
|
||||
<button
|
||||
className={collapsibleButtonClassName}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @ts-check
|
||||
import React from 'react';
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Permalink from './permalink';
|
||||
|
@ -9,10 +9,6 @@ import ShortNumber from 'mastodon/components/short_number';
|
|||
import Skeleton from 'mastodon/components/skeleton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
totalVolume: { id: 'hashtag.total_volume', defaultMessage: 'Total volume in the last {days, plural, one {day} other {{days} days}}' },
|
||||
});
|
||||
|
||||
class SilentErrorBoundary extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -69,7 +65,7 @@ ImmutableHashtag.propTypes = {
|
|||
hashtag: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
const Hashtag = injectIntl(({ name, href, to, people, uses, history, className, intl }) => (
|
||||
const Hashtag = ({ name, href, to, people, history, className }) => (
|
||||
<div className={classNames('trends__item', className)}>
|
||||
<div className='trends__item__name'>
|
||||
<Permalink href={href} to={to}>
|
||||
|
@ -79,11 +75,6 @@ const Hashtag = injectIntl(({ name, href, to, people, uses, history, className,
|
|||
{typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}
|
||||
</div>
|
||||
|
||||
<abbr className='trends__item__current' title={intl.formatMessage(messages.totalVolume, { days: 2 })}>
|
||||
{typeof uses !== 'undefined' ? <ShortNumber value={uses} /> : <Skeleton width={42} height={36} />}
|
||||
<span className='trends__item__current__asterisk'>*</span>
|
||||
</abbr>
|
||||
|
||||
<div className='trends__item__sparkline'>
|
||||
<SilentErrorBoundary>
|
||||
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
|
||||
|
@ -92,7 +83,7 @@ const Hashtag = injectIntl(({ name, href, to, people, uses, history, className,
|
|||
</SilentErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
|
||||
Hashtag.propTypes = {
|
||||
name: PropTypes.string,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
|
||||
const Logo = () => (
|
||||
<svg viewBox='0 0 216.4144 232.00976' className='logo'>
|
||||
<use xlinkHref='#mastodon-svg-logo' />
|
||||
<svg viewBox='0 0 261 66' className='logo'>
|
||||
<use xlinkHref='#logo-symbol-wordmark' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
|
|||
export default @injectIntl
|
||||
class Poll extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
poll: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -217,7 +221,7 @@ class Poll extends ImmutablePureComponent {
|
|||
</ul>
|
||||
|
||||
<div className='poll__footer'>
|
||||
{!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
|
||||
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
|
||||
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
|
||||
{votesCount}
|
||||
{poll.get('expires_at') && <span> · {timeRemaining}</span>}
|
||||
|
|
|
@ -85,6 +85,7 @@ class Status extends ImmutablePureComponent {
|
|||
onHeightChange: PropTypes.func,
|
||||
onToggleHidden: PropTypes.func,
|
||||
onToggleCollapsed: PropTypes.func,
|
||||
onTranslate: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
unread: PropTypes.bool,
|
||||
|
@ -171,6 +172,10 @@ class Status extends ImmutablePureComponent {
|
|||
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
|
||||
}
|
||||
|
||||
handleTranslate = () => {
|
||||
this.props.onTranslate(this._properStatus());
|
||||
}
|
||||