Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `README.md`:
  Discarded upstream changes: we have our own README
- `app/controllers/follower_accounts_controller.rb`:
  Port upstream's minor refactoring
pull/2014/head
Claire 2022-12-15 20:25:25 +01:00
commit f3a4d57be1
103 changed files with 1228 additions and 274 deletions

View File

@ -1,8 +1,8 @@
version: 2.1
orbs:
ruby: circleci/ruby@1.4.1
node: circleci/node@5.0.1
ruby: circleci/ruby@2.0.0
node: circleci/node@5.0.3
executors:
default:
@ -19,11 +19,11 @@ executors:
DB_USER: root
DISABLE_SIMPLECOV: true
RAILS_ENV: test
- image: cimg/postgres:14.0
- image: cimg/postgres:14.5
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: cimg/redis:6.2
- image: cimg/redis:7.0
commands:
install-system-dependencies:
@ -45,7 +45,7 @@ commands:
bundle config without 'development production'
name: Set bundler settings
- ruby/install-deps:
bundler-version: '2.3.8'
bundler-version: '2.3.26'
key: ruby<< parameters.ruby-version >>-gems-v1
wait-db:
steps:
@ -221,5 +221,5 @@ workflows:
pkg-manager: yarn
requires:
- build
version: lts
version: '16.18'
yarn-run: test:jest

View File

@ -9,7 +9,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
# [Choice] Node.js version: lts/*, 16, 14, 12, 10
# [Choice] Node.js version: lts/*, 18, 16, 14
ARG NODE_VERSION="lts/*"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"

View File

@ -2,7 +2,7 @@
"name": "Mastodon",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/mastodon",
"workspaceFolder": "/mastodon",
// Set *default* container specific settings.json values on container create.
"settings": {},
@ -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 && git checkout -- Gemfile.lock && ./bin/rails db:setup",
"postCreateCommand": ".devcontainer/post-create.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"

View File

@ -11,9 +11,9 @@ services:
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: '3.0-bullseye'
# Optional Node.js version to install
NODE_VERSION: '14'
NODE_VERSION: '16'
volumes:
- ..:/workspaces/mastodon:cached
- ..:/mastodon:cached
environment:
RAILS_ENV: development
NODE_ENV: development

21
.devcontainer/post-create.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
set -e # Fail the whole script on first error
# Fetch Ruby gem dependencies
bundle install --path vendor/bundle --with='development test'
# Fetch Javascript dependencies
yarn install
# Make Gemfile.lock pristine again
git checkout -- Gemfile.lock
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup
# Precompile assets for development
RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test
RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile

View File

@ -103,7 +103,7 @@ VAPID_PUBLIC_KEY=
# Sending mail
# ------------
SMTP_SERVER=smtp.mailgun.org
SMTP_SERVER=
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=

View File

@ -17,6 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: hadolint/hadolint-action@v3.0.0
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2

63
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '22 6 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'ruby' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

2
.nvmrc
View File

@ -1 +1 @@
14
16

View File

@ -1,12 +1,18 @@
require:
- rubocop-rails
- rubocop-rspec
- rubocop-performance
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
DisplayCopNames: true
DisplayStyleGuide: true
ExtraDetails: true
UseCache: true
CacheRootDirectory: tmp
NewCops: enable
Exclude:
- 'spec/**/*'
- 'db/**/*'
- db/schema.rb
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
@ -67,15 +73,57 @@ Lint/UselessAccessModifier:
- class_methods
Metrics/AbcSize:
Max: 115
Max: 34 # RuboCop default 17
Exclude:
- 'lib/mastodon/*_cli.rb'
- 'lib/**/*cli*.rb'
- db/*migrate/**/*
- lib/paperclip/color_extractor.rb
- app/workers/scheduler/follow_recommendations_scheduler.rb
- app/services/activitypub/fetch*_service.rb
- lib/paperclip/**/*
CountRepeatedAttributes: false
AllowedMethods:
- update_media_attachments!
- account_link_to
- attempt_oembed
- build_crutches
- calculate_scores
- cc
- dump_actor!
- filter_from_home?
- hydrate
- import_bookmarks!
- import_relationships!
- initialize
- link_to_mention
- log_target
- matches_time_window?
- parse_metadata
- perform_statuses_search!
- privatize_media_attachments!
- process_update
- publish_media_attachments!
- remotable_attachment
- render_initial_state
- render_with_cache
- searchable_by
- self.cached_filters_for
- set_fetchable_attributes!
- signed_request_actor
- statuses_to_delete
- update_poll!
Metrics/BlockLength:
Max: 55
Exclude:
- 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
CountComments: false
CountAsOne: [array, heredoc]
AllowedMethods:
- task
- namespace
- class_methods
- included
Metrics/BlockNesting:
Max: 3
@ -85,34 +133,144 @@ Metrics/BlockNesting:
Metrics/ClassLength:
CountComments: false
Max: 500
CountAsOne: [array, heredoc]
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/CyclomaticComplexity:
Max: 25
Max: 12
Exclude:
- 'lib/mastodon/*_cli.rb'
- lib/mastodon/*cli*.rb
- db/*migrate/**/*
AllowedMethods:
- attempt_oembed
- blocked?
- build_crutches
- calculate_scores
- cc
- discover_endpoint!
- filter_from_home?
- hydrate
- klass
- link_to_mention
- log_target
- matches_time_window?
- patch_for_forwarding!
- preprocess_attributes!
- process_update
- remotable_attachment
- scan_text!
- self.cached_filters_for
- set_fetchable_attributes!
- setup_redis_env_url
- update_media_attachments!
Layout/LineLength:
Max: 140 # RuboCop default 120
AllowHeredoc: true
AllowURI: true
Enabled: false
IgnoreCopDirectives: true
AllowedPatterns:
# Allow comments to be long lines
- !ruby/regexp / \# .*$/
- !ruby/regexp /^\# .*$/
Exclude:
- lib/**/*cli*.rb
- db/*migrate/**/*
- db/seeds/**/*
Metrics/MethodLength:
CountComments: false
Max: 65
CountAsOne: [array, heredoc]
Max: 25 # RuboCop default 10
Exclude:
- 'lib/mastodon/*_cli.rb'
AllowedMethods:
- account_link_to
- attempt_oembed
- body_with_limit
- build_crutches
- cached_filters_for
- calculate_scores
- check_webfinger!
- clean_feeds!
- collection_items
- collection_presenter
- copy_account_notes!
- deduplicate_accounts!
- deduplicate_conversations!
- deduplicate_local_accounts!
- deduplicate_statuses!
- deduplicate_tags!
- deduplicate_users!
- discover_endpoint!
- extract_extra_uris_with_indices
- extract_hashtags_with_indices
- extract_mentions_or_lists_with_indices
- filter_from_home?
- from_elasticsearch
- handle_explicit_update!
- handle_mark_as_sensitive!
- hsl_to_rgb
- import_bookmarks!
- import_domain_blocks!
- import_relationships!
- ldap_options
- matches_time_window?
- outbox_presenter
- pam_get_user
- parallelize_with_progress
- parse_and_transform
- patch_for_forwarding!
- populate_home
- post_process_style
- preload_cache_collection_target_statuses
- privatize_media_attachments!
- provides_callback_for
- publish_media_attachments!
- relevant_account_timestamp
- remotable_attachment
- rgb_to_hsl
- rss_status_content_format
- set_fetchable_attributes!
- setup_redis_env_url
- signed_request_actor
- to_preview_card_attributes
- upgrade_storage_filesystem
- upgrade_storage_s3
- user_settings_params
- hydrate
- cc
- self_destruct
Metrics/ModuleLength:
CountComments: false
Max: 200
CountAsOne: [array, heredoc]
Metrics/ParameterLists:
Max: 5
CountKeywordArgs: true
Max: 5 # RuboCop default 5
CountKeywordArgs: true # RuboCop default true
MaxOptionalParameters: 3 # RuboCop default 3
Exclude:
- app/models/concerns/account_interactions.rb
- app/services/activitypub/fetch_remote_account_service.rb
- app/services/activitypub/fetch_remote_actor_service.rb
Metrics/PerceivedComplexity:
Max: 25
Max: 16 # RuboCop default 8
AllowedMethods:
- attempt_oembed
- build_crutches
- calculate_scores
- deduplicate_users!
- discover_endpoint!
- filter_from_home?
- hydrate
- patch_for_forwarding!
- process_update
- remove_orphans
- update_media_attachments!
Naming/MemoizedInstanceVariableName:
Enabled: false
@ -267,9 +425,6 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs:
AutoCorrect: false
Style/RedundantAssignment:
Enabled: false
Style/RedundantFetchBlock:
Enabled: true
@ -292,7 +447,7 @@ Style/RegexpLiteral:
Enabled: false
Style/RescueStandardError:
Enabled: false
Enabled: true
Style/SignalException:
Enabled: false
@ -311,3 +466,14 @@ Style/TrailingCommaInHashLiteral:
Style/UnpackFirst:
Enabled: false
RSpec/ScatteredSetup:
Enabled: false
RSpec/ImplicitExpect:
Enabled: false
RSpec/NamedSubject:
Enabled: false
RSpec/DescribeClass:
Enabled: false
RSpec/LetSetup:
Enabled: false

22
Aptfile
View File

@ -1,26 +1,4 @@
ffmpeg
libicu[0-9][0-9]
libicu-dev
libidn12
libidn-dev
libpq-dev
libxdamage1
libxfixes3
zlib1g-dev
libcairo2
libcroco3
libdatrie1
libgdk-pixbuf2.0-0
libgraphite2-3
libharfbuzz0b
libpango-1.0-0
libpangocairo-1.0-0
libpangoft2-1.0-0
libpixman-1-0
librsvg2-2
libthai-data
libthai0
libvpx[5-9]
libxcb-render0
libxcb-shm0
libxrender1

View File

@ -15,7 +15,8 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
RUN apt update && \
# hadolint ignore=DL3008
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential \
ca-certificates \
git \
@ -50,10 +51,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use
# hadolint ignore=DL3008,DL3009
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
groupadd -g "${GID}" mastodon && \
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \

11
Gemfile
View File

@ -107,6 +107,10 @@ group :development, :test do
gem 'pry-byebug', '~> 3.10'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.1'
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop', require: false
end
group :production, :test do
@ -117,13 +121,14 @@ group :test do
gem 'capybara', '~> 3.38'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 3.0'
gem 'json-schema', '~> 3.0'
gem 'microformats', '~> 4.4'
gem 'rack-test', '~> 2.0'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec_junit_formatter', '~> 0.6'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.21', require: false
gem 'webmock', '~> 3.18'
gem 'rspec_junit_formatter', '~> 0.6'
gem 'rack-test', '~> 2.0'
end
group :development do
@ -135,8 +140,6 @@ group :development do
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'memory_profiler'
gem 'rubocop', '~> 1.30', require: false
gem 'rubocop-rails', '~> 2.15', require: false
gem 'brakeman', '~> 5.4', require: false
gem 'bundler-audit', '~> 0.9', require: false

View File

@ -346,6 +346,8 @@ GEM
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
json-schema (3.0.0)
addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.4.1)
kaminari (1.2.2)
@ -587,21 +589,27 @@ GEM
rspec-support (3.11.1)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.30.1)
rubocop (1.39.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
rubocop-ast (>= 1.23.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.18.0)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-rails (2.15.0)
rubocop-performance (1.15.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.17.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.15.0)
rubocop (~> 1.33)
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
nokogiri (>= 1.10.5)
@ -794,6 +802,7 @@ DEPENDENCIES
idn-ruby
json-ld
json-ld-preloaded (~> 3.2)
json-schema (~> 3.0)
kaminari (~> 1.2)
kt-paperclip (~> 7.1)
letter_opener (~> 1.8)
@ -843,8 +852,10 @@ DEPENDENCIES
rspec-rails (~> 5.1)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.6)
rubocop (~> 1.30)
rubocop-rails (~> 2.15)
rubocop
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-progressbar (~> 1.11)
sanitize (~> 6.0)
scenic (~> 1.6)
@ -869,3 +880,9 @@ DEPENDENCIES
webpacker (~> 5.4)
webpush!
xorcist (~> 1.1)
RUBY VERSION
ruby 3.0.4p208
BUNDLED WITH
2.2.33

View File

@ -55,12 +55,8 @@ module Admin
def update
authorize :domain_block, :update?
@domain_block.update(update_params)
severity_changed = @domain_block.severity_changed?
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
if @domain_block.update(update_params)
DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else

View File

@ -3,7 +3,7 @@
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
before_action :require_signatures_enabled!, only: [:new, :create, :enable]
before_action :warn_signatures_not_enabled!, only: [:new, :create, :enable]
def index
authorize :relay, :update?
@ -56,8 +56,8 @@ module Admin
params.require(:relay).permit(:inbox_url)
end
def require_signatures_enabled!
redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
def warn_signatures_not_enabled!
flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
end
end
end

View File

@ -16,6 +16,26 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
content_security_policy do |p|
# Set every directive that does not have a fallback
p.default_src :none
p.frame_ancestors :none
p.form_action :none
# Disable every directive with a fallback to cut on response size
p.base_uri false
p.font_src false
p.img_src false
p.style_src false
p.media_src false
p.frame_src false
p.manifest_src false
p.connect_src false
p.script_src false
p.child_src false
p.worker_src false
end
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422
end

View File

@ -40,10 +40,8 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
def update
authorize @domain_block, :update?
@domain_block.update(domain_block_params)
severity_changed = @domain_block.severity_changed?
@domain_block.save!
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
@domain_block.update!(domain_block_params)
DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?)
log_action :update, @domain_block
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
end

View File

@ -40,7 +40,7 @@ class Api::V1::NotificationsController < Api::BaseController
private
def load_notifications
notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id(
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

View File

@ -11,6 +11,8 @@ class Auth::PasswordsController < Devise::PasswordsController
super do |resource|
if resource.errors.empty?
resource.session_activations.destroy_all
resource.revoke_access!
end
end
end

View File

@ -57,8 +57,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
devise_parameter_sanitizer.permit(:sign_up) do |user_params|
user_params.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
end
end

View File

@ -58,7 +58,7 @@ module RateLimitHeaders
end
def api_throttle_data
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] }
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_key, value| value[:limit] - value[:count] }
request.env['rack.attack.throttle_data'][most_limited_type]
end

View File

@ -28,8 +28,8 @@ module SignatureVerification
end
class SignatureParamsTransformer < Parslet::Transform
rule(params: subtree(:p)) do
(p.is_a?(Array) ? p : [p]).each_with_object({}) { |(key, val), h| h[key] = val }
rule(params: subtree(:param)) do
(param.is_a?(Array) ? param : [param]).each_with_object({}) { |(key, value), hash| hash[key] = value }
end
rule(param: { key: simple(:key), value: simple(:val) }) do

View File

@ -63,7 +63,7 @@ class FollowerAccountsController < ApplicationController
if page_requested?
ActivityPub::CollectionPresenter.new(
id: account_followers_url(@account, page: params.fetch(:page, 1)),
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.account) },
part_of: account_followers_url(@account),
next: next_page_url,
prev: prev_page_url,

View File

@ -66,7 +66,7 @@ class FollowingAccountsController < ApplicationController
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
type: :ordered,
size: @account.following_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
items: follows.map { |follow| ActivityPub::TagManager.instance.uri_for(follow.target_account) },
part_of: account_following_index_url(@account),
next: next_page_url,
prev: prev_page_url

View File

@ -13,8 +13,8 @@ class MediaController < ApplicationController
before_action :allow_iframing, only: :player
before_action :set_pack, only: :player
content_security_policy only: :player do |p|
p.frame_ancestors(false)
content_security_policy only: :player do |policy|
policy.frame_ancestors(false)
end
def show

View File

@ -17,8 +17,8 @@ class StatusesController < ApplicationController
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
content_security_policy only: :embed do |p|
p.frame_ancestors(false)
content_security_policy only: :embed do |policy|
policy.frame_ancestors(false)
end
def show

View File

@ -65,7 +65,7 @@ class TagsController < ApplicationController
id: tag_url(@tag),
type: :ordered,
size: @tag.statuses.count,
items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
items: @statuses.map { |status| ActivityPub::TagManager.instance.uri_for(status) }
)
end
end

View File

@ -23,19 +23,28 @@ module FormattingHelper
before_html = begin
if status.spoiler_text?
"<p><strong>#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)}</strong> #{h(status.spoiler_text)}</p><hr />"
else
''
tag.p do
tag.strong do
I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)
end
status.spoiler_text
end + tag.hr
end
end.html_safe # rubocop:disable Rails/OutputSafety
end
after_html = begin
if status.preloadable_poll
"<p>#{status.preloadable_poll.options.map { |o| "<input type=#{status.preloadable_poll.multiple? ? 'checkbox' : 'radio'} disabled /> #{h(o)}" }.join('<br />')}</p>"
else
''
tag.p do
safe_join(
status.preloadable_poll.options.map do |o|
tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true)
end,
tag.br
)
end
end
end.html_safe # rubocop:disable Rails/OutputSafety
end
prerender_custom_emojis(
safe_join([before_html, html, after_html]),

View File

@ -190,12 +190,15 @@ module LanguagesHelper
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
sco: ['Scots', 'Scots'].freeze,
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
tok: ['Toki Pona', 'toki pona'].freeze,
zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,

View File

@ -21,7 +21,7 @@ module StatusesHelper
def media_summary(status)
attachments = { image: 0, video: 0, audio: 0 }
status.media_attachments.each do |media|
status.ordered_media_attachments.each do |media|
if media.video?
attachments[:video] += 1
elsif media.audio?

View File

@ -1,2 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="79" height="79" viewBox="0 0 79 75"><symbol id="logo-symbol-icon"><path d="M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z" fill="currentColor"/></symbol><use xlink:href="#logo-symbol-icon" style="color:#fff" /></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="79" height="79" viewBox="0 0 79 75"><symbol id="logo-symbol-icon"><path d="M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z" fill="currentColor"/></symbol><use xlink:href="#logo-symbol-icon"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -7,5 +7,5 @@
<stop stop-color="#6364FF"/>
<stop offset="1" stop-color="#563ACC"/>
</linearGradient>
</defs></symbol><use xlink:href="#logo-symbol-wordmark" style="color:#fff"/>
</defs></symbol><use xlink:href="#logo-symbol-wordmark"/>
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -102,7 +102,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name, alreadyAdded));
}
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
}).catch(err => {
if (!alreadyAdded) {
@ -136,7 +136,7 @@ export const addReactionFail = (announcementId, name, error) => ({
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));

View File

@ -246,12 +246,13 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
if (publicStatus) {
if (isRemote) {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
}
if (publicStatus && isRemote) {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
}
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}

View File

@ -23,7 +23,9 @@ export const store = configureStore();
const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction);
store.dispatch(fetchCustomEmojis());
if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,

View File

@ -0,0 +1,37 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'mastodon/components/icon';
export default class FollowRequestNote extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
};
render () {
const { account, onAuthorize, onReject } = this.props;
return (
<div className='follow-request-banner'>
<div className='follow-request-banner__message'>
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi> }} />
</div>
<div className='follow-request-banner__action'>
<button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}>
<Icon id='check' fixedWidth />
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
</button>
<button type='button' className='button button-tertiary button--destructive' onClick={onReject}>
<Icon id='times' fixedWidth />
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
</button>
</div>
</div>
);
}
}

View File

@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number';
import { NavLink } from 'react-router-dom';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import AccountNoteContainer from '../containers/account_note_container';
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
import { Helmet } from 'react-helmet';
@ -311,6 +312,8 @@ class Header extends ImmutablePureComponent {
return (
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
<div className='account__header__image'>
<div className='account__header__info'>
{!suspended && info}

View File

@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import FollowRequestNote from '../components/follow_request_note';
import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
const mapDispatchToProps = (dispatch, { account }) => ({
onAuthorize () {
dispatch(authorizeFollowRequest(account.get('id')));
},
onReject () {
dispatch(rejectFollowRequest(account.get('id')));
},
});
export default connect(null, mapDispatchToProps)(FollowRequestNote);

View File

@ -104,6 +104,7 @@ export default class MediaItem extends ImmutablePureComponent {
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
role='application'
src={attachment.get('url')}
onMouseEnter={this.handleMouseEnter}

View File

@ -16,7 +16,6 @@ import PollFormContainer from '../containers/poll_form_container';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
import LanguageDropdown from '../containers/language_dropdown_container';
import { isMobile } from '../../../is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';
@ -62,14 +61,14 @@ class ComposeForm extends ImmutablePureComponent {
onChangeSpoilerText: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired,
onPickEmoji: PropTypes.func.isRequired,
showSearch: PropTypes.bool,
autoFocus: PropTypes.bool,
anyMedia: PropTypes.bool,
isInReply: PropTypes.bool,
singleColumn: PropTypes.bool,
};
static defaultProps = {
showSearch: false,
autoFocus: false,
};
handleChange = (e) => {
@ -155,7 +154,7 @@ class ComposeForm extends ImmutablePureComponent {
// - Replying to zero or one users, places the cursor at the end of the textbox.
// - Replying to more than one user, selects any usernames past the first;
// this provides a convenient shortcut to drop everyone else from the conversation.
if (this.props.focusDate !== prevProps.focusDate) {
if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) {
let selectionEnd, selectionStart;
if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply) {
@ -181,7 +180,7 @@ class ComposeForm extends ImmutablePureComponent {
} else if (this.props.spoiler !== prevProps.spoiler) {
if (this.props.spoiler) {
this.spoilerText.input.focus();
} else {
} else if (prevProps.spoiler) {
this.autosuggestTextarea.textarea.focus();
}
}
@ -208,7 +207,7 @@ class ComposeForm extends ImmutablePureComponent {
}
render () {
const { intl, onPaste, showSearch } = this.props;
const { intl, onPaste, autoFocus } = this.props;
const disabled = this.props.isSubmitting;
let publishText = '';
@ -258,7 +257,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth)}
autoFocus={autoFocus}
>
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />

View File

@ -165,6 +165,7 @@ class PollForm extends ImmutablePureComponent {
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
<option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
<option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
<option value={43200}>{intl.formatMessage(messages.hours, { number: 12 })}</option>
<option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
<option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
<option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>

View File

@ -123,27 +123,24 @@ class Search extends React.PureComponent {
return (
<div className='search'>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
<input
ref={this.setRef}
className='search__input'
type='text'
placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
value={value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
</label>
<input
ref={this.setRef}
className='search__input'
type='text'
placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
aria-label={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
value={value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
<Icon id='search' className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
<Overlay show={expanded && !hasValue} placement='bottom' target={this} container={this}>
<SearchPopout />
</Overlay>
</div>

View File

@ -24,7 +24,6 @@ const mapStateToProps = state => ({
isEditing: state.getIn(['compose', 'id']) !== null,
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
});