mirror of
https://framagit.org/tykayn/mastodon.git
synced 2023-08-25 08:33:12 +02:00
up from glitch
This commit is contained in:
commit
8f91509323
@ -1,8 +1,8 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
ruby: circleci/ruby@1.2.0
|
||||
node: circleci/node@4.7.0
|
||||
ruby: circleci/ruby@1.4.0
|
||||
node: circleci/node@5.0.1
|
||||
|
||||
executors:
|
||||
default:
|
||||
@ -23,7 +23,7 @@ executors:
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
- image: circleci/redis:6-alpine
|
||||
- image: cimg/redis:6.2
|
||||
|
||||
commands:
|
||||
install-system-dependencies:
|
||||
@ -32,7 +32,7 @@ commands:
|
||||
name: Install system dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
install-ruby-dependencies:
|
||||
parameters:
|
||||
ruby-version:
|
||||
@ -45,7 +45,7 @@ commands:
|
||||
bundle config without 'development production'
|
||||
name: Set bundler settings
|
||||
- ruby/install-deps:
|
||||
bundler-version: '2.2.31'
|
||||
bundler-version: '2.3.8'
|
||||
key: ruby<< parameters.ruby-version >>-gems-v1
|
||||
wait-db:
|
||||
steps:
|
||||
@ -127,9 +127,18 @@ jobs:
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:populate_v2
|
||||
name: Populate database with test data
|
||||
- run:
|
||||
command: ./bin/rails db:migrate VERSION=20180514140000
|
||||
name: Run migrations up to v2.4.0
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:populate_v2_4
|
||||
name: Populate database with test data
|
||||
- run:
|
||||
command: ./bin/rails db:migrate
|
||||
name: Run all remaining migrations
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:check_database
|
||||
name: Check migration result
|
||||
|
||||
test-two-step-migrations:
|
||||
executor:
|
||||
@ -150,14 +159,25 @@ jobs:
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:populate_v2
|
||||
name: Populate database with test data
|
||||
- run:
|
||||
command: ./bin/rails db:migrate VERSION=20180514140000
|
||||
name: Run pre-deployment migrations up to v2.4.0
|
||||
environment:
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:populate_v2_4
|
||||
name: Populate database with test data
|
||||
- run:
|
||||
command: ./bin/rails db:migrate
|
||||
name: Run all pre-deployment migrations
|
||||
evironment:
|
||||
environment:
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||
- run:
|
||||
command: ./bin/rails db:migrate
|
||||
name: Run all post-deployment remaining migrations
|
||||
- run:
|
||||
command: ./bin/rails tests:migrations:check_database
|
||||
name: Check migration result
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "2"
|
||||
version: '2'
|
||||
checks:
|
||||
argument-count:
|
||||
enabled: false
|
||||
@ -34,8 +34,8 @@ plugins:
|
||||
sass-lint:
|
||||
enabled: true
|
||||
exclude_patterns:
|
||||
- spec/
|
||||
- vendor/asset/
|
||||
- spec/
|
||||
- vendor/asset/
|
||||
|
||||
- app/javascript/mastodon/locales/**/*.json
|
||||
- config/locales/**/*.yml
|
||||
- app/javascript/mastodon/locales/**/*.json
|
||||
- config/locales/**/*.yml
|
||||
|
24
.devcontainer/Dockerfile
Normal file
24
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
|
||||
ARG VARIANT=3.1-bullseye
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
|
||||
|
||||
# Install Rails
|
||||
# RUN gem install rails webdrivers
|
||||
|
||||
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
|
||||
# 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
|
||||
ARG NODE_VERSION="lts/*"
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev
|
||||
|
||||
# [Optional] Uncomment this line to install additional gems.
|
||||
RUN gem install foreman
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Mastodon",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/mastodon",
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"rebornix.Ruby"
|
||||
],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or the host.
|
||||
"forwardPorts": [3000, 4000],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
83
.devcontainer/docker-compose.yml
Normal file
83
.devcontainer/docker-compose.yml
Normal file
@ -0,0 +1,83 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
|
||||
# Append -bullseye or -buster to pin to an OS version.
|
||||
# Use -bullseye variants on local arm64/Apple Silicon.
|
||||
VARIANT: '3.0-bullseye'
|
||||
# Optional Node.js version to install
|
||||
NODE_VERSION: '14'
|
||||
volumes:
|
||||
- ..:/workspaces/mastodon:cached
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
NODE_ENV: development
|
||||
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: '6379'
|
||||
DB_HOST: db
|
||||
DB_USER: postgres
|
||||
DB_PASS: postgres
|
||||
DB_PORT: '5432'
|
||||
ES_ENABLED: 'true'
|
||||
ES_HOST: es
|
||||
ES_PORT: '9200'
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
user: vscode
|
||||
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
networks:
|
||||
- internal_network
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- internal_network
|
||||
|
||||
es:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ES_JAVA_OPTS: -Xms512m -Xmx512m
|
||||
cluster.name: es-mastodon
|
||||
discovery.type: single-node
|
||||
bootstrap.memory_lock: 'true'
|
||||
volumes:
|
||||
- es-data:/usr/share/elasticsearch/data
|
||||
networks:
|
||||
- internal_network
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
es-data:
|
||||
|
||||
networks:
|
||||
external_network:
|
||||
internal_network:
|
||||
internal: true
|
@ -107,7 +107,7 @@ SMTP_SERVER=smtp.mailgun.org
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_ADDRESS=notificatons@example.com
|
||||
SMTP_FROM_ADDRESS=notifications@example.com
|
||||
|
||||
|
||||
# File storage (optional)
|
||||
|
32
.github/CODEOWNERS
vendored
32
.github/CODEOWNERS
vendored
@ -1,32 +0,0 @@
|
||||
# CODEOWNERS for mastodon/mastodon
|
||||
|
||||
# Translators
|
||||
# To add translator, copy these lines, replace `fr` with appropriate language code and replace `@żelipapą` with user's GitHub nickname preceded by `@` sign or e-mail address.
|
||||
# /app/javascript/mastodon/locales/fr.json @żelipapą
|
||||
# /app/views/user_mailer/*.fr.html.erb @żelipapą
|
||||
# /app/views/user_mailer/*.fr.text.erb @żelipapą
|
||||
# /config/locales/*.fr.yml @żelipapą
|
||||
# /config/locales/fr.yml @żelipapą
|
||||
|
||||
# Polish
|
||||
/app/javascript/mastodon/locales/pl.json @m4sk1n
|
||||
/app/views/user_mailer/*.pl.html.erb @m4sk1n
|
||||
/app/views/user_mailer/*.pl.text.erb @m4sk1n
|
||||
/config/locales/*.pl.yml @m4sk1n
|
||||
/config/locales/pl.yml @m4sk1n
|
||||
|
||||
# French
|
||||
/app/javascript/mastodon/locales/fr.json @aldarone
|
||||
/app/javascript/mastodon/locales/whitelist_fr.json @aldarone
|
||||
/app/views/user_mailer/*.fr.html.erb @aldarone
|
||||
/app/views/user_mailer/*.fr.text.erb @aldarone
|
||||
/config/locales/*.fr.yml @aldarone
|
||||
/config/locales/fr.yml @aldarone
|
||||
|
||||
# Dutch
|
||||
/app/javascript/mastodon/locales/nl.json @jeroenpraat
|
||||
/app/javascript/mastodon/locales/whitelist_nl.json @jeroenpraat
|
||||
/app/views/user_mailer/*.nl.html.erb @jeroenpraat
|
||||
/app/views/user_mailer/*.nl.text.erb @jeroenpraat
|
||||
/config/locales/*.nl.yml @jeroenpraat
|
||||
/config/locales/nl.yml @jeroenpraat
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
||||
patreon: mastodon
|
||||
open_collective: mastodon
|
||||
github: [Gargron]
|
||||
custom: https://sponsor.joinmastodon.org
|
||||
|
1
.github/ISSUE_TEMPLATE/2.feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/2.feature_request.yml
vendored
@ -1,5 +1,6 @@
|
||||
name: Feature Request
|
||||
description: I have a suggestion
|
||||
labels: suggestion
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
10
.github/ISSUE_TEMPLATE/3.support.md
vendored
10
.github/ISSUE_TEMPLATE/3.support.md
vendored
@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Support
|
||||
about: Ask for help with your deployment
|
||||
title: DO NOT CREATE THIS ISSUE
|
||||
---
|
||||
|
||||
We primarily use GitHub as a bug and feature tracker. For usage questions, troubleshooting of deployments and other individual technical assistance, please use one of the resources below:
|
||||
|
||||
- https://discourse.joinmastodon.org
|
||||
- #mastodon on irc.freenode.net
|
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Mastodon Meta Discussion Board
|
||||
url: https://discourse.joinmastodon.org/
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/mastodon/mastodon/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: Bug Bounty Program
|
||||
url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
about: Please report security vulnerabilities here.
|
||||
|
27
.github/workflows/build-image.yml
vendored
27
.github/workflows/build-image.yml
vendored
@ -3,32 +3,41 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- 'main'
|
||||
tags:
|
||||
- "*"
|
||||
- '*'
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-image.yml
|
||||
- Dockerfile
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: github.event_name != 'pull_request'
|
||||
- uses: docker/metadata-action@v3
|
||||
id: meta
|
||||
with:
|
||||
images: tootsuite/mastodon
|
||||
images: ghcr.io/${{ github.repository_owner }}/mastodon
|
||||
flavor: |
|
||||
latest=auto
|
||||
latest=true
|
||||
tags: |
|
||||
type=edge,branch=main
|
||||
type=semver,pattern={{ raw }}
|
||||
type=match,pattern=v(.*),group=0
|
||||
type=ref,event=pr
|
||||
- uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=registry,ref=tootsuite/mastodon:latest
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/mastodon:latest
|
||||
cache-to: type=inline
|
||||
|
40
.github/workflows/check-i18n.yml
vendored
40
.github/workflows/check-i18n.yml
vendored
@ -2,9 +2,9 @@ name: Check i18n
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
RAILS_ENV: test
|
||||
@ -14,21 +14,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
bundler-cache: true
|
||||
- name: Check locale file normalization
|
||||
run: bundle exec i18n-tasks check-normalized
|
||||
- name: Check for unused strings
|
||||
run: bundle exec i18n-tasks unused -l en
|
||||
- name: Check for wrong string interpolations
|
||||
run: bundle exec i18n-tasks check-consistent-interpolations
|
||||
- name: Check that all required locale files exist
|
||||
run: bundle exec rake repo:check_locales_files
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
bundler-cache: true
|
||||
- name: Check locale file normalization
|
||||
run: bundle exec i18n-tasks check-normalized
|
||||
- name: Check for unused strings
|
||||
run: bundle exec i18n-tasks unused -l en
|
||||
- name: Check for wrong string interpolations
|
||||
run: bundle exec i18n-tasks check-consistent-interpolations
|
||||
- name: Check that all required locale files exist
|
||||
run: bundle exec rake repo:check_locales_files
|
||||
|
78
.prettierignore
Normal file
78
.prettierignore
Normal file
@ -0,0 +1,78 @@
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config and downloaded libraries.
|
||||
/.bundle
|
||||
/vendor/bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
.eslintcache
|
||||
/log/*
|
||||
!/log/.keep
|
||||
/tmp
|
||||
/coverage
|
||||
/public/system
|
||||
/public/assets
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
.env
|
||||
.env.production
|
||||
.env.development
|
||||
/node_modules/
|
||||
/build/
|
||||
|
||||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
/config/deploy/*
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
|
||||
/postgres
|
||||
/postgres14
|
||||
/redis
|
||||
/elasticsearch
|
||||
|
||||
# ignore Helm dependency charts
|
||||
/chart/charts/*.tgz
|
||||
|
||||
# Ignore Apple files
|
||||
.DS_Store
|
||||
|
||||
# Ignore vim files
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Ignore npm debug log
|
||||
npm-debug.log
|
||||
|
||||
# Ignore yarn log files
|
||||
yarn-error.log
|
||||
yarn-debug.log
|
||||
|
||||
# Ignore vagrant log files
|
||||
*-cloudimg-console.log
|
||||
|
||||
# Ignore Docker option files
|
||||
docker-compose.override.yml
|
||||
|
||||
# Ignore Helm files
|
||||
/chart
|
||||
|
||||
# Ignore emoji map file
|
||||
/app/javascript/mastodon/features/emoji/emoji_map.json
|
||||
|
||||
# Ignore locale files
|
||||
/app/javascript/mastodon/locales
|
||||
/config/locales
|
3
.prettierrc.js
Normal file
3
.prettierrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
singleQuote: true
|
||||
}
|
12
.rubocop.yml
12
.rubocop.yml
@ -14,8 +14,8 @@ AllCops:
|
||||
- 'node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
- 'lib/json_ld/*'
|
||||
- 'lib/templates/**/*'
|
||||
- 'lib/json_ld/*'
|
||||
- 'lib/templates/**/*'
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
@ -29,13 +29,17 @@ Layout/EmptyLineAfterMagicComment:
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
AllowAdjacentOneLineDefs: true
|
||||
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Enabled: true
|
||||
|
||||
Layout/FirstHashElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Layout/HashAlignment:
|
||||
Enabled: false
|
||||
# EnforcedHashRocketStyle: table
|
||||
# EnforcedColonStyle: table
|
||||
|
||||
Layout/SpaceAroundMethodCallOperator:
|
||||
Enabled: true
|
||||
|
675
AUTHORS.md
675
AUTHORS.md
File diff suppressed because it is too large
Load Diff
2
Aptfile
2
Aptfile
@ -4,10 +4,8 @@ libicu-dev
|
||||
libidn11
|
||||
libidn11-dev
|
||||
libpq-dev
|
||||
libprotobuf-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
protobuf-compiler
|
||||
zlib1g-dev
|
||||
libcairo2
|
||||
libcroco3
|
||||
|
222
CHANGELOG.md
222
CHANGELOG.md
@ -3,12 +3,202 @@ Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
|
||||
- **Add support for incoming edited posts** ([Gargron](https://github.com/mastodon/mastodon/pull/16697), [Gargron](https://github.com/mastodon/mastodon/pull/17727), [Gargron](https://github.com/mastodon/mastodon/pull/17728), [Gargron](https://github.com/mastodon/mastodon/pull/17320), [Gargron](https://github.com/mastodon/mastodon/pull/17404), [Gargron](https://github.com/mastodon/mastodon/pull/17390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17335), [Gargron](https://github.com/mastodon/mastodon/pull/17696), [Gargron](https://github.com/mastodon/mastodon/pull/17745), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17740), [Gargron](https://github.com/mastodon/mastodon/pull/17697), [Gargron](https://github.com/mastodon/mastodon/pull/17648), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17531), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17499), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17498), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17380), [Gargron](https://github.com/mastodon/mastodon/pull/17373), [Gargron](https://github.com/mastodon/mastodon/pull/17334), [Gargron](https://github.com/mastodon/mastodon/pull/17333), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17699), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17748))
|
||||
- Previous versions remain available for perusal and comparison
|
||||
- People who reblogged a post are notified when it's edited
|
||||
- New REST APIs:
|
||||
- `PUT /api/v1/statuses/:id`
|
||||
- `GET /api/v1/statuses/:id/history`
|
||||
- `GET /api/v1/statuses/:id/source`
|
||||
- New streaming API event:
|
||||
- `status.update`
|
||||
- **Add appeals for moderator decisions** ([Gargron](https://github.com/mastodon/mastodon/pull/17364), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17725), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17566), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17652), [Gargron](https://github.com/mastodon/mastodon/pull/17616), [Gargron](https://github.com/mastodon/mastodon/pull/17615), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17554), [Gargron](https://github.com/mastodon/mastodon/pull/17523))
|
||||
- All default moderator decisions now notify the affected user by e-mail
|
||||
- They now link to an appeal page instead of suggesting replying to the e-mail
|
||||
- They can now be found in account settings and not just e-mail
|
||||
- Users can submit one appeal within 20 days of the decision
|
||||
- Moderators can approve or reject the appeal
|
||||
- **Add notifications for posts deleted by moderators** ([Gargron](https://github.com/mastodon/mastodon/pull/17204), [Gargron](https://github.com/mastodon/mastodon/pull/17668), [Gargron](https://github.com/mastodon/mastodon/pull/17746), [Gargron](https://github.com/mastodon/mastodon/pull/17679), [Gargron](https://github.com/mastodon/mastodon/pull/17487))
|
||||
- New, redesigned report view in admin UI
|
||||
- Common report actions now only take one click to complete
|
||||
- Deleting posts or marking as sensitive from report now notifies user
|
||||
- Reports can be categorized by reason and specific rules violated
|
||||
- The reasons are automatically cited in the notifications, except for spam
|
||||
- Marking posts as sensitive now federates using post editing
|
||||
- **Add explore page with trending posts and links** ([Gargron](https://github.com/mastodon/mastodon/pull/17123), [Gargron](https://github.com/mastodon/mastodon/pull/17431), [Gargron](https://github.com/mastodon/mastodon/pull/16917), [Gargron](https://github.com/mastodon/mastodon/pull/17677), [Gargron](https://github.com/mastodon/mastodon/pull/16938), [Gargron](https://github.com/mastodon/mastodon/pull/17044), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16978), [Gargron](https://github.com/mastodon/mastodon/pull/16979), [tribela](https://github.com/mastodon/mastodon/pull/17066), [Gargron](https://github.com/mastodon/mastodon/pull/17072), [Gargron](https://github.com/mastodon/mastodon/pull/17403), [noiob](https://github.com/mastodon/mastodon/pull/17624), [mayaeh](https://github.com/mastodon/mastodon/pull/17755), [mayaeh](https://github.com/mastodon/mastodon/pull/17757), [Gargron](https://github.com/mastodon/mastodon/pull/17760), [mayaeh](https://github.com/mastodon/mastodon/pull/17762))
|
||||
- Hashtag trends algorithm is extended to work for posts and links
|
||||
- Links are only considered if they have an adequate preview card
|
||||
- Preview card generation has been improved to support structured data
|
||||
- Links can only trend if the publisher (domain) has been approved
|
||||
- Posts can only trend if the author has been approved
|
||||
- Individual approval and rejection for posts and links is also available
|
||||
- Moderators are notified about pending trends at most once every 2 hours
|
||||
- Posts and link trends are language-specific
|
||||
- Search page is redesigned into explore page in web UI
|
||||
- Discovery tab is coming soon in official iOS and Android apps
|
||||
- New REST APIs:
|
||||
- `GET /api/v1/trends/links`
|
||||
- `GET /api/v1/trends/statuses`
|
||||
- `GET /api/v1/trends/tags` (alias of `GET /api/v1/trends`)
|
||||
- `GET /api/v1/admin/trends/links`
|
||||
- `GET /api/v1/admin/trends/statuses`
|
||||
- `GET /api/v1/admin/trends/tags`
|
||||
- **Add graphs and retention metrics to admin dashboard** ([Gargron](https://github.com/mastodon/mastodon/pull/16829), [Gargron](https://github.com/mastodon/mastodon/pull/17617), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17570), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16910), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16909), [mashirozx](https://github.com/mastodon/mastodon/pull/16884), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16854))
|
||||
- Dashboard shows more numbers with development over time
|
||||
- Other data such as most used interface languages and sign-up sources
|
||||
- User retention graph shows how many new users stick around
|
||||
- New REST APIs:
|
||||
- `POST /api/v1/admin/measures`
|
||||
- `POST /api/v1/admin/dimensions`
|
||||
- `POST /api/v1/admin/retention`
|
||||
- Add `GET /api/v1/accounts/familiar_followers` to REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17700))
|
||||
- Add `POST /api/v1/accounts/:id/remove_from_followers` to REST API ([noellabo](https://github.com/mastodon/mastodon/pull/16864))
|
||||
- Add `category` and `rule_ids` params to `POST /api/v1/reports` IN REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17492), [Gargron](https://github.com/mastodon/mastodon/pull/17682), [Gargron](https://github.com/mastodon/mastodon/pull/17713))
|
||||
- `category` can be one of: `spam`, `violation`, `other` (default)
|
||||
- `rule_ids` must reference `rules` returned in `GET /api/v1/instance`
|
||||
- Add global `lang` param to REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17464), [Gargron](https://github.com/mastodon/mastodon/pull/17592))
|
||||
- Add `types` param to `GET /api/v1/notifications` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17767))
|
||||
- **Add notifications for moderators about new sign-ups** ([Gargron](https://github.com/mastodon/mastodon/pull/16953), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17629))
|
||||
- When a new user confirms e-mail, moderators receive a notification
|
||||
- New notification type:
|
||||
- `admin.sign_up`
|
||||
- Add authentication history ([Gargron](https://github.com/mastodon/mastodon/pull/16408), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16428), [baby-gnu](https://github.com/mastodon/mastodon/pull/16654))
|
||||
- Add ability to automatically delete old posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16529), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17691), [tribela](https://github.com/mastodon/mastodon/pull/16653))
|
||||
- Add ability to pin private posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16954), [tribela](https://github.com/mastodon/mastodon/pull/17326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17304), [MitarashiDango](https://github.com/mastodon/mastodon/pull/17647))
|
||||
- Add ability to filter search results by author using `from:` syntax ([tribela](https://github.com/mastodon/mastodon/pull/16526))
|
||||
- Add ability to delete canonical email blocks in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16644))
|
||||
- Add ability to purge undeliverable domains in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16686), [tribela](https://github.com/mastodon/mastodon/pull/17210), [tribela](https://github.com/mastodon/mastodon/pull/17741), [tribela](https://github.com/mastodon/mastodon/pull/17209))
|
||||
- Add ability to disable e-mail token authentication for specific users in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/16427))
|
||||
- **Add ability to suspend accounts in batches in admin UI** ([Gargron](https://github.com/mastodon/mastodon/pull/17009), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17301), [Gargron](https://github.com/mastodon/mastodon/pull/17444))
|
||||
- New, redesigned accounts list in admin UI
|
||||
- Batch suspensions are meant to help clean up spam and bot accounts
|
||||
- They do not generate notifications
|
||||
- Add ability to filter reports by origin of target account in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/16487))
|
||||
- Add support for login through OpenID Connect ([chandrn7](https://github.com/mastodon/mastodon/pull/16221))
|
||||
- Add lazy loading for emoji picker in web UI ([mashirozx](https://github.com/mastodon/mastodon/pull/16907), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17011))
|
||||
- Add single option votes tooltip in polls in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/16849))
|
||||
- Add confirmation modal when closing media edit modal with unsaved changes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16518))
|
||||
- Add support for fetching Create and Announce activities by URI in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16383))
|
||||
- Add `S3_FORCE_SINGLE_REQUEST` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16866))
|
||||
- Add `OMNIAUTH_ONLY` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17288), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17345))
|
||||
- Add `ES_USER` and `ES_PASS` environment variables for Elasticsearch authentication ([tribela](https://github.com/mastodon/mastodon/pull/16890))
|
||||
- Add `CAS_SECURITY_ASSUME_EMAIL_IS_VERIFIED` environment variable ([baby-gnu](https://github.com/mastodon/mastodon/pull/16655))
|
||||
- Add ability to pass specific domains to `tootctl accounts cull` ([tribela](https://github.com/mastodon/mastodon/pull/16511))
|
||||
- Add `--by-uri` option to `tootctl domains purge` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16434))
|
||||
- Add `--batch-size` option to `tootctl search deploy` ([aquarla](https://github.com/mastodon/mastodon/pull/17049))
|
||||
- Add `--remove-orphans` option to `tootctl statuses remove` ([noellabo](https://github.com/mastodon/mastodon/pull/17067))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change design of federation pages in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/17704), [noellabo](https://github.com/mastodon/mastodon/pull/17735), [Gargron](https://github.com/mastodon/mastodon/pull/17765))
|
||||
- Change design of account cards in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17689))
|
||||
- Change `follow` scope to be covered by `read` and `write` scopes in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17678))
|
||||
- Change design of authorized applications page ([Gargron](https://github.com/mastodon/mastodon/pull/17656), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17686))
|
||||
- Change e-mail domain blocks to block IPs dynamically ([Gargron](https://github.com/mastodon/mastodon/pull/17635), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17650), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17649))
|
||||
- Change report modal to include category selection in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17565), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17734), [Gargron](https://github.com/mastodon/mastodon/pull/17654), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17632))
|
||||
- Change reblogs to not count towards hashtag trends anymore ([Gargron](https://github.com/mastodon/mastodon/pull/17501))
|
||||
- Change languages to be listed under standard instead of native name in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/17485))
|
||||
- Change routing paths to use usernames in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/16171), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16772), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16773), [mashirozx](https://github.com/mastodon/mastodon/pull/16793), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17060))
|
||||
- Change list title input design in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17092))
|
||||
- Change "Opt-in to profile directory" preference to be general discoverability preference ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16637))
|
||||
- Change API rate limits to use /64 masking on IPv6 addresses ([tribela](https://github.com/mastodon/mastodon/pull/17588), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17600), [zunda](https://github.com/mastodon/mastodon/pull/17590))
|
||||
- Change allowed formats for locally uploaded custom emojis to include GIF ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/17706), [Gargron](https://github.com/mastodon/mastodon/pull/17759))
|
||||
- Change error message when chosen password is too long ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/17082))
|
||||
- Change minimum required Elasticsearch version from 6 to 7 ([noellabo](https://github.com/mastodon/mastodon/pull/16915))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove profile directory link from main navigation panel in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17688))
|
||||
- **Remove language detection through cld3** ([Gargron](https://github.com/mastodon/mastodon/pull/17478), [ykzts](https://github.com/mastodon/mastodon/pull/17539), [Gargron](https://github.com/mastodon/mastodon/pull/17496), [Gargron](https://github.com/mastodon/mastodon/pull/17722))
|
||||
- cld3 is very inaccurate on short-form content even with unique alphabets
|
||||
- Post language can be overriden individually using `language` param
|
||||
- Otherwise, it defaults to the user's interface language
|
||||
- Remove support for `OAUTH_REDIRECT_AT_SIGN_IN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17287))
|
||||
- Use `OMNIAUTH_ONLY` instead
|
||||
- Remove Keybase integration ([Gargron](https://github.com/mastodon/mastodon/pull/17045))
|
||||
- Remove old columns and indexes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17245), [Gargron](https://github.com/mastodon/mastodon/pull/16409), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17191))
|
||||
- Remove shortcodes from newly-created media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16730), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16763))
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `GET /api/v1/trends` → `GET /api/v1/trends/tags`
|
||||
- OAuth `follow` scope → `read` and/or `write`
|
||||
- `text` attribute on `DELETE /api/v1/statuses/:id` → `GET /api/v1/statuses/:id/source`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix web manifest not permitting PWA usage from alternate domains ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16714))
|
||||
- Fix not being able to edit media attachments for scheduled posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17690))
|
||||
- Fix subscribed relay activities being recorded as boosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17571))
|
||||
- Fix streaming API server error messages when JSON parsing fails not specifying the source ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17559))
|
||||
- Fix browsers autofilling new password field with old password ([mashirozx](https://github.com/mastodon/mastodon/pull/17702))
|
||||
- Fix text being invisible before fonts load in web UI ([tribela](https://github.com/mastodon/mastodon/pull/16330))
|
||||
- Fix public profile pages of unconfirmed users being accessible ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17385), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17457))
|
||||
- Fix nil error when trying to fetch key for signature verification ([Gargron](https://github.com/mastodon/mastodon/pull/17747))
|
||||
- Fix null values being included in some indexes ([Gargron](https://github.com/mastodon/mastodon/pull/17711))
|
||||
- Fix `POST /api/v1/emails/confirmations` not being available after sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/17743))
|
||||
- Fix rare race condition when reblogged post is deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17693), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17730))
|
||||
- Fix being able to add more than 4 hashtags to hashtag column in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17729))
|
||||
- Fix data integrity of featured tags ([Gargron](https://github.com/mastodon/mastodon/pull/17712))
|
||||
- Fix performance of account timelines ([Gargron](https://github.com/mastodon/mastodon/pull/17709))
|
||||
- Fix returning empty `<p>` tag for blank account `note` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17687))
|
||||
- Fix leak of existence of otherwise inaccessible posts in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17684))
|
||||
- Fix not showing loading indicator when searching in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17655))
|
||||
- Fix media modal footer's “external link” not being a link ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17561))
|
||||
- Fix reply button on media modal not giving focus to compose form ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17626))
|
||||
- Fix some media attachments being converted with too high framerates ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17619))
|
||||
- Fix sign in token and warning emails failing to send when contact e-mail address is malformed ([helloworldstack](https://github.com/mastodon/mastodon/pull/17589))
|
||||
- Fix opening the emoji picker scrolling the single-column view to the top ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17579))
|
||||
- Fix edge case where settings/admin page sidebar would be incorrectly hidden ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17580))
|
||||
- Fix performance of server-side filtering ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17575))
|
||||
- Fix privacy policy link not being visible on small screens ([Gargron](https://github.com/mastodon/mastodon/pull/17533))
|
||||
- Fix duplicate accounts when searching by IP range in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/17524), [tribela](https://github.com/mastodon/mastodon/pull/17150))
|
||||
- Fix error when performing a batch action on posts in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17532))
|
||||
- Fix deletes not being signed in authorized fetch mode ([Gargron](https://github.com/mastodon/mastodon/pull/17484))
|
||||
- Fix Undo Announce sometimes inlining the originally Announced status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17516))
|
||||
- Fix localization of cold-start follow recommendations ([Gargron](https://github.com/mastodon/mastodon/pull/17479), [Gargron](https://github.com/mastodon/mastodon/pull/17486))
|
||||
- Fix replies collection incorrectly looping ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17462))
|
||||
- Fix errors when multiple Delete are received for a given actor ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17460))
|
||||
- Fixed prototype pollution bug and only allow trusted origin ([r0hanSH](https://github.com/mastodon/mastodon/pull/17420))
|
||||
- Fix text being incorrectly pre-selected in composer textarea on /share ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17339))
|
||||
- Fix SMTP_ENABLE_STARTTLS_AUTO/SMTP_TLS/SMTP_SSL environment variables don't work ([kgtkr](https://github.com/mastodon/mastodon/pull/17216))
|
||||
- Fix media upload specific rate limits only being applied to v1 endpoint in REST API ([tribela](https://github.com/mastodon/mastodon/pull/17272))
|
||||
- Fix media descriptions not being used for client-side filtering ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17206))
|
||||
- Fix cold-start follow recommendation favouring older accounts due to wrong sorting ([noellabo](https://github.com/mastodon/mastodon/pull/17126))
|
||||
- Fix not redirect to the right page after authenticating with WebAuthn ([heguro](https://github.com/mastodon/mastodon/pull/17098))
|
||||
- Fix searching for additional hashtags in hashtag column ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17054))
|
||||
- Fix color of hashtag column settings inputs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17058))
|
||||
- Fix performance of `tootctl statuses remove` ([noellabo](https://github.com/mastodon/mastodon/pull/17052))
|
||||
- Fix `tootctl accounts cull` not excluding domains on timeouts and certificate issues ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16433))
|
||||
- Fix 404 error when filtering admin action logs by non-existent target account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16643))
|
||||
- Fix error when accessing streaming API without any OAuth scopes ([Brawaru](https://github.com/mastodon/mastodon/pull/16823))
|
||||
- Fix follow request count not updating when new follow requests arrive over streaming API in web UI ([matildepark](https://github.com/mastodon/mastodon/pull/16652))
|
||||
- Fix error when unsuspending a local account ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16605))
|
||||
- Fix crash when a notification contains a not yet processed media attachment in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16573))
|
||||
- Fix wrong color of download button in audio player in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16572))
|
||||
- Fix notes for others accounts not being deleted when an account is deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16579))
|
||||
- Fix error when logging occurrence of unsupported video file ([noellabo](https://github.com/mastodon/mastodon/pull/16581))
|
||||
- Fix wrong elements in trends widget being hidden on smaller screens in web UI ([tribela](https://github.com/mastodon/mastodon/pull/16570))
|
||||
- Fix link to about page being displayed in limited federation mode ([weex](https://github.com/mastodon/mastodon/pull/16432))
|
||||
- Fix styling of boost button in media modal not reflecting ability to boost ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16387))
|
||||
- Fix OCR failure when erroneous lang data is in cache ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16386))
|
||||
- Fix downloading media from blocked domains in `tootctl media refresh` ([tribela](https://github.com/mastodon/mastodon/pull/16914))
|
||||
- Fix login form being displayed on landing page when already logged in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17348))
|
||||
- Fix polling for media processing status too frequently in web UI ([tribela](https://github.com/mastodon/mastodon/pull/17271))
|
||||
- Fix hashtag autocomplete overriding user-typed case ([weex](https://github.com/mastodon/mastodon/pull/16460))
|
||||
- Fix WebAuthn authentication setup to not prompt for PIN ([truongnmt](https://github.com/mastodon/mastodon/pull/16545))
|
||||
|
||||
## [3.4.6] - 2022-02-03
|
||||
### Fixed
|
||||
|
||||
- Fix `mastodon:webpush:generate_vapid_key` task requiring a functional environment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17338))
|
||||
- Fix spurious errors when receiving an Add activity for a private post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17425))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix error-prone SQL queries ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15828))
|
||||
- Fix not compacting incoming signed JSON-LD activities ([puckipedia](https://github.com/mastodon/mastodon/pull/17426), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17428)) (CVE-2022-24307)
|
||||
- Fix insufficient sanitization of report comments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17430))
|
||||
@ -17,10 +207,12 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.4.5] - 2022-01-31
|
||||
### Added
|
||||
|
||||
- Add more advanced migration tests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17393))
|
||||
- Add github workflow to build Docker images ([unasuke](https://github.com/mastodon/mastodon/pull/16973), [Gargron](https://github.com/mastodon/mastodon/pull/16980), [Gargron](https://github.com/mastodon/mastodon/pull/17000))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix some old migrations failing when skipping releases ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17394))
|
||||
- Fix migrations script failing in certain edge cases ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17398))
|
||||
- Fix Docker build ([tribela](https://github.com/mastodon/mastodon/pull/17188))
|
||||
@ -83,7 +275,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix suspended accounts statuses being merged back into timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16628))
|
||||
- Fix crash when encountering invalid account fields ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16598))
|
||||
- Fix invalid blurhash handling for remote activities ([noellabo](https://github.com/mastodon/mastodon/pull/16583))
|
||||
- Fix newlines being added to accout notes when an account moves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16415), [noellabo](https://github.com/mastodon/mastodon/pull/16576))
|
||||
- Fix newlines being added to account notes when an account moves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16415), [noellabo](https://github.com/mastodon/mastodon/pull/16576))
|
||||
- Fix crash when creating an announcement with links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16941))
|
||||
- Fix logging out from one browser logging out all other sessions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16943))
|
||||
|
||||
@ -416,7 +608,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix inefficiency when fetching bookmarks ([akihikodaki](https://github.com/mastodon/mastodon/pull/14674))
|
||||
- Fix inefficiency when fetching favourites ([akihikodaki](https://github.com/mastodon/mastodon/pull/14673))
|
||||
- Fix inefficiency when fetching media-only account timeline ([akihikodaki](https://github.com/mastodon/mastodon/pull/14675))
|
||||
- Fix inefficieny when deleting accounts ([Gargron](https://github.com/mastodon/mastodon/pull/15387), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15409), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15407), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15408), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15402), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15416), [Gargron](https://github.com/mastodon/mastodon/pull/15421))
|
||||
- Fix inefficiency when deleting accounts ([Gargron](https://github.com/mastodon/mastodon/pull/15387), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15409), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15407), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15408), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15402), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15416), [Gargron](https://github.com/mastodon/mastodon/pull/15421))
|
||||
- Fix redundant query when processing batch actions on custom emojis ([niwatori24](https://github.com/mastodon/mastodon/pull/14534))
|
||||
- Fix slow distinct queries where grouped queries are faster ([Gargron](https://github.com/mastodon/mastodon/pull/15287))
|
||||
- Fix performance on instances list in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/15282))
|
||||
@ -503,7 +695,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Add blurhash to link previews ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13984), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14143), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/13985), [Sasha-Sorokin](https://github.com/mastodon/mastodon/pull/14267), [Sasha-Sorokin](https://github.com/mastodon/mastodon/pull/14278), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14126), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14261), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14260))
|
||||
- In web UI, toots cannot be marked as sensitive unless there is media attached
|
||||
- However, it's possible to do via API or ActivityPub
|
||||
- Thumnails of link previews of such posts now use blurhash in web UI
|
||||
- Thumbnails of link previews of such posts now use blurhash in web UI
|
||||
- The Card entity in REST API has a new `blurhash` attribute
|
||||
- Add support for `summary` field for media description in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13763))
|
||||
- Add hints about incomplete remote content to web UI ([Gargron](https://github.com/mastodon/mastodon/pull/14031), [noellabo](https://github.com/mastodon/mastodon/pull/14195))
|
||||
@ -526,7 +718,7 @@ All notable changes to this project will be documented in this file.
|
||||
- The `meta` attribute on the Media Attachment entity in REST API can now have a `colors` attribute which in turn contains three hex colors: `background`, `foreground`, and `accent`
|
||||
- The background color is chosen from the most dominant color around the edges of the thumbnail
|
||||
- The foreground and accent colors are chosen from the colors that are the most different from the background color using the CIEDE2000 algorithm
|
||||
- The most satured color of the two is designated as the accent color
|
||||
- The most saturated color of the two is designated as the accent color
|
||||
- The one with the highest W3C contrast is designated as the foreground color
|
||||
- If there are not enough colors in the thumbnail, new ones are generated using a monochrome pattern
|
||||
- Add a visibility indicator to toots in web UI ([noellabo](https://github.com/mastodon/mastodon/pull/14123), [highemerly](https://github.com/mastodon/mastodon/pull/14292))
|
||||
@ -552,7 +744,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Change boost button to no longer serve as visibility indicator in web UI ([noellabo](https://github.com/mastodon/mastodon/pull/14132), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14373))
|
||||
- Change contrast of flash messages ([cchoi12](https://github.com/mastodon/mastodon/pull/13892))
|
||||
- Change wording from "Hide media" to "Hide image/images" in web UI ([ariasuni](https://github.com/mastodon/mastodon/pull/13834))
|
||||
- Change appearence of settings pages to be more consistent ([ariasuni](https://github.com/mastodon/mastodon/pull/13938))
|
||||
- Change appearance of settings pages to be more consistent ([ariasuni](https://github.com/mastodon/mastodon/pull/13938))
|
||||
- Change "Add media" tooltip to not include long list of formats in web UI ([ariasuni](https://github.com/mastodon/mastodon/pull/13954))
|
||||
- Change how badly contrasting emoji are rendered in web UI ([leo60228](https://github.com/mastodon/mastodon/pull/13773), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/13772), [mfmfuyu](https://github.com/mastodon/mastodon/pull/14020), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14015))
|
||||
- Change structure of unavailable content section on about page ([ariasuni](https://github.com/mastodon/mastodon/pull/13930))
|
||||
@ -568,14 +760,14 @@ All notable changes to this project will be documented in this file.
|
||||
- `EMAIL_DOMAIN_WHITELIST` → `EMAIL_DOMAIN_ALLOWLIST`
|
||||
- CLI option changed:
|
||||
- `tootctl domains purge --whitelist-mode` → `tootctl domains purge --limited-federation-mode`
|
||||
- Remove some unnecessary database indices ([lfuelling](https://github.com/mastodon/mastodon/pull/13695), [noellabo](https://github.com/mastodon/mastodon/pull/14259))
|
||||
- Remove some unnecessary database indexes ([lfuelling](https://github.com/mastodon/mastodon/pull/13695), [noellabo](https://github.com/mastodon/mastodon/pull/14259))
|
||||
- Remove unnecessary Node.js version upper bound ([ykzts](https://github.com/mastodon/mastodon/pull/14139))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `following` param not working when exact match is found in account search ([noellabo](https://github.com/mastodon/mastodon/pull/14394))
|
||||
- Fix sometimes occuring duplicate mention notifications ([noellabo](https://github.com/mastodon/mastodon/pull/14378))
|
||||
- Fix RSS feeds not being cachable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14368))
|
||||
- Fix sometimes occurring duplicate mention notifications ([noellabo](https://github.com/mastodon/mastodon/pull/14378))
|
||||
- Fix RSS feeds not being cacheable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14368))
|
||||
- Fix lack of locking around processing of Announce activities in ActivityPub ([noellabo](https://github.com/mastodon/mastodon/pull/14365))
|
||||
- Fix boosted toots from blocked account not being retroactively removed from TL ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14339))
|
||||
- Fix large shortened numbers (like 1.2K) using incorrect pluralization ([Sasha-Sorokin](https://github.com/mastodon/mastodon/pull/14061))
|
||||
@ -587,7 +779,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix new posts pushing down origin of opened dropdown in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14271), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14348))
|
||||
- Fix timeline markers not being saved sometimes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13887), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/13889), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/14155))
|
||||
- Fix CSV uploads being rejected ([noellabo](https://github.com/mastodon/mastodon/pull/13835))
|
||||
- Fix incompatibility with ElasticSearch 7.x ([noellabo](https://github.com/mastodon/mastodon/pull/13828))
|
||||
- Fix incompatibility with Elasticsearch 7.x ([noellabo](https://github.com/mastodon/mastodon/pull/13828))
|
||||
- Fix being able to search posts where you're in the target audience but not actively mentioned ([noellabo](https://github.com/mastodon/mastodon/pull/13829))
|
||||
- Fix non-local posts appearing on local-only hashtag timelines in web UI ([noellabo](https://github.com/mastodon/mastodon/pull/13827))
|
||||
- Fix `tootctl media remove-orphans` choking on unknown files in storage ([Gargron](https://github.com/mastodon/mastodon/pull/13765))
|
||||
@ -702,7 +894,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix poll refresh button not being debounced in web UI ([rasjonell](https://github.com/mastodon/mastodon/pull/13485), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/13490))
|
||||
- Fix confusing error when failing to add an alias to an unknown account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13480))
|
||||
- Fix "Email changed" notification sometimes having wrong e-mail ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13475))
|
||||
- Fix varioues issues on the account aliases page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13452))
|
||||
- Fix various issues on the account aliases page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13452))
|
||||
- Fix API footer link in web UI ([bubblineyuri](https://github.com/mastodon/mastodon/pull/13441))
|
||||
- Fix pagination of following, followers, follow requests, blocks and mutes lists in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13445))
|
||||
- Fix styling of polls in JS-less fallback on public pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13436))
|
||||
@ -1191,7 +1383,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix URLs appearing twice in errors of ActivityPub::DeliveryWorker ([Gargron](https://github.com/mastodon/mastodon/pull/11231))
|
||||
- Fix support for HTTP proxies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/11245))
|
||||
- Fix HTTP requests to IPv6 hosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/11240))
|
||||
- Fix error in ElasticSearch index import ([mayaeh](https://github.com/mastodon/mastodon/pull/11192))
|
||||
- Fix error in Elasticsearch index import ([mayaeh](https://github.com/mastodon/mastodon/pull/11192))
|
||||
- Fix duplicate account error when seeding development database ([ysksn](https://github.com/mastodon/mastodon/pull/11366))
|
||||
- Fix performance of session clean-up scheduler ([abcang](https://github.com/mastodon/mastodon/pull/11871))
|
||||
- Fix older migrations not running ([zunda](https://github.com/mastodon/mastodon/pull/11377))
|
||||
@ -1201,8 +1393,8 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix muted text color not applying to all text ([trwnh](https://github.com/mastodon/mastodon/pull/11996))
|
||||
- Fix follower/following lists resetting on back-navigation in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/11986))
|
||||
- Fix n+1 query when approving multiple follow requests ([abcang](https://github.com/mastodon/mastodon/pull/12004))
|
||||
- Fix records not being indexed into ElasticSearch sometimes ([Gargron](https://github.com/mastodon/mastodon/pull/12024))
|
||||
- Fix needlessly indexing unsearchable statuses into ElasticSearch ([Gargron](https://github.com/mastodon/mastodon/pull/12041))
|
||||
- Fix records not being indexed into Elasticsearch sometimes ([Gargron](https://github.com/mastodon/mastodon/pull/12024))
|
||||
- Fix needlessly indexing unsearchable statuses into Elasticsearch ([Gargron](https://github.com/mastodon/mastodon/pull/12041))
|
||||
- Fix new user bootstrapping crashing when to-be-followed accounts are invalid ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/12037))
|
||||
- Fix featured hashtag URL being interpreted as media or replies tab ([Gargron](https://github.com/mastodon/mastodon/pull/12048))
|
||||
- Fix account counters being overwritten by parallel writes ([Gargron](https://github.com/mastodon/mastodon/pull/12045))
|
||||
@ -1492,7 +1684,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Change Docker image to use Ubuntu with jemalloc ([Sir-Boops](https://github.com/mastodon/mastodon/pull/10100), [BenLubar](https://github.com/mastodon/mastodon/pull/10212))
|
||||
- Change public pages to be cacheable by proxies ([BenLubar](https://github.com/mastodon/mastodon/pull/9059))
|
||||
- Change the 410 gone response for suspended accounts to be cacheable by proxies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10339))
|
||||
- Change web UI to not not empty timeline of blocked users on block ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10359))
|
||||
- Change web UI to not empty timeline of blocked users on block ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10359))
|
||||
- Change JSON serializer to remove unused `@context` values ([Gargron](https://github.com/mastodon/mastodon/pull/10378))
|
||||
- Change GIFV file size limit to be the same as for other videos ([rinsuki](https://github.com/mastodon/mastodon/pull/9924))
|
||||
- Change Webpack to not use @babel/preset-env to compile node_modules ([ykzts](https://github.com/mastodon/mastodon/pull/10289))
|
||||
@ -1669,7 +1861,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Limit maximum visibility of local silenced users to unlisted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9583))
|
||||
- Change API error message for unconfirmed accounts ([noellabo](https://github.com/mastodon/mastodon/pull/9625))
|
||||
- Change the icon to "reply-all" when it's a reply to other accounts ([mayaeh](https://github.com/mastodon/mastodon/pull/9378))
|
||||
- Do not ignore federated reports targetting already-reported accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9534))
|
||||
- Do not ignore federated reports targeting already-reported accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9534))
|
||||
- Upgrade default Ruby version to 2.6.0 ([Gargron](https://github.com/mastodon/mastodon/pull/9688))
|
||||
- Change e-mail digest frequency ([Gargron](https://github.com/mastodon/mastodon/pull/9689))
|
||||
- Change Docker images for Tor support in docker-compose.yml ([Sir-Boops](https://github.com/mastodon/mastodon/pull/9438))
|
||||
|
@ -5,7 +5,7 @@ SHELL ["/bin/bash", "-c"]
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
|
||||
# Install Node v16 (LTS)
|
||||
ENV NODE_VER="16.13.2"
|
||||
ENV NODE_VER="16.14.2"
|
||||
RUN ARCH= && \
|
||||
dpkgArch="$(dpkg --print-architecture)" && \
|
||||
case "${dpkgArch##*-}" in \
|
||||
@ -51,7 +51,7 @@ RUN npm install -g npm@latest && \
|
||||
gem install bundler && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
|
||||
libpq-dev libprotobuf-dev protobuf-compiler shared-mime-info
|
||||
libpq-dev shared-mime-info
|
||||
|
||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||
|
||||
@ -88,7 +88,7 @@ RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selectio
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
|
||||
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
|
||||
libicu66 libidn11 libyaml-0-2 \
|
||||
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
|
||||
ln -s /opt/mastodon /mastodon && \
|
||||
gem install bundler && \
|
||||
|
35
Gemfile
35
Gemfile
@ -6,8 +6,8 @@ ruby '>= 2.5.0', '< 3.1.0'
|
||||
gem 'pkg-config', '~> 1.4'
|
||||
gem 'rexml', '~> 3.2'
|
||||
|
||||
gem 'puma', '~> 5.5'
|
||||
gem 'rails', '~> 6.1.4'
|
||||
gem 'puma', '~> 5.6'
|
||||
gem 'rails', '~> 6.1.5'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.2'
|
||||
gem 'rack', '~> 2.2.3'
|
||||
@ -18,20 +18,18 @@ gem 'makara', '~> 0.5'
|
||||
gem 'pghero', '~> 2.8'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.111', require: false
|
||||
gem 'aws-sdk-s3', '~> 1.113', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.0'
|
||||
gem 'kt-paperclip', '~> 7.1'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'bootsnap', '~> 1.10.2', require: false
|
||||
gem 'bootsnap', '~> 1.10.3', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 7.2'
|
||||
gem 'cld3', '~> 3.4.4'
|
||||
gem 'devise', '~> 4.8'
|
||||
gem 'devise-two-factor', '~> 4.0'
|
||||
|
||||
@ -42,6 +40,7 @@ end
|
||||
gem 'net-ldap', '~> 0.17'
|
||||
gem 'omniauth-cas', '~> 2.0'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'gitlab-omniauth-openid-connect', '~>0.5.0', require: 'omniauth_openid_connect'
|
||||
gem 'omniauth', '~> 1.9'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||
|
||||
@ -67,9 +66,9 @@ gem 'oj', '~> 3.13'
|
||||
gem 'ox', '~> 2.14'
|
||||
gem 'parslet'
|
||||
gem 'posix-spawn'
|
||||
gem 'pundit', '~> 2.1'
|
||||
gem 'pundit', '~> 2.2'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 6.5'
|
||||
gem 'rack-attack', '~> 6.6'
|
||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 6.0'
|
||||
gem 'rails-settings-cached', '~> 0.6'
|
||||
@ -78,7 +77,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||
gem 'rqrcode', '~> 2.1'
|
||||
gem 'ruby-progressbar', '~> 1.11'
|
||||
gem 'sanitize', '~> 6.0'
|
||||
gem 'scenic', '~> 1.5'
|
||||
gem 'scenic', '~> 1.6'
|
||||
gem 'sidekiq', '~> 6.4'
|
||||
gem 'sidekiq-scheduler', '~> 3.1'
|
||||
gem 'sidekiq-unique-jobs', '~> 7.1'
|
||||
@ -90,7 +89,7 @@ gem 'stoplight', '~> 2.2.1'
|
||||
gem 'strong_migrations', '~> 0.7'
|
||||
gem 'tty-prompt', '~> 0.23', require: false
|
||||
gem 'twitter-text', '~> 3.1.0'
|
||||
gem 'tzinfo-data', '~> 1.2021'
|
||||
gem 'tzinfo-data', '~> 1.2022'
|
||||
gem 'webpacker', '~> 5.4'
|
||||
gem 'webpush', '~> 0.3'
|
||||
gem 'webauthn', '~> 3.0.0.alpha1'
|
||||
@ -102,12 +101,12 @@ gem 'rdf-normalize', '~> 0.5'
|
||||
gem 'redcarpet', '~> 3.5'
|
||||
|
||||
group :development, :test do
|
||||
gem 'fabrication', '~> 2.24'
|
||||
gem 'fabrication', '~> 2.27'
|
||||
gem 'fuubar', '~> 2.5'
|
||||
gem 'i18n-tasks', '~> 0.9', require: false
|
||||
gem 'pry-byebug', '~> 3.9'
|
||||
gem 'pry-rails', '~> 0.3'
|
||||
gem 'rspec-rails', '~> 5.0'
|
||||
gem 'rspec-rails', '~> 5.1'
|
||||
end
|
||||
|
||||
group :production, :test do
|
||||
@ -117,7 +116,7 @@ end
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.36'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.19'
|
||||
gem 'faker', '~> 2.20'
|
||||
gem 'microformats', '~> 4.2'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.1'
|
||||
@ -128,15 +127,15 @@ end
|
||||
|
||||
group :development do
|
||||
gem 'active_record_query_trace', '~> 1.8'
|
||||
gem 'annotate', '~> 3.1'
|
||||
gem 'annotate', '~> 3.2'
|
||||
gem 'better_errors', '~> 2.9'
|
||||
gem 'binding_of_caller', '~> 1.0'
|
||||
gem 'bullet', '~> 7.0'
|
||||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener', '~> 1.8'
|
||||
gem 'letter_opener_web', '~> 2.0'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 1.25', require: false
|
||||
gem 'rubocop-rails', '~> 2.13', require: false
|
||||
gem 'rubocop', '~> 1.26', require: false
|
||||
gem 'rubocop-rails', '~> 2.14', require: false
|
||||
gem 'brakeman', '~> 5.2', require: false
|
||||
gem 'bundler-audit', '~> 0.9', require: false
|
||||
|
||||
|
322
Gemfile.lock
322
Gemfile.lock
@ -1,40 +1,40 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actioncable (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionmailbox (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionmailer (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
actionview (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionpack (6.1.5)
|
||||
actionview (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actiontext (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionview (6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
@ -45,22 +45,22 @@ GEM
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activejob (6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activerecord (6.1.4.4)
|
||||
activemodel (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activestorage (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
marcel (~> 1.0.0)
|
||||
activemodel (6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
activerecord (6.1.5)
|
||||
activemodel (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
activestorage (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.4.4)
|
||||
activesupport (6.1.5)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@ -68,28 +68,30 @@ GEM
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
airbrussh (1.4.0)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
android_key_attestation (0.3.0)
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.2)
|
||||
attr_encrypted (3.1.0)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.1)
|
||||
awrence (1.1.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.549.0)
|
||||
aws-sdk-core (3.125.5)
|
||||
aws-partitions (1.558.0)
|
||||
aws-sdk-core (3.127.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.53.0)
|
||||
aws-sdk-core (~> 3, >= 3.125.0)
|
||||
aws-sdk-kms (1.55.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.111.3)
|
||||
aws-sdk-core (~> 3, >= 3.125.0)
|
||||
aws-sdk-s3 (1.113.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
@ -102,11 +104,11 @@ GEM
|
||||
bindata (2.4.10)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
blurhash (0.1.5)
|
||||
blurhash (0.1.6)
|
||||
ffi (~> 1.14)
|
||||
bootsnap (1.10.2)
|
||||
bootsnap (1.10.3)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (5.2.0)
|
||||
brakeman (5.2.1)
|
||||
browser (4.2.0)
|
||||
brpoplpush-redis_script (0.1.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
@ -126,7 +128,7 @@ GEM
|
||||
sshkit (>= 1.9.0)
|
||||
capistrano-bundler (2.0.1)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-rails (1.6.1)
|
||||
capistrano-rails (1.6.2)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-bundler (>= 1.1, < 3)
|
||||
capistrano-rbenv (2.2.0)
|
||||
@ -147,13 +149,11 @@ GEM
|
||||
activesupport
|
||||
cbor (0.5.9.6)
|
||||
charlock_holmes (0.7.7)
|
||||
chewy (7.2.3)
|
||||
chewy (7.2.4)
|
||||
activesupport (>= 5.2)
|
||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||
elasticsearch-dsl
|
||||
chunky_png (1.4.0)
|
||||
cld3 (3.4.4)
|
||||
ffi (>= 1.1.0, < 1.16.0)
|
||||
climate_control (0.2.0)
|
||||
coderay (1.1.3)
|
||||
color_diff (0.1)
|
||||
@ -183,7 +183,7 @@ GEM
|
||||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.4.4)
|
||||
diff-lcs (1.5.0)
|
||||
discard (1.2.1)
|
||||
activerecord (>= 4.2, < 8)
|
||||
docile (1.3.4)
|
||||
@ -208,31 +208,35 @@ GEM
|
||||
multi_json
|
||||
encryptor (3.0.0)
|
||||
erubi (1.10.0)
|
||||
et-orbi (1.2.4)
|
||||
et-orbi (1.2.6)
|
||||
tzinfo
|
||||
excon (0.76.0)
|
||||
fabrication (2.24.0)
|
||||
faker (2.19.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (1.8.0)
|
||||
fabrication (2.27.0)
|
||||
faker (2.20.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.9.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.2.6)
|
||||
ffi (1.15.5)
|
||||
@ -252,12 +256,16 @@ GEM
|
||||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
formatador (0.2.5)
|
||||
fugit (1.4.5)
|
||||
fugit (1.5.2)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.4)
|
||||
fuubar (2.5.1)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
gitlab-omniauth-openid-connect (0.5.0)
|
||||
addressable (~> 2.7)
|
||||
omniauth (~> 1.9)
|
||||
openid_connect (~> 1.2)
|
||||
globalid (1.0.0)
|
||||
activesupport (>= 5.0)
|
||||
hamlit (2.13.0)
|
||||
@ -286,10 +294,11 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.3.0)
|
||||
http_accept_language (2.1.1)
|
||||
httpclient (2.8.3)
|
||||
httplog (1.5.0)
|
||||
rack (>= 1.0)
|
||||
rainbow (>= 2.0.0)
|
||||
i18n (1.8.11)
|
||||
i18n (1.10.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (0.9.37)
|
||||
activesupport (>= 4.0.2)
|
||||
@ -303,10 +312,13 @@ GEM
|
||||
terminal-table (>= 1.5.1)
|
||||
idn-ruby (0.1.4)
|
||||
ipaddress (0.8.3)
|
||||
iso-639 (0.3.5)
|
||||
jmespath (1.5.0)
|
||||
jmespath (1.6.0)
|
||||
json (2.5.1)
|
||||
json-canonicalization (0.3.0)
|
||||
json-jwt (1.13.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
json-ld (3.2.0)
|
||||
htmlentities (~> 4.3)
|
||||
json-canonicalization (~> 0.3)
|
||||
@ -331,7 +343,7 @@ GEM
|
||||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
kt-paperclip (7.0.1)
|
||||
kt-paperclip (7.1.1)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
marcel (~> 1.0.1)
|
||||
@ -339,8 +351,8 @@ GEM
|
||||
terrapin (~> 0.6.0)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
letter_opener_web (2.0.0)
|
||||
actionmailer (>= 5.2)
|
||||
letter_opener (~> 1.7)
|
||||
@ -355,14 +367,14 @@ GEM
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.13.0)
|
||||
loofah (2.15.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
makara (0.5.1)
|
||||
activerecord (>= 5.2.0)
|
||||
marcel (1.0.1)
|
||||
marcel (1.0.2)
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
matrix (0.4.2)
|
||||
@ -373,9 +385,9 @@ GEM
|
||||
nokogiri (~> 1.10)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2021.1115)
|
||||
mime-types-data (3.2022.0105)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.7.1)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.15.0)
|
||||
msgpack (1.4.4)
|
||||
multi_json (1.15.0)
|
||||
@ -385,8 +397,8 @@ GEM
|
||||
net-ssh (>= 2.6.5, < 7.0.0)
|
||||
net-ssh (6.1.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.1)
|
||||
mini_portile2 (~> 2.7.0)
|
||||
nokogiri (1.13.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
activesupport (>= 4.2, < 7)
|
||||
@ -407,17 +419,27 @@ GEM
|
||||
omniauth-saml (1.10.3)
|
||||
omniauth (~> 1.3, >= 1.3.2)
|
||||
ruby-saml (~> 1.9)
|
||||
openid_connect (1.2.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
json-jwt (>= 1.5.0)
|
||||
rack-oauth2 (>= 1.6.1)
|
||||
swd (>= 1.0.0)
|
||||
tzinfo
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (>= 1.0.1)
|
||||
openssl (2.2.0)
|
||||
openssl-signature_algorithm (0.4.0)
|
||||
orm_adapter (0.5.0)
|
||||
ox (2.14.6)
|
||||
parallel (1.21.0)
|
||||
parser (3.1.0.0)
|
||||
ox (2.14.10)
|
||||
parallel (1.22.0)
|
||||
parser (3.1.1.0)
|
||||
ast (~> 2.4.1)
|
||||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.3.0)
|
||||
pg (1.3.4)
|
||||
pghero (2.8.2)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.4.7)
|
||||
@ -439,35 +461,41 @@ GEM
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.6)
|
||||
puma (5.5.2)
|
||||
puma (5.6.2)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.1)
|
||||
pundit (2.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack-attack (6.5.0)
|
||||
rack-attack (6.6.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (1.16.0)
|
||||
activesupport
|
||||
attr_required
|
||||
httpclient
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-proxy (0.7.0)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.1.4.4)
|
||||
actioncable (= 6.1.4.4)
|
||||
actionmailbox (= 6.1.4.4)
|
||||
actionmailer (= 6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
actiontext (= 6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activemodel (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
rails (6.1.5)
|
||||
actioncable (= 6.1.5)
|
||||
actionmailbox (= 6.1.5)
|
||||
actionmailer (= 6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
actiontext (= 6.1.5)
|
||||
actionview (= 6.1.5)
|
||||
activejob (= 6.1.5)
|
||||
activemodel (= 6.1.5)
|
||||
activerecord (= 6.1.5)
|
||||
activestorage (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.4.4)
|
||||
railties (= 6.1.5)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
@ -483,11 +511,11 @@ GEM
|
||||
railties (>= 6.0.0, < 7)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
railties (6.1.5)
|
||||
actionpack (= 6.1.5)
|
||||
activesupport (= 6.1.5)
|
||||
method_source
|
||||
rake (>= 0.13)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
@ -497,9 +525,9 @@ GEM
|
||||
rdf (~> 3.2)
|
||||
redcarpet (3.5.1)
|
||||
redis (4.5.1)
|
||||
redis-namespace (1.8.1)
|
||||
redis-namespace (1.8.2)
|
||||
redis (>= 3.0.4)
|
||||
regexp_parser (2.2.0)
|
||||
regexp_parser (2.2.1)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.1)
|
||||
@ -508,19 +536,19 @@ GEM
|
||||
rexml (3.2.5)
|
||||
rotp (6.2.0)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (2.1.0)
|
||||
rqrcode (2.1.1)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.10.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-expectations (3.10.1)
|
||||
rspec-core (3.11.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-expectations (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-mocks (3.10.2)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-mocks (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-rails (5.0.2)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-rails (5.1.1)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
@ -531,21 +559,21 @@ GEM
|
||||
rspec-sidekiq (3.1.0)
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.10.3)
|
||||
rspec-support (3.11.0)
|
||||
rspec_junit_formatter (0.5.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.25.0)
|
||||
rubocop (1.26.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml
|
||||
rubocop-ast (>= 1.15.1, < 2.0)
|
||||
rubocop-ast (>= 1.16.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.15.1)
|
||||
parser (>= 3.0.1.1)
|
||||
rubocop-rails (2.13.2)
|
||||
rubocop-ast (1.16.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rails (2.14.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
@ -554,32 +582,32 @@ GEM
|
||||
nokogiri (>= 1.10.5)
|
||||
rexml
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.7.0)
|
||||
rufus-scheduler (3.8.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
safety_net_attestation (0.4.0)
|
||||
jwt (~> 2.0)
|
||||
sanitize (6.0.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
scenic (1.5.5)
|
||||
scenic (1.6.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
securecompare (1.0.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.4.0)
|
||||
sidekiq (6.4.1)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.2.0)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (3.1.0)
|
||||
sidekiq-scheduler (3.1.1)
|
||||
e2mmap
|
||||
redis (>= 3, < 5)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
thwait
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (7.1.12)
|
||||
sidekiq-unique-jobs (7.1.15)
|
||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
sidekiq (>= 5.0, < 8.0)
|
||||
@ -605,11 +633,15 @@ GEM
|
||||
sshkit (1.21.2)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.17)
|
||||
stackprof (0.2.19)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (2.2.1)
|
||||
strong_migrations (0.7.9)
|
||||
activerecord (>= 5)
|
||||
swd (1.2.0)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
temple (0.8.2)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
@ -637,13 +669,19 @@ GEM
|
||||
unf (~> 0.1.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2021.5)
|
||||
tzinfo-data (1.2022.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unicode-display_width (2.1.0)
|
||||
uniform_notifier (1.14.2)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.13)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webauthn (3.0.0.alpha1)
|
||||
@ -656,6 +694,9 @@ GEM
|
||||
safety_net_attestation (~> 0.4.0)
|
||||
securecompare (~> 1.0)
|
||||
tpm-key_attestation (~> 0.9.0)
|
||||
webfinger (1.1.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.14.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
@ -675,7 +716,7 @@ GEM
|
||||
xorcist (1.1.2)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.5.3)
|
||||
zeitwerk (2.5.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -684,12 +725,12 @@ DEPENDENCIES
|
||||
active_model_serializers (~> 0.10)
|
||||
active_record_query_trace (~> 1.8)
|
||||
addressable (~> 2.8)
|
||||
annotate (~> 3.1)
|
||||
aws-sdk-s3 (~> 1.111)
|
||||
annotate (~> 3.2)
|
||||
aws-sdk-s3 (~> 1.113)
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.10.2)
|
||||
bootsnap (~> 1.10.3)
|
||||
brakeman (~> 5.2)
|
||||
browser
|
||||
bullet (~> 7.0)
|
||||
@ -701,7 +742,6 @@ DEPENDENCIES
|
||||
capybara (~> 3.36)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
chewy (~> 7.2)
|
||||
cld3 (~> 3.4.4)
|
||||
climate_control (~> 0.2)
|
||||
color_diff (~> 0.1)
|
||||
concurrent-ruby
|
||||
@ -713,13 +753,14 @@ DEPENDENCIES
|
||||
doorkeeper (~> 5.5)
|
||||
dotenv-rails (~> 2.7)
|
||||
ed25519 (~> 1.3)
|
||||
fabrication (~> 2.24)
|
||||
faker (~> 2.19)
|
||||
fabrication (~> 2.27)
|
||||
faker (~> 2.20)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
fog-openstack (~> 0.3)
|
||||
fuubar (~> 2.5)
|
||||
gitlab-omniauth-openid-connect (~> 0.5.0)
|
||||
hamlit-rails (~> 0.2)
|
||||
hcaptcha (~> 7.1)
|
||||
hiredis (~> 0.6)
|
||||
@ -729,12 +770,11 @@ DEPENDENCIES
|
||||
httplog (~> 1.5.0)
|
||||
i18n-tasks (~> 0.9)
|
||||
idn-ruby
|
||||
iso-639
|
||||
json-ld
|
||||
json-ld-preloaded (~> 3.2)
|
||||
kaminari (~> 1.2)
|
||||
kt-paperclip (~> 7.0)
|
||||
letter_opener (~> 1.7)
|
||||
kt-paperclip (~> 7.1)
|
||||
letter_opener (~> 1.8)
|
||||
letter_opener_web (~> 2.0)
|
||||
link_header (~> 0.0)
|
||||
lograge (~> 0.11)
|
||||
@ -761,12 +801,12 @@ DEPENDENCIES
|
||||
private_address_check (~> 0.5)
|
||||
pry-byebug (~> 3.9)
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 5.5)
|
||||
pundit (~> 2.1)
|
||||
puma (~> 5.6)
|
||||
pundit (~> 2.2)
|
||||
rack (~> 2.2.3)
|
||||
rack-attack (~> 6.5)
|
||||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 1.1)
|
||||
rails (~> 6.1.4)
|
||||
rails (~> 6.1.5)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
@ -776,14 +816,14 @@ DEPENDENCIES
|
||||
redis-namespace (~> 1.8)
|
||||
rexml (~> 3.2)
|
||||
rqrcode (~> 2.1)
|
||||
rspec-rails (~> 5.0)
|
||||
rspec-rails (~> 5.1)
|
||||
rspec-sidekiq (~> 3.1)
|
||||
rspec_junit_formatter (~> 0.5)
|
||||
rubocop (~> 1.25)
|
||||
rubocop-rails (~> 2.13)
|
||||
rubocop (~> 1.26)
|
||||
rubocop-rails (~> 2.14)
|
||||
ruby-progressbar (~> 1.11)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.5)
|
||||
scenic (~> 1.6)
|
||||
sidekiq (~> 6.4)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 3.1)
|
||||
@ -799,7 +839,7 @@ DEPENDENCIES
|
||||
thor (~> 1.2)
|
||||
tty-prompt (~> 0.23)
|
||||
twitter-text (~> 3.1.0)
|
||||
tzinfo-data (~> 1.2021)
|
||||
tzinfo-data (~> 1.2022)
|
||||
webauthn (~> 3.0.0.alpha1)
|
||||
webmock (~> 3.14)
|
||||
webpacker (~> 5.4)
|
||||
|
18
SECURITY.md
18
SECURITY.md
@ -1,13 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you should submit the report through our [Bug Bounty Program][bug-bounty]. Alternatively, you can reach us at <hello@joinmastodon.org>.
|
||||
|
||||
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||
|
||||
## Scope
|
||||
|
||||
A "vulnerability in Mastodon" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.4.x | :white_check_mark: |
|
||||
| 3.3.x | :white_check_mark: |
|
||||
| < 3.3 | :x: |
|
||||
| 3.4.x | Yes |
|
||||
| 3.3.x | Yes |
|
||||
| < 3.3 | No |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
hello@joinmastodon.org
|
||||
[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@ -33,11 +33,9 @@ sudo apt-get install \
|
||||
redis-tools \
|
||||
postgresql \
|
||||
postgresql-contrib \
|
||||
protobuf-compiler \
|
||||
yarn \
|
||||
libicu-dev \
|
||||
libidn11-dev \
|
||||
libprotobuf-dev \
|
||||
libreadline-dev \
|
||||
libpam0g-dev \
|
||||
-y
|
||||
|
5
app.json
5
app.json
@ -95,8 +95,5 @@
|
||||
"scripts": {
|
||||
"postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
|
||||
},
|
||||
"addons": [
|
||||
"heroku-postgresql",
|
||||
"heroku-redis"
|
||||
]
|
||||
"addons": ["heroku-postgresql", "heroku-redis"]
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class StatusesIndex < Chewy::Index
|
||||
field :id, type: 'long'
|
||||
field :account_id, type: 'long'
|
||||
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
|
||||
field :stemmed, type: 'text', analyzer: 'content'
|
||||
end
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
class ActivityPub::BaseController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
skip_around_action :set_locale
|
||||
|
||||
private
|
||||
|
||||
|
@ -62,7 +62,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||
return unless page_requested?
|
||||
|
||||
@statuses = cache_collection_paginated_by_id(
|
||||
@account.statuses.permitted_for(@account, signed_request_account),
|
||||
AccountStatusesFilter.new(@account, signed_request_account).results,
|
||||
Status,
|
||||
LIMIT,
|
||||
params_slice(:max_id, :min_id, :since_id)
|
||||
|
@ -63,15 +63,29 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||
end
|
||||
|
||||
def next_page
|
||||
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
|
||||
if only_other_accounts?
|
||||
# Only consider remote accounts
|
||||
return nil if @replies.size < DESCENDANTS_LIMIT
|
||||
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
|
||||
only_other_accounts: only_other_accounts
|
||||
)
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: @replies&.last&.id,
|
||||
only_other_accounts: true
|
||||
)
|
||||
else
|
||||
# For now, we're serving only self-replies, but next page might be other accounts
|
||||
next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT
|
||||
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: next_only_other_accounts ? nil : @replies&.last&.id,
|
||||
only_other_accounts: next_only_other_accounts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def page_params
|
||||
|
@ -28,7 +28,7 @@ module Admin
|
||||
@deletion_request = @account.deletion_request
|
||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||
@warnings = @account.strikes.custom.latest
|
||||
@warnings = @account.strikes.includes(:target_account, :account, :appeal).latest
|
||||
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||
end
|
||||
|
||||
@ -146,7 +146,7 @@ module Admin
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
|
||||
params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
|
||||
end
|
||||
|
||||
def form_account_batch_params
|
||||
|
@ -35,6 +35,9 @@ module Admin
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
error_message = action_from_button == 'copy' ? 'admin.custom_emojis.batch_copy_error' : 'admin.custom_emojis.batch_error'
|
||||
flash[:alert] = I18n.t(error_message, message: e.message)
|
||||
ensure
|
||||
redirect_to admin_custom_emojis_path(filter_params)
|
||||
end
|
||||
|
@ -8,6 +8,7 @@ module Admin
|
||||
@pending_users_count = User.pending.count
|
||||
@pending_reports_count = Report.unresolved.count
|
||||
@pending_tags_count = Tag.pending_review.count
|
||||
@pending_appeals_count = Appeal.pending.count
|
||||
end
|
||||
|
||||
private
|
||||
|
40
app/controllers/admin/disputes/appeals_controller.rb
Normal file
40
app/controllers/admin/disputes/appeals_controller.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Disputes::AppealsController < Admin::BaseController
|
||||
before_action :set_appeal, except: :index
|
||||
|
||||
def index
|
||||
authorize :appeal, :index?
|
||||
|
||||
@appeals = filtered_appeals.page(params[:page])
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize @appeal, :approve?
|
||||
log_action :approve, @appeal
|
||||
ApproveAppealService.new.call(@appeal, current_account)
|
||||
redirect_to disputes_strike_path(@appeal.strike)
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize @appeal, :approve?
|
||||
log_action :reject, @appeal
|
||||
@appeal.reject!(current_account)
|
||||
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
|
||||
redirect_to disputes_strike_path(@appeal.strike)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_appeals
|
||||
Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
|
||||
end
|
||||
|
||||
def set_appeal
|
||||
@appeal = Appeal.find(params[:id])
|
||||
end
|
||||
end
|
@ -56,10 +56,6 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_block, :show?
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_block, :destroy?
|
||||
UnblockDomainService.new.call(@domain_block)
|
||||
|
@ -6,7 +6,20 @@ module Admin
|
||||
|
||||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
|
||||
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
|
||||
@form = Form::EmailDomainBlockBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::EmailDomainBlockBatch.new(form_email_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
ensure
|
||||
redirect_to admin_email_domain_blocks_path
|
||||
end
|
||||
|
||||
def new
|
||||
@ -19,41 +32,27 @@ module Admin
|
||||
|
||||
@email_domain_block = EmailDomainBlock.new(resource_params)
|
||||
|
||||
if @email_domain_block.save
|
||||
log_action :create, @email_domain_block
|
||||
if action_from_button == 'save'
|
||||
EmailDomainBlock.transaction do
|
||||
@email_domain_block.save!
|
||||
log_action :create, @email_domain_block
|
||||
|
||||
if @email_domain_block.with_dns_records?
|
||||
hostnames = []
|
||||
ips = []
|
||||
(@email_domain_block.other_domains || []).uniq.each do |domain|
|
||||
next if EmailDomainBlock.where(domain: domain).exists?
|
||||
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
|
||||
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
|
||||
|
||||
([@email_domain_block.domain] + hostnames).uniq.each do |hostname|
|
||||
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
|
||||
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
|
||||
end
|
||||
end
|
||||
|
||||
(hostnames + ips).each do |hostname|
|
||||
another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block)
|
||||
log_action :create, another_email_domain_block if another_email_domain_block.save
|
||||
other_email_domain_block = EmailDomainBlock.create!(domain: domain, parent: @email_domain_block)
|
||||
log_action :create, other_email_domain_block
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
|
||||
else
|
||||
set_resolved_records
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @email_domain_block, :destroy?
|
||||
@email_domain_block.destroy!
|
||||
log_action :destroy, @email_domain_block
|
||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
set_resolved_records
|
||||
render :new
|
||||
end
|
||||
|
||||
private
|
||||
@ -62,8 +61,27 @@ module Admin
|
||||
@email_domain_block = EmailDomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def set_resolved_records
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:email_domain_block).permit(:domain, :with_dns_records)
|
||||
params.require(:email_domain_block).permit(:domain, other_domains: [])
|
||||
end
|
||||
|
||||
def form_email_domain_block_batch_params
|
||||
params.require(:form_email_domain_block_batch).permit(email_domain_block_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:delete]
|
||||
'delete'
|
||||
elsif params[:save]
|
||||
'save'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,28 +4,26 @@ module Admin
|
||||
class InstancesController < BaseController
|
||||
before_action :set_instances, only: :index
|
||||
before_action :set_instance, except: :index
|
||||
before_action :set_exhausted_deliveries_days, only: :show
|
||||
|
||||
def index
|
||||
authorize :instance, :index?
|
||||
preload_delivery_failures!
|
||||
end
|
||||
|
||||
def show
|
||||
authorize :instance, :show?
|
||||
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :instance, :destroy?
|
||||
|
||||
Admin::DomainPurgeWorker.perform_async(@instance.domain)
|
||||
|
||||
log_action :destroy, @instance
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
|
||||
end
|
||||
|
||||
def clear_delivery_errors
|
||||
authorize :delivery, :clear_delivery_errors?
|
||||
|
||||
@instance.delivery_failure_tracker.clear_failures!
|
||||
redirect_to admin_instance_path(@instance.domain)
|
||||
end
|
||||
@ -33,11 +31,9 @@ module Admin
|
||||
def restart_delivery
|
||||
authorize :delivery, :restart_delivery?
|
||||
|
||||
last_unavailable_domain = unavailable_domain
|
||||
|
||||
if last_unavailable_domain.present?
|
||||
if @instance.unavailable?
|
||||
@instance.delivery_failure_tracker.track_success!
|
||||
log_action :destroy, last_unavailable_domain
|
||||
log_action :destroy, @instance.unavailable_domain
|
||||
end
|
||||
|
||||
redirect_to admin_instance_path(@instance.domain)
|
||||
@ -45,8 +41,7 @@ module Admin
|
||||
|
||||
def stop_delivery
|
||||
authorize :delivery, :stop_delivery?
|
||||
|
||||
UnavailableDomain.create(domain: @instance.domain)
|
||||
unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
|
||||
log_action :create, unavailable_domain
|
||||
redirect_to admin_instance_path(@instance.domain)
|
||||
end
|
||||
@ -57,12 +52,11 @@ module Admin
|
||||
@instance = Instance.find(params[:id])
|
||||
end
|
||||
|
||||
def set_exhausted_deliveries_days
|
||||
@exhausted_deliveries_days = @instance.delivery_failure_tracker.exhausted_deliveries_days
|
||||
end
|
||||
|
||||
def set_instances
|
||||
@instances = filtered_instances.page(params[:page])
|
||||
end
|
||||
|
||||
def preload_delivery_failures!
|
||||
warning_domains_map = DeliveryFailureTracker.warning_domains_map
|
||||
|
||||
@instances.each do |instance|
|
||||
@ -70,10 +64,6 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def unavailable_domain
|
||||
UnavailableDomain.find_by(domain: @instance.domain)
|
||||
end
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||
end
|
||||
|
@ -9,7 +9,8 @@ module Admin
|
||||
def index
|
||||
authorize :account, :index?
|
||||
|
||||
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
|
||||
@accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
|
||||
@form = Form::AccountBatch.new
|
||||
end
|
||||
|
||||
private
|
||||
|
52
app/controllers/admin/reports/actions_controller.rb
Normal file
52
app/controllers/admin/reports/actions_controller.rb
Normal file
@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Reports::ActionsController < Admin::BaseController
|
||||
before_action :set_report
|
||||
|
||||
def create
|
||||
authorize @report, :show?
|
||||
|
||||
case action_from_button
|
||||
when 'delete', 'mark_as_sensitive'
|
||||
status_batch_action = Admin::StatusBatchAction.new(
|
||||
type: action_from_button,
|
||||
status_ids: @report.status_ids,
|
||||
current_account: current_account,
|
||||
report_id: @report.id,
|
||||
send_email_notification: !@report.spam?
|
||||
)
|
||||
|
||||
status_batch_action.save!
|
||||
when 'silence', 'suspend'
|
||||
account_action = Admin::AccountAction.new(
|
||||
type: action_from_button,
|
||||
report_id: @report.id,
|
||||
target_account: @report.target_account,
|
||||
current_account: current_account,
|
||||
send_email_notification: !@report.spam?
|
||||
)
|
||||
|
||||
account_action.save!
|
||||
end
|
||||
|
||||
redirect_to admin_reports_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_report
|
||||
@report = Report.find(params[:report_id])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:delete]
|
||||
'delete'
|
||||
elsif params[:mark_as_sensitive]
|
||||
'mark_as_sensitive'
|
||||
elsif params[:silence]
|
||||
'silence'
|
||||
elsif params[:suspend]
|
||||
'suspend'
|
||||
end
|
||||
end
|
||||
end
|
@ -29,8 +29,9 @@ module Admin
|
||||
end
|
||||
|
||||
def after_create_redirect_path
|
||||
if @status_batch_action.report_id.present?
|
||||
admin_report_path(@status_batch_action.report_id)
|
||||
report_id = @status_batch_action&.report_id || params[:report_id]
|
||||
if report_id.present?
|
||||
admin_report_path(report_id)
|
||||
else
|
||||
admin_account_statuses_path(params[:account_id], current_params)
|
||||
end
|
||||
@ -48,6 +49,10 @@ module Admin
|
||||
params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS)
|
||||
end
|
||||
|
||||
def current_params
|
||||
params.slice(:media, :page).permit(:media, :page)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:report]
|
||||
'report'
|
||||
|
@ -5,11 +5,11 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
|
||||
authorize :preview_card_provider, :index?
|
||||
|
||||
@preview_card_providers = filtered_preview_card_providers.page(params[:page])
|
||||
@form = Form::PreviewCardProviderBatch.new
|
||||
@form = Trends::PreviewCardProviderBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::PreviewCardProviderBatch.new(form_preview_card_provider_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form = Trends::PreviewCardProviderBatch.new(trends_preview_card_provider_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
@ -20,15 +20,15 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
|
||||
private
|
||||
|
||||
def filtered_preview_card_providers
|
||||
PreviewCardProviderFilter.new(filter_params).results
|
||||
Trends::PreviewCardProviderFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *PreviewCardProviderFilter::KEYS).permit(:page, *PreviewCardProviderFilter::KEYS)
|
||||
params.slice(:page, *Trends::PreviewCardProviderFilter::KEYS).permit(:page, *Trends::PreviewCardProviderFilter::KEYS)
|
||||
end
|
||||
|
||||
def form_preview_card_provider_batch_params
|
||||
params.require(:form_preview_card_provider_batch).permit(:action, preview_card_provider_ids: [])
|
||||
def trends_preview_card_provider_batch_params
|
||||
params.require(:trends_preview_card_provider_batch).permit(:action, preview_card_provider_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
@ -5,11 +5,11 @@ class Admin::Trends::LinksController < Admin::BaseController
|
||||
authorize :preview_card, :index?
|
||||
|
||||
@preview_cards = filtered_preview_cards.page(params[:page])
|
||||
@form = Form::PreviewCardBatch.new
|
||||
@form = Trends::PreviewCardBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::PreviewCardBatch.new(form_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form = Trends::PreviewCardBatch.new(trends_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
@ -20,26 +20,26 @@ class Admin::Trends::LinksController < Admin::BaseController
|
||||
private
|
||||
|
||||
def filtered_preview_cards
|
||||
PreviewCardFilter.new(filter_params.with_defaults(trending: 'all')).results
|
||||
Trends::PreviewCardFilter.new(filter_params.with_defaults(trending: 'all')).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *PreviewCardFilter::KEYS).permit(:page, *PreviewCardFilter::KEYS)
|
||||
params.slice(:page, *Trends::PreviewCardFilter::KEYS).permit(:page, *Trends::PreviewCardFilter::KEYS)
|
||||
end
|
||||
|
||||
def form_preview_card_batch_params
|
||||
params.require(:form_preview_card_batch).permit(:action, preview_card_ids: [])
|
||||
def trends_preview_card_batch_params
|
||||
params.require(:trends_preview_card_batch).permit(:action, preview_card_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:approve]
|
||||
'approve'
|
||||
elsif params[:approve_all]
|
||||
'approve_all'
|
||||
elsif params[:approve_providers]
|
||||
'approve_providers'
|
||||
elsif params[:reject]
|
||||
'reject'
|
||||
elsif params[:reject_all]
|
||||
'reject_all'
|
||||
elsif params[:reject_providers]
|
||||
'reject_providers'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
45
app/controllers/admin/trends/statuses_controller.rb
Normal file
45
app/controllers/admin/trends/statuses_controller.rb
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Trends::StatusesController < Admin::BaseController
|
||||
def index
|
||||
authorize :status, :index?
|
||||
|
||||
@statuses = filtered_statuses.page(params[:page])
|
||||
@form = Trends::StatusBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Trends::StatusBatch.new(trends_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
ensure
|
||||
redirect_to admin_trends_statuses_path(filter_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_statuses
|
||||
Trends::StatusFilter.new(filter_params.with_defaults(trending: 'all')).results.includes(:account, :media_attachments, :active_mentions)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *Trends::StatusFilter::KEYS).permit(:page, *Trends::StatusFilter::KEYS)
|
||||
end
|
||||
|
||||
def trends_status_batch_params
|
||||
params.require(:trends_status_batch).permit(:action, status_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:approve]
|
||||
'approve'
|
||||
elsif params[:approve_accounts]
|
||||
'approve_accounts'
|
||||
elsif params[:reject]
|
||||
'reject'
|
||||
elsif params[:reject_accounts]
|
||||
'reject_accounts'
|
||||
end
|
||||
end
|
||||
end
|
@ -5,11 +5,11 @@ class Admin::Trends::TagsController < Admin::BaseController
|
||||
authorize :tag, :index?
|
||||
|
||||
@tags = filtered_tags.page(params[:page])
|
||||
@form = Form::TagBatch.new
|
||||
@form = Trends::TagBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::TagBatch.new(form_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form = Trends::TagBatch.new(trends_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
@ -20,15 +20,15 @@ class Admin::Trends::TagsController < Admin::BaseController
|
||||
private
|
||||
|
||||
def filtered_tags
|
||||
TagFilter.new(filter_params).results
|
||||
Trends::TagFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *TagFilter::KEYS).permit(:page, *TagFilter::KEYS)
|
||||
params.slice(:page, *Trends::TagFilter::KEYS).permit(:page, *Trends::TagFilter::KEYS)
|
||||
end
|
||||
|
||||
def form_tag_batch_params
|
||||
params.require(:form_tag_batch).permit(:action, tag_ids: [])
|
||||
def trends_tag_batch_params
|
||||
params.require(:trends_tag_batch).permit(:action, tag_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
@ -5,6 +5,7 @@ class Api::BaseController < ApplicationController
|
||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||
|
||||
include RateLimitHeaders
|
||||
include AccessTokenTrackingConcern
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
@ -14,8 +15,6 @@ class Api::BaseController < ApplicationController
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
||||
before_action :require_user!
|
||||
before_action :set_accounts
|
||||
|
||||
def index
|
||||
render json: familiar_followers.accounts, each_serializer: REST::FamiliarFollowersSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
|
||||
end
|
||||
|
||||
def familiar_followers
|
||||
FamiliarFollowersPresenter.new(@accounts, current_user.account_id)
|
||||
end
|
||||
|
||||
def account_ids
|
||||
Array(params[:id]).map(&:to_i)
|
||||
end
|
||||
end
|
@ -22,53 +22,16 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def cached_account_statuses
|
||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||
|
||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||
|
||||
cache_collection_paginated_by_id(
|
||||
statuses,
|
||||
AccountStatusesFilter.new(@account, current_account, params).results,
|
||||
Status,
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_account_statuses
|
||||
@account.statuses.permitted_for(@account, current_account)
|
||||
end
|
||||
|
||||
def only_media_scope
|
||||
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
||||
end
|
||||
|
||||
def pinned_scope
|
||||
@account.pinned_statuses.permitted_for(@account, current_account)
|
||||
end
|
||||
|
||||
def no_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def no_reblogs_scope
|
||||
Status.without_reblogs
|
||||
end
|
||||
|
||||
def hashtag_scope
|
||||
tag = Tag.find_normalized(params[:tagged])
|
||||
|
||||
if tag
|
||||
Status.tagged_with(tag.id)
|
||||
else
|
||||
Status.none
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||
params.slice(:limit, *AccountStatusesFilter::KEYS).permit(:limit, *AccountStatusesFilter::KEYS).merge(core_params)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
class Api::V1::AccountsController < Api::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
|
||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, only: [:block, :unblock]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
|
||||
|
||||
before_action :require_user!, except: [:show, :create]
|
||||
|
19
app/controllers/api/v1/admin/trends/links_controller.rb
Normal file
19
app/controllers/api/v1/admin/trends/links_controller.rb
Normal file
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::LinksController < Api::BaseController
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_links
|
||||
|
||||
def index
|
||||
render json: @links, each_serializer: REST::Trends::LinkSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_links
|
||||
@links = Trends.links.query.limit(limit_param(10))
|
||||
end
|
||||
end
|
19
app/controllers/api/v1/admin/trends/statuses_controller.rb
Normal file
19
app/controllers/api/v1/admin/trends/statuses_controller.rb
Normal file
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::Trends::StatusesController < Api::BaseController
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||
before_action :require_staff!
|
||||
before_action :set_statuses
|
||||
|
||||
def index
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_statuses
|
||||
@statuses = cache_collection(Trends.statuses.query.limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
|
||||
end
|
||||
end
|
@ -14,6 +14,6 @@ class Api::V1::Admin::Trends::TagsController < Api::BaseController
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = Trends.tags.get(false, limit_param(10))
|
||||
@tags = Trends.tags.query.limit(limit_param(10))
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::BlocksController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow, :'read:blocks' }
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:blocks' }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
class Api::V1::DomainBlocksController < Api::BaseController
|
||||
BLOCK_LIMIT = 100
|
||||
|
||||
before_action -> { doorkeeper_authorize! :follow, :'read:blocks' }, only: :show
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, except: :show
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:blocks' }, only: :show
|
||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, except: :show
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :show
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Emails::ConfirmationsController < Api::BaseController
|
||||
before_action :doorkeeper_authorize!
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||
before_action :require_user_owned_by_application!
|
||||
before_action :require_user_not_confirmed!
|
||||
|
||||
@ -19,6 +19,6 @@ class Api::V1::Emails::ConfirmationsController < Api::BaseController
|
||||
end
|
||||
|
||||
def require_user_not_confirmed!
|
||||
render json: { error: 'This method is only available while the e-mail is awaiting confirmation' }, status: :forbidden if current_user.confirmed? || current_user.unconfirmed_email.blank?
|
||||
render json: { error: 'This method is only available while the e-mail is awaiting confirmation' }, status: :forbidden unless !current_user.confirmed? || current_user.unconfirmed_email.present?
|
||||
end
|
||||
end
|
||||
|
@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FollowRequestsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow, :'read:follows' }, only: :index
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, except: :index
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, only: :index
|
||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, except: :index
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||
|
||||
def authorize
|
||||
AuthorizeFollowService.new.call(account, current_account)
|
||||
NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account))
|
||||
LocalNotificationWorker.perform_async(current_account.id, Follow.find_by(account: account, target_account: current_account).id, 'Follow', 'follow')
|
||||
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Api::V1::MediaController < Api::BaseController
|
||||
end
|
||||
|
||||
def update
|
||||
@media_attachment.update!(media_attachment_params)
|
||||
@media_attachment.update!(updateable_media_attachment_params)
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
end
|
||||
|
||||
@ -31,7 +31,7 @@ class Api::V1::MediaController < Api::BaseController
|
||||
end
|
||||
|
||||
def set_media_attachment
|
||||
@media_attachment = current_account.media_attachments.unattached.find(params[:id])
|
||||
@media_attachment = current_account.media_attachments.where(status_id: nil).find(params[:id])
|
||||
end
|
||||
|
||||
def check_processing
|
||||
@ -42,6 +42,10 @@ class Api::V1::MediaController < Api::BaseController
|
||||
params.permit(:file, :thumbnail, :description, :focus)
|
||||
end
|
||||
|
||||
def updateable_media_attachment_params
|
||||
params.permit(:thumbnail, :description, :focus)
|
||||
end
|
||||
|
||||
def file_type_error
|
||||
{ error: 'File type of uploaded media could not be verified' }
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MutesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow, :'read:mutes' }
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:mutes' }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
|
@ -44,13 +44,18 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
|
||||
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
|
||||
cache_collection(target_statuses, Status)
|
||||
end
|
||||
end
|
||||
|
||||
def browserable_account_notifications
|
||||
current_account.notifications.without_suspended.browserable(exclude_types, from_account)
|
||||
current_account.notifications.without_suspended.browserable(
|
||||
types: Array(browserable_params[:types]),
|
||||
exclude_types: Array(browserable_params[:exclude_types]),
|
||||
from_account_id: browserable_params[:account_id]
|
||||
)
|
||||
end
|
||||
|
||||
def target_statuses_from_notifications
|
||||
@ -81,17 +86,11 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||
@notifications.first.id
|
||||
end
|
||||
|
||||
def exclude_types
|
||||
val = params.permit(exclude_types: [])[:exclude_types] || []
|
||||
val = [val] unless val.is_a?(Enumerable)
|
||||
val
|
||||
end
|
||||
|
||||
def from_account
|
||||
params[:account_id]
|
||||
def browserable_params
|
||||
params.permit(:account_id, types: [], exclude_types: [])
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params)
|
||||
params.slice(:limit, :account_id, :types, :exclude_types).permit(:limit, :account_id, types: [], exclude_types: []).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
@ -10,9 +10,7 @@ class Api::V1::ReportsController < Api::BaseController
|
||||
@report = ReportService.new.call(
|
||||
current_account,
|
||||
reported_account,
|
||||
status_ids: reported_status_ids,
|
||||
comment: report_params[:comment],
|
||||
forward: report_params[:forward]
|
||||
report_params
|
||||
)
|
||||
|
||||
render json: @report, serializer: REST::ReportSerializer
|
||||
@ -20,19 +18,11 @@ class Api::V1::ReportsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def reported_status_ids
|
||||
reported_account.statuses.with_discarded.find(status_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def status_ids
|
||||
Array(report_params[:status_ids])
|
||||
end
|
||||
|
||||
def reported_account
|
||||
Account.find(report_params[:account_id])
|
||||
end
|
||||
|
||||
def report_params
|
||||
params.permit(:account_id, :comment, :forward, status_ids: [])
|
||||
params.permit(:account_id, :comment, :category, :forward, status_ids: [], rule_ids: [])
|
||||
end
|
||||
end
|
||||
|
@ -7,7 +7,7 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||
before_action :set_status
|
||||
|
||||
def show
|
||||
render json: @status.edits, each_serializer: REST::StatusEditSerializer
|
||||
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -3,13 +3,14 @@
|
||||
class Api::V1::StatusesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
|
||||
before_action :require_user!, except: [:show, :context]
|
||||
before_action :set_status, only: [:show, :context]
|
||||
before_action :set_thread, only: [:create]
|
||||
|
||||
override_rate_limit_headers :create, family: :statuses
|
||||
override_rate_limit_headers :update, family: :statuses
|
||||
|
||||
# This API was originally unlimited, pagination cannot be introduced without
|
||||
# breaking backwards-compatibility. Arbitrarily high number to cover most
|
||||
@ -35,25 +36,46 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
text: status_params[:status],
|
||||
thread: @thread,
|
||||
media_ids: status_params[:media_ids],
|
||||
sensitive: status_params[:sensitive],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
visibility: status_params[:visibility],
|
||||
scheduled_at: status_params[:scheduled_at],
|
||||
application: doorkeeper_token.application,
|
||||
poll: status_params[:poll],
|
||||
content_type: status_params[:content_type],
|
||||
idempotency: request.headers['Idempotency-Key'],
|
||||
with_rate_limit: true)
|
||||
@status = PostStatusService.new.call(
|
||||
current_user.account,
|
||||
text: status_params[:status],
|
||||
thread: @thread,
|
||||
media_ids: status_params[:media_ids],
|
||||
sensitive: status_params[:sensitive],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
visibility: status_params[:visibility],
|
||||
language: status_params[:language],
|
||||
scheduled_at: status_params[:scheduled_at],
|
||||
application: doorkeeper_token.application,
|
||||
poll: status_params[:poll],
|
||||
content_type: status_params[:content_type],
|
||||
idempotency: request.headers['Idempotency-Key'],
|
||||
with_rate_limit: true
|
||||
)
|
||||
|
||||
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@status = Status.where(account: current_account).find(params[:id])
|
||||
authorize @status, :update?
|
||||
|
||||
UpdateStatusService.new.call(
|
||||
@status,
|
||||
current_account.id,
|
||||
text: status_params[:status],
|
||||
media_ids: status_params[:media_ids],
|
||||
sensitive: status_params[:sensitive],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
poll: status_params[:poll],
|
||||
content_type: status_params[:content_type]
|
||||
)
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
@status = Status.where(account_id: current_user.account).find(params[:id])
|
||||
@status = Status.where(account: current_account).find(params[:id])
|
||||
authorize @status, :destroy?
|
||||
|
||||
@status.discard
|
||||
@ -73,8 +95,9 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def set_thread
|
||||
@thread = status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
@thread = Status.find(status_params[:in_reply_to_id]) if status_params[:in_reply_to_id].present?
|
||||
authorize(@thread, :show?) if @thread.present?
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
|
||||
end
|
||||
|
||||
@ -85,6 +108,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
:sensitive,
|
||||
:spoiler_text,
|
||||
:visibility,
|
||||
:language,
|
||||
:scheduled_at,
|
||||
:content_type,
|
||||
media_ids: [],
|
||||
|
@ -12,10 +12,14 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
||||
def set_links
|
||||
@links = begin
|
||||
if Setting.trends
|
||||
Trends.links.get(true, limit_param(10))
|
||||
links_from_trends
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def links_from_trends
|
||||
Trends.links.query.allowed.in_locale(content_locale).limit(limit_param(10))
|
||||
end
|
||||
end
|
||||
|
27
app/controllers/api/v1/trends/statuses_controller.rb
Normal file
27
app/controllers/api/v1/trends/statuses_controller.rb
Normal file
@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Trends::StatusesController < Api::BaseController
|
||||
before_action :set_statuses
|
||||
|
||||
def index
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_statuses
|
||||
@statuses = begin
|
||||
if Setting.trends
|
||||
cache_collection(statuses_from_trends, Status)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def statuses_from_trends
|
||||
scope = Trends.statuses.query.allowed.in_locale(content_locale)
|
||||
scope = scope.filtered_for(current_account) if user_signed_in?
|
||||
scope.limit(limit_param(DEFAULT_STATUSES_LIMIT))
|
||||
end
|
||||
end
|
@ -12,7 +12,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
||||
def set_tags
|
||||
@tags = begin
|
||||
if Setting.trends
|
||||
Trends.tags.get(true, limit_param(10))
|
||||
Trends.tags.query.allowed.limit(limit_param(10))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -17,16 +17,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||
|
||||
data = {
|
||||
policy: 'all',
|
||||
|
||||
alerts: {
|
||||
follow: alerts_enabled,
|
||||
follow_request: alerts_enabled,
|
||||
favourite: alerts_enabled,
|
||||
reblog: alerts_enabled,
|
||||
mention: alerts_enabled,
|
||||
poll: alerts_enabled,
|
||||
status: alerts_enabled,
|
||||
},
|
||||
alerts: Notification::TYPES.index_with { alerts_enabled },
|
||||
}
|
||||
|
||||
data.deep_merge!(data_params) if params[:data]
|
||||
@ -61,6 +52,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||
end
|
||||
|
||||
def data_params
|
||||
@data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
|
||||
@data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
|
||||
end
|
||||
end
|
||||
|
@ -136,16 +136,6 @@ class ApplicationController < ActionController::Base
|
||||
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
||||
end
|
||||
|
||||
def current_flavour
|
||||
return Setting.flavour unless Themes.instance.flavours.include? current_user&.setting_flavour
|
||||
current_user.setting_flavour
|
||||
end
|
||||
|
||||
def current_skin
|
||||
return Setting.skin unless Themes.instance.skins_for(current_flavour).include? current_user&.setting_skin
|
||||
current_user.setting_skin
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any do
|
||||
|
@ -4,8 +4,6 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
def self.provides_callback_for(provider)
|
||||
provider_id = provider.to_s.chomp '_oauth2'
|
||||
|
||||
define_method provider do
|
||||
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
|
||||
|
||||
@ -20,7 +18,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
)
|
||||
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format?
|
||||
set_flash_message(:notice, :success, kind: Devise.omniauth_configs[provider].strategy.display_name.capitalize) if is_navigational_format?
|
||||
else
|
||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||
redirect_to new_user_registration_url
|
||||
@ -33,7 +31,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
if resource.email_verified?
|
||||
if resource.email_present?
|
||||
root_path
|
||||
else
|
||||
auth_setup_path(missing_email: '1')
|
||||
|
@ -10,6 +10,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :set_pack
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_strikes, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
@ -116,8 +117,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def set_invite
|
||||
invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
||||
@invite = invite&.valid_for_use? ? invite : nil
|
||||
@invite = begin
|
||||
invite = Invite.find_by(code: invite_code) if invite_code.present?
|
||||
invite if invite&.valid_for_use?
|
||||
end
|
||||
end
|
||||
|
||||
def determine_layout
|
||||
@ -128,6 +131,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def set_strikes
|
||||
@strikes = current_account.strikes.recent.latest
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
21
app/controllers/concerns/access_token_tracking_concern.rb
Normal file
21
app/controllers/concerns/access_token_tracking_concern.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AccessTokenTrackingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ACCESS_TOKEN_UPDATE_FREQUENCY = 24.hours.freeze
|
||||
|
||||
included do
|
||||
before_action :update_access_token_last_used
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_access_token_last_used
|
||||
doorkeeper_token.update_last_used(request) if access_token_needs_update?
|
||||
end
|
||||
|
||||
def access_token_needs_update?
|
||||
doorkeeper_token.present? && (doorkeeper_token.last_used_at.nil? || doorkeeper_token.last_used_at < ACCESS_TOKEN_UPDATE_FREQUENCY.ago)
|
||||
end
|
||||
end
|
@ -3,7 +3,7 @@
|
||||
module Authorization
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
|
||||
def pundit_user
|
||||
current_account
|
||||
|
@ -7,27 +7,28 @@ module Localized
|
||||
around_action :set_locale
|
||||
end
|
||||
|
||||
def set_locale
|
||||
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
|
||||
locale ||= session[:locale] ||= default_locale
|
||||
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
|
||||
|
||||
I18n.with_locale(locale) do
|
||||
yield
|
||||
end
|
||||
def set_locale(&block)
|
||||
I18n.with_locale(requested_locale || I18n.default_locale, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_locale
|
||||
if ENV['DEFAULT_LOCALE'].present?
|
||||
I18n.default_locale
|
||||
else
|
||||
request_locale || I18n.default_locale
|
||||
end
|
||||
def requested_locale
|
||||
requested_locale_name = available_locale_or_nil(params[:lang])
|
||||
requested_locale_name ||= available_locale_or_nil(current_user.locale) if respond_to?(:user_signed_in?) && user_signed_in?
|
||||
requested_locale_name ||= http_accept_language if ENV['DEFAULT_LOCALE'].blank?
|
||||
requested_locale_name
|
||||
end
|
||||
|
||||
def request_locale
|
||||
http_accept_language.language_region_compatible_from(I18n.available_locales)
|
||||
def http_accept_language
|
||||
HttpAcceptLanguage::Parser.new(request.headers.fetch('Accept-Language')).language_region_compatible_from(I18n.available_locales) if request.headers.key?('Accept-Language')
|
||||
end
|
||||
|
||||
def available_locale_or_nil(locale_name)
|
||||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||
end
|
||||
|
||||
def content_locale
|
||||
@content_locale ||= I18n.locale.to_s.split(/[_-]/).first
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,7 @@
|
||||
module SessionTrackingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
UPDATE_SIGN_IN_HOURS = 24
|
||||
SESSION_UPDATE_FREQUENCY = 24.hours.freeze
|
||||
|
||||
included do
|
||||
before_action :set_session_activity
|
||||
@ -17,6 +17,6 @@ module SessionTrackingConcern
|
||||
end
|
||||
|
||||
def session_needs_update?
|
||||
!current_session.nil? && current_session.updated_at < UPDATE_SIGN_IN_HOURS.hours.ago
|
||||
!current_session.nil? && current_session.updated_at < SESSION_UPDATE_FREQUENCY.ago
|
||||
end
|
||||
end
|
||||
|
@ -10,8 +10,17 @@ module ThemingConcern
|
||||
|
||||
private
|
||||
|
||||
def current_flavour
|
||||
[current_user&.setting_flavour, Setting.flavour, 'glitch', 'vanilla'].find { |flavour| Themes.instance.flavours.include?(flavour) }
|
||||
end
|
||||
|
||||
def current_skin
|
||||
skins = Themes.instance.skins_for(current_flavour)
|
||||
[current_user&.setting_skin, Setting.skin, 'default'].find { |skin| skins.include?(skin) }
|
||||
end
|
||||
|
||||
def valid_pack_data?(data, pack_name)
|
||||
data['pack'].is_a?(Hash) && [String, Hash].any? { |c| data['pack'][pack_name].is_a?(c) }
|
||||
data['pack'].is_a?(Hash) && data['pack'][pack_name].present?
|
||||
end
|
||||
|
||||
def nil_pack(data)
|
||||
|
@ -3,7 +3,7 @@
|
||||
module UserTrackingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
UPDATE_SIGN_IN_FREQUENCY = 24.hours.freeze
|
||||
SIGN_IN_UPDATE_FREQUENCY = 24.hours.freeze
|
||||
|
||||
included do
|
||||
before_action :update_user_sign_in
|
||||
@ -16,6 +16,6 @@ module UserTrackingConcern
|
||||
end
|
||||
|
||||
def user_needs_sign_in_update?
|
||||
user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < UPDATE_SIGN_IN_FREQUENCY.ago)
|
||||
user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < SIGN_IN_UPDATE_FREQUENCY.ago)
|
||||
end
|
||||
end
|
||||
|
26
app/controllers/disputes/appeals_controller.rb
Normal file
26
app/controllers/disputes/appeals_controller.rb
Normal file
@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::AppealsController < Disputes::BaseController
|
||||
before_action :set_strike
|
||||
|
||||
def create
|
||||
authorize @strike, :appeal?
|
||||
|
||||
@appeal = AppealService.new.call(@strike, appeal_params[:text])
|
||||
|
||||
redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
@appeal = e.record
|
||||
render template: 'disputes/strikes/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_strike
|
||||
@strike = current_account.strikes.find(params[:strike_id])
|
||||
end
|
||||
|
||||
def appeal_params
|
||||
params.require(:appeal).permit(:text)
|
||||
end
|
||||
end
|
23
app/controllers/disputes/base_controller.rb
Normal file
23
app/controllers/disputes/base_controller.rb
Normal file
@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::BaseController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'admin'
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :authenticate_user!
|
||||
before_action :set_pack
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
use_pack 'admin'
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
end
|
21
app/controllers/disputes/strikes_controller.rb
Normal file
21
app/controllers/disputes/strikes_controller.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::StrikesController < Disputes::BaseController
|
||||
before_action :set_strike, only: [:show]
|
||||
|
||||
def index
|
||||
@strikes = current_account.strikes.latest
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @strike, :show?
|
||||
|
||||
@appeal = @strike.appeal || @strike.build_appeal
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_strike
|
||||
@strike = AccountWarning.find(params[:id])
|
||||
end
|
||||
end
|
@ -16,13 +16,13 @@ class FollowerAccountsController < ApplicationController
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
@ -83,7 +83,7 @@ class FollowerAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if page_requested? || !@account.user_hides_network?
|
||||
if page_requested? || !@account.hide_collections?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type total_items)
|
||||
|
@ -16,13 +16,13 @@ class FollowingAccountsController < ApplicationController
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
@ -83,7 +83,7 @@ class FollowingAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if page_requested? || !@account.user_hides_network?
|
||||
if page_requested? || !@account.hide_collections?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type total_items)
|
||||
|
@ -3,6 +3,7 @@
|
||||
class InstanceActorsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
skip_before_action :check_account_confirmation
|
||||
skip_around_action :set_locale
|
||||
|
||||
def show
|
||||
|
@ -48,7 +48,6 @@ class Settings::PreferencesController < Settings::BaseController
|
||||
:setting_system_font_ui,
|
||||
:setting_system_emoji_font,
|
||||
:setting_noindex,
|
||||
:setting_hide_network,
|
||||
:setting_hide_followers_count,
|
||||
:setting_aggregate_reblogs,
|
||||
:setting_show_application,
|
||||
@ -58,7 +57,7 @@ class Settings::PreferencesController < Settings::BaseController
|
||||
:setting_use_pending_items,
|
||||
:setting_trends,
|
||||
:setting_crop_images,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag trending_link trending_status),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||
)
|
||||
end
|
||||
|
@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::AccountModerationNotesHelper
|
||||
def admin_account_link_to(account)
|
||||
def admin_account_link_to(account, path: nil)
|
||||
return if account.nil?
|
||||
|
||||
link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||
safe_join([
|
||||
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
|
||||
content_tag(:span, account.acct, class: 'username'),
|
||||
|
@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
|
||||
"#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})"
|
||||
when 'Instance'
|
||||
record.domain
|
||||
when 'Appeal'
|
||||
link_to record.account.acct, disputes_strike_path(record.strike)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,9 +5,10 @@ module Admin::FilterHelper
|
||||
AccountFilter::KEYS,
|
||||
CustomEmojiFilter::KEYS,
|
||||
ReportFilter::KEYS,
|
||||
TagFilter::KEYS,
|
||||
PreviewCardProviderFilter::KEYS,
|
||||
PreviewCardFilter::KEYS,
|
||||
Trends::TagFilter::KEYS,
|
||||
Trends::PreviewCardProviderFilter::KEYS,
|
||||
Trends::PreviewCardFilter::KEYS,
|
||||
Trends::StatusFilter::KEYS,
|
||||
InstanceFilter::KEYS,
|
||||
InviteFilter::KEYS,
|
||||
RelationshipFilter::KEYS,
|
||||
|
20
app/helpers/admin/trends/statuses_helper.rb
Normal file
20
app/helpers/admin/trends/statuses_helper.rb
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin::Trends::StatusesHelper
|
||||
def one_line_preview(status)
|
||||
text = begin
|
||||
if status.local?
|
||||
status.text.split("\n").first
|
||||
else
|
||||
Nokogiri::HTML(status.text).css('html > body > *').first&.text
|
||||
end
|
||||
end
|
||||
|
||||
return '' if text.blank?
|
||||
|
||||
html = Formatter.instance.send(:encode, text)
|
||||
html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
|
||||
|
||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||
end
|
||||
end
|
@ -9,9 +9,9 @@ module ApplicationHelper
|
||||
|
||||
RTL_LOCALES = %i(
|
||||
ar
|
||||
ckb
|
||||
fa
|
||||
he
|
||||
ku
|
||||
).freeze
|
||||
|
||||
def friendly_number_to_human(number, **options)
|
||||
@ -225,4 +225,19 @@ module ApplicationHelper
|
||||
content_tag(:script, json_escape(json).html_safe, id: 'initial-state', type: 'application/json')
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def grouped_scopes(scopes)
|
||||
scope_parser = ScopeParser.new
|
||||
scope_transformer = ScopeTransformer.new
|
||||
|
||||
scopes.each_with_object({}) do |str, h|
|
||||
scope = scope_transformer.apply(scope_parser.parse(str))
|
||||
|
||||
if h[scope.key]
|
||||
h[scope.key].merge!(scope)
|
||||
else
|
||||
h[scope.key] = scope
|
||||
end
|
||||
end.values
|
||||
end
|
||||
end
|
||||
|
@ -15,6 +15,14 @@ module JsonLdHelper
|
||||
value.is_a?(Array) ? value.first : value
|
||||
end
|
||||
|
||||
def uri_from_bearcap(str)
|
||||
if str&.start_with?('bear:')
|
||||
Addressable::URI.parse(str).query_values['u']
|
||||
else
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
# The url attribute can be a string, an array of strings, or an array of objects.
|
||||
# The objects could include a mimeType. Not-included mimeType means it's text/html.
|
||||
def url_to_href(value, preferred_type = nil)
|
||||
@ -54,7 +62,7 @@ module JsonLdHelper
|
||||
end
|
||||
|
||||
def unsupported_uri_scheme?(uri)
|
||||
!uri.start_with?('http://', 'https://')
|
||||
uri.nil? || !uri.start_with?('http://', 'https://')
|
||||
end
|
||||
|
||||
def invalid_origin?(url)
|
||||
|
@ -1,94 +1,257 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module LanguagesHelper
|
||||
HUMAN_LOCALES = {
|
||||
af: 'Afrikaans',
|
||||
ar: 'العربية',
|
||||
ast: 'Asturianu',
|
||||
bg: 'Български',
|
||||
bn: 'বাংলা',
|
||||
br: 'Breton',
|
||||
ca: 'Català',
|
||||
co: 'Corsu',
|
||||
cs: 'Čeština',
|
||||
cy: 'Cymraeg',
|
||||
da: 'Dansk',
|
||||
de: 'Deutsch',
|
||||
el: 'Ελληνικά',
|
||||
en: 'English',
|
||||
eo: 'Esperanto',
|
||||
ISO_639_1 = {
|
||||
aa: ['Afar', 'Afaraf'].freeze,
|
||||
ab: ['Abkhaz', 'аҧсуа бызшәа'].freeze,
|
||||
ae: ['Avestan', 'avesta'].freeze,
|
||||
af: ['Afrikaans', 'Afrikaans'].freeze,
|
||||
ak: ['Akan', 'Akan'].freeze,
|
||||
am: ['Amharic', 'አማርኛ'].freeze,
|
||||
an: ['Aragonese', 'aragonés'].freeze,
|
||||
ar: ['Arabic', 'اللغة العربية'].freeze,
|
||||
as: ['Assamese', 'অসমীয়া'].freeze,
|
||||
av: ['Avaric', 'авар мацӀ'].freeze,
|
||||
ay: ['Aymara', 'aymar aru'].freeze,
|
||||
az: ['Azerbaijani', 'azərbaycan dili'].freeze,
|
||||
ba: ['Bashkir', 'башҡорт теле'].freeze,
|
||||
be: ['Belarusian', 'беларуская мова'].freeze,
|
||||
bg: ['Bulgarian', 'български език'].freeze,
|
||||
bh: ['Bihari', 'भोजपुरी'].freeze,
|
||||
bi: ['Bislama', 'Bislama'].freeze,
|
||||
bm: ['Bambara', 'bamanankan'].freeze,
|
||||
bn: ['Bengali', 'বাংলা'].freeze,
|
||||
bo: ['Tibetan', 'བོད་ཡིག'].freeze,
|
||||
br: ['Breton', 'brezhoneg'].freeze,
|
||||
bs: ['Bosnian', 'bosanski jezik'].freeze,
|
||||
ca: ['Catalan', 'Català'].freeze,
|
||||
ce: ['Chechen', 'нохчийн мотт'].freeze,
|
||||
ch: ['Chamorro', 'Chamoru'].freeze,
|
||||
co: ['Corsican', 'corsu'].freeze,
|
||||
cr: ['Cree', 'ᓀᐦᐃᔭᐍᐏᐣ'].freeze,
|
||||
cs: ['Czech', 'čeština'].freeze,
|
||||
cu: ['Old Church Slavonic', 'ѩзыкъ словѣньскъ'].freeze,
|
||||
cv: ['Chuvash', 'чӑваш чӗлхи'].freeze,
|
||||
cy: ['Welsh', 'Cymraeg'].freeze,
|
||||
da: ['Danish', 'dansk'].freeze,
|
||||
de: ['German', 'Deutsch'].freeze,
|
||||
dv: ['Divehi', 'Dhivehi'].freeze,
|
||||
dz: ['Dzongkha', 'རྫོང་ཁ'].freeze,
|
||||
ee: ['Ewe', 'Eʋegbe'].freeze,
|
||||
el: ['Greek', 'Ελληνικά'].freeze,
|
||||
en: ['English', 'English'].freeze,
|
||||
eo: ['Esperanto', 'Esperanto'].freeze,
|
||||
es: ['Spanish', 'Español'].freeze,
|
||||
et: ['Estonian', 'eesti'].freeze,
|
||||
eu: ['Basque', 'euskara'].freeze,
|
||||
fa: ['Persian', 'فارسی'].freeze,
|
||||
ff: ['Fula', 'Fulfulde'].freeze,
|
||||
fi: ['Finnish', 'suomi'].freeze,
|
||||
fj: ['Fijian', 'Vakaviti'].freeze,
|
||||
fo: ['Faroese', 'føroyskt'].freeze,
|
||||
fr: ['French', 'Français'].freeze,
|
||||
fy: ['Western Frisian', 'Frysk'].freeze,
|
||||
ga: ['Irish', 'Gaeilge'].freeze,
|
||||
gd: ['Scottish Gaelic', 'Gàidhlig'].freeze,
|
||||
gl: ['Galician', 'galego'].freeze,
|
||||
gu: ['Gujarati', 'ગુજરાતી'].freeze,
|
||||
gv: ['Manx', 'Gaelg'].freeze,
|
||||
ha: ['Hausa', 'هَوُسَ'].freeze,
|
||||
he: ['Hebrew', 'עברית'].freeze,
|
||||
hi: ['Hindi', 'हिन्दी'].freeze,
|
||||
ho: ['Hiri Motu', 'Hiri Motu'].freeze,
|
||||
hr: ['Croatian', 'Hrvatski'].freeze,
|
||||
ht: ['Haitian', 'Kreyòl ayisyen'].freeze,
|
||||
hu: ['Hungarian', 'magyar'].freeze,
|
||||
hy: ['Armenian', 'Հայերեն'].freeze,
|
||||
hz: ['Herero', 'Otjiherero'].freeze,
|
||||
ia: ['Interlingua', 'Interlingua'].freeze,
|
||||
id: ['Indonesian', 'Bahasa Indonesia'].freeze,
|
||||
ie: ['Interlingue', 'Interlingue'].freeze,
|
||||
ig: ['Igbo', 'Asụsụ Igbo'].freeze,
|
||||
ii: ['Nuosu', 'ꆈꌠ꒿ Nuosuhxop'].freeze,
|
||||
ik: ['Inupiaq', 'Iñupiaq'].freeze,
|
||||
io: ['Ido', 'Ido'].freeze,
|
||||
is: ['Icelandic', 'Íslenska'].freeze,
|
||||
it: ['Italian', 'Italiano'].freeze,
|
||||
iu: ['Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'].freeze,
|
||||
ja: ['Japanese', '日本語'].freeze,
|
||||
jv: ['Javanese', 'basa Jawa'].freeze,
|
||||
ka: ['Georgian', 'ქართული'].freeze,
|
||||
kg: ['Kongo', 'Kikongo'].freeze,
|
||||
ki: ['Kikuyu', 'Gĩkũyũ'].freeze,
|
||||
kj: ['Kwanyama', 'Kuanyama'].freeze,
|
||||
kk: ['Kazakh', 'қазақ тілі'].freeze,
|
||||
kl: ['Kalaallisut', 'kalaallisut'].freeze,
|
||||
km: ['Khmer', 'ខេមរភាសា'].freeze,
|
||||
kn: ['Kannada', 'ಕನ್ನಡ'].freeze,
|
||||
ko: ['Korean', '한국어'].freeze,
|
||||
kr: ['Kanuri', 'Kanuri'].freeze,
|
||||
ks: ['Kashmiri', 'कश्मीरी'].freeze,
|
||||
ku: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
|
||||
kv: ['Komi', 'коми кыв'].freeze,
|
||||
kw: ['Cornish', 'Kernewek'].freeze,
|
||||
ky: ['Kyrgyz', 'Кыргызча'].freeze,
|
||||
la: ['Latin', 'latine'].freeze,
|
||||
lb: ['Luxembourgish', 'Lëtzebuergesch'].freeze,
|
||||
lg: ['Ganda', 'Luganda'].freeze,
|
||||
li: ['Limburgish', 'Limburgs'].freeze,
|
||||
ln: ['Lingala', 'Lingála'].freeze,
|
||||
lo: ['Lao', 'ພາສາ'].freeze,
|
||||
lt: ['Lithuanian', 'lietuvių kalba'].freeze,
|
||||
lu: ['Luba-Katanga', 'Tshiluba'].freeze,
|
||||
lv: ['Latvian', 'latviešu valoda'].freeze,
|
||||
mg: ['Malagasy', 'fiteny malagasy'].freeze,
|
||||
mh: ['Marshallese', 'Kajin M̧ajeļ'].freeze,
|
||||
mi: ['Māori', 'te reo Māori'].freeze,
|
||||
mk: ['Macedonian', 'македонски јазик'].freeze,
|
||||
ml: ['Malayalam', 'മലയാളം'].freeze,
|
||||
mn: ['Mongolian', 'Монгол хэл'].freeze,
|
||||
mr: ['Marathi', 'मराठी'].freeze,
|
||||
ms: ['Malay', 'Bahasa Melayu'].freeze,
|
||||
mt: ['Maltese', 'Malti'].freeze,
|
||||
my: ['Burmese', 'ဗမာစာ'].freeze,
|
||||
na: ['Nauru', 'Ekakairũ Naoero'].freeze,
|
||||
nb: ['Norwegian Bokmål', 'Norsk bokmål'].freeze,
|
||||
nd: ['Northern Ndebele', 'isiNdebele'].freeze,
|
||||
ne: ['Nepali', 'नेपाली'].freeze,
|
||||
ng: ['Ndonga', 'Owambo'].freeze,
|
||||
nl: ['Dutch', 'Nederlands'].freeze,
|
||||
nn: ['Norwegian Nynorsk', 'Norsk Nynorsk'].freeze,
|
||||
no: ['Norwegian', 'Norsk'].freeze,
|
||||
nr: ['Southern Ndebele', 'isiNdebele'].freeze,
|
||||
nv: ['Navajo', 'Diné bizaad'].freeze,
|
||||
ny: ['Chichewa', 'chiCheŵa'].freeze,
|
||||
oc: ['Occitan', 'occitan'].freeze,
|
||||
oj: ['Ojibwe', 'ᐊᓂᔑᓈᐯᒧᐎᓐ'].freeze,
|
||||
om: ['Oromo', 'Afaan Oromoo'].freeze,
|
||||
or: ['Oriya', 'ଓଡ଼ିଆ'].freeze,
|
||||
os: ['Ossetian', 'ирон æвзаг'].freeze,
|
||||
pa: ['Panjabi', 'ਪੰਜਾਬੀ'].freeze,
|
||||
pi: ['Pāli', 'पाऴि'].freeze,
|
||||
pl: ['Polish', 'Polski'].freeze,
|
||||
ps: ['Pashto', 'پښتو'].freeze,
|
||||
pt: ['Portuguese', 'Português'].freeze,
|
||||
qu: ['Quechua', 'Runa Simi'].freeze,
|
||||
rm: ['Romansh', 'rumantsch grischun'].freeze,
|
||||
rn: ['Kirundi', 'Ikirundi'].freeze,
|
||||
ro: ['Romanian', 'Română'].freeze,
|
||||
ru: ['Russian', 'Русский'].freeze,
|
||||
rw: ['Kinyarwanda', 'Ikinyarwanda'].freeze,
|
||||
sa: ['Sanskrit', 'संस्कृतम्'].freeze,
|
||||
sc: ['Sardinian', 'sardu'].freeze,
|
||||
sd: ['Sindhi', 'सिन्धी'].freeze,
|
||||
se: ['Northern Sami', 'Davvisámegiella'].freeze,
|
||||
sg: ['Sango', 'yângâ tî sängö'].freeze,
|
||||
si: ['Sinhala', 'සිංහල'].freeze,
|
||||
sk: ['Slovak', 'slovenčina'].freeze,
|
||||
sl: ['Slovenian', 'slovenščina'].freeze,
|
||||
sn: ['Shona', 'chiShona'].freeze,
|
||||
so: ['Somali', 'Soomaaliga'].freeze,
|
||||
sq: ['Albanian', 'Shqip'].freeze,
|
||||
sr: ['Serbian', 'српски језик'].freeze,
|
||||
ss: ['Swati', 'SiSwati'].freeze,
|
||||
st: ['Southern Sotho', 'Sesotho'].freeze,
|
||||
su: ['Sundanese', 'Basa Sunda'].freeze,
|
||||
sv: ['Swedish', 'Svenska'].freeze,
|
||||
sw: ['Swahili', 'Kiswahili'].freeze,
|
||||
ta: ['Tamil', 'தமிழ்'].freeze,
|
||||
te: ['Telugu', 'తెలుగు'].freeze,
|
||||
tg: ['Tajik', 'тоҷикӣ'].freeze,
|
||||
th: ['Thai', 'ไทย'].freeze,
|
||||
ti: ['Tigrinya', 'ትግርኛ'].freeze,
|
||||
tk: ['Turkmen', 'Türkmen'].freeze,
|
||||
tl: ['Tagalog', 'Wikang Tagalog'].freeze,
|
||||
tn: ['Tswana', 'Setswana'].freeze,
|
||||
to: ['Tonga', 'faka Tonga'].freeze,
|
||||
tr: ['Turkish', 'Türkçe'].freeze,
|
||||
ts: ['Tsonga', 'Xitsonga'].freeze,
|
||||
tt: ['Tatar', 'татар теле'].freeze,
|
||||
tw: ['Twi', 'Twi'].freeze,
|
||||
ty: ['Tahitian', 'Reo Tahiti'].freeze,
|
||||
ug: ['Uyghur', 'ئۇيغۇرچە'].freeze,
|
||||
uk: ['Ukrainian', 'Українська'].freeze,
|
||||
ur: ['Urdu', 'اردو'].freeze,
|
||||
uz: ['Uzbek', 'Ўзбек'].freeze,
|
||||
ve: ['Venda', 'Tshivenḓa'].freeze,
|
||||
vi: ['Vietnamese', 'Tiếng Việt'].freeze,
|
||||
vo: ['Volapük', 'Volapük'].freeze,
|
||||
wa: ['Walloon', 'walon'].freeze,
|
||||
wo: ['Wolof', 'Wollof'].freeze,
|
||||
xh: ['Xhosa', 'isiXhosa'].freeze,
|
||||
yi: ['Yiddish', 'ייִדיש'].freeze,
|
||||
yo: ['Yoruba', 'Yorùbá'].freeze,
|
||||
za: ['Zhuang', 'Saɯ cueŋƅ'].freeze,
|
||||
zh: ['Chinese', '中文'].freeze,
|
||||
zu: ['Zulu', 'isiZulu'].freeze,
|
||||
}.freeze
|
||||
|
||||
ISO_639_3 = {
|
||||
ast: ['Asturian', 'Asturianu'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_3).freeze
|
||||
|
||||
# For ISO-639-1 and ISO-639-3 language codes, we have their official
|
||||
# names, but for some translations, we need the names of the
|
||||
# regional variants specifically
|
||||
REGIONAL_LOCALE_NAMES = {
|
||||
'es-AR': 'Español (Argentina)',
|
||||
'es-MX': 'Español (México)',
|
||||
es: 'Español',
|
||||
et: 'Eesti',
|
||||
eu: 'Euskara',
|
||||
fa: 'فارسی',
|
||||
fi: 'Suomi',
|
||||
fr: 'Français',
|
||||
ga: 'Gaeilge',
|
||||
gd: 'Gàidhlig',
|
||||
gl: 'Galego',
|
||||
he: 'עברית',
|
||||
hi: 'हिन्दी',
|
||||
hr: 'Hrvatski',
|
||||
hu: 'Magyar',
|
||||
hy: 'Հայերեն',
|
||||
id: 'Bahasa Indonesia',
|
||||
io: 'Ido',
|
||||
is: 'Íslenska',
|
||||
it: 'Italiano',
|
||||
ja: '日本語',
|
||||
ka: 'ქართული',
|
||||
kab: 'Taqbaylit',
|
||||
kk: 'Қазақша',
|
||||
kmr: 'Kurmancî',
|
||||
kn: 'ಕನ್ನಡ',
|
||||
ko: '한국어',
|
||||
ku: 'سۆرانی',
|
||||
lt: 'Lietuvių',
|
||||
lv: 'Latviešu',
|
||||
mk: 'Македонски',
|
||||
ml: 'മലയാളം',
|
||||
mr: 'मराठी',
|
||||
ms: 'Bahasa Melayu',
|
||||
nl: 'Nederlands',
|
||||
nn: 'Nynorsk',
|
||||
no: 'Norsk',
|
||||
oc: 'Occitan',
|
||||
pl: 'Polski',
|
||||
'pt-BR': 'Português (Brasil)',
|
||||
'pt-PT': 'Português (Portugal)',
|
||||
pt: 'Português',
|
||||
ro: 'Română',
|
||||
ru: 'Русский',
|
||||
sa: 'संस्कृतम्',
|
||||
sc: 'Sardu',
|
||||
si: 'සිංහල',
|
||||
sk: 'Slovenčina',
|
||||
sl: 'Slovenščina',
|
||||
sq: 'Shqip',
|
||||
'sr-Latn': 'Srpski (latinica)',
|
||||
sr: 'Српски',
|
||||
sv: 'Svenska',
|
||||
ta: 'தமிழ்',
|
||||
te: 'తెలుగు',
|
||||
th: 'ไทย',
|
||||
tr: 'Türkçe',
|
||||
uk: 'Українська',
|
||||
ur: 'اُردُو',
|
||||
vi: 'Tiếng Việt',
|
||||
zgh: 'ⵜⴰⵎⴰⵣⵉⵖⵜ',
|
||||
'zh-CN': '简体中文',
|
||||
'zh-HK': '繁體中文(香港)',
|
||||
'zh-TW': '繁體中文(臺灣)',
|
||||
zh: '中文',
|
||||
}.freeze
|
||||
|
||||
def human_locale(locale)
|
||||
if locale == 'und'
|
||||
def native_locale_name(locale)
|
||||
if locale.blank? || locale == 'und'
|
||||
I18n.t('generic.none')
|
||||
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym])
|
||||
supported_locale[1]
|
||||
elsif (regional_locale = REGIONAL_LOCALE_NAMES[locale.to_sym])
|
||||
regional_locale
|
||||
else
|
||||
HUMAN_LOCALES[locale.to_sym] || locale
|
||||
locale
|
||||
end
|
||||
end
|
||||
|
||||
def standard_locale_name(locale)
|
||||
if locale.blank?
|
||||
I18n.t('generic.none')
|
||||
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym])
|
||||
supported_locale[0]
|
||||
else
|
||||
locale
|
||||
end
|
||||
end
|
||||
|
||||
def valid_locale_or_nil(str)
|
||||
return if str.blank?
|
||||
|
||||
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP
|
||||
|
||||
return unless valid_locale?(code)
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
def valid_locale_cascade(*arr)
|
||||
arr.each do |str|
|
||||
locale = valid_locale_or_nil(str)
|
||||
return locale if locale.present?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def valid_locale?(locale)
|
||||
locale.present? && SUPPORTED_LOCALES.key?(locale.to_sym)
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
module SettingsHelper
|
||||
def filterable_languages
|
||||
LanguageDetector.instance.language_names.select(&LanguagesHelper::HUMAN_LOCALES.method(:key?))
|
||||
LanguagesHelper::SUPPORTED_LOCALES.keys
|
||||
end
|
||||
|
||||
def hash_to_object(hash)
|
||||
|
@ -132,7 +132,7 @@ module StatusesHelper
|
||||
end
|
||||
|
||||
def render_video_component(status, **options)
|
||||
video = status.media_attachments.first
|
||||
video = status.ordered_media_attachments.first
|
||||
|
||||
meta = video.file.meta || {}
|
||||
|
||||
@ -150,12 +150,12 @@ module StatusesHelper
|
||||
}.merge(**options)
|
||||
|
||||
react_component :video, component_params do
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||
end
|
||||
end
|
||||
|
||||
def render_audio_component(status, **options)
|
||||
audio = status.media_attachments.first
|
||||
audio = status.ordered_media_attachments.first
|
||||
|
||||
meta = audio.file.meta || {}
|
||||
|
||||
@ -170,7 +170,7 @@ module StatusesHelper
|
||||
}.merge(**options)
|
||||
|
||||
react_component :audio, component_params do
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||
end
|
||||
end
|
||||
|
||||
@ -178,11 +178,11 @@ module StatusesHelper
|
||||
component_params = {
|
||||
sensitive: sensitized?(status, current_account),
|
||||
autoplay: prefers_autoplay?,
|
||||
media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json },
|
||||
media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json },
|
||||
}.merge(**options)
|
||||
|
||||
react_component :media_gallery, component_params do
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
|
||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,6 +75,8 @@ export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL';
|
||||
export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION';
|
||||
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
|
||||
|
||||
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||
|
||||
const messages = defineMessages({
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
@ -88,6 +90,15 @@ export const ensureComposeIsVisible = (getState, routerHistory) => {
|
||||
}
|
||||
};
|
||||
|
||||
export function setComposeToStatus(status, text, spoiler_text) {
|
||||
return{
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
text,
|
||||
spoiler_text,
|
||||
};
|
||||
};
|
||||
|
||||
export function changeCompose(text) {
|
||||
return {
|
||||
type: COMPOSE_CHANGE,
|
||||
@ -150,8 +161,9 @@ export function directCompose(account, routerHistory) {
|
||||
|
||||
export function submitCompose(routerHistory) {
|
||||
return function (dispatch, getState) {
|
||||
let status = getState().getIn(['compose', 'text'], '');
|
||||
let media = getState().getIn(['compose', 'media_attachments']);
|
||||
let status = getState().getIn(['compose', 'text'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
const statusId = getState().getIn(['compose', 'id'], null);
|
||||
const spoilers = getState().getIn(['compose', 'spoiler']) || getState().getIn(['local_settings', 'always_show_spoilers_field']);
|
||||
let spoilerText = spoilers ? getState().getIn(['compose', 'spoiler_text'], '') : '';
|
||||
|
||||
@ -159,20 +171,25 @@ export function submitCompose(routerHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
if (getState().getIn(['compose', 'advanced_options', 'do_not_federate'])) {
|
||||
status = status + ' 👁️';
|
||||
}
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
status,
|
||||
content_type: getState().getIn(['compose', 'content_type']),
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: media.map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0),
|
||||
spoiler_text: spoilerText,
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
poll: getState().getIn(['compose', 'poll'], null),
|
||||
}, {
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
|
||||
api(getState).request({
|
||||
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
|
||||
method: statusId === null ? 'post' : 'put',
|
||||
data: {
|
||||
status,
|
||||
content_type: getState().getIn(['compose', 'content_type']),
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: media.map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0),
|
||||
spoiler_text: spoilerText,
|
||||
visibility: getState().getIn(['compose', 'privacy']),
|
||||
poll: getState().getIn(['compose', 'poll'], null),
|
||||
},
|
||||
headers: {
|
||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||
},
|
||||
@ -202,14 +219,16 @@ export function submitCompose(routerHistory) {
|
||||
}
|
||||
};
|
||||
|
||||
insertIfOnline('home');
|
||||
if (statusId === null) {
|
||||
insertIfOnline('home');
|
||||
}
|
||||
|
||||
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
||||
if (statusId === null && response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
||||
insertIfOnline('community');
|
||||
if (!response.data.local_only) {
|
||||
insertIfOnline('public');
|
||||
}
|
||||
} else if (response.data.visibility === 'direct') {
|
||||
} else if (statusId === null && response.data.visibility === 'direct') {
|
||||
insertIfOnline('direct');
|
||||
}
|
||||
}).catch(function (error) {
|
||||
|
37
app/javascript/flavours/glitch/actions/history.js
Normal file
37
app/javascript/flavours/glitch/actions/history.js
Normal file
@ -0,0 +1,37 @@
|
||||
import api from 'flavours/glitch/util/api';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
export const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST';
|
||||
export const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS';
|
||||
export const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL';
|
||||
|
||||
export const fetchHistory = statusId => (dispatch, getState) => {
|
||||
const loading = getState().getIn(['history', statusId, 'loading']);
|
||||
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchHistoryRequest(statusId));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data.map(x => x.account)));
|
||||
dispatch(fetchHistorySuccess(statusId, data));
|
||||
}).catch(error => dispatch(fetchHistoryFail(error)));
|
||||
};
|
||||
|
||||
export const fetchHistoryRequest = statusId => ({
|
||||
type: HISTORY_FETCH_REQUEST,
|
||||
statusId,
|
||||
});
|
||||
|
||||
export const fetchHistorySuccess = (statusId, history) => ({
|
||||
type: HISTORY_FETCH_SUCCESS,
|
||||
statusId,
|
||||
history,
|
||||
});
|
||||
|
||||
export const fetchHistoryFail = error => ({
|
||||
type: HISTORY_FETCH_FAIL,
|
||||
error,
|
||||
});
|
@ -9,9 +9,10 @@ export function openModal(type, props) {
|
||||
};
|
||||
};
|
||||
|
||||
export function closeModal(type) {
|
||||
export function closeModal(type, options = { ignoreFocus: false }) {
|
||||
return {
|
||||
type: MODAL_CLOSE,
|
||||
modalType: type,
|
||||
ignoreFocus: options.ignoreFocus,
|
||||
};
|
||||
};
|
||||
|
@ -47,7 +47,6 @@ export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
|
||||
|
||||
export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
|
||||
|
||||
|
||||
export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
|
||||
|
||||
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
|
||||
@ -58,7 +57,7 @@ defineMessages({
|
||||
});
|
||||
|
||||
const fetchRelatedRelationships = (dispatch, notifications) => {
|
||||
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
|
||||
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
|
||||
|
||||
if (accountIds > 0) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
@ -136,7 +135,18 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification
|
||||
|
||||
|
||||
const excludeTypesFromFilter = filter => {
|
||||
const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']);
|
||||
const allTypes = ImmutableList([
|
||||
'follow',
|
||||
'follow_request',
|
||||
'favourite',
|
||||
'reblog',
|
||||
'mention',
|
||||
'poll',
|
||||
'status',
|
||||
'update',
|
||||
'admin.sign_up',
|
||||
]);
|
||||
|
||||
return allTypes.filterNot(item => item === filter).toJS();
|
||||
};
|
||||
|
||||
|
@ -1,89 +1,38 @@
|
||||
import api from 'flavours/glitch/util/api';
|
||||
import { openModal, closeModal } from './modal';
|
||||
|
||||
export const REPORT_INIT = 'REPORT_INIT';
|
||||
export const REPORT_CANCEL = 'REPORT_CANCEL';
|
||||
import { openModal } from './modal';
|
||||
|
||||
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
|
||||
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
|
||||
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
|
||||
|
||||
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
|
||||
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
|
||||
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
|
||||
export const initReport = (account, status) => dispatch =>
|
||||
dispatch(openModal('REPORT', {
|
||||
accountId: account.get('id'),
|
||||
statusId: status?.get('id'),
|
||||
}));
|
||||
|
||||
export function initReport(account, status) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: REPORT_INIT,
|
||||
account,
|
||||
status,
|
||||
});
|
||||
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
|
||||
dispatch(submitReportRequest());
|
||||
|
||||
dispatch(openModal('REPORT'));
|
||||
};
|
||||
api(getState).post('/api/v1/reports', params).then(response => {
|
||||
dispatch(submitReportSuccess(response.data));
|
||||
if (onSuccess) onSuccess();
|
||||
}).catch(error => {
|
||||
dispatch(submitReportFail(error));
|
||||
if (onFail) onFail();
|
||||
});
|
||||
};
|
||||
|
||||
export function cancelReport() {
|
||||
return {
|
||||
type: REPORT_CANCEL,
|
||||
};
|
||||
};
|
||||
export const submitReportRequest = () => ({
|
||||
type: REPORT_SUBMIT_REQUEST,
|
||||
});
|
||||
|
||||
export function toggleStatusReport(statusId, checked) {
|
||||
return {
|
||||
type: REPORT_STATUS_TOGGLE,
|
||||
statusId,
|
||||
checked,
|
||||
};
|
||||
};
|
||||
export const submitReportSuccess = report => ({
|
||||
type: REPORT_SUBMIT_SUCCESS,
|
||||
report,
|
||||
});
|
||||
|
||||
export function submitReport() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(submitReportRequest());
|
||||
|
||||
api(getState).post('/api/v1/reports', {
|
||||
account_id: getState().getIn(['reports', 'new', 'account_id']),
|
||||
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
|
||||
comment: getState().getIn(['reports', 'new', 'comment']),
|
||||
forward: getState().getIn(['reports', 'new', 'forward']),
|
||||
}).then(response => {
|
||||
dispatch(closeModal());
|
||||
dispatch(submitReportSuccess(response.data));
|
||||
}).catch(error => dispatch(submitReportFail(error)));
|
||||
};
|
||||
};
|
||||
|
||||
export function submitReportRequest() {
|
||||
return {
|
||||
type: REPORT_SUBMIT_REQUEST,
|
||||
};
|
||||
};
|
||||
|
||||
export function submitReportSuccess(report) {
|
||||
return {
|
||||
type: REPORT_SUBMIT_SUCCESS,
|
||||
report,
|
||||
};
|
||||
};
|
||||
|
||||
export function submitReportFail(error) {
|
||||
return {
|
||||
type: REPORT_SUBMIT_FAIL,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function changeReportComment(comment) {
|
||||
return {
|
||||
type: REPORT_COMMENT_CHANGE,
|
||||
comment,
|
||||
};
|
||||
};
|
||||
|
||||
export function changeReportForward(forward) {
|
||||
return {
|
||||
type: REPORT_FORWARD_CHANGE,
|
||||
forward,
|
||||
};
|
||||
};
|
||||
export const submitReportFail = error => ({
|
||||
type: REPORT_SUBMIT_FAIL,
|
||||
error,
|
||||
});
|
||||
|
27
app/javascript/flavours/glitch/actions/rules.js
Normal file
27
app/javascript/flavours/glitch/actions/rules.js
Normal file
@ -0,0 +1,27 @@
|
||||
import api from 'flavours/glitch/util/api';
|
||||
|
||||
export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
|
||||
export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
|
||||
export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL';
|
||||
|
||||
export const fetchRules = () => (dispatch, getState) => {
|
||||
dispatch(fetchRulesRequest());
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
|
||||
.catch(err => dispatch(fetchRulesFail(err)));
|
||||
};
|
||||
|
||||
const fetchRulesRequest = () => ({
|
||||
type: RULES_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchRulesSuccess = rules => ({
|
||||
type: RULES_FETCH_SUCCESS,
|
||||
rules,
|
||||
});
|
||||
|
||||
const fetchRulesFail = error => ({
|
||||
type: RULES_FETCH_FAIL,
|
||||
error,
|
||||
});
|
@ -2,7 +2,7 @@ import api from 'flavours/glitch/util/api';
|
||||
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import { ensureComposeIsVisible } from './compose';
|
||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||
|
||||
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
|
||||
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
|
||||
@ -26,6 +26,10 @@ export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
|
||||
|
||||
export const REDRAFT = 'REDRAFT';
|
||||
|
||||
export const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST';
|
||||
export const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS';
|
||||
export const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL';
|
||||
|
||||
export function fetchStatusRequest(id, skipLoading) {
|
||||
return {
|
||||
type: STATUS_FETCH_REQUEST,
|
||||
@ -81,6 +85,37 @@ export function redraft(status, raw_text, content_type) {
|
||||
};
|
||||
};
|
||||
|
||||
export const editStatus = (id, routerHistory) => (dispatch, getState) => {
|
||||
let status = getState().getIn(['statuses', id]);
|
||||
|
||||
if (status.get('poll')) {
|
||||
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
|
||||
}
|
||||
|
||||
dispatch(fetchStatusSourceRequest());
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/source`).then(response => {
|
||||
dispatch(fetchStatusSourceSuccess());
|
||||
ensureComposeIsVisible(getState, routerHistory);
|
||||
dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text));
|
||||
}).catch(error => {
|
||||
dispatch(fetchStatusSourceFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchStatusSourceRequest = () => ({
|
||||
type: STATUS_FETCH_SOURCE_REQUEST,
|
||||
});
|
||||
|
||||
export const fetchStatusSourceSuccess = () => ({
|
||||
type: STATUS_FETCH_SOURCE_SUCCESS,
|
||||
});
|
||||
|
||||
export const fetchStatusSourceFail = error => ({
|
||||
type: STATUS_FETCH_SOURCE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export function deleteStatus(id, routerHistory, withRedraft = false) {
|
||||
return (dispatch, getState) => {
|
||||
let status = getState().getIn(['statuses', id]);
|
||||
|
@ -68,12 +68,12 @@ export default class Counter extends React.PureComponent {
|
||||
);
|
||||
} else {
|
||||
const measure = data[0];
|
||||
const percentChange = percIncrease(measure.previous_total * 1, measure.total * 1);
|
||||
const percentChange = measure.previous_total && percIncrease(measure.previous_total * 1, measure.total * 1);
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<span className='sparkline__value__total'><FormattedNumber value={measure.total} /></span>
|
||||
<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>
|
||||
<span className='sparkline__value__total'>{measure.human_value || <FormattedNumber value={measure.total} />}</span>
|
||||
{measure.previous_total && (<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
9
app/javascript/flavours/glitch/components/check.js
Normal file
9
app/javascript/flavours/glitch/components/check.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const Check = () => (
|
||||
<svg width='14' height='11' viewBox='0 0 14 11'>
|
||||
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Check;
|
@ -6,6 +6,8 @@ import Overlay from 'react-overlays/lib/Overlay';
|
||||
import Motion from 'flavours/glitch/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import classNames from 'classnames';
|
||||
import { CircularProgress } from 'mastodon/components/loading_indicator';
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
let id = 0;
|
||||
@ -17,13 +19,18 @@ class DropdownMenu extends React.PureComponent {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
|
||||
loading: PropTypes.bool,
|
||||
scrollable: PropTypes.bool,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
arrowOffsetLeft: PropTypes.string,
|
||||
arrowOffsetTop: PropTypes.string,
|
||||
openedViaKeyboard: PropTypes.bool,
|
||||
renderItem: PropTypes.func,
|
||||
renderHeader: PropTypes.func,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -45,9 +52,11 @@ class DropdownMenu extends React.PureComponent {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('keydown', this.handleKeyDown, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
|
||||
if (this.focusedItem && this.props.openedViaKeyboard) {
|
||||
this.focusedItem.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
this.setState({ mounted: true });
|
||||
}
|
||||
|
||||
@ -66,7 +75,7 @@ class DropdownMenu extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
const items = Array.from(this.node.getElementsByTagName('a'));
|
||||
const items = Array.from(this.node.querySelectorAll('a, button'));
|
||||
const index = items.indexOf(document.activeElement);
|
||||
let element = null;
|
||||
|
||||
@ -109,30 +118,20 @@ class DropdownMenu extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const { action, to } = this.props.items[i];
|
||||
|
||||
this.props.onClose();
|
||||
|
||||
if (typeof action === 'function') {
|
||||
e.preventDefault();
|
||||
action();
|
||||
} else if (to) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(to);
|
||||
}
|
||||
const { onItemClick } = this.props;
|
||||
onItemClick(e);
|
||||
}
|
||||
|
||||
renderItem (option, i) {
|
||||
renderItem = (option, i) => {
|
||||
if (option === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { text, href = '#' } = option;
|
||||
const { text, href = '#', target = '_blank', method } = option;
|
||||
|
||||
return (
|
||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
@ -140,21 +139,37 @@ class DropdownMenu extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
|
||||
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop, scrollable, renderHeader, loading } = this.props;
|
||||
const { mounted } = this.state;
|
||||
|
||||
let renderItem = this.props.renderItem || this.renderItem;
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
// It should not be transformed when mounting because the resulting
|
||||
// size will be used to determine the coordinate of the menu by
|
||||
// react-overlays
|
||||
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
|
||||
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
|
||||
|
||||
<ul>
|
||||
{items.map((option, i) => this.renderItem(option, i))}
|
||||
</ul>
|
||||
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })}>
|
||||
{loading && (
|
||||
<CircularProgress size={30} strokeWidth={3.5} />
|
||||
)}
|
||||
|
||||
{!loading && renderHeader && (
|
||||
<div className='dropdown-menu__container__header'>
|
||||
{renderHeader(items)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
|
||||
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
@ -170,11 +185,14 @@ export default class Dropdown extends React.PureComponent {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
children: PropTypes.node,
|
||||
icon: PropTypes.string,
|
||||
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
|
||||
loading: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
title: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
scrollable: PropTypes.bool,
|
||||
status: ImmutablePropTypes.map,
|
||||
isUserTouching: PropTypes.func,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
@ -182,6 +200,9 @@ export default class Dropdown extends React.PureComponent {
|
||||
dropdownPlacement: PropTypes.string,
|
||||
openDropdownId: PropTypes.number,
|
||||
openedViaKeyboard: PropTypes.bool,
|
||||
renderItem: PropTypes.func,
|
||||
renderHeader: PropTypes.func,
|
||||
onItemClick: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -236,17 +257,22 @@ export default class Dropdown extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleItemClick = (i, e) => {
|
||||
const { action, to } = this.props.items[i];
|
||||
handleItemClick = e => {
|
||||
const { onItemClick } = this.props;
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const item = this.props.items[i];
|
||||
|
||||
this.handleClose();
|
||||
|
||||
if (typeof action === 'function') {
|
||||
if (typeof onItemClick === 'function') {
|
||||
e.preventDefault();
|
||||
action();
|
||||
} else if (to) {
|
||||
onItemClick(item, i);
|
||||
} else if (item && typeof item.action === 'function') {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(to);
|
||||
item.action();
|
||||
} else if (item && item.to) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(item.to);
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,29 +290,67 @@ export default class Dropdown extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.handleClose();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard } = this.props;
|
||||
const {
|
||||
icon,
|
||||
items,
|
||||
size,
|
||||
title,
|
||||
disabled,
|
||||
loading,
|
||||
scrollable,
|
||||
dropdownPlacement,
|
||||
openDropdownId,
|
||||
openedViaKeyboard,
|
||||
children,
|
||||
renderItem,
|
||||
renderHeader,
|
||||
} = this.props;
|
||||
|
||||
const open = this.state.id === openDropdownId;
|
||||
|
||||
const button = children ? React.cloneElement(React.Children.only(children), {
|
||||
ref: this.setTargetRef,
|
||||
onClick: this.handleClick,
|
||||
onMouseDown: this.handleMouseDown,
|
||||
onKeyDown: this.handleButtonKeyDown,
|
||||
onKeyPress: this.handleKeyPress,
|
||||
}) : (
|
||||
<IconButton
|
||||
icon={icon}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
ref={this.setTargetRef}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleButtonKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
icon={icon}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
ref={this.setTargetRef}
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleButtonKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
/>
|
||||
<React.Fragment>
|
||||
{button}
|
||||
|
||||
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
|
||||
<DropdownMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} />
|
||||
<DropdownMenu
|
||||
items={items}
|
||||
loading={loading}
|
||||
scrollable={scrollable}
|
||||
onClose={this.handleClose}
|
||||
openedViaKeyboard={openedViaKeyboard}
|
||||
renderItem={renderItem}
|
||||
renderHeader={renderHeader}
|
||||
onItemClick={this.handleItemClick}
|
||||
/>
|
||||
</Overlay>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user