Browse Source

Merge pull request #1324 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
master^2
ThibG GitHub 11 months ago
parent
commit
75b0fa8b76
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 2301 additions and 634 deletions
  1. +61
    -63
      .circleci/config.yml
  2. +2
    -1
      Gemfile
  3. +12
    -9
      Gemfile.lock
  4. +7
    -7
      app/controllers/accounts_controller.rb
  5. +10
    -7
      app/controllers/activitypub/collections_controller.rb
  6. +3
    -3
      app/controllers/activitypub/outboxes_controller.rb
  7. +15
    -6
      app/controllers/activitypub/replies_controller.rb
  8. +1
    -1
      app/controllers/api/v1/polls/votes_controller.rb
  9. +1
    -1
      app/controllers/api/v1/polls_controller.rb
  10. +6
    -5
      app/controllers/api/v1/push/subscriptions_controller.rb
  11. +1
    -2
      app/controllers/api/v1/statuses/mutes_controller.rb
  12. +1
    -1
      app/controllers/api/v1/statuses_controller.rb
  13. +1
    -1
      app/controllers/media_controller.rb
  14. +1
    -1
      app/controllers/remote_interaction_controller.rb
  15. +1
    -1
      app/controllers/statuses_controller.rb
  16. +1
    -1
      app/javascript/core/settings.js
  17. +1
    -1
      app/javascript/flavours/glitch/actions/timelines.js
  18. +1
    -1
      app/javascript/flavours/glitch/components/dropdown_menu.js
  19. +2
    -2
      app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
  20. +6
    -8
      app/javascript/flavours/glitch/packs/public.js
  21. +1
    -1
      app/javascript/flavours/glitch/reducers/statuses.js
  22. +5
    -5
      app/javascript/flavours/glitch/reducers/timelines.js
  23. +5
    -0
      app/javascript/flavours/glitch/styles/about.scss
  24. +20
    -0
      app/javascript/flavours/glitch/styles/admin.scss
  25. +16
    -0
      app/javascript/flavours/glitch/styles/basics.scss
  26. +12
    -0
      app/javascript/flavours/glitch/styles/components/accounts.scss
  27. +21
    -0
      app/javascript/flavours/glitch/styles/forms.scss
  28. +30
    -0
      app/javascript/flavours/glitch/styles/polls.scss
  29. +17
    -0
      app/javascript/flavours/glitch/styles/statuses.scss
  30. +1
    -1
      app/javascript/mastodon/actions/timelines.js
  31. +1
    -1
      app/javascript/mastodon/components/dropdown_menu.js
  32. +1
    -1
      app/javascript/mastodon/features/compose/components/privacy_dropdown.js
  33. +1
    -1
      app/javascript/mastodon/reducers/statuses.js
  34. +5
    -5
      app/javascript/mastodon/reducers/timelines.js
  35. +6
    -8
      app/javascript/packs/public.js
  36. +5
    -0
      app/javascript/styles/mastodon/about.scss
  37. +20
    -0
      app/javascript/styles/mastodon/admin.scss
  38. +16
    -0
      app/javascript/styles/mastodon/basics.scss
  39. +12
    -0
      app/javascript/styles/mastodon/components.scss
  40. +21
    -0
      app/javascript/styles/mastodon/forms.scss
  41. +30
    -0
      app/javascript/styles/mastodon/polls.scss
  42. +17
    -0
      app/javascript/styles/mastodon/statuses.scss
  43. +46
    -44
      app/models/account.rb
  44. +1
    -1
      app/models/concerns/omniauthable.rb
  45. +15
    -14
      app/models/custom_emoji.rb
  46. +18
    -17
      app/models/media_attachment.rb
  47. +24
    -19
      app/models/preview_card.rb
  48. +2
    -6
      app/models/status.rb
  49. +5
    -1
      app/serializers/rest/instance_serializer.rb
  50. +12
    -1
      app/services/fetch_resource_service.rb
  51. +3
    -3
      app/views/about/show.html.haml
  52. +4
    -2
      app/views/accounts/_moved.html.haml
  53. +1
    -1
      app/views/admin/accounts/_account.html.haml
  54. +13
    -13
      app/views/admin/accounts/show.html.haml
  55. +1
    -1
      app/views/admin/instances/index.html.haml
  56. +3
    -3
      app/views/admin/instances/show.html.haml
  57. +4
    -4
      app/views/admin/pending_accounts/index.html.haml
  58. +1
    -1
      app/views/admin/relationships/index.html.haml
  59. +4
    -2
      app/views/admin/reports/show.html.haml
  60. +1
    -1
      app/views/admin/statuses/index.html.haml
  61. +1
    -1
      app/views/admin/statuses/show.html.haml
  62. +4
    -4
      app/views/admin/tags/index.html.haml
  63. +0
    -1
      app/views/application/_card.html.haml
  64. +2
    -2
      app/views/auth/registrations/new.html.haml
  65. +1
    -1
      app/views/auth/sessions/two_factor.html.haml
  66. +0
    -1
      app/views/directories/index.html.haml
  67. +1
    -1
      app/views/layouts/application.html.haml
  68. +1
    -1
      app/views/layouts/embedded.html.haml
  69. +1
    -1
      app/views/public_timelines/show.html.haml
  70. +2
    -2
      app/views/settings/preferences/appearance/show.html.haml
  71. +1
    -1
      app/views/settings/profiles/show.html.haml
  72. +3
    -3
      app/views/statuses/_detailed_status.html.haml
  73. +5
    -3
      app/views/statuses/_poll.html.haml
  74. +6
    -6
      app/views/statuses/_simple_status.html.haml
  75. +19
    -3
      config/initializers/paperclip.rb
  76. +9
    -0
      db/migrate/20200417125749_add_storage_schema_version.rb
  77. +6
    -1
      db/schema.rb
  78. +12
    -0
      ide-helper.js
  79. +4
    -0
      lib/cli.rb
  80. +4
    -0
      lib/mastodon/cli_helper.rb
  81. +43
    -1
      lib/mastodon/emoji_cli.rb
  82. +17
    -7
      lib/mastodon/media_cli.rb
  83. +148
    -0
      lib/mastodon/upgrade_cli.rb
  84. +9
    -0
      lib/paperclip/attachment_extensions.rb
  85. +122
    -10
      spec/controllers/activitypub/collections_controller_spec.rb
  86. +17
    -11
      spec/controllers/activitypub/inboxes_controller_spec.rb
  87. +162
    -8
      spec/controllers/activitypub/outboxes_controller_spec.rb
  88. +196
    -0
      spec/controllers/activitypub/replies_controller_spec.rb
  89. +768
    -75
      spec/controllers/statuses_controller_spec.rb
  90. +1
    -1
      spec/fabricators/status_pin_fabricator.rb
  91. +4
    -8
      spec/models/status_spec.rb
  92. +10
    -2
      spec/services/fetch_resource_service_spec.rb
  93. +167
    -200
      yarn.lock

+ 61
- 63
.circleci/config.yml View File

@@ -5,12 +5,13 @@ aliases:
docker:
- image: circleci/ruby:2.7-buster-node
environment: &ruby_environment
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3
BUNDLE_APP_CONFIG: ./.bundle/
BUNDLE_PATH: ./vendor/bundle/
DB_HOST: localhost
DB_USER: root
RAILS_ENV: test
PARALLEL_TEST_PROCESSORS: 4
ALLOW_NOPAM: true
CONTINUOUS_INTEGRATION: true
DISABLE_SIMPLECOV: true
@@ -32,9 +33,9 @@ aliases:
- &restore_ruby_dependencies
restore_cache:
keys:
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
- v2-ruby-dependencies-
- v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
- v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
- v3-ruby-dependencies-

- &install_steps
steps:
@@ -42,11 +43,13 @@ aliases:
- *attach_workspace
- restore_cache:
keys:
- v1-node-dependencies-{{ checksum "yarn.lock" }}
- v1-node-dependencies-
- run: yarn install --frozen-lockfile
- v2-node-dependencies-{{ checksum "yarn.lock" }}
- v2-node-dependencies-
- run:
name: Install yarn dependencies
command: yarn install --frozen-lockfile
- save_cache:
key: v1-node-dependencies-{{ checksum "yarn.lock" }}
key: v2-node-dependencies-{{ checksum "yarn.lock" }}
paths:
- ./node_modules/
- *persist_to_workspace
@@ -57,27 +60,28 @@ aliases:
command: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
## TODO: FIX THESE BUSTER DEPENDANCES
sudo wget http://ftp.au.debian.org/debian/pool/main/i/icu/libicu57_57.1-6+deb9u3_amd64.deb
sudo dpkg -i libicu57_57.1-6+deb9u3_amd64.deb
sudo wget http://ftp.au.debian.org/debian/pool/main/p/protobuf/libprotobuf10_3.0.0-9_amd64.deb
sudo dpkg -i libprotobuf10_3.0.0-9_amd64.deb

- &install_ruby_dependencies
steps:
- *attach_workspace
- *install_system_dependencies
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- run:
name: Set Ruby version
command: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- *restore_ruby_dependencies
- run: bundle config set clean 'true'
- run: bundle config set deployment 'true'
- run: bundle config set with 'pam_authentication'
- run: bundle config set without 'development production'
- run: bundle config set frozen 'true'
- run: bundle install --jobs 16 --retry 3 && bundle clean
- run:
name: Set bundler settings
command: |
bundle config clean 'true'
bundle config deployment 'true'
bundle config with 'pam_authentication'
bundle config without 'development production'
bundle config frozen 'true'
- run:
name: Install bundler dependencies
command: bundle check || (bundle install && bundle clean)
- save_cache:
key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
key: v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
paths:
- ./.bundle/
- ./vendor/bundle/
@@ -88,17 +92,26 @@ aliases:
- ./mastodon/vendor/bundle/

- &test_steps
parallelism: 4
steps:
- *attach_workspace
- *install_system_dependencies
- run: sudo apt-get install -y ffmpeg
- run:
name: Prepare Tests
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare
name: Install FFMPEG
command: sudo apt-get install -y ffmpeg
- run:
name: Run Tests
command: ./bin/retry bundle exec parallel_test ./spec/ --group-by filesize --type rspec

name: Load database schema
command: ./bin/rails db:create db:schema:load db:seed
- run:
name: Run rspec in parallel
command: |
bundle exec rspec --profile 10 \
--format RspecJunitFormatter \
--out test_results/rspec.xml \
--format progress \
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
- store_test_results:
path: test_results
jobs:
install:
<<: *defaults
@@ -115,19 +128,14 @@ jobs:
environment: *ruby_environment
<<: *install_ruby_dependencies

install-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5-buster-node
environment: *ruby_environment
<<: *install_ruby_dependencies

build:
<<: *defaults
steps:
- *attach_workspace
- *install_system_dependencies
- run: ./bin/rails assets:precompile
- run:
name: Precompile assets
command: ./bin/rails assets:precompile
- persist_to_workspace:
root: ~/projects/
paths:
@@ -149,10 +157,10 @@ jobs:
- *install_system_dependencies
- run:
name: Create database
command: ./bin/rails parallel:create
command: ./bin/rails db:create
- run:
name: Run migrations
command: ./bin/rails parallel:migrate
command: ./bin/rails db:migrate

test-ruby2.7:
<<: *defaults
@@ -178,35 +186,33 @@ jobs:
- image: circleci/redis:5-alpine
<<: *test_steps

test-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5-buster-node
environment: *ruby_environment
- image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps

test-webui:
<<: *defaults
docker:
- image: circleci/node:12-buster
steps:
- *attach_workspace
- run: ./bin/retry yarn test:jest
- run:
name: Run jest
command: yarn test:jest

check-i18n:
<<: *defaults
steps:
- *attach_workspace
- *install_system_dependencies
- run: bundle exec i18n-tasks check-normalized
- run: bundle exec i18n-tasks unused -l en
- run: bundle exec i18n-tasks check-consistent-interpolations
- run: bundle exec rake repo:check_locales_files
- run:
name: Check locale file normalization
command: bundle exec i18n-tasks check-normalized
- run:
name: Check for unused strings
command: bundle exec i18n-tasks unused -l en
- run:
name: Check for wrong string interpolations
command: bundle exec i18n-tasks check-consistent-interpolations
- run:
name: Check that all required locale files exist
command: bundle exec rake repo:check_locales_files

workflows:
version: 2
@@ -220,10 +226,6 @@ workflows:
requires:
- install
- install-ruby2.7
- install-ruby2.5:
requires:
- install
- install-ruby2.7
- build:
requires:
- install-ruby2.7
@@ -238,10 +240,6 @@ workflows:
requires:
- install-ruby2.6
- build
- test-ruby2.5:
requires:
- install-ruby2.5
- build
- test-webui:
requires:
- install


+ 2
- 1
Gemfile View File

@@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.4'
gem 'dotenv-rails', '~> 2.7'

gem 'aws-sdk-s3', '~> 1.61', require: false
gem 'aws-sdk-s3', '~> 1.63', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
@@ -129,6 +129,7 @@ group :test do
gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 2.32'
gem 'rspec_junit_formatter', '~> 0.4'
end

group :development do


+ 12
- 9
Gemfile.lock View File

@@ -92,7 +92,7 @@ GEM
av (0.9.0)
cocaine (~> 0.5.3)
aws-eventstream (1.1.0)
aws-partitions (1.296.0)
aws-partitions (1.303.0)
aws-sdk-core (3.94.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
@@ -101,7 +101,7 @@ GEM
aws-sdk-kms (1.30.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.61.2)
aws-sdk-s3 (1.63.0)
aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@@ -277,7 +277,7 @@ GEM
http-parser (~> 1.2.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (2.2.0)
http-form_data (2.3.0)
http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0)
http_accept_language (2.1.1)
@@ -303,7 +303,7 @@ GEM
jmespath (1.4.0)
json (2.3.0)
json-canonicalization (0.2.0)
json-ld (3.1.2)
json-ld (3.1.3)
htmlentities (~> 4.3)
json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8)
@@ -359,7 +359,7 @@ GEM
nokogiri (~> 1.10)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mime-types-data (3.2020.0425)
mimemagic (0.3.4)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
@@ -382,7 +382,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.5)
oj (3.10.6)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@@ -409,7 +409,7 @@ GEM
parallel
parser (2.7.1.1)
ast (~> 2.4.0)
parslet (1.8.2)
parslet (2.0.0)
pastel (0.7.3)
equatable (~> 0.6)
tty-color (~> 0.5)
@@ -542,6 +542,8 @@ GEM
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.9.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (0.79.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
@@ -554,7 +556,7 @@ GEM
rack (>= 1.1)
rubocop (>= 0.72.0)
ruby-progressbar (1.10.1)
ruby-saml (1.9.0)
ruby-saml (1.11.0)
nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
@@ -668,7 +670,7 @@ DEPENDENCIES
active_record_query_trace (~> 1.7)
addressable (~> 2.7)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.61)
aws-sdk-s3 (~> 1.63)
better_errors (~> 2.6)
binding_of_caller (~> 0.7)
blurhash (~> 0.1)
@@ -765,6 +767,7 @@ DEPENDENCIES
rqrcode (~> 1.1)
rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.0)
rspec_junit_formatter (~> 0.4)
rubocop (~> 0.79)
rubocop-rails (~> 2.5)
ruby-progressbar (~> 1.10)


+ 7
- 7
app/controllers/accounts_controller.rb View File

@@ -28,7 +28,7 @@ class AccountsController < ApplicationController
end

@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page(params)
@statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status)
@rss_url = rss_url

@@ -141,12 +141,12 @@ class AccountsController < ApplicationController
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end

def filtered_status_page(params)
if params[:min_id].present?
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
def filtered_status_page
filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end

def restrict_fields_to


+ 10
- 7
app/controllers/activitypub/collections_controller.rb View File

@@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size
case params[:id]
when 'featured'
@account.pinned_statuses.count
@size = @account.pinned_statuses.count
else
raise ActiveRecord::RecordNotFound
not_found
end
end

def scope_for_collection
case params[:id]
when 'featured'
return Status.none if @account.blocking?(signed_request_account)

@account.pinned_statuses
else
raise ActiveRecord::RecordNotFound
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
Status.none
else
@account.pinned_statuses
end
end
end



+ 3
- 3
app/controllers/activitypub/outboxes_controller.rb View File

@@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers

def show
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end

@@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
return unless page_requested?

@statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id])
@statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
@statuses = cache_collection(@statuses, Status)
end

def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end

def page_params


+ 15
- 6
app/controllers/activitypub/replies_controller.rb View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true

class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureAuthentication
include SignatureVerification
include Authorization
include AccountOwnedConcern

@@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController

private

def pundit_user
signed_request_account
end

def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_replies
@replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end
@@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
type: :unordered,
part_of: account_status_replies_url(@account, @status),
next: next_page,
items: @replies.map { |status| status.local ? status : status.uri }
items: @replies.map { |status| status.local? ? status : status.uri }
)

return page if page_requested?
@@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end

def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end

def only_other_accounts?
truthy_param?(:only_other_accounts)
end

def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)

account_status_replies_url(
@account,
@status,
page: true,
min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
only_other_accounts: only_other_accounts
)
end


+ 1
- 1
app/controllers/api/v1/polls/votes_controller.rb View File

@@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
@poll = Poll.attached.find(params[:poll_id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def vote_params


+ 1
- 1
app/controllers/api/v1/polls_controller.rb View File

@@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
@poll = Poll.attached.find(params[:id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def refresh_poll


+ 6
- 5
app/controllers/api/v1/push/subscriptions_controller.rb View File

@@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push }
before_action :require_user!
before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]

def create
@web_subscription&.destroy!
@@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end

def show
raise ActiveRecord::RecordNotFound if @web_subscription.nil?

render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end

def update
raise ActiveRecord::RecordNotFound if @web_subscription.nil?

@web_subscription.update!(data: data_params)

render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end

@@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end

def check_web_push_subscription
not_found if @web_subscription.nil?
end

def subscription_params
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end

def data_params
return {} if params[:data].blank?

params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end
end

+ 1
- 2
app/controllers/api/v1/statuses/mutes_controller.rb View File

@@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound
not_found
end

def set_conversation


+ 1
- 1
app/controllers/api/v1/statuses_controller.rb View File

@@ -68,7 +68,7 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_thread


+ 1
- 1
app/controllers/media_controller.rb View File

@@ -33,7 +33,7 @@ class MediaController < ApplicationController
def verify_permitted_status!
authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def check_playable


+ 1
- 1
app/controllers/remote_interaction_controller.rb View File

@@ -42,7 +42,7 @@ class RemoteInteractionController < ApplicationController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_body_classes


+ 1
- 1
app/controllers/statuses_controller.rb View File

@@ -49,7 +49,7 @@ class StatusesController < ApplicationController

def embed
use_pack 'embed'
return not_found if @status.hidden?
return not_found if @status.hidden? || @status.reblog?

expires_in 180, public: true
response.headers['X-Frame-Options'] = 'ALLOWALL'


+ 1
- 1
app/javascript/core/settings.js View File

@@ -10,7 +10,7 @@ delegate(document, '#account_display_name', 'input', ({ target }) => {
if (target.value) {
name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
} else {
name.textContent = document.querySelector('#default_account_display_name').textContent;
name.textContent = name.textContent = target.dataset.default;
}
}
});


+ 1
- 1
app/javascript/flavours/glitch/actions/timelines.js View File

@@ -55,7 +55,7 @@ export function updateTimeline(timeline, status, accept) {
export function deleteFromTimelines(id) {
return (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account']);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id'));
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);

dispatch({


+ 1
- 1
app/javascript/flavours/glitch/components/dropdown_menu.js View File

@@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent {
document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus();
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}


+ 2
- 2
app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js View File

@@ -64,9 +64,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, withPassive);
if (this.focusedItem) {
this.focusedItem.focus();
this.focusedItem.focus({ preventScroll: true });
} else {
this.node.firstChild.focus();
this.node.firstChild.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}


+ 6
- 8
app/javascript/flavours/glitch/packs/public.js View File

@@ -99,15 +99,13 @@ function main() {
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));

delegate(document, '.status__content__spoiler-link', 'click', function() {
const contentEl = this.parentNode.parentNode.querySelector('.e-content');
const statusEl = this.parentNode.parentNode;

if (contentEl.style.display === 'block') {
contentEl.style.display = 'none';
this.parentNode.style.marginBottom = 0;
if (statusEl.dataset.spoiler === 'expanded') {
statusEl.dataset.spoiler = 'folded';
this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
} else {
contentEl.style.display = 'block';
this.parentNode.style.marginBottom = null;
statusEl.dataset.spoiler = 'expanded';
this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
}

@@ -115,8 +113,8 @@ function main() {
});

[].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content');
const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
const statusEl = spoilerLink.parentNode.parentNode;
const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
});
});


+ 1
- 1
app/javascript/flavours/glitch/reducers/statuses.js View File

@@ -24,7 +24,7 @@ const importStatuses = (state, statuses) =>

const deleteStatus = (state, id, references) => {
references.forEach(ref => {
state = deleteStatus(state, ref[0], []);
state = deleteStatus(state, ref, []);
});

return state.delete(id);


+ 5
- 5
app/javascript/flavours/glitch/reducers/timelines.js View File

@@ -94,7 +94,7 @@ const updateTimeline = (state, timeline, status, usePendingItems, filtered) => {
}));
};

const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
const deleteStatus = (state, id, references, exclude_account = null) => {
state.keySeq().forEach(timeline => {
if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
const helper = list => list.filterNot(item => item === id);
@@ -104,7 +104,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null)

// Remove reblogs of deleted status
references.forEach(ref => {
state = deleteStatus(state, ref[0], ref[1], [], exclude_account);
state = deleteStatus(state, ref, [], exclude_account);
});

return state;
@@ -122,8 +122,8 @@ const filterTimelines = (state, relationship, statuses) => {
return;
}

references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id);
references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
state = deleteStatus(state, status.get('id'), references, relationship.id);
});

return state;
@@ -155,7 +155,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems, action.filtered);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
return deleteStatus(state, action.id, action.references, action.reblogOf);
case TIMELINE_CLEAR:
return clearTimeline(state, action.timeline);
case ACCOUNT_BLOCK_SUCCESS:


+ 5
- 0
app/javascript/flavours/glitch/styles/about.scss View File

@@ -760,8 +760,13 @@ $small-breakpoint: 960px;
}
}

&__counters__wrapper {
display: flex;
}

&__counter {
padding: 10px;
width: 50%;

strong {
font-family: $font-display, sans-serif;


+ 20
- 0
app/javascript/flavours/glitch/styles/admin.scss View File

@@ -567,6 +567,18 @@ body,
}
}

.special-action-button,
.back-link {
text-align: right;
flex: 1 1 auto;
}

.action-buttons {
display: flex;
overflow: hidden;
justify-content: space-between;
}

.spacer {
flex: 1 1 auto;
}
@@ -904,3 +916,11 @@ a.name-tag,
}
}
}

.account-badges {
margin: -2px 0;
}

.dashboard__counters.admin-account-counters {
margin-top: 10px;
}

+ 16
- 0
app/javascript/flavours/glitch/styles/basics.scss View File

@@ -150,3 +150,19 @@ button {
height: 100%;
}
}

.logo-resources {
display: none;
}

// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
// to set the z-index to a high value, which messes with modals and dropdowns.
// Blocked elements can in theory only be media and frames/embeds, so they
// should only appear in statuses, under divs and articles.
body,
div,
article {
.__ns__pop2top {
z-index: unset !important;
}
}

+ 12
- 0
app/javascript/flavours/glitch/styles/components/accounts.scss View File

@@ -80,6 +80,12 @@
&-base {
@include avatar-radius();
@include avatar-size(36px);

img {
@include avatar-radius;
width: 100%;
height: 100%;
}
}

&-overlay {
@@ -90,6 +96,12 @@
bottom: 0;
right: 0;
z-index: 1;

img {
@include avatar-radius;
width: 100%;
height: 100%;
}
}
}



+ 21
- 0
app/javascript/flavours/glitch/styles/forms.scss View File

@@ -133,6 +133,10 @@ code {
}
}

.otp-hint {
margin-bottom: 25px;
}

.card {
margin-bottom: 15px;
}
@@ -276,6 +280,14 @@ code {
margin-bottom: 25px;
}
}

.fields-group.invited-by {
margin-bottom: 30px;

.hint {
text-align: center;
}
}
}

.input.radio_buttons .radio label {
@@ -626,6 +638,15 @@ code {
@media screen and (max-width: 740px) and (min-width: 441px) {
margin-top: 40px;
}

&.translation-prompt {
text-align: unset;
color: unset;

a {
text-decoration: underline;
}
}
}

.form-footer {


+ 30
- 0
app/javascript/flavours/glitch/styles/polls.scss View File

@@ -25,6 +25,36 @@
}
}

progress {
border: 0;
display: block;
width: 100%;
height: 5px;
appearance: none;
background: transparent;

&::-webkit-progress-bar {
background: transparent;
}

// Those rules need to be entirely separate or they won't work, hence the
// duplication
&::-moz-progress-bar {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}

&::-ms-fill {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}

&::-webkit-progress-value {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}
}

&__option {
position: relative;
display: flex;


+ 17
- 0
app/javascript/flavours/glitch/styles/statuses.scss View File

@@ -124,6 +124,16 @@

.embed,
.public-layout {
.status__content[data-spoiler=folded] {
.e-content {
display: none;
}

p:first-child {
margin-bottom: 0;
}
}

.detailed-status {
padding: 15px;
}
@@ -162,6 +172,13 @@
.video-player {
margin-top: 10px;
}

&__action-bar-button {
font-size: 18px;
width: 23.1429px;
height: 23.1429px;
line-height: 23.15px;
}
}
}



+ 1
- 1
app/javascript/mastodon/actions/timelines.js View File

@@ -42,7 +42,7 @@ export function updateTimeline(timeline, status, accept) {
export function deleteFromTimelines(id) {
return (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account']);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id'));
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);

dispatch({


+ 1
- 1
app/javascript/mastodon/components/dropdown_menu.js View File

@@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent {
document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus();
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}


+ 1
- 1
app/javascript/mastodon/features/compose/components/privacy_dropdown.js View File

@@ -100,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem) this.focusedItem.focus();
if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
this.setState({ mounted: true });
}



+ 1
- 1
app/javascript/mastodon/reducers/statuses.js View File

@@ -25,7 +25,7 @@ const importStatuses = (state, statuses) =>

const deleteStatus = (state, id, references) => {
references.forEach(ref => {
state = deleteStatus(state, ref[0], []);
state = deleteStatus(state, ref, []);
});

return state.delete(id);


+ 5
- 5
app/javascript/mastodon/reducers/timelines.js View File

@@ -89,7 +89,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => {
}));
};

const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
const deleteStatus = (state, id, references, exclude_account = null) => {
state.keySeq().forEach(timeline => {
if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
const helper = list => list.filterNot(item => item === id);
@@ -99,7 +99,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null)

// Remove reblogs of deleted status
references.forEach(ref => {
state = deleteStatus(state, ref[0], ref[1], [], exclude_account);
state = deleteStatus(state, ref, [], exclude_account);
});

return state;
@@ -117,8 +117,8 @@ const filterTimelines = (state, relationship, statuses) => {
return;
}

references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id);
references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
state = deleteStatus(state, status.get('id'), references, relationship.id);
});

return state;
@@ -150,7 +150,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
return deleteStatus(state, action.id, action.references, action.reblogOf);
case TIMELINE_CLEAR:
return clearTimeline(state, action.timeline);
case ACCOUNT_BLOCK_SUCCESS:


+ 6
- 8
app/javascript/packs/public.js View File

@@ -103,15 +103,13 @@ function main() {
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));

delegate(document, '.status__content__spoiler-link', 'click', function() {
const contentEl = this.parentNode.parentNode.querySelector('.e-content');
const statusEl = this.parentNode.parentNode;

if (contentEl.style.display === 'block') {
contentEl.style.display = 'none';
this.parentNode.style.marginBottom = 0;
if (statusEl.dataset.spoiler === 'expanded') {
statusEl.dataset.spoiler = 'folded';
this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
} else {
contentEl.style.display = 'block';
this.parentNode.style.marginBottom = null;
statusEl.dataset.spoiler = 'expanded';
this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
}

@@ -119,8 +117,8 @@ function main() {
});

[].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content');
const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
const statusEl = spoilerLink.parentNode.parentNode;
const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
});
});


+ 5
- 0
app/javascript/styles/mastodon/about.scss View File

@@ -757,8 +757,13 @@ $small-breakpoint: 960px;
}
}

&__counters__wrapper {
display: flex;
}

&__counter {
padding: 10px;
width: 50%;

strong {
font-family: $font-display, sans-serif;


+ 20
- 0
app/javascript/styles/mastodon/admin.scss View File

@@ -583,6 +583,18 @@ body,
}
}

.special-action-button,
.back-link {
text-align: right;
flex: 1 1 auto;
}

.action-buttons {
display: flex;
overflow: hidden;
justify-content: space-between;
}

.spacer {
flex: 1 1 auto;
}
@@ -920,3 +932,11 @@ a.name-tag,
}
}
}

.account-badges {
margin: -2px 0;
}

.dashboard__counters.admin-account-counters {
margin-top: 10px;
}

+ 16
- 0
app/javascript/styles/mastodon/basics.scss View File

@@ -229,3 +229,19 @@ button {
}
}
}

.logo-resources {
display: none;
}

// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
// to set the z-index to a high value, which messes with modals and dropdowns.
// Blocked elements can in theory only be media and frames/embeds, so they
// should only appear in statuses, under divs and articles.
body,
div,
article {
.__ns__pop2top {
z-index: unset !important;
}
}

+ 12
- 0
app/javascript/styles/mastodon/components.scss View File

@@ -1362,6 +1362,12 @@ a .account__avatar {
&-base {
@include avatar-radius;
@include avatar-size(36px);

img {
@include avatar-radius;
width: 100%;
height: 100%;
}
}

&-overlay {
@@ -1372,6 +1378,12 @@ a .account__avatar {
bottom: 0;
right: 0;
z-index: 1;

img {
@include avatar-radius;
width: 100%;
height: 100%;
}
}
}



+ 21
- 0
app/javascript/styles/mastodon/forms.scss View File

@@ -142,6 +142,10 @@ code {
}
}

.otp-hint {
margin-bottom: 25px;
}

.card {
margin-bottom: 15px;
}
@@ -285,6 +289,14 @@ code {
margin-bottom: 25px;
}
}

.fields-group.invited-by {
margin-bottom: 30px;

.hint {
text-align: center;
}
}
}

.input.radio_buttons .radio label {
@@ -635,6 +647,15 @@ code {
@media screen and (max-width: 740px) and (min-width: 441px) {
margin-top: 40px;
}

&.translation-prompt {
text-align: unset;
color: unset;

a {
text-decoration: underline;
}
}
}

.form-footer {


+ 30
- 0
app/javascript/styles/mastodon/polls.scss View File

@@ -19,6 +19,36 @@
}
}

progress {
border: 0;
display: block;
width: 100%;
height: 5px;
appearance: none;
background: transparent;

&::-webkit-progress-bar {
background: transparent;
}

// Those rules need to be entirely separate or they won't work, hence the
// duplication
&::-moz-progress-bar {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}

&::-ms-fill {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}

&::-webkit-progress-value {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}
}

&__option {
position: relative;
display: flex;


+ 17
- 0
app/javascript/styles/mastodon/statuses.scss View File

@@ -128,6 +128,16 @@

.embed,
.public-layout {
.status__content[data-spoiler=folded] {
.e-content {
display: none;
}

p:first-child {
margin-bottom: 0;
}
}

.detailed-status {
padding: 15px;
}
@@ -159,5 +169,12 @@
.video-player {
margin-top: 10px;
}

&__action-bar-button {
font-size: 18px;
width: 23.1429px;
height: 23.1429px;
line-height: 23.15px;
}
}
}

+ 46
- 44
app/models/account.rb View File

@@ -3,50 +3,52 @@
#
# Table name: accounts
#
# id :bigint(8) not null, primary key
# username :string default(""), not null
# domain :string
# secret :string default(""), not null
# private_key :text
# public_key :text default(""), not null
# remote_url :string default(""), not null
# salmon_url :string default(""), not null
# hub_url :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# note :text default(""), not null
# display_name :string default(""), not null
# uri :string default(""), not null
# url :string
# avatar_file_name :string
# avatar_content_type :string
# avatar_file_size :integer
# avatar_updated_at :datetime
# header_file_name :string
# header_content_type :string
# header_file_size :integer
# header_updated_at :datetime
# avatar_remote_url :string
# subscription_expires_at :datetime
# locked :boolean default(FALSE), not null
# header_remote_url :string default(""), not null
# last_webfingered_at :datetime
# inbox_url :string default(""), not null
# outbox_url :string default(""), not null
# shared_inbox_url :string default(""), not null
# followers_url :string default(""), not null
# protocol :integer default("ostatus"), not null
# memorial :boolean default(FALSE), not null
# moved_to_account_id :bigint(8)
# featured_collection_url :string
# fields :jsonb
# actor_type :string
# discoverable :boolean
# also_known_as :string is an Array
# silenced_at :datetime
# suspended_at :datetime
# trust_level :integer
# hide_collections :boolean
# id :bigint(8) not null, primary key
# username :string default(""), not null
# domain :string
# secret :string default(""), not null
# private_key :text
# public_key :text default(""), not null
# remote_url :string default(""), not null
# salmon_url :string default(""), not null
# hub_url :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# note :text default(""), not null
# display_name :string default(""), not null
# uri :string default(""), not null
# url :string
# avatar_file_name :string
# avatar_content_type :string
# avatar_file_size :integer
# avatar_updated_at :datetime
# header_file_name :string
# header_content_type :string
# header_file_size :integer
# header_updated_at :datetime
# avatar_remote_url :string
# subscription_expires_at :datetime
# locked :boolean default(FALSE), not null
# header_remote_url :string default(""), not null
# last_webfingered_at :datetime
# inbox_url :string default(""), not null
# outbox_url :string default(""), not null
# shared_inbox_url :string default(""), not null
# followers_url :string default(""), not null
# protocol :integer default("ostatus"), not null
# memorial :boolean default(FALSE), not null
# moved_to_account_id :bigint(8)
# featured_collection_url :string
# fields :jsonb
# actor_type :string
# discoverable :boolean
# also_known_as :string is an Array
# silenced_at :datetime
# suspended_at :datetime
# trust_level :integer
# hide_collections :boolean
# avatar_storage_schema_version :integer
# header_storage_schema_version :integer
#

class Account < ApplicationRecord


+ 1
- 1
app/models/concerns/omniauthable.rb View File

@@ -82,7 +82,7 @@ module Omniauthable
username = starting_username
i = 0

while Account.exists?(username: username)
while Account.exists?(username: username, domain: nil)
i += 1
username = "#{starting_username}_#{i}"
end


+ 15
- 14
app/models/custom_emoji.rb View File

@@ -3,20 +3,21 @@
#
# Table name: custom_emojis
#
# id :bigint(8) not null, primary key
# shortcode :string default(""), not null
# domain :string
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# disabled :boolean default(FALSE), not null
# uri :string
# image_remote_url :string
# visible_in_picker :boolean default(TRUE), not null
# category_id :bigint(8)
# id :bigint(8) not null, primary key
# shortcode :string default(""), not null
# domain :string
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# disabled :boolean default(FALSE), not null
# uri :string
# image_remote_url :string
# visible_in_picker :boolean default(TRUE), not null
# category_id :bigint(8)
# image_storage_schema_version :integer
#

class CustomEmoji < ApplicationRecord


+ 18
- 17
app/models/media_attachment.rb View File

@@ -3,23 +3,24 @@
#
# Table name: media_attachments
#
# id :bigint(8) not null, primary key
# status_id :bigint(8)
# file_file_name :string
# file_content_type :string
# file_file_size :integer
# file_updated_at :datetime
# remote_url :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# shortcode :string
# type :integer default("image"), not null
# file_meta :json
# account_id :bigint(8)
# description :text
# scheduled_status_id :bigint(8)
# blurhash :string
# processing :integer
# id :bigint(8) not null, primary key
# status_id :bigint(8)
# file_file_name :string
# file_content_type :string
# file_file_size :integer
# file_updated_at :datetime
# remote_url :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# shortcode :string
# type :integer default("image"), not null
# file_meta :json
# account_id :bigint(8)
# description :text
# scheduled_status_id :bigint(8)
# blurhash :string
# processing :integer
# file_storage_schema_version :integer
#

class MediaAttachment < ApplicationRecord


+ 24
- 19
app/models/preview_card.rb View File

@@ -3,25 +3,26 @@
#
# Table name: preview_cards
#
# id :bigint(8) not null, primary key
# url :string default(""), not null
# title :string default(""), not null
# description :string default(""), not null
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
# type :integer default("link"), not null
# html :text default(""), not null
# author_name :string default(""), not null
# author_url :string default(""), not null
# provider_name :string default(""), not null
# provider_url :string default(""), not null
# width :integer default(0), not null
# height :integer default(0), not null
# created_at :datetime not null
# updated_at :datetime not null
# embed_url :string default(""), not null
# id :bigint(8) not null, primary key
# url :string default(""), not null
# title :string default(""), not null
# description :string default(""), not null
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
# type :integer default("link"), not null
# html :text default(""), not null
# author_name :string default(""), not null
# author_url :string default(""), not null
# provider_name :string default(""), not null
# provider_url :string default(""), not null
# width :integer default(0), not null
# height :integer default(0), not null
# created_at :datetime not null
# updated_at :datetime not null
# embed_url :string default(""), not null
# image_storage_schema_version :integer
#

class PreviewCard < ApplicationRecord
@@ -47,6 +48,10 @@ class PreviewCard < ApplicationRecord

before_save :extract_dimensions, if: :link?

def local?
false
end

def missing_image?
width.present? && height.present? && image_file_name.blank?
end


+ 2
- 6
app/models/status.rb View File

@@ -206,12 +206,8 @@ class Status < ApplicationRecord
def title
if destroyed?
"#{account.acct} deleted status"
elsif reblog?
preview = sensitive ? '<sensitive>' : text.slice(0, 10).split("\n")[0]
"#{account.acct} shared #{reblog.account.acct}'s: #{preview}"
else
preview = sensitive ? '<sensitive>' : text.slice(0, 20).split("\n")[0]
"#{account.acct}: #{preview}"
reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
end
end

@@ -404,7 +400,7 @@ class Status < ApplicationRecord

if account.nil?
where(visibility: visibility).not_local_only
elsif target_account.blocking?(account) # get rid of blocked peeps
elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
none
elsif account.id == target_account.id # author can see own stuff
all


+ 5
- 1
app/serializers/rest/instance_serializer.rb View File

@@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer

attributes :uri, :title, :short_description, :description, :email,
:version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
:languages, :registrations, :approval_required
:languages, :registrations, :approval_required, :invites_enabled

has_one :contact_account, serializer: REST::AccountSerializer

@@ -76,6 +76,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
Setting.registrations_mode == 'approved'
end

def invites_enabled
Setting.min_invite_role == 'user'
end

private

def instance_presenter


+ 12
- 1
app/services/fetch_resource_service.rb View File

@@ -25,7 +25,18 @@ class FetchResourceService < BaseService
end

def perform_request(&block)
Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block)
Request.new(:get, @url).tap do |request|
request.add_headers('Accept' => ACCEPT_HEADER)

# In a real setting we want to sign all outgoing requests,
# in case the remote server has secure mode enabled and requires
# authentication on all resources. However, during development,
# sending request signatures with an inaccessible host is useless
# and prevents even public resources from being fetched, so
# don't do it

request.on_behalf_of(Account.representative) unless Rails.env.development?
end.perform(&block)
end

def process_response(response, terminal = false)


+ 3
- 3
app/views/about/show.html.haml View File

@@ -68,11 +68,11 @@
.hero-widget__footer__column
%h4= t 'about.server_stats'

%div{ style: 'display: flex' }
.hero-widget__counter{ style: 'width: 50%' }
.hero-widget__counters__wrapper
.hero-widget__counter
%strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true
%span= t 'about.user_count_after', count: @instance_presenter.user_count
.hero-widget__counter{ style: 'width: 50%' }
.hero-widget__counter
%strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true
%span
= t 'about.active_count_after'


+ 4
- 2
app/views/accounts/_moved.html.haml View File

@@ -9,8 +9,10 @@
= link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
.detailed-status__display-avatar
.account__avatar-overlay
.account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
.account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" }
.account__avatar-overlay-base
= image_tag moved_to_account.avatar_static_url
.account__avatar-overlay-overlay
= image_tag account.avatar_static_url

%span.display-name
%bdi


+ 1
- 1
app/views/admin/accounts/_account.html.haml View File

@@ -2,7 +2,7 @@
%td
= admin_account_link_to(account)
%td
%div{ style: 'margin: -2px 0' }= account_badge(account, all: true)
%div.account-badges= account_badge(account, all: true)
%td
- if account.user_current_sign_in_ip
%samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip


+ 13
- 13
app/views/admin/accounts/show.html.haml View File

@@ -31,7 +31,7 @@
%div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)

.dashboard__counters{ style: 'margin-top: 10px' }
.dashboard__counters.admin-account-counters
%div
= link_to admin_account_statuses_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.statuses_count
@@ -178,18 +178,8 @@
= @account.shared_inbox_url
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'

%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)
- if !@account.memorial? && @account.user_approved?
= 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' }
%div.action-buttons
%div
- if @account.local? && @account.user_approved?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
- if @account.silenced?
@@ -216,6 +206,16 @@
- else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'

%div
- 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)
- if !@account.memorial? && @account.user_approved?
= 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)

%hr.spacer/

- unless @warnings.empty?


+ 1
- 1
app/views/admin/instances/index.html.haml View File

@@ -10,7 +10,7 @@
- unless whitelist_mode?
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'

%div{ style: 'flex: 1 1 auto; text-align: right' }
%div.special-action-button
- if whitelist_mode?
= link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
- else


+ 3
- 3
app/views/admin/instances/show.html.haml View File

@@ -45,11 +45,11 @@

%hr.spacer/

%div{ style: 'overflow: hidden' }
%div{ style: 'float: left' }
%div.action-buttons
%div
= link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'

%div{ style: 'float: right' }
%div
- if @domain_allow
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
- elsif @domain_block


+ 4
- 4
app/views/admin/pending_accounts/index.html.haml View File

@@ -22,9 +22,9 @@

%hr.spacer/

%div{ style: 'overflow: hidden' }
%div{ style: 'float: right' }
= link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'

%div.action-buttons
%div
= link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'

%div
= link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'

+ 1
- 1
app/views/admin/relationships/index.html.haml View File

@@ -17,7 +17,7 @@
%li= filter_link_to t('admin.accounts.location.local'), location: 'local'
%li= filter_link_to t('admin.accounts.location.remote'), location: 'remote'

.back-link{ style: 'flex: 1 1 auto; text-align: right' }
.back-link
= link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw'
= t('admin.statuses.back_to_account')


+ 4
- 2
app/views/admin/reports/show.html.haml View File

@@ -65,9 +65,11 @@

%hr.spacer

%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' }
%div.action-buttons
%div

- if @report.unresolved?
%div{ style: 'float: right' }
%div
- if @report.target_account.local?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'


+ 1
- 1
app/views/admin/statuses/index.html.haml View File

@@ -9,7 +9,7 @@
%ul
%li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'