Merge tag 'v2.7.0rc1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira 2019-01-09 10:47:10 +01:00
commit 4207973809
585 changed files with 16065 additions and 8146 deletions

View File

@ -3,7 +3,7 @@ version: 2
aliases: aliases:
- &defaults - &defaults
docker: docker:
- image: circleci/ruby:2.5.1-stretch-node - image: circleci/ruby:2.6.0-stretch-node
environment: &ruby_environment environment: &ruby_environment
BUNDLE_APP_CONFIG: ./.bundle/ BUNDLE_APP_CONFIG: ./.bundle/
DB_HOST: localhost DB_HOST: localhost
@ -98,21 +98,21 @@ jobs:
<<: *defaults <<: *defaults
<<: *install_steps <<: *install_steps
install-ruby2.6:
<<: *defaults
<<: *install_ruby_dependencies
install-ruby2.5: install-ruby2.5:
<<: *defaults <<: *defaults
docker:
- image: circleci/ruby:2.5.3-stretch-node
environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
install-ruby2.4: install-ruby2.4:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.4.4-stretch-node - image: circleci/ruby:2.4.5-stretch-node
environment: *ruby_environment
<<: *install_ruby_dependencies
install-ruby2.3:
<<: *defaults
docker:
- image: circleci/ruby:2.3.7-stretch-node
environment: *ruby_environment environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
@ -128,43 +128,43 @@ jobs:
- ./mastodon/public/assets - ./mastodon/public/assets
- ./mastodon/public/packs-test/ - ./mastodon/public/packs-test/
test-ruby2.6:
<<: *defaults
docker:
- image: circleci/ruby:2.6.0-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:5.0.3-alpine3.8
<<: *test_steps
test-ruby2.5: test-ruby2.5:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.5.1-stretch-node - image: circleci/ruby:2.5.3-stretch-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.3-alpine - image: circleci/postgres:10.6-alpine
environment: environment:
POSTGRES_USER: root POSTGRES_USER: root
- image: circleci/redis:4.0.9-alpine - image: circleci/redis:4.0.12-alpine
<<: *test_steps <<: *test_steps
test-ruby2.4: test-ruby2.4:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/ruby:2.4.4-stretch-node - image: circleci/ruby:2.4.5-stretch-node
environment: *ruby_environment environment: *ruby_environment
- image: circleci/postgres:10.3-alpine - image: circleci/postgres:10.6-alpine
environment: environment:
POSTGRES_USER: root POSTGRES_USER: root
- image: circleci/redis:4.0.9-alpine - image: circleci/redis:4.0.12-alpine
<<: *test_steps
test-ruby2.3:
<<: *defaults
docker:
- image: circleci/ruby:2.3.7-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.3-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:4.0.9-alpine
<<: *test_steps <<: *test_steps
test-webui: test-webui:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/node:8.11.1-stretch - image: circleci/node:8.15.0-stretch
steps: steps:
- *attach_workspace - *attach_workspace
- run: ./bin/retry yarn test:jest - run: ./bin/retry yarn test:jest
@ -183,20 +183,24 @@ workflows:
build-and-test: build-and-test:
jobs: jobs:
- install - install
- install-ruby2.6:
requires:
- install
- install-ruby2.5: - install-ruby2.5:
requires: requires:
- install - install
- install-ruby2.6
- install-ruby2.4: - install-ruby2.4:
requires: requires:
- install - install
- install-ruby2.5 - install-ruby2.6
- install-ruby2.3:
requires:
- install
- install-ruby2.5
- build: - build:
requires: requires:
- install-ruby2.5 - install-ruby2.6
- test-ruby2.6:
requires:
- install-ruby2.6
- build
- test-ruby2.5: - test-ruby2.5:
requires: requires:
- install-ruby2.5 - install-ruby2.5
@ -205,13 +209,9 @@ workflows:
requires: requires:
- install-ruby2.4 - install-ruby2.4
- build - build
- test-ruby2.3:
requires:
- install-ruby2.3
- build
- test-webui: - test-webui:
requires: requires:
- install - install
- check-i18n: - check-i18n:
requires: requires:
- install-ruby2.5 - install-ruby2.6

View File

@ -27,7 +27,7 @@ plugins:
enabled: true enabled: true
eslint: eslint:
enabled: true enabled: true
channel: eslint-4 channel: eslint-5
rubocop: rubocop:
enabled: true enabled: true
channel: rubocop-0-54 channel: rubocop-0-54

View File

@ -1,30 +1,13 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files. /build/**
# /coverage/**
# If you find yourself ignoring temporary files generated by your text editor /db/**
# or operating system, you probably want to add a global ignore instead: /lib/**
# git config --global core.excludesfile '~/.gitignore_global' /log/**
/node_modules/**
# Ignore bundler config. /nonobox/**
/.bundle /public/**
!/public/embed.js
# Ignore the default SQLite database. /spec/**
/db/*.sqlite3 /tmp/**
/db/*.sqlite3-journal /vendor/**
!.eslintrc.js
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
coverage
public/system
public/assets
.env
.env.production
node_modules/
neo4j/
# Ignore Vagrant files
.vagrant/
# Ignore Capistrano customizations
config/deploy/*

199
.eslintrc.js Normal file
View File

@ -0,0 +1,199 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
jest: true,
},
globals: {
ATTACHMENT_HOST: false,
},
parser: 'babel-eslint',
plugins: [
'react',
'jsx-a11y',
'import',
'promise',
],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
ecmaVersion: 2018,
},
settings: {
react: {
version: 'detect',
},
'import/extensions': [
'.js',
],
'import/ignore': [
'node_modules',
'\\.(css|scss|json)$',
],
},
rules: {
'brace-style': 'warn',
'comma-dangle': ['error', 'always-multiline'],
'comma-spacing': [
'warn',
{
before: false,
after: true,
},
],
'comma-style': ['warn', 'last'],
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: 'error',
indent: ['warn', 2],
'jsx-quotes': ['error', 'prefer-single'],
'no-catch-shadow': 'error',
'no-cond-assign': 'error',
'no-console': [
'warn',
{
allow: [
'error',
'warn',
],
},
],
'no-fallthrough': 'error',
'no-irregular-whitespace': 'error',
'no-mixed-spaces-and-tabs': 'warn',
'no-nested-ternary': 'warn',
'no-trailing-spaces': 'warn',
'no-undef': 'error',
'no-unreachable': 'error',
'no-unused-expressions': 'error',
'no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
ignoreRestSiblings: true,
},
],
'object-curly-spacing': ['error', 'always'],
'padded-blocks': [
'error',
{
classes: 'always',
},
],
quotes: ['error', 'single'],
semi: 'error',
strict: 'off',
'valid-typeof': 'error',
'react/jsx-boolean-value': 'error',
'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
'react/jsx-curly-spacing': 'error',
'react/jsx-equals-spacing': 'error',
'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
'react/jsx-indent': ['error', 2],
'react/jsx-no-bind': 'error',
'react/jsx-no-duplicate-props': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-tag-spacing': 'error',
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'react/jsx-wrap-multilines': 'error',
'react/no-multi-comp': 'off',
'react/no-string-refs': 'error',
'react/prop-types': 'error',
'react/self-closing-comp': 'error',
'jsx-a11y/accessible-emoji': 'warn',
'jsx-a11y/alt-text': 'warn',
'jsx-a11y/anchor-has-content': 'warn',
'jsx-a11y/anchor-is-valid': [
'warn',
{
components: [
'Link',
'NavLink',
],
specialLink: [
'to',
],
aspect: [
'noHref',
'invalidHref',
'preferButton',
],
},
],
'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
'jsx-a11y/aria-props': 'warn',
'jsx-a11y/aria-proptypes': 'warn',
'jsx-a11y/aria-role': 'warn',
'jsx-a11y/aria-unsupported-elements': 'warn',
'jsx-a11y/heading-has-content': 'warn',
'jsx-a11y/html-has-lang': 'warn',
'jsx-a11y/iframe-has-title': 'warn',
'jsx-a11y/img-redundant-alt': 'warn',
'jsx-a11y/interactive-supports-focus': 'warn',
'jsx-a11y/label-has-for': 'off',
'jsx-a11y/mouse-events-have-key-events': 'warn',
'jsx-a11y/no-access-key': 'warn',
'jsx-a11y/no-distracting-elements': 'warn',
'jsx-a11y/no-noninteractive-element-interactions': [
'warn',
{
handlers: [
'onClick',
],
},
],
'jsx-a11y/no-onchange': 'warn',
'jsx-a11y/no-redundant-roles': 'warn',
'jsx-a11y/no-static-element-interactions': [
'warn',
{
handlers: [
'onClick',
],
},
],
'jsx-a11y/role-has-required-aria-props': 'warn',
'jsx-a11y/role-supports-aria-props': 'off',
'jsx-a11y/scope': 'warn',
'jsx-a11y/tabindex-no-positive': 'warn',
'import/extensions': [
'error',
'always',
{
js: 'never',
},
],
'import/newline-after-import': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'config/webpack/**',
'app/javascript/mastodon/test_setup.js',
'app/javascript/**/__tests__/**',
],
},
],
'import/no-unresolved': 'error',
'import/no-webpack-loader-syntax': 'error',
'promise/catch-or-return': 'error',
},
};

View File

@ -1,170 +0,0 @@
---
root: true
env:
browser: true
node: true
es6: true
jest: true
globals:
ATTACHMENT_HOST: false
parser: babel-eslint
plugins:
- react
- jsx-a11y
- import
- promise
parserOptions:
sourceType: module
ecmaFeatures:
experimentalObjectRestSpread: true
jsx: true
ecmaVersion: 2018
settings:
import/extensions:
- .js
import/ignore:
- node_modules
- \\.(css|scss|json)$
rules:
brace-style: warn
comma-dangle:
- error
- always-multiline
comma-spacing:
- warn
- before: false
after: true
comma-style:
- warn
- last
consistent-return: error
dot-notation: error
eqeqeq: error
indent:
- warn
- 2
jsx-quotes:
- error
- prefer-single
no-catch-shadow: error
no-cond-assign: error
no-console:
- warn
- allow:
- error
- warn
no-fallthrough: error
no-irregular-whitespace: error
no-mixed-spaces-and-tabs: warn
no-nested-ternary: warn
no-trailing-spaces: warn
no-undef: error
no-unreachable: error
no-unused-expressions: error
no-unused-vars:
- error
- vars: all
args: after-used
ignoreRestSiblings: true
object-curly-spacing:
- error
- always
padded-blocks:
- error
- classes: always
quotes:
- error
- single
semi: error
strict: off
valid-typeof: error
react/jsx-boolean-value: error
react/jsx-closing-bracket-location:
- error
- line-aligned
react/jsx-curly-spacing: error
react/jsx-equals-spacing: error
react/jsx-first-prop-new-line:
- error
- multiline-multiprop
react/jsx-indent:
- error
- 2
react/jsx-no-bind: error
react/jsx-no-duplicate-props: error
react/jsx-no-undef: error
react/jsx-tag-spacing: error
react/jsx-uses-react: error
react/jsx-uses-vars: error
react/jsx-wrap-multilines: error
react/no-multi-comp: off
react/no-string-refs: error
react/prop-types: error
react/self-closing-comp: error
jsx-a11y/accessible-emoji: warn
jsx-a11y/alt-text: warn
jsx-a11y/anchor-has-content: warn
jsx-a11y/anchor-is-valid:
- warn
- components:
- Link
- NavLink
specialLink:
- to
aspect:
- noHref
- invalidHref
- preferButton
jsx-a11y/aria-activedescendant-has-tabindex: warn
jsx-a11y/aria-props: warn
jsx-a11y/aria-proptypes: warn
jsx-a11y/aria-role: warn
jsx-a11y/aria-unsupported-elements: warn
jsx-a11y/heading-has-content: warn
jsx-a11y/html-has-lang: warn
jsx-a11y/iframe-has-title: warn
jsx-a11y/img-redundant-alt: warn
jsx-a11y/interactive-supports-focus: warn
jsx-a11y/label-has-for: off
jsx-a11y/mouse-events-have-key-events: warn
jsx-a11y/no-access-key: warn
jsx-a11y/no-distracting-elements: warn
jsx-a11y/no-noninteractive-element-interactions:
- warn
- handlers:
- onClick
jsx-a11y/no-onchange: warn
jsx-a11y/no-redundant-roles: warn
jsx-a11y/no-static-element-interactions:
- warn
- handlers:
- onClick
jsx-a11y/role-has-required-aria-props: warn
jsx-a11y/role-supports-aria-props: off
jsx-a11y/scope: warn
jsx-a11y/tabindex-no-positive: warn
import/extensions:
- error
- always
- js: never
import/newline-after-import: error
import/no-extraneous-dependencies:
- error
- devDependencies:
- "config/webpack/**"
- "app/javascript/mastodon/test_setup.js"
- "app/javascript/**/__tests__/**"
import/no-unresolved: error
import/no-webpack-loader-syntax: error
promise/catch-or-return: error

View File

@ -1,9 +0,0 @@
plugins:
postcss-smart-import: {}
precss: {}
autoprefixer:
browsers:
- last 2 versions
- IE >= 11
- iOS >= 9
postcss-object-fit-images: {}

View File

@ -1 +1 @@
2.5.3 2.6.0

View File

@ -4,38 +4,38 @@ and provided thanks to the work of the following contributors:
* [Gargron](https://github.com/Gargron) * [Gargron](https://github.com/Gargron)
* [ykzts](https://github.com/ykzts) * [ykzts](https://github.com/ykzts)
* [akihikodaki](https://github.com/akihikodaki) * [akihikodaki](https://github.com/akihikodaki)
* [mjankowski](https://github.com/mjankowski)
* [ThibG](https://github.com/ThibG) * [ThibG](https://github.com/ThibG)
* [mjankowski](https://github.com/mjankowski)
* [unarist](https://github.com/unarist) * [unarist](https://github.com/unarist)
* [m4sk1n](https://github.com/m4sk1n) * [m4sk1n](https://github.com/m4sk1n)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [yiskah](https://github.com/yiskah) * [yiskah](https://github.com/yiskah)
* [nolanlawson](https://github.com/nolanlawson) * [nolanlawson](https://github.com/nolanlawson)
* [sorin-davidoi](https://github.com/sorin-davidoi) * [sorin-davidoi](https://github.com/sorin-davidoi)
* [ysksn](https://github.com/ysksn)
* [abcang](https://github.com/abcang) * [abcang](https://github.com/abcang)
* [lynlynlynx](https://github.com/lynlynlynx) * [lynlynlynx](https://github.com/lynlynlynx)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [alpaca-tc](https://github.com/alpaca-tc) * [alpaca-tc](https://github.com/alpaca-tc)
* [mayaeh](https://github.com/mayaeh)
* [renatolond](https://github.com/renatolond)
* [nclm](https://github.com/nclm) * [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble) * [ineffyble](https://github.com/ineffyble)
* [renatolond](https://github.com/renatolond)
* [jeroenpraat](https://github.com/jeroenpraat) * [jeroenpraat](https://github.com/jeroenpraat)
* [mayaeh](https://github.com/mayaeh)
* [blackle](https://github.com/blackle) * [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in) * [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP) * [JantsoP](https://github.com/JantsoP)
* [mabkenar](https://github.com/mabkenar)
* [nullkal](https://github.com/nullkal) * [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala) * [yookoala](https://github.com/yookoala)
* [mabkenar](https://github.com/mabkenar) * [Kjwon15](https://github.com/Kjwon15)
* [ysksn](https://github.com/ysksn)
* [shuheiktgw](https://github.com/shuheiktgw) * [shuheiktgw](https://github.com/shuheiktgw)
* [ashfurrow](https://github.com/ashfurrow) * [ashfurrow](https://github.com/ashfurrow)
* [Kjwon15](https://github.com/Kjwon15) * [Quenty31](https://github.com/Quenty31)
* [zunda](https://github.com/zunda) * [zunda](https://github.com/zunda)
* [eramdam](https://github.com/eramdam) * [eramdam](https://github.com/eramdam)
* [masarakki](https://github.com/masarakki)
* [takayamaki](https://github.com/takayamaki) * [takayamaki](https://github.com/takayamaki)
* [masarakki](https://github.com/masarakki)
* [ticky](https://github.com/ticky) * [ticky](https://github.com/ticky)
* [Quenty31](https://github.com/Quenty31)
* [danhunsaker](https://github.com/danhunsaker) * [danhunsaker](https://github.com/danhunsaker)
* [ThisIsMissEm](https://github.com/ThisIsMissEm) * [ThisIsMissEm](https://github.com/ThisIsMissEm)
* [hcmiya](https://github.com/hcmiya) * [hcmiya](https://github.com/hcmiya)
@ -88,16 +88,19 @@ and provided thanks to the work of the following contributors:
* [mistydemeo](https://github.com/mistydemeo) * [mistydemeo](https://github.com/mistydemeo)
* [dunn](https://github.com/dunn) * [dunn](https://github.com/dunn)
* [xqus](https://github.com/xqus) * [xqus](https://github.com/xqus)
* [hugogameiro](https://github.com/hugogameiro)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp) * [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [fakenine](https://github.com/fakenine) * [fakenine](https://github.com/fakenine)
* [tsuwatch](https://github.com/tsuwatch) * [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck) * [victorhck](https://github.com/victorhck)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [kedamaDQ](https://github.com/kedamaDQ)
* [puckipedia](https://github.com/puckipedia) * [puckipedia](https://github.com/puckipedia)
* [fvh-P](https://github.com/fvh-P) * [fvh-P](https://github.com/fvh-P)
* [contraexemplo](https://github.com/contraexemplo) * [contraexemplo](https://github.com/contraexemplo)
* [hugogameiro](https://github.com/hugogameiro)
* [kazu9su](https://github.com/kazu9su) * [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic) * [Komic](https://github.com/Komic)
* [lmorchard](https://github.com/lmorchard)
* [diomed](https://github.com/diomed) * [diomed](https://github.com/diomed)
* [ariasuni](https://github.com/ariasuni) * [ariasuni](https://github.com/ariasuni)
* [Neetshin](mailto:neetshin@neetsh.in) * [Neetshin](mailto:neetshin@neetsh.in)
@ -105,7 +108,6 @@ and provided thanks to the work of the following contributors:
* [ProgVal](https://github.com/ProgVal) * [ProgVal](https://github.com/ProgVal)
* [valentin2105](https://github.com/valentin2105) * [valentin2105](https://github.com/valentin2105)
* [yuntan](https://github.com/yuntan) * [yuntan](https://github.com/yuntan)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [goofy-bz](mailto:goofy@babelzilla.org) * [goofy-bz](mailto:goofy@babelzilla.org)
* [kadiix](https://github.com/kadiix) * [kadiix](https://github.com/kadiix)
* [kodacs](https://github.com/kodacs) * [kodacs](https://github.com/kodacs)
@ -119,35 +121,37 @@ and provided thanks to the work of the following contributors:
* [northerner](https://github.com/northerner) * [northerner](https://github.com/northerner)
* [fhemberger](https://github.com/fhemberger) * [fhemberger](https://github.com/fhemberger)
* [greysteil](https://github.com/greysteil) * [greysteil](https://github.com/greysteil)
* [hnrysmth](https://github.com/hnrysmth) * [hensmith](https://github.com/hensmith)
* [hinaloe](https://github.com/hinaloe)
* [d6rkaiz](https://github.com/d6rkaiz) * [d6rkaiz](https://github.com/d6rkaiz)
* [Reverite](https://github.com/Reverite)
* [JMendyk](https://github.com/JMendyk) * [JMendyk](https://github.com/JMendyk)
* [JohnD28](https://github.com/JohnD28) * [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz) * [znz](https://github.com/znz)
* [Naouak](https://github.com/Naouak) * [Naouak](https://github.com/Naouak)
* [pawelngei](https://github.com/pawelngei)
* [reneklacan](https://github.com/reneklacan) * [reneklacan](https://github.com/reneklacan)
* [ekiru](https://github.com/ekiru) * [ekiru](https://github.com/ekiru)
* [tcitworld](https://github.com/tcitworld) * [tcitworld](https://github.com/tcitworld)
* [geta6](https://github.com/geta6) * [geta6](https://github.com/geta6)
* [happycoloredbanana](https://github.com/happycoloredbanana) * [happycoloredbanana](https://github.com/happycoloredbanana)
* [kedamaDQ](https://github.com/kedamaDQ)
* [leopku](https://github.com/leopku) * [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix) * [SansPseudoFix](https://github.com/SansPseudoFix)
* [tomfhowe](https://github.com/tomfhowe) * [tomfhowe](https://github.com/tomfhowe)
* [noraworld](https://github.com/noraworld) * [noraworld](https://github.com/noraworld)
* [theboss](https://github.com/theboss) * [theboss](https://github.com/theboss)
* [178inaba](https://github.com/178inaba) * [178inaba](https://github.com/178inaba)
* [Aditoo17](https://github.com/Aditoo17)
* [alyssais](https://github.com/alyssais) * [alyssais](https://github.com/alyssais)
* [kodnaplakal](https://github.com/kodnaplakal) * [kodnaplakal](https://github.com/kodnaplakal)
* [stalker314314](https://github.com/stalker314314) * [stalker314314](https://github.com/stalker314314)
* [huertanix](https://github.com/huertanix) * [huertanix](https://github.com/huertanix)
* [genesixx](https://github.com/genesixx) * [genesixx](https://github.com/genesixx)
* [halkeye](https://github.com/halkeye) * [halkeye](https://github.com/halkeye)
* [hinaloe](https://github.com/hinaloe)
* [treby](https://github.com/treby) * [treby](https://github.com/treby)
* [Reverite](https://github.com/Reverite)
* [jpdevries](https://github.com/jpdevries) * [jpdevries](https://github.com/jpdevries)
* [H-C-F](https://github.com/H-C-F) * [gdpelican](https://github.com/gdpelican)
* [kmichl](https://github.com/kmichl)
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name) * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
* [saper](https://github.com/saper) * [saper](https://github.com/saper)
* [nevillepark](https://github.com/nevillepark) * [nevillepark](https://github.com/nevillepark)
@ -157,6 +161,7 @@ and provided thanks to the work of the following contributors:
* [Ram Lmn](mailto:ramlmn@users.noreply.github.com) * [Ram Lmn](mailto:ramlmn@users.noreply.github.com)
* [harukasan](https://github.com/harukasan) * [harukasan](https://github.com/harukasan)
* [stamak](https://github.com/stamak) * [stamak](https://github.com/stamak)
* [noellabo](https://github.com/noellabo)
* [Technowix](mailto:technowix@users.noreply.github.com) * [Technowix](mailto:technowix@users.noreply.github.com)
* [Eychics](https://github.com/Eychics) * [Eychics](https://github.com/Eychics)
* [Thor Harald Johansen](mailto:thj@thj.no) * [Thor Harald Johansen](mailto:thj@thj.no)
@ -165,22 +170,27 @@ and provided thanks to the work of the following contributors:
* [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl) * [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl)
* [R0ckweb](https://github.com/R0ckweb) * [R0ckweb](https://github.com/R0ckweb)
* [caasi](https://github.com/caasi) * [caasi](https://github.com/caasi)
* [chr-1x](https://github.com/chr-1x)
* [esetomo](https://github.com/esetomo) * [esetomo](https://github.com/esetomo)
* [foxiehkins](https://github.com/foxiehkins) * [foxiehkins](https://github.com/foxiehkins)
* [hoodie](mailto:hoodiekitten@outlook.com) * [hoodie](mailto:hoodiekitten@outlook.com)
* [luzi82](https://github.com/luzi82) * [luzi82](https://github.com/luzi82)
* [duxovni](https://github.com/duxovni) * [duxovni](https://github.com/duxovni)
* [trwnh](https://github.com/trwnh)
* [unsmell](https://github.com/unsmell) * [unsmell](https://github.com/unsmell)
* [valerauko](https://github.com/valerauko)
* [chriswmartin](https://github.com/chriswmartin) * [chriswmartin](https://github.com/chriswmartin)
* [vahnj](https://github.com/vahnj) * [vahnj](https://github.com/vahnj)
* [ikuradon](https://github.com/ikuradon) * [ikuradon](https://github.com/ikuradon)
* [AndreLewin](https://github.com/AndreLewin) * [AndreLewin](https://github.com/AndreLewin)
* [rinsuki](https://github.com/rinsuki) * [rinsuki](https://github.com/rinsuki)
* [0xflotus](https://github.com/0xflotus)
* [redtachyons](https://github.com/redtachyons) * [redtachyons](https://github.com/redtachyons)
* [thurloat](https://github.com/thurloat) * [thurloat](https://github.com/thurloat)
* [aaribaud](https://github.com/aaribaud) * [aaribaud](https://github.com/aaribaud)
* [Andrew](mailto:andrewlchronister@gmail.com) * [Andrew](mailto:andrewlchronister@gmail.com)
* [estuans](https://github.com/estuans) * [estuans](https://github.com/estuans)
* [BenLubar](https://github.com/BenLubar)
* [dissolve](https://github.com/dissolve) * [dissolve](https://github.com/dissolve)
* [PurpleBooth](https://github.com/PurpleBooth) * [PurpleBooth](https://github.com/PurpleBooth)
* [bradurani](https://github.com/bradurani) * [bradurani](https://github.com/bradurani)
@ -192,7 +202,7 @@ and provided thanks to the work of the following contributors:
* [cdutson](https://github.com/cdutson) * [cdutson](https://github.com/cdutson)
* [farlistener](https://github.com/farlistener) * [farlistener](https://github.com/farlistener)
* [DavidLibeau](https://github.com/DavidLibeau) * [DavidLibeau](https://github.com/DavidLibeau)
* [SirCmpwn](https://github.com/SirCmpwn) * [ddevault](https://github.com/ddevault)
* [Fjoerfoks](https://github.com/Fjoerfoks) * [Fjoerfoks](https://github.com/Fjoerfoks)
* [fmauNeko](https://github.com/fmauNeko) * [fmauNeko](https://github.com/fmauNeko)
* [gloaec](https://github.com/gloaec) * [gloaec](https://github.com/gloaec)
@ -207,6 +217,7 @@ and provided thanks to the work of the following contributors:
* [jasonrhodes](https://github.com/jasonrhodes) * [jasonrhodes](https://github.com/jasonrhodes)
* [Jason Snell](mailto:jason@newrelic.com) * [Jason Snell](mailto:jason@newrelic.com)
* [jviide](https://github.com/jviide) * [jviide](https://github.com/jviide)
* [YuleZ](https://github.com/YuleZ)
* [crakaC](https://github.com/crakaC) * [crakaC](https://github.com/crakaC)
* [tkbky](https://github.com/tkbky) * [tkbky](https://github.com/tkbky)
* [Kaylee](mailto:kaylee@codethat.sucks) * [Kaylee](mailto:kaylee@codethat.sucks)
@ -223,10 +234,12 @@ and provided thanks to the work of the following contributors:
* [petzah](https://github.com/petzah) * [petzah](https://github.com/petzah)
* [ignisf](https://github.com/ignisf) * [ignisf](https://github.com/ignisf)
* [raymestalez](https://github.com/raymestalez) * [raymestalez](https://github.com/raymestalez)
* [remram44](https://github.com/remram44)
* [sascha-sl](https://github.com/sascha-sl) * [sascha-sl](https://github.com/sascha-sl)
* [u1-liquid](https://github.com/u1-liquid) * [u1-liquid](https://github.com/u1-liquid)
* [sim6](https://github.com/sim6) * [sim6](https://github.com/sim6)
* [stemid](https://github.com/stemid) * [stemid](https://github.com/stemid)
* [sumdog](https://github.com/sumdog)
* [ThomasLeister](https://github.com/ThomasLeister) * [ThomasLeister](https://github.com/ThomasLeister)
* [mcat-ee](https://github.com/mcat-ee) * [mcat-ee](https://github.com/mcat-ee)
* [tototoshi](https://github.com/tototoshi) * [tototoshi](https://github.com/tototoshi)
@ -243,7 +256,6 @@ and provided thanks to the work of the following contributors:
* [aus-social](https://github.com/aus-social) * [aus-social](https://github.com/aus-social)
* [imbsky](https://github.com/imbsky) * [imbsky](https://github.com/imbsky)
* [bsky](mailto:me@imbsky.net) * [bsky](mailto:me@imbsky.net)
* [chr-1x](https://github.com/chr-1x)
* [codl](https://github.com/codl) * [codl](https://github.com/codl)
* [cpsdqs](https://github.com/cpsdqs) * [cpsdqs](https://github.com/cpsdqs)
* [barzamin](https://github.com/barzamin) * [barzamin](https://github.com/barzamin)
@ -252,6 +264,7 @@ and provided thanks to the work of the following contributors:
* [ik11235](https://github.com/ik11235) * [ik11235](https://github.com/ik11235)
* [kawax](https://github.com/kawax) * [kawax](https://github.com/kawax)
* [007lva](https://github.com/007lva) * [007lva](https://github.com/007lva)
* [mbajur](https://github.com/mbajur)
* [matsurai25](https://github.com/matsurai25) * [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab) * [mecab](https://github.com/mecab)
* [nicobz25](https://github.com/nicobz25) * [nicobz25](https://github.com/nicobz25)
@ -259,7 +272,6 @@ and provided thanks to the work of the following contributors:
* [pinfort](https://github.com/pinfort) * [pinfort](https://github.com/pinfort)
* [rbaumert](https://github.com/rbaumert) * [rbaumert](https://github.com/rbaumert)
* [rhoio](https://github.com/rhoio) * [rhoio](https://github.com/rhoio)
* [trwnh](https://github.com/trwnh)
* [usagi-f](https://github.com/usagi-f) * [usagi-f](https://github.com/usagi-f)
* [vidarlee](https://github.com/vidarlee) * [vidarlee](https://github.com/vidarlee)
* [vjackson725](https://github.com/vjackson725) * [vjackson725](https://github.com/vjackson725)
@ -269,11 +281,11 @@ and provided thanks to the work of the following contributors:
* [Awea](https://github.com/Awea) * [Awea](https://github.com/Awea)
* [halcy](https://github.com/halcy) * [halcy](https://github.com/halcy)
* [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf) * [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf)
* [NecroTechno](https://github.com/NecroTechno)
* [8398a7](https://github.com/8398a7) * [8398a7](https://github.com/8398a7)
* [857b](https://github.com/857b) * [857b](https://github.com/857b)
* [insom](https://github.com/insom) * [insom](https://github.com/insom)
* [Aditoo17](https://github.com/Aditoo17) * [tachyons](https://github.com/tachyons)
* [Esteth](https://github.com/Esteth)
* [unascribed](https://github.com/unascribed) * [unascribed](https://github.com/unascribed)
* [Aguay-val](https://github.com/Aguay-val) * [Aguay-val](https://github.com/Aguay-val)
* [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp) * [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp)
@ -283,6 +295,7 @@ and provided thanks to the work of the following contributors:
* [alxrcs](https://github.com/alxrcs) * [alxrcs](https://github.com/alxrcs)
* [console-cowboy](https://github.com/console-cowboy) * [console-cowboy](https://github.com/console-cowboy)
* [pointlessone](https://github.com/pointlessone) * [pointlessone](https://github.com/pointlessone)
* [Alkarex](https://github.com/Alkarex)
* [a2](https://github.com/a2) * [a2](https://github.com/a2)
* [0xa](https://github.com/0xa) * [0xa](https://github.com/0xa)
* [palindromordnilap](https://github.com/palindromordnilap) * [palindromordnilap](https://github.com/palindromordnilap)
@ -299,7 +312,6 @@ and provided thanks to the work of the following contributors:
* [ayumin](https://github.com/ayumin) * [ayumin](https://github.com/ayumin)
* [BaptisteGelez](https://github.com/BaptisteGelez) * [BaptisteGelez](https://github.com/BaptisteGelez)
* [bzg](https://github.com/bzg) * [bzg](https://github.com/bzg)
* [BenLubar](https://github.com/BenLubar)
* [benediktg](https://github.com/benediktg) * [benediktg](https://github.com/benediktg)
* [blakebarnett](https://github.com/blakebarnett) * [blakebarnett](https://github.com/blakebarnett)
* [bradj](https://github.com/bradj) * [bradj](https://github.com/bradj)
@ -341,6 +353,7 @@ and provided thanks to the work of the following contributors:
* [espenronnevik](https://github.com/espenronnevik) * [espenronnevik](https://github.com/espenronnevik)
* [Finariel](https://github.com/Finariel) * [Finariel](https://github.com/Finariel)
* [siuying](https://github.com/siuying) * [siuying](https://github.com/siuying)
* [fwenzel](https://github.com/fwenzel)
* [GenbuHase](https://github.com/GenbuHase) * [GenbuHase](https://github.com/GenbuHase)
* [hattori6789](https://github.com/hattori6789) * [hattori6789](https://github.com/hattori6789)
* [algernon](https://github.com/algernon) * [algernon](https://github.com/algernon)
@ -375,10 +388,9 @@ and provided thanks to the work of the following contributors:
* [jguerder](https://github.com/jguerder) * [jguerder](https://github.com/jguerder)
* [Jehops](https://github.com/Jehops) * [Jehops](https://github.com/Jehops)
* [joshuap](https://github.com/joshuap) * [joshuap](https://github.com/joshuap)
* [YuleZ](https://github.com/YuleZ)
* [Tiwy57](https://github.com/Tiwy57) * [Tiwy57](https://github.com/Tiwy57)
* [xuv](https://github.com/xuv) * [xuv](https://github.com/xuv)
* [Jnsll](https://github.com/Jnsll) * [June Sallou](mailto:jnsll@users.noreply.github.com)
* [j0k3r](https://github.com/j0k3r) * [j0k3r](https://github.com/j0k3r)
* [KEINOS](https://github.com/KEINOS) * [KEINOS](https://github.com/KEINOS)
* [futoase](https://github.com/futoase) * [futoase](https://github.com/futoase)
@ -389,7 +401,6 @@ and provided thanks to the work of the following contributors:
* [k0ta0uchi](https://github.com/k0ta0uchi) * [k0ta0uchi](https://github.com/k0ta0uchi)
* [KrzysiekJ](https://github.com/KrzysiekJ) * [KrzysiekJ](https://github.com/KrzysiekJ)
* [leowzukw](https://github.com/leowzukw) * [leowzukw](https://github.com/leowzukw)
* [lmorchard](https://github.com/lmorchard)
* [Tak](https://github.com/Tak) * [Tak](https://github.com/Tak)
* [cacheflow](https://github.com/cacheflow) * [cacheflow](https://github.com/cacheflow)
* [ldidry](https://github.com/ldidry) * [ldidry](https://github.com/ldidry)
@ -426,6 +437,7 @@ and provided thanks to the work of the following contributors:
* [lae](https://github.com/lae) * [lae](https://github.com/lae)
* [Nanamachi](https://github.com/Nanamachi) * [Nanamachi](https://github.com/Nanamachi)
* [orinthe](https://github.com/orinthe) * [orinthe](https://github.com/orinthe)
* [NecroTechno](https://github.com/NecroTechno)
* [Dar13](https://github.com/Dar13) * [Dar13](https://github.com/Dar13)
* [ngerakines](https://github.com/ngerakines) * [ngerakines](https://github.com/ngerakines)
* [vonneudeck](https://github.com/vonneudeck) * [vonneudeck](https://github.com/vonneudeck)
@ -443,7 +455,6 @@ and provided thanks to the work of the following contributors:
* [Pangoraw](https://github.com/Pangoraw) * [Pangoraw](https://github.com/Pangoraw)
* [peterkeen](https://github.com/peterkeen) * [peterkeen](https://github.com/peterkeen)
* [pgate](https://github.com/pgate) * [pgate](https://github.com/pgate)
* [remram44](https://github.com/remram44)
* [retokromer](https://github.com/retokromer) * [retokromer](https://github.com/retokromer)
* [rfwatson](https://github.com/rfwatson) * [rfwatson](https://github.com/rfwatson)
* [rfreebern](https://github.com/rfreebern) * [rfreebern](https://github.com/rfreebern)
@ -455,19 +466,22 @@ and provided thanks to the work of the following contributors:
* [sts10](https://github.com/sts10) * [sts10](https://github.com/sts10)
* [skoji](https://github.com/skoji) * [skoji](https://github.com/skoji)
* [ScienJus](https://github.com/ScienJus) * [ScienJus](https://github.com/ScienJus)
* [larkinscott](https://github.com/larkinscott) * [Scott Larkin](mailto:scott@codeclimate.com)
* [imolein](https://github.com/imolein) * [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
* [blinry](https://github.com/blinry) * [Sebastian Morr](mailto:sebastian@morr.cc)
* [Noiwex](https://github.com/Noiwex) * [Sergei Č](mailto:noiwex1911@gmail.com)
* [yuki764](https://github.com/yuki764) * [Setuu](mailto:yuki764setuu@gmail.com)
* [shnjp](https://github.com/shnjp) * [Shaun Gillies](mailto:me@shaungillies.net)
* [ernix](https://github.com/ernix) * [Shin Adachi](mailto:shn@glucose.jp)
* [rosylilly](https://github.com/rosylilly) * [Shin Kojima](mailto:shin@kojima.org)
* [shouko](https://github.com/shouko) * [Sho Kusano](mailto:rosylilly@aduca.org)
* [Shouko Yu](mailto:imshouko@gmail.com)
* [Sina Mashek](mailto:sina@mashek.xyz) * [Sina Mashek](mailto:sina@mashek.xyz)
* [sossii](https://github.com/sossii) * [Sir-Boops](mailto:admin@boops.me)
* [Soshi Kato](mailto:mail@sossii.com)
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com) * [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
* [StefOfficiel](mailto:pichard.stephane@free.fr) * [StefOfficiel](mailto:pichard.stephane@free.fr)
* [Steven Tappert](mailto:admin@dark-it.net)
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com) * [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
* [Sébastien Santoro](mailto:dereckson@espace-win.org) * [Sébastien Santoro](mailto:dereckson@espace-win.org)
* [Tad Thorley](mailto:phaedryx@users.noreply.github.com) * [Tad Thorley](mailto:phaedryx@users.noreply.github.com)
@ -521,11 +535,13 @@ and provided thanks to the work of the following contributors:
* [jacob](mailto:jacobherringtondeveloper@gmail.com) * [jacob](mailto:jacobherringtondeveloper@gmail.com)
* [jenn kaplan](mailto:me@jkap.io) * [jenn kaplan](mailto:me@jkap.io)
* [jirayudech](mailto:jirayudech@gmail.com) * [jirayudech](mailto:jirayudech@gmail.com)
* [jomo](mailto:github@jomo.tv)
* [jooops](mailto:joops@autistici.org) * [jooops](mailto:joops@autistici.org)
* [jukper](mailto:jukkaperanto@gmail.com) * [jukper](mailto:jukkaperanto@gmail.com)
* [jumoru](mailto:jumoru@mailbox.org) * [jumoru](mailto:jumoru@mailbox.org)
* [karlyeurl](mailto:karl.yeurl@gmail.com) * [karlyeurl](mailto:karl.yeurl@gmail.com)
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com) * [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
* [kodai](mailto:shirafuta.kodai@gmail.com)
* [kuro5hin](mailto:rusty@kuro5hin.org) * [kuro5hin](mailto:rusty@kuro5hin.org)
* [luzpaz](mailto:luzpaz@users.noreply.github.com) * [luzpaz](mailto:luzpaz@users.noreply.github.com)
* [maxypy](mailto:maxime@mpigou.fr) * [maxypy](mailto:maxime@mpigou.fr)
@ -533,6 +549,7 @@ and provided thanks to the work of the following contributors:
* [mimikun](mailto:dzdzble_effort_311@outlook.jp) * [mimikun](mailto:dzdzble_effort_311@outlook.jp)
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com) * [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
* [muan](mailto:muan@github.com) * [muan](mailto:muan@github.com)
* [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
* [neetshin](mailto:neetshin@neetsh.in) * [neetshin](mailto:neetshin@neetsh.in)
* [nightpool](mailto:nightpool@users.noreply.github.com) * [nightpool](mailto:nightpool@users.noreply.github.com)
* [rch850](mailto:rich850@gmail.com) * [rch850](mailto:rich850@gmail.com)

View File

@ -3,6 +3,111 @@ Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
- Add link for adding a user to a list from their profile (#9062)
- Add joining several hashtags in a single column (#8904)
- Add volume sliders for videos (#9366)
- Add a tooltip explaining what a locked account is (#9403)
- Add preloaded cache for common JSON-LD contexts (#9412)
- Add profile directory (#9427)
- Add setting to not group reblogs in home feed (#9248)
- Add admin ability to remove a user's header image (#9495)
- Add account hashtags to ActivityPub actor JSON (#9450)
- Add error message for avatar image that's too large (#9518)
- Add notification quick-filter bar (#9399)
- Add new first-time tutorial (#9531)
- Add moderation warnings (#9519)
- Add emoji codepoint mappings for v11.0 (#9618)
- Add REST API for creating an account (#9572)
- Add support for Malayalam in language filter (#9624)
- Add exclude_reblogs option to account statuses API (#9640)
- Add local followers page to admin account UI (#9610)
- Add healthcheck commands to docker-compose.yml (#9143)
- Add handler for Move activity to migrate followers (#9629)
- Add CSV export for lists and domain blocks (#9677)
- Add `tootctl accounts follow ACCT` (#9414)
- Add scheduled statuses (#9706)
- Add immutable caching for S3 objects (#9722)
- Add cache to custom emojis API (#9732)
- Add preview cards to non-detailed statuses on public pages (#9714)
- Add `mod` and `moderator` to list of default reserved usernames (#9713)
- Add quick links to the admin interface in the web UI (#8545)
### Changed
- Temporarily pause timeline if mouse moved recently (#9200)
- Change the password form order (#9267)
- Redesign admin UI for accounts (#9340, #9643)
- Redesign admin UI for instances/domain blocks (#9645)
- Swap avatar and header input fields in profile page (#9271)
- When posting in mobile mode, go back to previous history location (#9502)
- Split out is_changing_upload from is_submitting (#9536)
- Back to the getting-started when pins the timeline. (#9561)
- Allow unauthenticated REST API access to GET /api/v1/accounts/:id/statuses (#9573)
- Limit maximum visibility of local silenced users to unlisted (#9583)
- Change API error message for unconfirmed accounts (#9625)
- Change the icon to "reply-all" when it's a reply to other accounts (#9378)
- Do not ignore federated reports targetting already-reported accounts (#9534)
- Upgrade default Ruby version to 2.6.0 (#9688)
- Change e-mail digest frequency (#9689)
- Change Docker images for Tor support in docker-compose.yml (#9438)
- Display fallback link card thumbnail when none is given (#9715)
- Change account bio length validation to ignore mention domains and URLs (#9717)
- Use configured contact user for "anonymous" federation activities (#9661)
- Change remote interaction dialog to use specific actions instead of generic "interact" (#9743)
- Always re-fetch public key when signature verification fails to support blind key rotation (#9667)
- Make replies to boosts impossible, connect reply to original status instead (#9129)
- Change e-mail MX validation to check both A and MX records against blacklist (#9489)
### Removed
- Remove links to bridge.joinmastodon.org (non-functional) (#9608)
- Remove LD-Signatures from activities that do not need them (#9659)
### Fixed
- Remove unused computation of reblog references from updateTimeline (#9244)
- Fix loaded embeds resetting if a status arrives from API again (#9270)
- Fix race condition causing shallow status with only a "favourited" attribute (#9272)
- Remove intermediary arrays when creating hash maps from results (#9291)
- Extract counters from accounts table to account_stats table to improve performance (#9295)
- Change identities id column to a bigint (#9371)
- Fix conversations API pagination (#9407)
- Improve account suspension speed and completeness (#9290)
- Fix thread depth computation in statuses_controller (#9426)
- Fix database deadlocks by moving account stats update outside transaction (#9437)
- Escape HTML in profile name preview in profile settings (#9446)
- Use same CORS policy for /@:username and /users/:username (#9485)
- Make custom emoji domains case insensitive (#9474)
- Various fixes to scrollable lists and media gallery (#9501)
- Fix bootsnap cache directory being declared relatively (#9511)
- Fix timeline pagination in the web UI (#9516)
- Fix padding on dropdown elements in preferences (#9517)
- Make avatar and headers respect GIF autoplay settings (#9515)
- Do no retry Web Push workers if the server returns a 4xx response (#9434)
- Minor scrollable list fixes (#9551)
- Ignore low-confidence CharlockHolmes guesses when parsing link cards (#9510)
- Fix `tootctl accounts rotate` not updating public keys (#9556)
- Fix CSP / X-Frame-Options for media players (#9558)
- Fix unnecessary loadMore calls when the end of a timeline has been reached (#9581)
- Skip mailer job retries when a record no longer exists (#9590)
- Fix composer not getting focus after reply confirmation dialog (#9602)
- Fix signature verification stoplight triggering on non-timeout errors (#9617)
- Fix ThreadResolveWorker getting queued with invalid URLs (#9628)
- Fix crash when clearing uninitialized timeline (#9662)
- Avoid duplicate work by merging ReplyDistributionWorker into DistributionWorker (#9660)
- Skip full text search if it fails, instead of erroring out completely (#9654)
- Fix profile metadata links not verifying correctly sometimes (#9673)
- Ensure blocked user unfollows blocker if Block/Undo-Block activities are processed out of order (#9687)
- Fix unreadable text color in report modal for some statuses (#9716)
- Stop GIFV timeline preview explicitly when it's opened in modal (#9749)
### Security
- Sanitize and sandbox toot embeds in web UI (#9552)
## [2.6.5] - 2018-12-01 ## [2.6.5] - 2018-12-01
### Changed ### Changed

View File

@ -1,4 +1,4 @@
FROM node:8.12.0-alpine as node FROM node:8.14.0-alpine as node
FROM ruby:2.4.5-alpine3.8 FROM ruby:2.4.5-alpine3.8
LABEL maintainer="https://github.com/tootsuite/mastodon" \ LABEL maintainer="https://github.com/tootsuite/mastodon" \
@ -31,6 +31,8 @@ RUN apk -U upgrade \
libidn-dev \ libidn-dev \
libressl \ libressl \
libtool \ libtool \
libxml2-dev \
libxslt-dev \
postgresql-dev \ postgresql-dev \
protobuf-dev \ protobuf-dev \
python \ python \
@ -43,6 +45,8 @@ RUN apk -U upgrade \
imagemagick \ imagemagick \
libidn \ libidn \
libpq \ libpq \
libxml2 \
libxslt \
protobuf \ protobuf \
tini \ tini \
tzdata \ tzdata \
@ -64,7 +68,7 @@ RUN apk -U upgrade \
COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/ COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \ RUN bundle config build.nokogiri --use-system-libraries --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \ && bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
&& yarn install --pure-lockfile --ignore-engines \ && yarn install --pure-lockfile --ignore-engines \
&& yarn cache clean && yarn cache clean

36
Gemfile
View File

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '>= 2.3.0', '< 2.6.0' ruby '>= 2.4.0', '< 2.7.0'
gem 'pkg-config', '~> 1.3' gem 'pkg-config', '~> 1.3'
gem 'puma', '~> 3.12' gem 'puma', '~> 3.12'
gem 'rails', '~> 5.2.1' gem 'rails', '~> 5.2.2'
gem 'thor', '~> 0.20' gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.2' gem 'pghero', '~> 2.2'
gem 'dotenv-rails', '~> 2.5' gem 'dotenv-rails', '~> 2.5'
gem 'aws-sdk-s3', '~> 1.23', require: false gem 'aws-sdk-s3', '~> 1.30', require: false
gem 'fog-core', '<= 2.1.0' gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0' gem 'paperclip', '~> 6.0'
@ -29,7 +29,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.6' gem 'charlock_holmes', '~> 0.7.6'
gem 'iso-639' gem 'iso-639'
gem 'chewy', '~> 5.0' gem 'chewy', '~> 5.0'
gem 'cld3', '~> 3.2.0' gem 'cld3', '~> 3.2.3'
gem 'devise', '~> 4.5' gem 'devise', '~> 4.5'
gem 'devise-two-factor', '~> 3.0' gem 'devise-two-factor', '~> 3.0'
@ -40,7 +40,7 @@ end
gem 'net-ldap', '~> 0.10' gem 'net-ldap', '~> 0.10'
gem 'omniauth-cas', '~> 1.1' gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.2' gem 'omniauth', '~> 1.9'
gem 'doorkeeper', '~> 5.0' gem 'doorkeeper', '~> 5.0'
gem 'fast_blank', '~> 1.0' gem 'fast_blank', '~> 1.0'
@ -52,12 +52,12 @@ gem 'htmlentities', '~> 4.3'
gem 'http', '~> 3.3' gem 'http', '~> 3.3'
gem 'http_accept_language', '~> 2.1' gem 'http_accept_language', '~> 2.1'
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2' gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
gem 'httplog', '~> 1.1' gem 'httplog', '~> 1.2'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1' gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0' gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar' gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.8' gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2' gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.7' gem 'oj', '~> 3.7'
gem 'ostatus2', '~> 2.0' gem 'ostatus2', '~> 2.0'
@ -69,28 +69,28 @@ gem 'rack-attack', '~> 5.4'
gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rack-cors', '~> 1.0', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1' gem 'rails-i18n', '~> 5.1'
gem 'rails-settings-cached', '~> 0.6' gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis'] gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10' gem 'rqrcode', '~> 0.10'
gem 'sanitize', '~> 5.0' gem 'sanitize', '~> 5.0'
gem 'sidekiq', '~> 5.2' gem 'sidekiq', '~> 5.2'
gem 'sidekiq-scheduler', '~> 3.0' gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 5.0' gem 'sidekiq-unique-jobs', '~> 5.0'
gem 'sidekiq-bulk', '~>0.1.1' gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.0' gem 'simple-navigation', '~> 4.0'
gem 'simple_form', '~> 4.0' gem 'simple_form', '~> 4.1'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.1.3' gem 'stoplight', '~> 2.1.3'
gem 'strong_migrations', '~> 0.3' gem 'strong_migrations', '~> 0.3'
gem 'tty-command', '~> 0.8', require: false gem 'tty-command', '~> 0.8', require: false
gem 'tty-prompt', '~> 0.17', require: false gem 'tty-prompt', '~> 0.18', require: false
gem 'twitter-text', '~> 1.14' gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2018' gem 'tzinfo-data', '~> 1.2018'
gem 'webpacker', '~> 3.5' gem 'webpacker', '~> 3.5'
gem 'webpush' gem 'webpush'
gem 'json-ld', '~> 2.2' gem 'json-ld', '~> 2.2'
gem 'json-ld-preloaded', '~> 2.2' gem 'json-ld-preloaded', '~> 3.0'
gem 'rdf-normalize', '~> 0.3' gem 'rdf-normalize', '~> 0.3'
group :development, :test do group :development, :test do
@ -107,15 +107,15 @@ group :production, :test do
end end
group :test do group :test do
gem 'capybara', '~> 3.10' gem 'capybara', '~> 3.12'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.9' gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.0' gem 'microformats', '~> 4.0'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.16', require: false gem 'simplecov', '~> 0.16', require: false
gem 'webmock', '~> 3.4' gem 'webmock', '~> 3.5'
gem 'parallel_tests', '~> 2.26' gem 'parallel_tests', '~> 2.27'
end end
group :development do group :development do
@ -123,11 +123,11 @@ group :development do
gem 'annotate', '~> 2.7' gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.5' gem 'better_errors', '~> 2.5'
gem 'binding_of_caller', '~> 0.7' gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 5.7' gem 'bullet', '~> 5.9'
gem 'letter_opener', '~> 1.4' gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3' gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler' gem 'memory_profiler'
gem 'rubocop', '~> 0.60', require: false gem 'rubocop', '~> 0.62', require: false
gem 'brakeman', '~> 4.3', require: false gem 'brakeman', '~> 4.3', require: false
gem 'bundler-audit', '~> 0.6', require: false gem 'bundler-audit', '~> 0.6', require: false
gem 'scss_lint', '~> 0.57', require: false gem 'scss_lint', '~> 0.57', require: false

View File

@ -15,49 +15,49 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.2.1) actioncable (5.2.2)
actionpack (= 5.2.1) actionpack (= 5.2.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.2.1) actionmailer (5.2.2)
actionpack (= 5.2.1) actionpack (= 5.2.2)
actionview (= 5.2.1) actionview (= 5.2.2)
activejob (= 5.2.1) activejob (= 5.2.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.2.1) actionpack (5.2.2)
actionview (= 5.2.1) actionview (= 5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
rack (~> 2.0) rack (~> 2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.1) actionview (5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.10.7) active_model_serializers (0.10.8)
actionpack (>= 4.1, < 6) actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6) activemodel (>= 4.1, < 6)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.5.4) active_record_query_trace (1.5.4)
activejob (5.2.1) activejob (5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.2.1) activemodel (5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
activerecord (5.2.1) activerecord (5.2.2)
activemodel (= 5.2.1) activemodel (= 5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
arel (>= 9.0) arel (>= 9.0)
activestorage (5.2.1) activestorage (5.2.2)
actionpack (= 5.2.1) actionpack (= 5.2.2)
activerecord (= 5.2.1) activerecord (= 5.2.2)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (5.2.1) activesupport (5.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -76,17 +76,17 @@ GEM
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
aws-eventstream (1.0.1) aws-eventstream (1.0.1)
aws-partitions (1.106.0) aws-partitions (1.122.0)
aws-sdk-core (3.35.0) aws-sdk-core (3.43.0)
aws-eventstream (~> 1.0) aws-eventstream (~> 1.0)
aws-partitions (~> 1.0) aws-partitions (~> 1.0)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.11.0) aws-sdk-kms (1.13.0)
aws-sdk-core (~> 3, >= 3.26.0) aws-sdk-core (~> 3, >= 3.39.0)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
aws-sdk-s3 (1.23.0) aws-sdk-s3 (1.30.0)
aws-sdk-core (~> 3, >= 3.26.0) aws-sdk-core (~> 3, >= 3.39.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.3) aws-sigv4 (1.0.3)
@ -103,9 +103,9 @@ GEM
brakeman (4.3.1) brakeman (4.3.1)
browser (2.5.3) browser (2.5.3)
builder (3.2.3) builder (3.2.3)
bullet (5.7.6) bullet (5.9.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11.0) uniform_notifier (~> 1.11)
bundler-audit (0.6.0) bundler-audit (0.6.0)
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
@ -126,7 +126,7 @@ GEM
sshkit (~> 1.3) sshkit (~> 1.3)
capistrano-yarn (2.0.2) capistrano-yarn (2.0.2)
capistrano (~> 3.0) capistrano (~> 3.0)
capybara (3.10.0) capybara (3.12.0)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -142,7 +142,7 @@ GEM
elasticsearch (>= 2.0.0) elasticsearch (>= 2.0.0)
elasticsearch-dsl elasticsearch-dsl
chunky_png (1.3.10) chunky_png (1.3.10)
cld3 (3.2.2) cld3 (3.2.3)
ffi (>= 1.1.0, < 1.10.0) ffi (>= 1.1.0, < 1.10.0)
climate_control (0.2.0) climate_control (0.2.0)
cocaine (0.5.8) cocaine (0.5.8)
@ -210,7 +210,7 @@ GEM
faraday (0.15.0) faraday (0.15.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
fast_blank (1.0.0) fast_blank (1.0.0)
fastimage (2.1.4) fastimage (2.1.5)
ffi (1.9.25) ffi (1.9.25)
fog-core (2.1.0) fog-core (2.1.0)
builder builder
@ -251,11 +251,10 @@ GEM
hamster (3.0.0) hamster (3.0.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
hashdiff (0.3.7) hashdiff (0.3.7)
hashie (3.5.7) hashie (3.6.0)
heapy (0.1.4) heapy (0.1.4)
highline (2.0.0) highline (2.0.0)
hiredis (0.6.1) hiredis (0.6.3)
hitimes (1.3.0)
hkdf (0.3.0) hkdf (0.3.0)
htmlentities (4.3.4) htmlentities (4.3.4)
http (3.3.0) http (3.3.0)
@ -267,7 +266,7 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (2.1.1) http-form_data (2.1.1)
http_accept_language (2.1.1) http_accept_language (2.1.1)
httplog (1.1.1) httplog (1.2.0)
rack (>= 1.0) rack (>= 1.0)
rainbow (>= 2.0.0) rainbow (>= 2.0.0)
i18n (1.1.1) i18n (1.1.1)
@ -291,10 +290,10 @@ GEM
json-ld (2.2.1) json-ld (2.2.1)
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (>= 2.2.8, < 4.0) rdf (>= 2.2.8, < 4.0)
json-ld-preloaded (2.2.3) json-ld-preloaded (3.0.0)
json-ld (>= 2.2, < 4.0) json-ld (>= 2.2, < 4.0)
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (>= 2.2, < 4.0) rdf (~> 3.0)
jsonapi-renderer (0.2.0) jsonapi-renderer (0.2.0)
jwt (2.1.0) jwt (2.1.0)
kaminari (1.1.1) kaminari (1.1.1)
@ -311,7 +310,7 @@ GEM
kaminari-core (1.1.1) kaminari-core (1.1.1)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.6.0) letter_opener (1.7.0)
launchy (~> 2.2) launchy (~> 2.2)
letter_opener_web (1.3.4) letter_opener_web (1.3.4)
actionmailer (>= 3.2) actionmailer (>= 3.2)
@ -326,16 +325,16 @@ GEM
loofah (2.2.3) loofah (2.2.3)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.0) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
makara (0.4.0) makara (0.4.0)
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
marcel (0.3.2) marcel (0.3.3)
mimemagic (~> 0.3.2) mimemagic (~> 0.3.2)
mario-redis-lock (1.2.1) mario-redis-lock (1.2.1)
redis (>= 3.0.5) redis (>= 3.0.5)
memory_profiler (0.9.12) memory_profiler (0.9.12)
method_source (0.9.0) method_source (0.9.2)
microformats (4.0.7) microformats (4.0.7)
json json
nokogiri nokogiri
@ -344,7 +343,7 @@ GEM
mime-types-data (3.2018.0812) mime-types-data (3.2018.0812)
mimemagic (0.3.2) mimemagic (0.3.2)
mini_mime (1.0.1) mini_mime (1.0.1)
mini_portile2 (2.3.0) mini_portile2 (2.4.0)
minitest (5.11.3) minitest (5.11.3)
msgpack (1.2.4) msgpack (1.2.4)
multi_json (1.13.1) multi_json (1.13.1)
@ -355,8 +354,8 @@ GEM
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (5.0.2) net-ssh (5.0.2)
nio4r (2.3.1) nio4r (2.3.1)
nokogiri (1.8.5) nokogiri (1.10.0)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.4.0)
nokogumbo (2.0.0) nokogumbo (2.0.0)
nokogiri (~> 1.8, >= 1.8.4) nokogiri (~> 1.8, >= 1.8.4)
nsa (0.2.4) nsa (0.2.4)
@ -364,9 +363,9 @@ GEM
concurrent-ruby (~> 1.0.0) concurrent-ruby (~> 1.0.0)
sidekiq (>= 3.5.0) sidekiq (>= 3.5.0)
statsd-ruby (~> 1.2.0) statsd-ruby (~> 1.2.0)
oj (3.7.0) oj (3.7.6)
omniauth (1.8.1) omniauth (1.9.0)
hashie (>= 3.4.6, < 3.6.0) hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
omniauth-cas (1.1.1) omniauth-cas (1.1.1)
addressable (~> 2.3) addressable (~> 2.3)
@ -391,7 +390,7 @@ GEM
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.12.1) parallel (1.12.1)
parallel_tests (2.26.0) parallel_tests (2.27.1)
parallel parallel
parser (2.5.3.0) parser (2.5.3.0)
ast (~> 2.4.0) ast (~> 2.4.0)
@ -401,7 +400,7 @@ GEM
pg (1.1.3) pg (1.1.3)
pghero (2.2.0) pghero (2.2.0)
activerecord activerecord
pkg-config (1.3.1) pkg-config (1.3.2)
powerpack (0.1.2) powerpack (0.1.2)
premailer (1.11.1) premailer (1.11.1)
addressable addressable
@ -411,21 +410,21 @@ GEM
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
pry (0.11.3) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
pry-byebug (3.6.0) pry-byebug (3.6.0)
byebug (~> 10.0) byebug (~> 10.0)
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.6) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (3.0.3) public_suffix (3.0.3)
puma (3.12.0) puma (3.12.0)
pundit (2.0.0) pundit (2.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.1.6) raabro (1.1.6)
rack (2.0.5) rack (2.0.6)
rack-attack (5.4.1) rack-attack (5.4.2)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-cors (1.0.2) rack-cors (1.0.2)
rack-protection (2.0.4) rack-protection (2.0.4)
@ -434,23 +433,23 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.2.1) rails (5.2.2)
actioncable (= 5.2.1) actioncable (= 5.2.2)
actionmailer (= 5.2.1) actionmailer (= 5.2.2)
actionpack (= 5.2.1) actionpack (= 5.2.2)
actionview (= 5.2.1) actionview (= 5.2.2)
activejob (= 5.2.1) activejob (= 5.2.2)
activemodel (= 5.2.1) activemodel (= 5.2.2)
activerecord (= 5.2.1) activerecord (= 5.2.2)
activestorage (= 5.2.1) activestorage (= 5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.2.1) railties (= 5.2.2)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2) rails-controller-testing (1.0.4)
actionpack (~> 5.x, >= 5.0.1) actionpack (>= 5.0.1.x)
actionview (~> 5.x, >= 5.0.1) actionview (>= 5.0.1.x)
activesupport (~> 5.x) activesupport (>= 5.0.1.x)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
@ -461,9 +460,9 @@ GEM
railties (>= 5.0, < 6) railties (>= 5.0, < 6)
rails-settings-cached (0.6.6) rails-settings-cached (0.6.6)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (5.2.1) railties (5.2.2)
actionpack (= 5.2.1) actionpack (= 5.2.2)
activesupport (= 5.2.1) activesupport (= 5.2.2)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0) thor (>= 0.19.0, < 2.0)
@ -472,12 +471,12 @@ GEM
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.9.10) rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
rdf (3.0.2) rdf (3.0.7)
hamster (~> 3.0) hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.3.3) rdf-normalize (0.3.3)
rdf (>= 2.2, < 4.0) rdf (>= 2.2, < 4.0)
redis (4.0.2) redis (4.1.0)
redis-actionpack (5.0.2) redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6) actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3) redis-rack (>= 1, < 3)
@ -496,7 +495,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.5.0) redis-store (1.5.0)
redis (>= 2.2, < 5) redis (>= 2.2, < 5)
regexp_parser (1.2.0) regexp_parser (1.3.0)
request_store (1.4.1) request_store (1.4.1)
rack (>= 1.4) rack (>= 1.4)
responders (2.4.0) responders (2.4.0)
@ -526,7 +525,7 @@ GEM
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.8.0) rspec-support (3.8.0)
rubocop (0.60.0) rubocop (0.62.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1) parser (>= 2.5, != 2.5.1.1)
@ -552,12 +551,11 @@ GEM
scss_lint (0.57.1) scss_lint (0.57.1)
rake (>= 0.9, < 13) rake (>= 0.9, < 13)
sass (~> 3.5, >= 3.5.5) sass (~> 3.5, >= 3.5.5)
sidekiq (5.2.2) sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2) connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5) redis (>= 3.3.5, < 5)
sidekiq-bulk (0.1.1) sidekiq-bulk (0.2.0)
activesupport
sidekiq sidekiq
sidekiq-scheduler (3.0.0) sidekiq-scheduler (3.0.0)
redis (>= 3, < 5) redis (>= 3, < 5)
@ -569,7 +567,7 @@ GEM
thor (~> 0) thor (~> 0)
simple-navigation (4.0.5) simple-navigation (4.0.5)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
simple_form (4.0.1) simple_form (4.1.0)
actionpack (>= 5.0) actionpack (>= 5.0)
activemodel (>= 5.0) activemodel (>= 5.0)
simplecov (0.16.1) simplecov (0.16.1)
@ -599,22 +597,21 @@ GEM
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0) terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
thor (0.20.0) thor (0.20.3)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.8) tilt (2.0.8)
timers (4.1.2) timers (4.2.0)
hitimes
tty-color (0.4.3) tty-color (0.4.3)
tty-command (0.8.2) tty-command (0.8.2)
pastel (~> 0.7.0) pastel (~> 0.7.0)
tty-cursor (0.6.0) tty-cursor (0.6.0)
tty-prompt (0.17.1) tty-prompt (0.18.1)
necromancer (~> 0.4.0) necromancer (~> 0.4.0)
pastel (~> 0.7.0) pastel (~> 0.7.0)
timers (~> 4.0) timers (~> 4.0)
tty-cursor (~> 0.6.0) tty-cursor (~> 0.6.0)
tty-reader (~> 0.4.0) tty-reader (~> 0.5.0)
tty-reader (0.4.0) tty-reader (0.5.0)
tty-cursor (~> 0.6.0) tty-cursor (~> 0.6.0)
tty-screen (~> 0.6.4) tty-screen (~> 0.6.4)
wisper (~> 2.0.0) wisper (~> 2.0.0)
@ -623,16 +620,16 @@ GEM
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.5) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2018.7) tzinfo-data (1.2018.9)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.5) unf_ext (0.0.7.5)
unicode-display_width (1.4.0) unicode-display_width (1.4.1)
uniform_notifier (1.11.0) uniform_notifier (1.12.1)
warden (1.2.7) warden (1.2.7)
rack (>= 1.0) rack (>= 1.0)
webmock (3.4.2) webmock (3.5.1)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff
@ -640,7 +637,7 @@ GEM
activesupport (>= 4.2) activesupport (>= 4.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 4.2) railties (>= 4.2)
webpush (0.3.4) webpush (0.3.5)
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) jwt (~> 2.0)
websocket-driver (0.7.0) websocket-driver (0.7.0)
@ -658,22 +655,22 @@ DEPENDENCIES
active_record_query_trace (~> 1.5) active_record_query_trace (~> 1.5)
addressable (~> 2.5) addressable (~> 2.5)
annotate (~> 2.7) annotate (~> 2.7)
aws-sdk-s3 (~> 1.23) aws-sdk-s3 (~> 1.30)
better_errors (~> 2.5) better_errors (~> 2.5)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
bootsnap (~> 1.3) bootsnap (~> 1.3)
brakeman (~> 4.3) brakeman (~> 4.3)
browser browser
bullet (~> 5.7) bullet (~> 5.9)
bundler-audit (~> 0.6) bundler-audit (~> 0.6)
capistrano (~> 3.11) capistrano (~> 3.11)
capistrano-rails (~> 1.4) capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1) capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 3.10) capybara (~> 3.12)
charlock_holmes (~> 0.7.6) charlock_holmes (~> 0.7.6)
chewy (~> 5.0) chewy (~> 5.0)
cld3 (~> 3.2.0) cld3 (~> 3.2.3)
climate_control (~> 0.2) climate_control (~> 0.2)
derailed_benchmarks derailed_benchmarks
devise (~> 4.5) devise (~> 4.5)
@ -695,14 +692,14 @@ DEPENDENCIES
http (~> 3.3) http (~> 3.3)
http_accept_language (~> 2.1) http_accept_language (~> 2.1)
http_parser.rb (~> 0.6)! http_parser.rb (~> 0.6)!
httplog (~> 1.1) httplog (~> 1.2)
i18n-tasks (~> 0.9) i18n-tasks (~> 0.9)
idn-ruby idn-ruby
iso-639 iso-639
json-ld (~> 2.2) json-ld (~> 2.2)
json-ld-preloaded (~> 2.2) json-ld-preloaded (~> 3.0)
kaminari (~> 1.1) kaminari (~> 1.1)
letter_opener (~> 1.4) letter_opener (~> 1.7)
letter_opener_web (~> 1.3) letter_opener_web (~> 1.3)
link_header (~> 0.0) link_header (~> 0.0)
lograge (~> 0.10) lograge (~> 0.10)
@ -712,17 +709,17 @@ DEPENDENCIES
microformats (~> 4.0) microformats (~> 4.0)
mime-types (~> 3.2) mime-types (~> 3.2)
net-ldap (~> 0.10) net-ldap (~> 0.10)
nokogiri (~> 1.8) nokogiri (~> 1.10)
nsa (~> 0.2) nsa (~> 0.2)
oj (~> 3.7) oj (~> 3.7)
omniauth (~> 1.2) omniauth (~> 1.9)
omniauth-cas (~> 1.1) omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
ostatus2 (~> 2.0) ostatus2 (~> 2.0)
ox (~> 2.10) ox (~> 2.10)
paperclip (~> 6.0) paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6) paperclip-av-transcoder (~> 0.6)
parallel_tests (~> 2.26) parallel_tests (~> 2.27)
pg (~> 1.1) pg (~> 1.1)
pghero (~> 2.2) pghero (~> 2.2)
pkg-config (~> 1.3) pkg-config (~> 1.3)
@ -735,26 +732,26 @@ DEPENDENCIES
pundit (~> 2.0) pundit (~> 2.0)
rack-attack (~> 5.4) rack-attack (~> 5.4)
rack-cors (~> 1.0) rack-cors (~> 1.0)
rails (~> 5.2.1) rails (~> 5.2.2)
rails-controller-testing (~> 1.0) rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1) rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6) rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.3) rdf-normalize (~> 0.3)
redis (~> 4.0) redis (~> 4.1)
redis-namespace (~> 1.5) redis-namespace (~> 1.5)
redis-rails (~> 5.0) redis-rails (~> 5.0)
rqrcode (~> 0.10) rqrcode (~> 0.10)
rspec-rails (~> 3.8) rspec-rails (~> 3.8)
rspec-sidekiq (~> 3.0) rspec-sidekiq (~> 3.0)
rubocop (~> 0.60) rubocop (~> 0.62)
sanitize (~> 5.0) sanitize (~> 5.0)
scss_lint (~> 0.57) scss_lint (~> 0.57)
sidekiq (~> 5.2) sidekiq (~> 5.2)
sidekiq-bulk (~> 0.1.1) sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0) sidekiq-scheduler (~> 3.0)
sidekiq-unique-jobs (~> 5.0) sidekiq-unique-jobs (~> 5.0)
simple-navigation (~> 4.0) simple-navigation (~> 4.0)
simple_form (~> 4.0) simple_form (~> 4.1)
simplecov (~> 0.16) simplecov (~> 0.16)
sprockets-rails (~> 3.2) sprockets-rails (~> 3.2)
stackprof stackprof
@ -763,10 +760,10 @@ DEPENDENCIES
strong_migrations (~> 0.3) strong_migrations (~> 0.3)
thor (~> 0.20) thor (~> 0.20)
tty-command (~> 0.8) tty-command (~> 0.8)
tty-prompt (~> 0.17) tty-prompt (~> 0.18)
twitter-text (~> 1.14) twitter-text (~> 1.14)
tzinfo-data (~> 1.2018) tzinfo-data (~> 1.2018)
webmock (~> 3.4) webmock (~> 3.5)
webpacker (~> 3.5) webpacker (~> 3.5)
webpush webpush

2
Vagrantfile vendored
View File

@ -44,7 +44,7 @@ sudo apt-get install \
# Install rvm # Install rvm
read RUBY_VERSION < .ruby-version read RUBY_VERSION < .ruby-version
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
source /home/vagrant/.rvm/scripts/rvm source /home/vagrant/.rvm/scripts/rvm

View File

@ -31,7 +31,7 @@ class ActivityPub::CollectionsController < Api::BaseController
when 'featured' when 'featured'
@account.pinned_statuses.count @account.pinned_statuses.count
else else
raise ActiveRecord::NotFound raise ActiveRecord::RecordNotFound
end end
end end
@ -42,7 +42,7 @@ class ActivityPub::CollectionsController < Api::BaseController
scope.merge!(@account.pinned_statuses) scope.merge!(@account.pinned_statuses)
end end
else else
raise ActiveRecord::NotFound raise ActiveRecord::RecordNotFound
end end
end end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Admin
class AccountActionsController < BaseController
before_action :set_account
def new
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true)
@warning_presets = AccountWarningPreset.all
end
def create
account_action = Admin::AccountAction.new(resource_params)
account_action.target_account = @account
account_action.current_account = current_account
account_action.save!
if account_action.with_report?
redirect_to admin_reports_path
else
redirect_to admin_account_path(@account.id)
end
end
private
def set_account
@account = Account.find(params[:account_id])
end
def resource_params
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification)
end
end
end

View File

@ -14,6 +14,7 @@ module Admin
else else
@account = @account_moderation_note.target_account @account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest @moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
render template: 'admin/accounts/show' render template: 'admin/accounts/show'
end end

View File

@ -2,9 +2,9 @@
module Admin module Admin
class AccountsController < BaseController class AccountsController < BaseController
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :enable, :disable, :memorialize] before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize]
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
before_action :require_local_account!, only: [:enable, :disable, :memorialize] before_action :require_local_account!, only: [:enable, :memorialize]
def index def index
authorize :account, :index? authorize :account, :index?
@ -13,8 +13,10 @@ module Admin
def show def show
authorize @account, :show? authorize @account, :show?
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest @moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
end end
def subscribe def subscribe
@ -43,19 +45,25 @@ module Admin
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end
def disable def unsilence
authorize @account.user, :disable? authorize @account, :unsilence?
@account.user.disable! @account.unsilence!
log_action :disable, @account.user log_action :unsilence, @account
redirect_to admin_account_path(@account.id)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end
def redownload def redownload
authorize @account, :redownload? authorize @account, :redownload?
@account.reset_avatar! @account.update!(last_webfingered_at: nil)
@account.reset_header! ResolveAccountService.new.call(@account)
@account.save!
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end
@ -71,6 +79,17 @@ module Admin
redirect_to admin_account_path(@account.id) redirect_to admin_account_path(@account.id)
end end
def remove_header
authorize @account, :remove_header?
@account.header = nil
@account.save!
log_action :remove_header, @account.user
redirect_to admin_account_path(@account.id)
end
private private
def set_account def set_account
@ -94,8 +113,8 @@ module Admin
:local, :local,
:remote, :remote,
:by_domain, :by_domain,
:active,
:silenced, :silenced,
:alphabetic,
:suspended, :suspended,
:username, :username,
:display_name, :display_name,

View File

@ -15,5 +15,9 @@ module Admin
def set_body_classes def set_body_classes
@body_classes = 'admin' @body_classes = 'admin'
end end
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end end
end end

View File

@ -25,10 +25,6 @@ module Admin
private private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
def check_confirmation def check_confirmation
if @user.confirmed? if @user.confirmed?
flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed')

View File

@ -28,6 +28,7 @@ module Admin
@pam_enabled = ENV['PAM_ENABLED'] == 'true' @pam_enabled = ENV['PAM_ENABLED'] == 'true'
@hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true' @hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
@trending_hashtags = TrendingTags.get(7) @trending_hashtags = TrendingTags.get(7)
@profile_directory = Setting.profile_directory
end end
private private

View File

@ -4,14 +4,9 @@ module Admin
class DomainBlocksController < BaseController class DomainBlocksController < BaseController
before_action :set_domain_block, only: [:show, :destroy] before_action :set_domain_block, only: [:show, :destroy]
def index
authorize :domain_block, :index?
@domain_blocks = DomainBlock.page(params[:page])
end
def new def new
authorize :domain_block, :create? authorize :domain_block, :create?
@domain_block = DomainBlock.new @domain_block = DomainBlock.new(domain: params[:_domain])
end end
def create def create
@ -22,7 +17,7 @@ module Admin
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id) DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block log_action :create, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg') redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else else
render :new render :new
end end
@ -36,7 +31,7 @@ module Admin
authorize @domain_block, :destroy? authorize @domain_block, :destroy?
UnblockDomainService.new.call(@domain_block, retroactive_unblock?) UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
log_action :destroy, @domain_block log_action :destroy, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg')
end end
private private

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Admin
class FollowersController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
end
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@ -4,14 +4,21 @@ module Admin
class InstancesController < BaseController class InstancesController < BaseController
def index def index
authorize :instance, :index? authorize :instance, :index?
@instances = ordered_instances @instances = ordered_instances
end end
def resubscribe def show
authorize :instance, :resubscribe? authorize :instance, :show?
params.require(:by_domain)
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id)) @instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
redirect_to admin_instances_path @following_count = Follow.where(account: Account.where(domain: params[:id])).count
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@domain_block = DomainBlock.find_by(domain: params[:id])
end end
private private
@ -27,17 +34,11 @@ module Admin
helper_method :paginated_instances helper_method :paginated_instances
def ordered_instances def ordered_instances
paginated_instances.map { |account| Instance.new(account) } paginated_instances.map { |resource| Instance.new(resource) }
end
def subscribeable_accounts
Account.with_followers.remote.where(domain: params[:by_domain])
end end
def filter_params def filter_params
params.permit( params.permit(:limited)
:domain_name
)
end end
end end
end end

View File

@ -13,75 +13,42 @@ module Admin
authorize @report, :show? authorize @report, :show?
@report_note = @report.notes.new @report_note = @report.notes.new
@report_notes = (@report.notes.latest + @report.history).sort_by(&:created_at) @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
@form = Form::StatusBatch.new @form = Form::StatusBatch.new
end end
def update def assign_to_self
authorize @report, :update? authorize @report, :update?
process_report @report.update!(assigned_account_id: current_account.id)
log_action :assigned_to_self, @report
redirect_to admin_report_path(@report)
end
if @report.action_taken? def unassign
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') authorize @report, :update?
else @report.update!(assigned_account_id: nil)
redirect_to admin_report_path(@report) log_action :unassigned, @report
end redirect_to admin_report_path(@report)
end
def reopen
authorize @report, :update?
@report.unresolve!
log_action :reopen, @report
redirect_to admin_report_path(@report)
end
def resolve
authorize @report, :update?
@report.resolve!(current_account)
log_action :resolve, @report
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
end end
private private
def process_report
case params[:outcome].to_s
when 'assign_to_self'
@report.update!(assigned_account_id: current_account.id)
log_action :assigned_to_self, @report
when 'unassign'
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
when 'reopen'
@report.unresolve!
log_action :reopen, @report
when 'resolve'
@report.resolve!(current_account)
log_action :resolve, @report
when 'disable'
@report.resolve!(current_account)
@report.target_account.user.disable!
log_action :resolve, @report
log_action :disable, @report.target_account.user
resolve_all_target_account_reports
when 'silence'
@report.resolve!(current_account)
@report.target_account.update!(silenced: true)
log_action :resolve, @report
log_action :silence, @report.target_account
resolve_all_target_account_reports
else
raise ActiveRecord::RecordNotFound
end
@report.reload
end
def resolve_all_target_account_reports
unresolved_reports_for_target_account.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
end
def unresolved_reports_for_target_account
Report.where(
target_account: @report.target_account
).unresolved
end
def filtered_reports def filtered_reports
ReportFilter.new(filter_params).results.order(id: :desc).includes( ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
:account,
:target_account
)
end end
def filter_params def filter_params

View File

@ -10,11 +10,5 @@ module Admin
log_action :reset_password, @user log_action :reset_password, @user
redirect_to admin_accounts_path redirect_to admin_accounts_path
end end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end end
end end

View File

@ -17,11 +17,5 @@ module Admin
log_action :demote, @user log_action :demote, @user
redirect_to admin_account_path(@user.account_id) redirect_to admin_account_path(@user.account_id)
end end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end end
end end

View File

@ -26,6 +26,7 @@ module Admin
show_known_fediverse_at_about_page show_known_fediverse_at_about_page
preview_sensitive_media preview_sensitive_media
custom_css custom_css
profile_directory
).freeze ).freeze
BOOLEAN_SETTINGS = %w( BOOLEAN_SETTINGS = %w(
@ -37,6 +38,7 @@ module Admin
peers_api_enabled peers_api_enabled
show_known_fediverse_at_about_page show_known_fediverse_at_about_page
preview_sensitive_media preview_sensitive_media
profile_directory
).freeze ).freeze
UPLOAD_SETTINGS = %w( UPLOAD_SETTINGS = %w(

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
module Admin
class SilencesController < BaseController
before_action :set_account
def create
authorize @account, :silence?
@account.update!(silenced: true)
log_action :silence, @account
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsilence?
@account.update!(silenced: false)
log_action :unsilence, @account
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@ -22,6 +22,15 @@ module Admin
@form = Form::StatusBatch.new @form = Form::StatusBatch.new
end end
def show
authorize :status, :index?
@statuses = @account.statuses.where(id: params[:id])
authorize @statuses.first, :show?
@form = Form::StatusBatch.new
end
def create def create
authorize :status, :update? authorize :status, :update?

View File

@ -1,60 +0,0 @@
# frozen_string_literal: true
module Admin
class SuspensionsController < BaseController
before_action :set_account
def new
@suspension = Form::AdminSuspensionConfirmation.new(report_id: params[:report_id])
end
def create
authorize @account, :suspend?
@suspension = Form::AdminSuspensionConfirmation.new(suspension_params)
if suspension_params[:acct] == @account.acct
resolve_report! if suspension_params[:report_id].present?
perform_suspend!
mark_reports_resolved!
redirect_to admin_accounts_path
else
flash.now[:alert] = I18n.t('admin.suspensions.bad_acct_msg')
render :new
end
end
def destroy
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
def suspension_params
params.require(:form_admin_suspension_confirmation).permit(:acct, :report_id)
end
def resolve_report!
report = Report.find(suspension_params[:report_id])
report.resolve!(current_account)
log_action :resolve, report
end
def perform_suspend!
@account.suspend!
Admin::SuspensionWorker.perform_async(@account.id)
log_action :suspend, @account
end
def mark_reports_resolved!
Report.where(target_account: @account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
module Admin
class TagsController < BaseController
before_action :set_tags, only: :index
before_action :set_tag, except: :index
before_action :set_filter_params
def index
authorize :tag, :index?
end
def hide
authorize @tag, :hide?
@tag.account_tag_stat.update!(hidden: true)
redirect_to admin_tags_path(@filter_params)
end
def unhide
authorize @tag, :unhide?
@tag.account_tag_stat.update!(hidden: false)
redirect_to admin_tags_path(@filter_params)
end
private
def set_tags
@tags = Tag.discoverable
@tags.merge!(Tag.hidden) if filter_params[:hidden]
end
def set_tag
@tag = Tag.find(params[:id])
end
def set_filter_params
@filter_params = filter_params.to_hash.symbolize_keys
end
def filter_params
params.permit(:hidden)
end
end
end

View File

@ -2,7 +2,7 @@
module Admin module Admin
class TwoFactorAuthenticationsController < BaseController class TwoFactorAuthenticationsController < BaseController
before_action :set_user before_action :set_target_user
def destroy def destroy
authorize @user, :disable_2fa? authorize @user, :disable_2fa?
@ -13,7 +13,7 @@ module Admin
private private
def set_user def set_target_user
@user = User.find(params[:user_id]) @user = User.find(params[:user_id])
end end
end end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
module Admin
class WarningPresetsController < BaseController
before_action :set_warning_preset, except: [:index, :create]
def index
authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all
@warning_preset = AccountWarningPreset.new
end
def create
authorize :account_warning_preset, :create?
@warning_preset = AccountWarningPreset.new(warning_preset_params)
if @warning_preset.save
redirect_to admin_warning_presets_path
else
@warning_presets = AccountWarningPreset.all
render :index
end
end
def edit
authorize @warning_preset, :update?
end
def update
authorize @warning_preset, :update?
if @warning_preset.update(warning_preset_params)
redirect_to admin_warning_presets_path
else
render :edit
end
end
def destroy
authorize @warning_preset, :destroy?
@warning_preset.destroy!
redirect_to admin_warning_presets_path
end
private
def set_warning_preset
@warning_preset = AccountWarningPreset.find(params[:id])
end
def warning_preset_params
params.require(:account_warning_preset).permit(:text)
end
end
end

View File

@ -68,12 +68,14 @@ class Api::BaseController < ApplicationController
end end
def require_user! def require_user!
if current_user && !current_user.disabled? if !current_user
set_user_activity
elsif current_user
render json: { error: 'Your login is currently disabled' }, status: 403
else
render json: { error: 'This method requires an authenticated user' }, status: 422 render json: { error: 'This method requires an authenticated user' }, status: 422
elsif current_user.disabled?
render json: { error: 'Your login is currently disabled' }, status: 403
elsif !current_user.confirmed?
render json: { error: 'Email confirmation is not completed' }, status: 403
else
set_user_activity
end end
end end

View File

@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
private private
def account_params def account_params
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value]) params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
end end
def user_settings_params def user_settings_params

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:active_relationships).references(:active_relationships) Account.includes(:active_relationships, :account_stat).references(:active_relationships)
end end
def paginated_follows def paginated_follows

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:passive_relationships).references(:passive_relationships) Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
end end
def paginated_follows def paginated_follows

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Accounts::StatusesController < Api::BaseController class Api::V1::Accounts::StatusesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' } before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_account before_action :set_account
after_action :insert_pagination_headers after_action :insert_pagination_headers
@ -28,13 +28,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
def account_statuses def account_statuses
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
statuses = statuses.paginate_by_id( statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
statuses.merge!(only_media_scope) if truthy_param?(:only_media) statuses.merge!(only_media_scope) if truthy_param?(:only_media)
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
statuses statuses
end end
@ -65,6 +63,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
Status.without_replies Status.without_replies
end end
def no_reblogs_scope
Status.without_reblogs
end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
end end

View File

@ -1,14 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::AccountsController < Api::BaseController class Api::V1::AccountsController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute] before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute]
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow] before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute] before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock] before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
before_action :require_user!, except: [:show] before_action :require_user!, except: [:show, :create]
before_action :set_account before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show] before_action :check_account_suspension, only: [:show]
before_action :check_enabled_registrations, only: [:create]
respond_to :json respond_to :json
@ -16,6 +18,16 @@ class Api::V1::AccountsController < Api::BaseController
render json: @account, serializer: REST::AccountSerializer render json: @account, serializer: REST::AccountSerializer
end end
def create
token = AppSignUpService.new.call(doorkeeper_token.application, account_params)
response = Doorkeeper::OAuth::TokenResponse.new(token)
headers.merge!(response.headers)
self.response_body = Oj.dump(response.body)
self.status = response.status
end
def follow def follow
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs)) FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
@ -62,4 +74,12 @@ class Api::V1::AccountsController < Api::BaseController
def check_account_suspension def check_account_suspension
gone if @account.suspended? gone if @account.suspended?
end end
def account_params
params.permit(:username, :email, :password, :agreement, :locale)
end
def check_enabled_registrations
forbidden if single_user_mode? || !Setting.open_registrations
end
end end

View File

@ -19,7 +19,7 @@ class Api::V1::BlocksController < Api::BaseController
end end
def paginated_blocks def paginated_blocks
@paginated_blocks ||= Block.eager_load(:target_account) @paginated_blocks ||= Block.eager_load(target_account: :account_stat)
.where(account: current_account) .where(account: current_account)
.paginate_by_max_id( .paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT), limit_param(DEFAULT_ACCOUNTS_LIMIT),

View File

@ -4,6 +4,8 @@ class Api::V1::CustomEmojisController < Api::BaseController
respond_to :json respond_to :json
def index def index
render json: CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
end
end end
end end

View File

@ -27,7 +27,7 @@ class Api::V1::EndorsementsController < Api::BaseController
end end
def endorsed_accounts def endorsed_accounts
current_account.endorsed_accounts current_account.endorsed_accounts.includes(:account_stat)
end end
def insert_pagination_headers def insert_pagination_headers

View File

@ -33,7 +33,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:follow_requests).references(:follow_requests) Account.includes(:follow_requests, :account_stat).references(:follow_requests)
end end
def paginated_follow_requests def paginated_follow_requests

View File

@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts def load_accounts
if unlimited? if unlimited?
@list.accounts.all @list.accounts.includes(:account_stat).all
else else
@list.accounts.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) @list.accounts.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end end
end end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
class Api::V1::ScheduledStatusesController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
before_action :set_statuses, only: :index
before_action :set_status, except: :index
after_action :insert_pagination_headers, only: :index
def index
render json: @statuses, each_serializer: REST::ScheduledStatusSerializer
end
def show
render json: @status, serializer: REST::ScheduledStatusSerializer
end
def update
@status.update!(scheduled_status_params)
render json: @status, serializer: REST::ScheduledStatusSerializer
end
def destroy
@status.destroy!
render_empty
end
private
def set_statuses
@statuses = current_account.scheduled_statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_status
@status = current_account.scheduled_statuses.find(params[:id])
end
def scheduled_status_params
params.permit(:scheduled_at)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
if records_continue?
api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id)
end
end
def prev_path
unless @statuses.empty?
api_v1_scheduled_statuses_url pagination_params(min_id: pagination_since_id)
end
end
def records_continue?
@statuses.size == limit_param(DEFAULT_STATUSES_LIMIT)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@ -22,7 +22,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts def default_accounts
Account Account
.includes(:favourites) .includes(:favourites, :account_stat)
.references(:favourites) .references(:favourites)
.where(favourites: { status_id: @status.id }) .where(favourites: { status_id: @status.id })
end end

View File

@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:statuses).references(:statuses) Account.includes(:statuses, :account_stat).references(:statuses)
end end
def paginated_statuses def paginated_statuses

View File

@ -45,17 +45,18 @@ class Api::V1::StatusesController < Api::BaseController
def create def create
@status = PostStatusService.new.call(current_user.account, @status = PostStatusService.new.call(current_user.account,
status_params[:status], text: status_params[:status],
status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
media_ids: status_params[:media_ids], media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive], sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text], spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility], visibility: status_params[:visibility],
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application, application: doorkeeper_token.application,
idempotency: request.headers['Idempotency-Key'], idempotency: request.headers['Idempotency-Key'],
local_only: status_params[:local_only]) local_only: status_params[:local_only])
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
end end
def destroy def destroy
@ -78,7 +79,7 @@ class Api::V1::StatusesController < Api::BaseController
end end
def status_params def status_params
params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, :local_only, media_ids: []) params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, :scheduled_at, :local_only, media_ids: [])
end end
def pagination_params(core_params) def pagination_params(core_params)

View File

@ -45,7 +45,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end end
def tag_timeline_statuses def tag_timeline_statuses
Status.as_tag_timeline(@tag, current_account, truthy_param?(:local)) HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, truthy_param?(:local))
end end
def insert_pagination_headers def insert_pagination_headers

View File

@ -10,6 +10,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
render json: status, serializer: OEmbedSerializer, width: 400 render json: status, serializer: OEmbedSerializer, width: 400
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
oembed = FetchOEmbedService.new.call(params[:url]) oembed = FetchOEmbedService.new.call(params[:url])
oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED) if oembed[:html].present?
if oembed if oembed
render json: oembed render json: oembed

View File

@ -6,9 +6,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
before_action :set_body_classes before_action :set_body_classes
before_action :set_user, only: [:finish_signup] before_action :set_user, only: [:finish_signup]
# GET/PATCH /users/:id/finish_signup
def finish_signup def finish_signup
return unless request.patch? && params[:user] return unless request.patch? && params[:user]
if @user.update(user_params) if @user.update(user_params)
@user.skip_reconfirmation! @user.skip_reconfirmation!
bypass_sign_in(@user) bypass_sign_in(@user)
@ -31,4 +31,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
def user_params def user_params
params.require(:user).permit(:email) params.require(:user).permit(:email)
end end
def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app)
user.created_by_application.redirect_uri
else
super
end
end
end end

View File

@ -26,6 +26,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
resource.locale = I18n.locale resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank? resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.agreement = true
resource.build_account if resource.account.nil? resource.build_account if resource.account.nil?
end end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
module RemoteAccountControllerConcern
extend ActiveSupport::Concern
included do
layout 'public'
before_action :set_account
before_action :check_account_suspension
end
private
def set_account
@account = Account.find_remote!(params[:acct])
end
def check_account_suspension
gone if @account.suspended?
end
end

View File

@ -43,7 +43,13 @@ module SignatureVerification
return return
end end
account = account_from_key_id(signature_params['keyId']) account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) }
.with_fallback { nil }
.with_threshold(1)
.with_cool_off_time(5.minutes.seconds)
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
account = account_stoplight.run
if account.nil? if account.nil?
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}" @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
@ -54,23 +60,26 @@ module SignatureVerification
signature = Base64.decode64(signature_params['signature']) signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string(signature_params['headers']) compare_signed_string = build_signed_string(signature_params['headers'])
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string) return account unless verify_signature(account, signature, compare_signed_string).nil?
@signed_request_account = account
@signed_request_account
elsif account.possibly_stale?
account = account.refresh!
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string) account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
@signed_request_account = account .with_fallback { nil }
@signed_request_account .with_threshold(1)
else .with_cool_off_time(5.minutes.seconds)
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}" .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
@signed_request_account = nil
end account = account_stoplight.run
else
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}" if account.nil?
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
@signed_request_account = nil @signed_request_account = nil
return
end end
return account unless verify_signature(account, signature, compare_signed_string).nil?
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
@signed_request_account = nil
end end
def request_body def request_body
@ -79,6 +88,15 @@ module SignatureVerification
private private
def verify_signature(account, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
end
rescue OpenSSL::PKey::RSAError
nil
end
def build_signed_string(signed_headers) def build_signed_string(signed_headers)
signed_headers = 'date' if signed_headers.blank? signed_headers = 'date' if signed_headers.blank?
@ -125,4 +143,9 @@ module SignatureVerification
account account
end end
end end
def account_refresh_key(account)
return if account.local? || !account.activitypub?
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
end
end end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
class DirectoriesController < ApplicationController
layout 'public'
before_action :check_enabled
before_action :set_instance_presenter
before_action :set_tag, only: :show
before_action :set_tags
before_action :set_accounts
def index
render :index
end
def show
render :index
end
private
def check_enabled
return not_found unless Setting.profile_directory
end
def set_tag
@tag = Tag.discoverable.find_by!(name: params[:id].downcase)
end
def set_tags
@tags = Tag.discoverable.limit(30)
end
def set_accounts
@accounts = Account.discoverable.page(params[:page]).per(40).tap do |query|
query.merge!(Account.tagged_with(@tag.id)) if @tag
end
end
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
end

View File

@ -6,12 +6,17 @@ class MediaController < ApplicationController
before_action :set_media_attachment before_action :set_media_attachment
before_action :verify_permitted_status! before_action :verify_permitted_status!
content_security_policy only: :player do |p|
p.frame_ancestors(false)
end
def show def show
redirect_to @media_attachment.file.url(:original) redirect_to @media_attachment.file.url(:original)
end end
def player def player
@body_classes = 'player' @body_classes = 'player'
response.headers['X-Frame-Options'] = 'ALLOWALL'
raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv? raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
end end

View File

@ -5,6 +5,7 @@ class RemoteInteractionController < ApplicationController
layout 'modal' layout 'modal'
before_action :set_interaction_type
before_action :set_status before_action :set_status
before_action :set_body_classes before_action :set_body_classes
@ -45,4 +46,8 @@ class RemoteInteractionController < ApplicationController
@body_classes = 'modal-layout' @body_classes = 'modal-layout'
@hide_header = true @hide_header = true
end end
def set_interaction_type
@interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
end
end end

View File

@ -1,12 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ApplicationsController < ApplicationController class Settings::ApplicationsController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate] before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update] before_action :prepare_scopes, only: [:create, :update]
before_action :set_body_classes
def index def index
@applications = current_user.applications.order(id: :desc).page(params[:page]) @applications = current_user.applications.order(id: :desc).page(params[:page])
@ -70,8 +69,4 @@ class Settings::ApplicationsController < ApplicationController
scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil) scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class Settings::BaseController < ApplicationController
before_action :set_body_classes
private
def set_body_classes
@body_classes = 'admin'
end
end

View File

@ -1,11 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::DeletesController < ApplicationController class Settings::DeletesController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :check_enabled_deletion before_action :check_enabled_deletion
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show def show
@confirmation = Form::DeleteConfirmation.new @confirmation = Form::DeleteConfirmation.new
@ -30,8 +29,4 @@ class Settings::DeletesController < ApplicationController
def delete_params def delete_params
params.require(:form_delete_confirmation).permit(:password) params.require(:form_delete_confirmation).permit(:password)
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Settings
module Exports
class BlockedDomainsController < ApplicationController
include ExportControllerConcern
def index
send_export_file
end
private
def export_data
@export.to_blocked_domains_csv
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Settings
module Exports
class ListsController < ApplicationController
include ExportControllerConcern
def index
send_export_file
end
private
def export_data
@export.to_lists_csv
end
end
end
end

View File

@ -1,12 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ExportsController < ApplicationController class Settings::ExportsController < Settings::BaseController
include Authorization include Authorization
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show def show
@export = Export.new(current_account) @export = Export.new(current_account)
@ -21,10 +20,4 @@ class Settings::ExportsController < ApplicationController
redirect_to settings_export_path redirect_to settings_export_path
end end
private
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::FollowerDomainsController < ApplicationController class Settings::FollowerDomainsController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show def show
@account = current_account @account = current_account
@ -26,8 +25,4 @@ class Settings::FollowerDomainsController < ApplicationController
def bulk_params def bulk_params
params.permit(select: []) params.permit(select: [])
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,11 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ImportsController < ApplicationController class Settings::ImportsController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_account before_action :set_account
before_action :set_body_classes
def show def show
@import = Import.new @import = Import.new
@ -32,8 +31,4 @@ class Settings::ImportsController < ApplicationController
def import_params def import_params
params.require(:import).permit(:data, :type) params.require(:import).permit(:data, :type)
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::MigrationsController < ApplicationController class Settings::MigrationsController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show def show
@migration = Form::Migration.new(account: current_account.moved_to_account) @migration = Form::Migration.new(account: current_account.moved_to_account)
@ -32,8 +31,4 @@ class Settings::MigrationsController < ApplicationController
current_account.moved_to_account_id != @migration.account&.id && current_account.moved_to_account_id != @migration.account&.id &&
current_account.id != @migration.account&.id current_account.id != @migration.account&.id
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::NotificationsController < ApplicationController class Settings::NotificationsController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show; end def show; end
@ -30,8 +29,4 @@ class Settings::NotificationsController < ApplicationController
interactions: %i(must_be_follower must_be_following must_be_following_dm) interactions: %i(must_be_follower must_be_following must_be_following_dm)
) )
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::PreferencesController < ApplicationController class Settings::PreferencesController < Settings::BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def show; end def show; end
@ -49,12 +48,9 @@ class Settings::PreferencesController < ApplicationController
:setting_noindex, :setting_noindex,
:setting_theme, :setting_theme,
:setting_hide_network, :setting_hide_network,
:setting_aggregate_reblogs,
notification_emails: %i(follow follow_request reblog favourite mention digest report), notification_emails: %i(follow follow_request reblog favourite mention digest report),
interactions: %i(must_be_follower must_be_following) interactions: %i(must_be_follower must_be_following)
) )
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,13 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ProfilesController < ApplicationController class Settings::ProfilesController < Settings::BaseController
include ObfuscateFilename include ObfuscateFilename
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_account before_action :set_account
before_action :set_body_classes
obfuscate_filename [:account, :avatar] obfuscate_filename [:account, :avatar]
obfuscate_filename [:account, :header] obfuscate_filename [:account, :header]
@ -29,14 +28,10 @@ class Settings::ProfilesController < ApplicationController
private private
def account_params def account_params
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value]) params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
end end
def set_account def set_account
@account = current_user.account @account = current_user.account
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -1,8 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::SessionsController < ApplicationController class Settings::SessionsController < Settings::BaseController
before_action :set_session, only: :destroy before_action :set_session, only: :destroy
before_action :set_body_classes
def destroy def destroy
@session.destroy! @session.destroy!
@ -15,8 +14,4 @@ class Settings::SessionsController < ApplicationController
def set_session def set_session
@session = current_user.session_activations.find(params[:id]) @session = current_user.session_activations.find(params[:id])
end end
def set_body_classes
@body_classes = 'admin'
end
end end

View File

@ -2,12 +2,11 @@
module Settings module Settings
module TwoFactorAuthentication module TwoFactorAuthentication
class ConfirmationsController < ApplicationController class ConfirmationsController < BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :ensure_otp_secret before_action :ensure_otp_secret
before_action :set_body_classes
def new def new
prepare_two_factor_form prepare_two_factor_form
@ -44,10 +43,6 @@ module Settings
def ensure_otp_secret def ensure_otp_secret
redirect_to settings_two_factor_authentication_path unless current_user.otp_secret redirect_to settings_two_factor_authentication_path unless current_user.otp_secret
end end
def set_body_classes
@body_classes = 'admin'
end
end end
end end
end end

View File

@ -2,11 +2,10 @@
module Settings module Settings
module TwoFactorAuthentication module TwoFactorAuthentication
class RecoveryCodesController < ApplicationController class RecoveryCodesController < BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_body_classes
def create def create
@recovery_codes = current_user.generate_otp_backup_codes! @recovery_codes = current_user.generate_otp_backup_codes!
@ -14,12 +13,6 @@ module Settings
flash[:notice] = I18n.t('two_factor_authentication.recovery_codes_regenerated') flash[:notice] = I18n.t('two_factor_authentication.recovery_codes_regenerated')
render :index render :index
end end
private
def set_body_classes
@body_classes = 'admin'
end
end end
end end
end end

View File

@ -1,12 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
module Settings module Settings
class TwoFactorAuthenticationsController < ApplicationController class TwoFactorAuthenticationsController < BaseController
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
before_action :verify_otp_required, only: [:create] before_action :verify_otp_required, only: [:create]
before_action :set_body_classes
def show def show
@confirmation = Form::TwoFactorConfirmation.new @confirmation = Form::TwoFactorConfirmation.new
@ -44,9 +43,5 @@ module Settings
current_user.validate_and_consume_otp!(confirmation_params[:code]) || current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
current_user.invalidate_otp_backup_code!(confirmation_params[:code]) current_user.invalidate_otp_backup_code!(confirmation_params[:code])
end end
def set_body_classes
@body_classes = 'admin'
end
end end
end end

View File

@ -65,12 +65,13 @@ class StatusesController < ApplicationController
private private
def create_descendant_thread(depth, statuses) def create_descendant_thread(starting_depth, statuses)
depth = starting_depth + statuses.size
if depth < DESCENDANTS_DEPTH_LIMIT if depth < DESCENDANTS_DEPTH_LIMIT
{ statuses: statuses } { statuses: statuses, starting_depth: starting_depth }
else else
next_status = statuses.pop next_status = statuses.pop
{ statuses: statuses, next_status: next_status } { statuses: statuses, starting_depth: starting_depth, next_status: next_status }
end end
end end
@ -101,16 +102,19 @@ class StatusesController < ApplicationController
@descendant_threads = [] @descendant_threads = []
if descendants.present? if descendants.present?
statuses = [descendants.first] statuses = [descendants.first]
depth = 1 starting_depth = 0
descendants.drop(1).each_with_index do |descendant, index| descendants.drop(1).each_with_index do |descendant, index|
if descendants[index].id == descendant.in_reply_to_id if descendants[index].id == descendant.in_reply_to_id
depth += 1
statuses << descendant statuses << descendant
else else
@descendant_threads << create_descendant_thread(depth, statuses) @descendant_threads << create_descendant_thread(starting_depth, statuses)
# The thread is broken, assume it's a reply to the root status
starting_depth = 0
# ... unless we can find its ancestor in one of the already-processed threads
@descendant_threads.reverse_each do |descendant_thread| @descendant_threads.reverse_each do |descendant_thread|
statuses = descendant_thread[:statuses] statuses = descendant_thread[:statuses]
@ -119,18 +123,16 @@ class StatusesController < ApplicationController
end end
if index.present? if index.present?
depth += index - statuses.size starting_depth = descendant_thread[:starting_depth] + index + 1
break break
end end
depth -= statuses.size
end end
statuses = [descendant] statuses = [descendant]
end end
end end
@descendant_threads << create_descendant_thread(depth, statuses) @descendant_threads << create_descendant_thread(starting_depth, statuses)
end end
@max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT

View File

@ -16,14 +16,15 @@ class TagsController < ApplicationController
end end
format.rss do format.rss do
@statuses = Status.as_tag_timeline(@tag).limit(PAGE_SIZE) @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none)).limit(PAGE_SIZE)
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
render xml: RSS::TagSerializer.render(@tag, @statuses) render xml: RSS::TagSerializer.render(@tag, @statuses)
end end
format.json do format.json do
@statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id]) @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local])
.paginate_by_max_id(PAGE_SIZE, params[:max_id])
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
render json: collection_presenter, render json: collection_presenter,
@ -46,7 +47,7 @@ class TagsController < ApplicationController
def collection_presenter def collection_presenter
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(
id: tag_url(@tag), id: tag_url(@tag, params.slice(:any, :all, :none)),
type: :ordered, type: :ordered,
size: @tag.statuses.count, size: @tag.statuses.count,
items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) } items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }

View File

@ -24,7 +24,7 @@ module Admin::AccountModerationNotesHelper
def name_tag_classes(account, inline = false) def name_tag_classes(account, inline = false)
classes = [inline ? 'inline-name-tag' : 'name-tag'] classes = [inline ? 'inline-name-tag' : 'name-tag']
classes << 'suspended' if account.suspended? classes << 'suspended' if account.suspended? || (account.local? && account.user.nil?)
classes.join(' ') classes.join(' ')
end end
end end

View File

@ -23,6 +23,8 @@ module Admin::ActionLogsHelper
link_to record.domain, "https://#{record.domain}" link_to record.domain, "https://#{record.domain}"
when 'Status' when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record) link_to record.account.acct, TagManager.instance.url_for(record)
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
end end
end end
@ -34,6 +36,7 @@ module Admin::ActionLogsHelper
link_to attributes['domain'], "https://#{attributes['domain']}" link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status' when 'Status'
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count')) tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
if tmp_status.account if tmp_status.account
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id) link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
else else
@ -81,6 +84,8 @@ module Admin::ActionLogsHelper
'envelope' 'envelope'
when 'Status' when 'Status'
'pencil' 'pencil'
when 'AccountWarning'
'warning'
end end
end end
@ -92,7 +97,7 @@ module Admin::ActionLogsHelper
opposite_verbs?(log) ? 'negative' : 'positive' opposite_verbs?(log) ? 'negative' : 'positive'
when :update, :reset_password, :disable_2fa, :memorialize, :change_email when :update, :reset_password, :disable_2fa, :memorialize, :change_email
'neutral' 'neutral'
when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen when :demote, :silence, :disable, :suspend, :remove_avatar, :remove_header, :reopen
'negative' 'negative'
when :destroy when :destroy
opposite_verbs?(log) ? 'positive' : 'negative' opposite_verbs?(log) ? 'positive' : 'negative'
@ -104,6 +109,6 @@ module Admin::ActionLogsHelper
private private
def opposite_verbs?(log) def opposite_verbs?(log)
%w(DomainBlock EmailDomainBlock).include?(log.target_type) %w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
end end
end end

View File

@ -1,12 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
module Admin::FilterHelper module Admin::FilterHelper
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended alphabetic username display_name email ip staff).freeze ACCOUNT_FILTERS = %i(local remote by_domain active silenced suspended username display_name email ip staff).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
INVITE_FILTER = %i(available expired).freeze INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
TAGS_FILTERS = %i(hidden).freeze
INSTANCES_FILTERS = %i(limited).freeze
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS
def filter_link_to(text, link_to_params, link_class_params = link_to_params) def filter_link_to(text, link_to_params, link_class_params = link_to_params)
new_url = filtered_url_for(link_to_params) new_url = filtered_url_for(link_to_params)

View File

@ -23,7 +23,7 @@ module HomeHelper
else else
link_to(path || TagManager.instance.url_for(account), class: 'account__display-name') do link_to(path || TagManager.instance.url_for(account), class: 'account__display-name') do
content_tag(:div, class: 'account__avatar-wrapper') do content_tag(:div, class: 'account__avatar-wrapper') do
content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{account.avatar.url})") content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
end + end +
content_tag(:span, class: 'display-name') do content_tag(:span, class: 'display-name') do
content_tag(:bdi) do content_tag(:bdi) do

View File

@ -1,4 +0,0 @@
# frozen_string_literal: true
module MailerHelper
end

View File

@ -30,6 +30,7 @@ module SettingsHelper
ja: '日本語', ja: '日本語',
ka: 'ქართული', ka: 'ქართული',
ko: '한국어', ko: '한국어',
ml: 'മലയാളം',
nl: 'Nederlands', nl: 'Nederlands',
no: 'Norsk', no: 'Norsk',
oc: 'Occitan', oc: 'Occitan',

View File

@ -34,12 +34,14 @@ module StreamEntriesHelper
end end
end end
def account_badge(account) def account_badge(account, all: false)
if account.bot? if account.bot?
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
elsif Setting.show_staff_badge && account.user_staff? elsif (Setting.show_staff_badge && account.user_staff?) || all
content_tag(:div, class: 'roles') do content_tag(:div, class: 'roles') do
if account.user_admin? if all && !account.user_staff?
content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role')
elsif account.user_admin?
content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin')
elsif account.user_moderator? elsif account.user_moderator?
content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator')

View File

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"/>
</svg>

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -132,6 +132,12 @@ export function submitCompose(routerHistory) {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
}, },
}).then(function (response) { }).then(function (response) {
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
routerHistory.push('/timelines/direct');
} else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
routerHistory.goBack();
}
dispatch(insertIntoTagHistory(response.data.tags, status)); dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(submitComposeSuccess({ ...response.data })); dispatch(submitComposeSuccess({ ...response.data }));
@ -144,9 +150,7 @@ export function submitCompose(routerHistory) {
} }
}; };
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) { if (response.data.visibility !== 'direct') {
routerHistory.push('/timelines/direct');
} else if (response.data.visibility !== 'direct') {
insertIfOnline('home'); insertIfOnline('home');
} }

View File

@ -38,7 +38,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
const params = { max_id: maxId }; const params = { max_id: maxId };
if (!maxId) { if (!maxId) {
params.since_id = getState().getIn(['conversations', 0, 'last_status']); params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']);
} }
api(getState).get('/api/v1/conversations', { params }) api(getState).get('/api/v1/conversations', { params })

View File

@ -19,6 +19,7 @@ export function fetchCustomEmojis() {
export function fetchCustomEmojisRequest() { export function fetchCustomEmojisRequest() {
return { return {
type: CUSTOM_EMOJIS_FETCH_REQUEST, type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
}; };
}; };
@ -26,6 +27,7 @@ export function fetchCustomEmojisSuccess(custom_emojis) {
return { return {
type: CUSTOM_EMOJIS_FETCH_SUCCESS, type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis, custom_emojis,
skipLoading: true,
}; };
}; };
@ -33,5 +35,6 @@ export function fetchCustomEmojisFail(error) {
return { return {
type: CUSTOM_EMOJIS_FETCH_FAIL, type: CUSTOM_EMOJIS_FETCH_FAIL,
error, error,
skipLoading: true,
}; };
}; };

View File

@ -30,6 +30,7 @@ export function fetchFavouritedStatuses() {
export function fetchFavouritedStatusesRequest() { export function fetchFavouritedStatusesRequest() {
return { return {
type: FAVOURITED_STATUSES_FETCH_REQUEST, type: FAVOURITED_STATUSES_FETCH_REQUEST,
skipLoading: true,
}; };
}; };
@ -38,6 +39,7 @@ export function fetchFavouritedStatusesSuccess(statuses, next) {
type: FAVOURITED_STATUSES_FETCH_SUCCESS, type: FAVOURITED_STATUSES_FETCH_SUCCESS,
statuses, statuses,
next, next,
skipLoading: true,
}; };
}; };
@ -45,6 +47,7 @@ export function fetchFavouritedStatusesFail(error) {
return { return {
type: FAVOURITED_STATUSES_FETCH_FAIL, type: FAVOURITED_STATUSES_FETCH_FAIL,
error, error,
skipLoading: true,
}; };
}; };

View File

@ -42,6 +42,13 @@ export const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST';
export const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS'; export const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS';
export const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL'; export const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL';
export const LIST_ADDER_RESET = 'LIST_ADDER_RESET';
export const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP';
export const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST';
export const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS';
export const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL';
export const fetchList = id => (dispatch, getState) => { export const fetchList = id => (dispatch, getState) => {
if (getState().getIn(['lists', id])) { if (getState().getIn(['lists', id])) {
return; return;
@ -316,3 +323,50 @@ export const removeFromListFail = (listId, accountId, error) => ({
accountId, accountId,
error, error,
}); });
export const resetListAdder = () => ({
type: LIST_ADDER_RESET,
});
export const setupListAdder = accountId => (dispatch, getState) => {
dispatch({
type: LIST_ADDER_SETUP,
account: getState().getIn(['accounts', accountId]),
});
dispatch(fetchLists());
dispatch(fetchAccountLists(accountId));
};
export const fetchAccountLists = accountId => (dispatch, getState) => {
dispatch(fetchAccountListsRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/lists`)
.then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data)))
.catch(err => dispatch(fetchAccountListsFail(accountId, err)));
};
export const fetchAccountListsRequest = id => ({
type:LIST_ADDER_LISTS_FETCH_REQUEST,
id,
});
export const fetchAccountListsSuccess = (id, lists) => ({
type: LIST_ADDER_LISTS_FETCH_SUCCESS,
id,
lists,
});
export const fetchAccountListsFail = (id, err) => ({
type: LIST_ADDER_LISTS_FETCH_FAIL,
id,
err,
});
export const addToListAdder = listId => (dispatch, getState) => {
dispatch(addToList(listId, getState().getIn(['listAdder', 'accountId'])));
};
export const removeFromListAdder = listId => (dispatch, getState) => {
dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
};

View File

@ -8,6 +8,7 @@ import {
importFetchedStatuses, importFetchedStatuses,
} from './importer'; } from './importer';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from '../utils/html'; import { unescapeHTML } from '../utils/html';
import { getFilters, regexFromFilters } from '../selectors'; import { getFilters, regexFromFilters } from '../selectors';
@ -18,6 +19,8 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
@ -88,11 +91,18 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
const excludeTypesFromFilter = filter => {
const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
return allTypes.filterNot(item => item === filter).toJS();
};
const noOp = () => {}; const noOp = () => {};
export function expandNotifications({ maxId } = {}, done = noOp) { export function expandNotifications({ maxId } = {}, done = noOp) {
return (dispatch, getState) => { return (dispatch, getState) => {
const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
const notifications = getState().get('notifications'); const notifications = getState().get('notifications');
const isLoadingMore = !!maxId;
if (notifications.get('isLoading')) { if (notifications.get('isLoading')) {
done(); done();
@ -101,14 +111,16 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
const params = { const params = {
max_id: maxId, max_id: maxId,
exclude_types: excludeTypesFromSettings(getState()), exclude_types: activeFilter === 'all'
? excludeTypesFromSettings(getState())
: excludeTypesFromFilter(activeFilter),
}; };
if (!maxId && notifications.get('items').size > 0) { if (!maxId && notifications.get('items').size > 0) {
params.since_id = notifications.getIn(['items', 0]); params.since_id = notifications.getIn(['items', 0, 'id']);
} }
dispatch(expandNotificationsRequest()); dispatch(expandNotificationsRequest(isLoadingMore));
api(getState).get('/api/v1/notifications', { params }).then(response => { api(getState).get('/api/v1/notifications', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
@ -116,34 +128,37 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));
fetchRelatedRelationships(dispatch, response.data); fetchRelatedRelationships(dispatch, response.data);
done(); done();
}).catch(error => { }).catch(error => {
dispatch(expandNotificationsFail(error)); dispatch(expandNotificationsFail(error, isLoadingMore));
done(); done();
}); });
}; };
}; };
export function expandNotificationsRequest() { export function expandNotificationsRequest(isLoadingMore) {
return { return {
type: NOTIFICATIONS_EXPAND_REQUEST, type: NOTIFICATIONS_EXPAND_REQUEST,
skipLoading: !isLoadingMore,
}; };
}; };
export function expandNotificationsSuccess(notifications, next) { export function expandNotificationsSuccess(notifications, next, isLoadingMore) {
return { return {
type: NOTIFICATIONS_EXPAND_SUCCESS, type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications, notifications,
next, next,
skipLoading: !isLoadingMore,
}; };
}; };
export function expandNotificationsFail(error) { export function expandNotificationsFail(error, isLoadingMore) {
return { return {
type: NOTIFICATIONS_EXPAND_FAIL, type: NOTIFICATIONS_EXPAND_FAIL,
error, error,
skipLoading: !isLoadingMore,
}; };
}; };
@ -163,3 +178,14 @@ export function scrollTopNotifications(top) {
top, top,
}; };
}; };
export function setFilter (filterType) {
return dispatch => {
dispatch({
type: NOTIFICATIONS_FILTER_SET,
path: ['notifications', 'quickFilter', 'active'],
value: filterType,
});
dispatch(expandNotifications());
};
};

View File

@ -1,14 +1,8 @@
import { openModal } from './modal';
import { changeSetting, saveSettings } from './settings'; import { changeSetting, saveSettings } from './settings';
export function showOnboardingOnce() { export const INTRODUCTION_VERSION = 20181216044202;
return (dispatch, getState) => {
const alreadySeen = getState().getIn(['settings', 'onboarded']);
if (!alreadySeen) { export const closeOnboarding = () => dispatch => {
dispatch(openModal('ONBOARDING')); dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
dispatch(changeSetting(['onboarded'], true)); dispatch(saveSettings());
dispatch(saveSettings());
}
};
}; };

View File

@ -12,7 +12,7 @@ import { getLocale } from '../locales';
const { messages } = getLocale(); const { messages } = getLocale();
export function connectTimelineStream (timelineId, path, pollingRefresh = null) { export function connectTimelineStream (timelineId, path, pollingRefresh = null, accept = null) {
return connectStream (path, pollingRefresh, (dispatch, getState) => { return connectStream (path, pollingRefresh, (dispatch, getState) => {
const locale = getState().getIn(['meta', 'locale']); const locale = getState().getIn(['meta', 'locale']);
@ -24,7 +24,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
onReceive (data) { onReceive (data) {
switch(data.event) { switch(data.event) {
case 'update': case 'update':
dispatch(updateTimeline(timelineId, JSON.parse(data.payload))); dispatch(updateTimeline(timelineId, JSON.parse(data.payload), accept));
break; break;
case 'delete': case 'delete':
dispatch(deleteFromTimelines(data.payload)); dispatch(deleteFromTimelines(data.payload));
@ -51,6 +51,6 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
export const connectHashtagStream = tag => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);

View File

@ -4,6 +4,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE';
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
@ -13,9 +14,11 @@ export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export function updateTimeline(timeline, status) { export function updateTimeline(timeline, status, accept) {
return (dispatch, getState) => { return dispatch => {
const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; if (typeof accept === 'function' && !accept(status)) {
return;
}
dispatch(importFetchedStatus(status)); dispatch(importFetchedStatus(status));
@ -23,7 +26,6 @@ export function updateTimeline(timeline, status) {
type: TIMELINE_UPDATE, type: TIMELINE_UPDATE,
timeline, timeline,
status, status,
references,
}); });
}; };
}; };
@ -44,11 +46,24 @@ export function deleteFromTimelines(id) {
}; };
}; };
export function clearTimeline(timeline) {
return (dispatch) => {
dispatch({ type: TIMELINE_CLEAR, timeline });
};
};
const noOp = () => {}; const noOp = () => {};
const parseTags = (tags = {}, mode) => {
return (tags[mode] || []).map((tag) => {
return tag.value;
});
};
export function expandTimeline(timelineId, path, params = {}, done = noOp) { export function expandTimeline(timelineId, path, params = {}, done = noOp) {
return (dispatch, getState) => { return (dispatch, getState) => {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
const isLoadingMore = !!params.max_id;
if (timeline.get('isLoading')) { if (timeline.get('isLoading')) {
done(); done();
@ -59,15 +74,17 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
params.since_id = timeline.getIn(['items', 0]); params.since_id = timeline.getIn(['items', 0]);
} }
dispatch(expandTimelineRequest(timelineId)); const isLoadingRecent = !!params.since_id;
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
api(getState).get(path, { params }).then(response => { api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data)); dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206)); dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
done(); done();
}).catch(error => { }).catch(error => {
dispatch(expandTimelineFail(timelineId, error)); dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
done(); done();
}); });
}; };
@ -79,31 +96,42 @@ export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done =
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done);
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
max_id: maxId,
any: parseTags(tags, 'any'),
all: parseTags(tags, 'all'),
none: parseTags(tags, 'none'),
}, done);
};
export function expandTimelineRequest(timeline) { export function expandTimelineRequest(timeline, isLoadingMore) {
return { return {
type: TIMELINE_EXPAND_REQUEST, type: TIMELINE_EXPAND_REQUEST,
timeline, timeline,
skipLoading: !isLoadingMore,
}; };
}; };
export function expandTimelineSuccess(timeline, statuses, next, partial) { export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
return { return {
type: TIMELINE_EXPAND_SUCCESS, type: TIMELINE_EXPAND_SUCCESS,
timeline, timeline,
statuses, statuses,
next, next,
partial, partial,
isLoadingRecent,
skipLoading: !isLoadingMore,
}; };
}; };
export function expandTimelineFail(timeline, error) { export function expandTimelineFail(timeline, error, isLoadingMore) {
return { return {
type: TIMELINE_EXPAND_FAIL, type: TIMELINE_EXPAND_FAIL,
timeline, timeline,
error, error,
skipLoading: !isLoadingMore,
}; };
}; };

View File

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import LinkHeader from 'http-link-header';
import ready from './ready'; import ready from './ready';
import LinkHeader from './link_header';
export const getLinks = response => { export const getLinks = response => {
const value = response.headers.link; const value = response.headers.link;

View File

@ -68,10 +68,10 @@ class Account extends ImmutablePureComponent {
if (hidden) { if (hidden) {
return ( return (
<div> <Fragment>
{account.get('display_name')} {account.get('display_name')}
{account.get('username')} {account.get('username')}
</div> </Fragment>
); );
} }

View File

@ -37,6 +37,14 @@ class ColumnHeader extends React.PureComponent {
animating: false, animating: false,
}; };
historyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
this.context.router.history.goBack();
}
}
handleToggleClick = (e) => { handleToggleClick = (e) => {
e.stopPropagation(); e.stopPropagation();
this.setState({ collapsed: !this.state.collapsed, animating: true }); this.setState({ collapsed: !this.state.collapsed, animating: true });
@ -55,16 +63,22 @@ class ColumnHeader extends React.PureComponent {
} }
handleBackClick = () => { handleBackClick = () => {
if (window.history && window.history.length === 1) this.context.router.history.push('/'); this.historyBack();
else this.context.router.history.goBack();
} }
handleTransitionEnd = () => { handleTransitionEnd = () => {
this.setState({ animating: false }); this.setState({ animating: false });
} }
handlePin = () => {
if (!this.props.pinned) {
this.historyBack();
}
this.props.onPin();
}
render () { render () {
const { title, icon, active, children, pinned, onPin, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props; const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', { const wrapperClassName = classNames('column-header__wrapper', {
@ -95,7 +109,7 @@ class ColumnHeader extends React.PureComponent {
} }
if (multiColumn && pinned) { if (multiColumn && pinned) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
moveButtons = ( moveButtons = (
<div key='move-buttons' className='column-header__setting-arrows'> <div key='move-buttons' className='column-header__setting-arrows'>
@ -104,7 +118,7 @@ class ColumnHeader extends React.PureComponent {
</div> </div>
); );
} else if (multiColumn) { } else if (multiColumn) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
} }
if (!pinned && (multiColumn || showBackButton)) { if (!pinned && (multiColumn || showBackButton)) {

View File

@ -51,6 +51,10 @@ class Item extends React.PureComponent {
const { index, onClick } = this.props; const { index, onClick } = this.props;
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
if (this.hoverToPlay()) {
e.target.pause();
e.target.currentTime = 0;
}
e.preventDefault(); e.preventDefault();
onClick(index); onClick(index);
} }

Some files were not shown because too many files have changed in this diff Show More