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

View File

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

View File

@ -1,30 +1,13 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# 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/*
/build/**
/coverage/**
/db/**
/lib/**
/log/**
/node_modules/**
/nonobox/**
/public/**
!/public/embed.js
/spec/**
/tmp/**
/vendor/**
!.eslintrc.js

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)
* [ykzts](https://github.com/ykzts)
* [akihikodaki](https://github.com/akihikodaki)
* [mjankowski](https://github.com/mjankowski)
* [ThibG](https://github.com/ThibG)
* [mjankowski](https://github.com/mjankowski)
* [unarist](https://github.com/unarist)
* [m4sk1n](https://github.com/m4sk1n)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [yiskah](https://github.com/yiskah)
* [nolanlawson](https://github.com/nolanlawson)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [ysksn](https://github.com/ysksn)
* [abcang](https://github.com/abcang)
* [lynlynlynx](https://github.com/lynlynlynx)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [alpaca-tc](https://github.com/alpaca-tc)
* [mayaeh](https://github.com/mayaeh)
* [renatolond](https://github.com/renatolond)
* [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble)
* [renatolond](https://github.com/renatolond)
* [jeroenpraat](https://github.com/jeroenpraat)
* [mayaeh](https://github.com/mayaeh)
* [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP)
* [mabkenar](https://github.com/mabkenar)
* [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala)
* [mabkenar](https://github.com/mabkenar)
* [ysksn](https://github.com/ysksn)
* [Kjwon15](https://github.com/Kjwon15)
* [shuheiktgw](https://github.com/shuheiktgw)
* [ashfurrow](https://github.com/ashfurrow)
* [Kjwon15](https://github.com/Kjwon15)
* [Quenty31](https://github.com/Quenty31)
* [zunda](https://github.com/zunda)
* [eramdam](https://github.com/eramdam)
* [masarakki](https://github.com/masarakki)
* [takayamaki](https://github.com/takayamaki)
* [masarakki](https://github.com/masarakki)
* [ticky](https://github.com/ticky)
* [Quenty31](https://github.com/Quenty31)
* [danhunsaker](https://github.com/danhunsaker)
* [ThisIsMissEm](https://github.com/ThisIsMissEm)
* [hcmiya](https://github.com/hcmiya)
@ -88,16 +88,19 @@ and provided thanks to the work of the following contributors:
* [mistydemeo](https://github.com/mistydemeo)
* [dunn](https://github.com/dunn)
* [xqus](https://github.com/xqus)
* [hugogameiro](https://github.com/hugogameiro)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [fakenine](https://github.com/fakenine)
* [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [kedamaDQ](https://github.com/kedamaDQ)
* [puckipedia](https://github.com/puckipedia)
* [fvh-P](https://github.com/fvh-P)
* [contraexemplo](https://github.com/contraexemplo)
* [hugogameiro](https://github.com/hugogameiro)
* [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic)
* [lmorchard](https://github.com/lmorchard)
* [diomed](https://github.com/diomed)
* [ariasuni](https://github.com/ariasuni)
* [Neetshin](mailto:neetshin@neetsh.in)
@ -105,7 +108,6 @@ and provided thanks to the work of the following contributors:
* [ProgVal](https://github.com/ProgVal)
* [valentin2105](https://github.com/valentin2105)
* [yuntan](https://github.com/yuntan)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [goofy-bz](mailto:goofy@babelzilla.org)
* [kadiix](https://github.com/kadiix)
* [kodacs](https://github.com/kodacs)
@ -119,35 +121,37 @@ and provided thanks to the work of the following contributors:
* [northerner](https://github.com/northerner)
* [fhemberger](https://github.com/fhemberger)
* [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)
* [Reverite](https://github.com/Reverite)
* [JMendyk](https://github.com/JMendyk)
* [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz)
* [Naouak](https://github.com/Naouak)
* [pawelngei](https://github.com/pawelngei)
* [reneklacan](https://github.com/reneklacan)
* [ekiru](https://github.com/ekiru)
* [tcitworld](https://github.com/tcitworld)
* [geta6](https://github.com/geta6)
* [happycoloredbanana](https://github.com/happycoloredbanana)
* [kedamaDQ](https://github.com/kedamaDQ)
* [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix)
* [tomfhowe](https://github.com/tomfhowe)
* [noraworld](https://github.com/noraworld)
* [theboss](https://github.com/theboss)
* [178inaba](https://github.com/178inaba)
* [Aditoo17](https://github.com/Aditoo17)
* [alyssais](https://github.com/alyssais)
* [kodnaplakal](https://github.com/kodnaplakal)
* [stalker314314](https://github.com/stalker314314)
* [huertanix](https://github.com/huertanix)
* [genesixx](https://github.com/genesixx)
* [halkeye](https://github.com/halkeye)
* [hinaloe](https://github.com/hinaloe)
* [treby](https://github.com/treby)
* [Reverite](https://github.com/Reverite)
* [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)
* [saper](https://github.com/saper)
* [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)
* [harukasan](https://github.com/harukasan)
* [stamak](https://github.com/stamak)
* [noellabo](https://github.com/noellabo)
* [Technowix](mailto:technowix@users.noreply.github.com)
* [Eychics](https://github.com/Eychics)
* [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)
* [R0ckweb](https://github.com/R0ckweb)
* [caasi](https://github.com/caasi)
* [chr-1x](https://github.com/chr-1x)
* [esetomo](https://github.com/esetomo)
* [foxiehkins](https://github.com/foxiehkins)
* [hoodie](mailto:hoodiekitten@outlook.com)
* [luzi82](https://github.com/luzi82)
* [duxovni](https://github.com/duxovni)
* [trwnh](https://github.com/trwnh)
* [unsmell](https://github.com/unsmell)
* [valerauko](https://github.com/valerauko)
* [chriswmartin](https://github.com/chriswmartin)
* [vahnj](https://github.com/vahnj)
* [ikuradon](https://github.com/ikuradon)
* [AndreLewin](https://github.com/AndreLewin)
* [rinsuki](https://github.com/rinsuki)
* [0xflotus](https://github.com/0xflotus)
* [redtachyons](https://github.com/redtachyons)
* [thurloat](https://github.com/thurloat)
* [aaribaud](https://github.com/aaribaud)
* [Andrew](mailto:andrewlchronister@gmail.com)
* [estuans](https://github.com/estuans)
* [BenLubar](https://github.com/BenLubar)
* [dissolve](https://github.com/dissolve)
* [PurpleBooth](https://github.com/PurpleBooth)
* [bradurani](https://github.com/bradurani)
@ -192,7 +202,7 @@ and provided thanks to the work of the following contributors:
* [cdutson](https://github.com/cdutson)
* [farlistener](https://github.com/farlistener)
* [DavidLibeau](https://github.com/DavidLibeau)
* [SirCmpwn](https://github.com/SirCmpwn)
* [ddevault](https://github.com/ddevault)
* [Fjoerfoks](https://github.com/Fjoerfoks)
* [fmauNeko](https://github.com/fmauNeko)
* [gloaec](https://github.com/gloaec)
@ -207,6 +217,7 @@ and provided thanks to the work of the following contributors:
* [jasonrhodes](https://github.com/jasonrhodes)
* [Jason Snell](mailto:jason@newrelic.com)
* [jviide](https://github.com/jviide)
* [YuleZ](https://github.com/YuleZ)
* [crakaC](https://github.com/crakaC)
* [tkbky](https://github.com/tkbky)
* [Kaylee](mailto:kaylee@codethat.sucks)
@ -223,10 +234,12 @@ and provided thanks to the work of the following contributors:
* [petzah](https://github.com/petzah)
* [ignisf](https://github.com/ignisf)
* [raymestalez](https://github.com/raymestalez)
* [remram44](https://github.com/remram44)
* [sascha-sl](https://github.com/sascha-sl)
* [u1-liquid](https://github.com/u1-liquid)
* [sim6](https://github.com/sim6)
* [stemid](https://github.com/stemid)
* [sumdog](https://github.com/sumdog)
* [ThomasLeister](https://github.com/ThomasLeister)
* [mcat-ee](https://github.com/mcat-ee)
* [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)
* [imbsky](https://github.com/imbsky)
* [bsky](mailto:me@imbsky.net)
* [chr-1x](https://github.com/chr-1x)
* [codl](https://github.com/codl)
* [cpsdqs](https://github.com/cpsdqs)
* [barzamin](https://github.com/barzamin)
@ -252,6 +264,7 @@ and provided thanks to the work of the following contributors:
* [ik11235](https://github.com/ik11235)
* [kawax](https://github.com/kawax)
* [007lva](https://github.com/007lva)
* [mbajur](https://github.com/mbajur)
* [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab)
* [nicobz25](https://github.com/nicobz25)
@ -259,7 +272,6 @@ and provided thanks to the work of the following contributors:
* [pinfort](https://github.com/pinfort)
* [rbaumert](https://github.com/rbaumert)
* [rhoio](https://github.com/rhoio)
* [trwnh](https://github.com/trwnh)
* [usagi-f](https://github.com/usagi-f)
* [vidarlee](https://github.com/vidarlee)
* [vjackson725](https://github.com/vjackson725)
@ -269,11 +281,11 @@ and provided thanks to the work of the following contributors:
* [Awea](https://github.com/Awea)
* [halcy](https://github.com/halcy)
* [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf)
* [NecroTechno](https://github.com/NecroTechno)
* [8398a7](https://github.com/8398a7)
* [857b](https://github.com/857b)
* [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)
* [Aguay-val](https://github.com/Aguay-val)
* [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)
* [console-cowboy](https://github.com/console-cowboy)
* [pointlessone](https://github.com/pointlessone)
* [Alkarex](https://github.com/Alkarex)
* [a2](https://github.com/a2)
* [0xa](https://github.com/0xa)
* [palindromordnilap](https://github.com/palindromordnilap)
@ -299,7 +312,6 @@ and provided thanks to the work of the following contributors:
* [ayumin](https://github.com/ayumin)
* [BaptisteGelez](https://github.com/BaptisteGelez)
* [bzg](https://github.com/bzg)
* [BenLubar](https://github.com/BenLubar)
* [benediktg](https://github.com/benediktg)
* [blakebarnett](https://github.com/blakebarnett)
* [bradj](https://github.com/bradj)
@ -341,6 +353,7 @@ and provided thanks to the work of the following contributors:
* [espenronnevik](https://github.com/espenronnevik)
* [Finariel](https://github.com/Finariel)
* [siuying](https://github.com/siuying)
* [fwenzel](https://github.com/fwenzel)
* [GenbuHase](https://github.com/GenbuHase)
* [hattori6789](https://github.com/hattori6789)
* [algernon](https://github.com/algernon)
@ -375,10 +388,9 @@ and provided thanks to the work of the following contributors:
* [jguerder](https://github.com/jguerder)
* [Jehops](https://github.com/Jehops)
* [joshuap](https://github.com/joshuap)
* [YuleZ](https://github.com/YuleZ)
* [Tiwy57](https://github.com/Tiwy57)
* [xuv](https://github.com/xuv)
* [Jnsll](https://github.com/Jnsll)
* [June Sallou](mailto:jnsll@users.noreply.github.com)
* [j0k3r](https://github.com/j0k3r)
* [KEINOS](https://github.com/KEINOS)
* [futoase](https://github.com/futoase)
@ -389,7 +401,6 @@ and provided thanks to the work of the following contributors:
* [k0ta0uchi](https://github.com/k0ta0uchi)
* [KrzysiekJ](https://github.com/KrzysiekJ)
* [leowzukw](https://github.com/leowzukw)
* [lmorchard](https://github.com/lmorchard)
* [Tak](https://github.com/Tak)
* [cacheflow](https://github.com/cacheflow)
* [ldidry](https://github.com/ldidry)
@ -426,6 +437,7 @@ and provided thanks to the work of the following contributors:
* [lae](https://github.com/lae)
* [Nanamachi](https://github.com/Nanamachi)
* [orinthe](https://github.com/orinthe)
* [NecroTechno](https://github.com/NecroTechno)
* [Dar13](https://github.com/Dar13)
* [ngerakines](https://github.com/ngerakines)
* [vonneudeck](https://github.com/vonneudeck)
@ -443,7 +455,6 @@ and provided thanks to the work of the following contributors:
* [Pangoraw](https://github.com/Pangoraw)
* [peterkeen](https://github.com/peterkeen)
* [pgate](https://github.com/pgate)
* [remram44](https://github.com/remram44)
* [retokromer](https://github.com/retokromer)
* [rfwatson](https://github.com/rfwatson)
* [rfreebern](https://github.com/rfreebern)
@ -455,19 +466,22 @@ and provided thanks to the work of the following contributors:
* [sts10](https://github.com/sts10)
* [skoji](https://github.com/skoji)
* [ScienJus](https://github.com/ScienJus)
* [larkinscott](https://github.com/larkinscott)
* [imolein](https://github.com/imolein)
* [blinry](https://github.com/blinry)
* [Noiwex](https://github.com/Noiwex)
* [yuki764](https://github.com/yuki764)
* [shnjp](https://github.com/shnjp)
* [ernix](https://github.com/ernix)
* [rosylilly](https://github.com/rosylilly)
* [shouko](https://github.com/shouko)
* [Scott Larkin](mailto:scott@codeclimate.com)
* [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
* [Sebastian Morr](mailto:sebastian@morr.cc)
* [Sergei Č](mailto:noiwex1911@gmail.com)
* [Setuu](mailto:yuki764setuu@gmail.com)
* [Shaun Gillies](mailto:me@shaungillies.net)
* [Shin Adachi](mailto:shn@glucose.jp)
* [Shin Kojima](mailto:shin@kojima.org)
* [Sho Kusano](mailto:rosylilly@aduca.org)
* [Shouko Yu](mailto:imshouko@gmail.com)
* [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)
* [StefOfficiel](mailto:pichard.stephane@free.fr)
* [Steven Tappert](mailto:admin@dark-it.net)
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
* [Sébastien Santoro](mailto:dereckson@espace-win.org)
* [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)
* [jenn kaplan](mailto:me@jkap.io)
* [jirayudech](mailto:jirayudech@gmail.com)
* [jomo](mailto:github@jomo.tv)
* [jooops](mailto:joops@autistici.org)
* [jukper](mailto:jukkaperanto@gmail.com)
* [jumoru](mailto:jumoru@mailbox.org)
* [karlyeurl](mailto:karl.yeurl@gmail.com)
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
* [kodai](mailto:shirafuta.kodai@gmail.com)
* [kuro5hin](mailto:rusty@kuro5hin.org)
* [luzpaz](mailto:luzpaz@users.noreply.github.com)
* [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)
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
* [muan](mailto:muan@github.com)
* [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
* [neetshin](mailto:neetshin@neetsh.in)
* [nightpool](mailto:nightpool@users.noreply.github.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.
## [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
### 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
LABEL maintainer="https://github.com/tootsuite/mastodon" \
@ -31,6 +31,8 @@ RUN apk -U upgrade \
libidn-dev \
libressl \
libtool \
libxml2-dev \
libxslt-dev \
postgresql-dev \
protobuf-dev \
python \
@ -43,6 +45,8 @@ RUN apk -U upgrade \
imagemagick \
libidn \
libpq \
libxml2 \
libxslt \
protobuf \
tini \
tzdata \
@ -64,7 +68,7 @@ RUN apk -U upgrade \
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 \
&& yarn install --pure-lockfile --ignore-engines \
&& yarn cache clean

36
Gemfile
View File

@ -1,12 +1,12 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.3.0', '< 2.6.0'
ruby '>= 2.4.0', '< 2.7.0'
gem 'pkg-config', '~> 1.3'
gem 'puma', '~> 3.12'
gem 'rails', '~> 5.2.1'
gem 'rails', '~> 5.2.2'
gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2'
@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.2'
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-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
@ -29,7 +29,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.6'
gem 'iso-639'
gem 'chewy', '~> 5.0'
gem 'cld3', '~> 3.2.0'
gem 'cld3', '~> 3.2.3'
gem 'devise', '~> 4.5'
gem 'devise-two-factor', '~> 3.0'
@ -40,7 +40,7 @@ end
gem 'net-ldap', '~> 0.10'
gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.2'
gem 'omniauth', '~> 1.9'
gem 'doorkeeper', '~> 5.0'
gem 'fast_blank', '~> 1.0'
@ -52,12 +52,12 @@ gem 'htmlentities', '~> 4.3'
gem 'http', '~> 3.3'
gem 'http_accept_language', '~> 2.1'
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 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.8'
gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.7'
gem 'ostatus2', '~> 2.0'
@ -69,28 +69,28 @@ gem 'rack-attack', '~> 5.4'
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1'
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 'rqrcode', '~> 0.10'
gem 'sanitize', '~> 5.0'
gem 'sidekiq', '~> 5.2'
gem 'sidekiq-scheduler', '~> 3.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_form', '~> 4.0'
gem 'simple_form', '~> 4.1'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.1.3'
gem 'strong_migrations', '~> 0.3'
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 'tzinfo-data', '~> 1.2018'
gem 'webpacker', '~> 3.5'
gem 'webpush'
gem 'json-ld', '~> 2.2'
gem 'json-ld-preloaded', '~> 2.2'
gem 'json-ld-preloaded', '~> 3.0'
gem 'rdf-normalize', '~> 0.3'
group :development, :test do
@ -107,15 +107,15 @@ group :production, :test do
end
group :test do
gem 'capybara', '~> 3.10'
gem 'capybara', '~> 3.12'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.0'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.16', require: false
gem 'webmock', '~> 3.4'
gem 'parallel_tests', '~> 2.26'
gem 'webmock', '~> 3.5'
gem 'parallel_tests', '~> 2.27'
end
group :development do
@ -123,11 +123,11 @@ group :development do
gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.5'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 5.7'
gem 'letter_opener', '~> 1.4'
gem 'bullet', '~> 5.9'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
gem 'rubocop', '~> 0.60', require: false
gem 'rubocop', '~> 0.62', require: false
gem 'brakeman', '~> 4.3', require: false
gem 'bundler-audit', '~> 0.6', require: false
gem 'scss_lint', '~> 0.57', require: false

View File

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

2
Vagrantfile vendored
View File

@ -44,7 +44,7 @@ sudo apt-get install \
# Install rvm
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
source /home/vagrant/.rvm/scripts/rvm

View File

@ -31,7 +31,7 @@ class ActivityPub::CollectionsController < Api::BaseController
when 'featured'
@account.pinned_statuses.count
else
raise ActiveRecord::NotFound
raise ActiveRecord::RecordNotFound
end
end
@ -42,7 +42,7 @@ class ActivityPub::CollectionsController < Api::BaseController
scope.merge!(@account.pinned_statuses)
end
else
raise ActiveRecord::NotFound
raise ActiveRecord::RecordNotFound
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
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
render template: 'admin/accounts/show'
end

View File

@ -2,9 +2,9 @@
module Admin
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_local_account!, only: [:enable, :disable, :memorialize]
before_action :require_local_account!, only: [:enable, :memorialize]
def index
authorize :account, :index?
@ -13,8 +13,10 @@ module Admin
def show
authorize @account, :show?
@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
def subscribe
@ -43,19 +45,25 @@ module Admin
redirect_to admin_account_path(@account.id)
end
def disable
authorize @account.user, :disable?
@account.user.disable!
log_action :disable, @account.user
def unsilence
authorize @account, :unsilence?
@account.unsilence!
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)
end
def redownload
authorize @account, :redownload?
@account.reset_avatar!
@account.reset_header!
@account.save!
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
redirect_to admin_account_path(@account.id)
end
@ -71,6 +79,17 @@ module Admin
redirect_to admin_account_path(@account.id)
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
def set_account
@ -94,8 +113,8 @@ module Admin
:local,
:remote,
:by_domain,
:active,
:silenced,
:alphabetic,
:suspended,
:username,
:display_name,

View File

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

View File

@ -25,10 +25,6 @@ module Admin
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
def check_confirmation
if @user.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'
@hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
@trending_hashtags = TrendingTags.get(7)
@profile_directory = Setting.profile_directory
end
private

View File

@ -4,14 +4,9 @@ module Admin
class DomainBlocksController < BaseController
before_action :set_domain_block, only: [:show, :destroy]
def index
authorize :domain_block, :index?
@domain_blocks = DomainBlock.page(params[:page])
end
def new
authorize :domain_block, :create?
@domain_block = DomainBlock.new
@domain_block = DomainBlock.new(domain: params[:_domain])
end
def create
@ -22,7 +17,7 @@ module Admin
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
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
render :new
end
@ -36,7 +31,7 @@ module Admin
authorize @domain_block, :destroy?
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
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
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
def index
authorize :instance, :index?
@instances = ordered_instances
end
def resubscribe
authorize :instance, :resubscribe?
params.require(:by_domain)
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
redirect_to admin_instances_path
def show
authorize :instance, :show?
@instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
@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
private
@ -27,17 +34,11 @@ module Admin
helper_method :paginated_instances
def ordered_instances
paginated_instances.map { |account| Instance.new(account) }
end
def subscribeable_accounts
Account.with_followers.remote.where(domain: params[:by_domain])
paginated_instances.map { |resource| Instance.new(resource) }
end
def filter_params
params.permit(
:domain_name
)
params.permit(:limited)
end
end
end

View File

@ -13,75 +13,42 @@ module Admin
authorize @report, :show?
@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
end
def update
def assign_to_self
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?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
else
redirect_to admin_report_path(@report)
end
def unassign
authorize @report, :update?
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
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
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
ReportFilter.new(filter_params).results.order(id: :desc).includes(
:account,
:target_account
)
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
end
def filter_params

View File

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

View File

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

View File

@ -26,6 +26,7 @@ module Admin
show_known_fediverse_at_about_page
preview_sensitive_media
custom_css
profile_directory
).freeze
BOOLEAN_SETTINGS = %w(
@ -37,6 +38,7 @@ module Admin
peers_api_enabled
show_known_fediverse_at_about_page
preview_sensitive_media
profile_directory
).freeze
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
end
def show
authorize :status, :index?
@statuses = @account.statuses.where(id: params[:id])
authorize @statuses.first, :show?
@form = Form::StatusBatch.new
end
def create
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
class TwoFactorAuthenticationsController < BaseController
before_action :set_user
before_action :set_target_user
def destroy
authorize @user, :disable_2fa?
@ -13,7 +13,7 @@ module Admin
private
def set_user
def set_target_user
@user = User.find(params[:user_id])
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
def require_user!
if current_user && !current_user.disabled?
set_user_activity
elsif current_user
render json: { error: 'Your login is currently disabled' }, status: 403
else
if !current_user
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

View File

@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
private
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
def user_settings_params

View File

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

View File

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

View File

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

View File

@ -1,14 +1,16 @@
# frozen_string_literal: true
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:mutes' }, only: [:mute, :unmute]
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 :set_account
before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show]
before_action :check_enabled_registrations, only: [:create]
respond_to :json
@ -16,6 +18,16 @@ class Api::V1::AccountsController < Api::BaseController
render json: @account, serializer: REST::AccountSerializer
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
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
gone if @account.suspended?
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

View File

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

View File

@ -4,6 +4,8 @@ class Api::V1::CustomEmojisController < Api::BaseController
respond_to :json
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

View File

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

View File

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

View File

@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts
if unlimited?
@list.accounts.all
@list.accounts.includes(:account_stat).all
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

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
Account
.includes(:favourites)
.includes(:favourites, :account_stat)
.references(:favourites)
.where(favourites: { status_id: @status.id })
end

View File

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

View File

@ -45,17 +45,18 @@ class Api::V1::StatusesController < Api::BaseController
def create
@status = PostStatusService.new.call(current_user.account,
status_params[:status],
status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
text: status_params[:status],
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application,
idempotency: request.headers['Idempotency-Key'],
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
def destroy
@ -78,7 +79,7 @@ class Api::V1::StatusesController < Api::BaseController
end
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
def pagination_params(core_params)

View File

@ -45,7 +45,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end
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
def insert_pagination_headers

View File

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

View File

@ -6,9 +6,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
before_action :set_body_classes
before_action :set_user, only: [:finish_signup]
# GET/PATCH /users/:id/finish_signup
def finish_signup
return unless request.patch? && params[:user]
if @user.update(user_params)
@user.skip_reconfirmation!
bypass_sign_in(@user)
@ -31,4 +31,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
def user_params
params.require(:user).permit(:email)
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

View File

@ -26,6 +26,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.agreement = true
resource.build_account if resource.account.nil?
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
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?
@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'])
compare_signed_string = build_signed_string(signature_params['headers'])
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
elsif account.possibly_stale?
account = account.refresh!
return account unless verify_signature(account, signature, compare_signed_string).nil?
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
else
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
@signed_request_account = nil
end
else
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
.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?
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
@signed_request_account = nil
return
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
def request_body
@ -79,6 +88,15 @@ module SignatureVerification
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)
signed_headers = 'date' if signed_headers.blank?
@ -125,4 +143,9 @@ module SignatureVerification
account
end
end
def account_refresh_key(account)
return if account.local? || !account.activitypub?
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
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 :verify_permitted_status!
content_security_policy only: :player do |p|
p.frame_ancestors(false)
end
def show
redirect_to @media_attachment.file.url(:original)
end
def player
@body_classes = 'player'
response.headers['X-Frame-Options'] = 'ALLOWALL'
raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
end

View File

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

View File

@ -1,12 +1,11 @@
# frozen_string_literal: true
class Settings::ApplicationsController < ApplicationController
class Settings::ApplicationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update]
before_action :set_body_classes
def index
@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)
params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
end
def set_body_classes
@body_classes = 'admin'
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
class Settings::DeletesController < ApplicationController
class Settings::DeletesController < Settings::BaseController
layout 'admin'
before_action :check_enabled_deletion
before_action :authenticate_user!
before_action :set_body_classes
def show
@confirmation = Form::DeleteConfirmation.new
@ -30,8 +29,4 @@ class Settings::DeletesController < ApplicationController
def delete_params
params.require(:form_delete_confirmation).permit(:password)
end
def set_body_classes
@body_classes = 'admin'
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
class Settings::ExportsController < ApplicationController
class Settings::ExportsController < Settings::BaseController
include Authorization
layout 'admin'
before_action :authenticate_user!
before_action :set_body_classes
def show
@export = Export.new(current_account)
@ -21,10 +20,4 @@ class Settings::ExportsController < ApplicationController
redirect_to settings_export_path
end
private
def set_body_classes
@body_classes = 'admin'
end
end

View File

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

View File

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

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
class Settings::MigrationsController < ApplicationController
class Settings::MigrationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_body_classes
def show
@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.id != @migration.account&.id
end
def set_body_classes
@body_classes = 'admin'
end
end

View File

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

View File

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

View File

@ -1,13 +1,12 @@
# frozen_string_literal: true
class Settings::ProfilesController < ApplicationController
class Settings::ProfilesController < Settings::BaseController
include ObfuscateFilename
layout 'admin'
before_action :authenticate_user!
before_action :set_account
before_action :set_body_classes
obfuscate_filename [:account, :avatar]
obfuscate_filename [:account, :header]
@ -29,14 +28,10 @@ class Settings::ProfilesController < ApplicationController
private
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
def set_account
@account = current_user.account
end
def set_body_classes
@body_classes = 'admin'
end
end

View File

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

View File

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

View File

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

View File

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

View File

@ -65,12 +65,13 @@ class StatusesController < ApplicationController
private
def create_descendant_thread(depth, statuses)
def create_descendant_thread(starting_depth, statuses)
depth = starting_depth + statuses.size
if depth < DESCENDANTS_DEPTH_LIMIT
{ statuses: statuses }
{ statuses: statuses, starting_depth: starting_depth }
else
next_status = statuses.pop
{ statuses: statuses, next_status: next_status }
{ statuses: statuses, starting_depth: starting_depth, next_status: next_status }
end
end
@ -101,16 +102,19 @@ class StatusesController < ApplicationController
@descendant_threads = []
if descendants.present?
statuses = [descendants.first]
depth = 1
statuses = [descendants.first]
starting_depth = 0
descendants.drop(1).each_with_index do |descendant, index|
if descendants[index].id == descendant.in_reply_to_id
depth += 1
statuses << descendant
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|
statuses = descendant_thread[:statuses]
@ -119,18 +123,16 @@ class StatusesController < ApplicationController
end
if index.present?
depth += index - statuses.size
starting_depth = descendant_thread[:starting_depth] + index + 1
break
end
depth -= statuses.size
end
statuses = [descendant]
end
end
@descendant_threads << create_descendant_thread(depth, statuses)
@descendant_threads << create_descendant_thread(starting_depth, statuses)
end
@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
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)
render xml: RSS::TagSerializer.render(@tag, @statuses)
end
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)
render json: collection_presenter,
@ -46,7 +47,7 @@ class TagsController < ApplicationController
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: tag_url(@tag),
id: tag_url(@tag, params.slice(:any, :all, :none)),
type: :ordered,
size: @tag.statuses.count,
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)
classes = [inline ? 'inline-name-tag' : 'name-tag']
classes << 'suspended' if account.suspended?
classes << 'suspended' if account.suspended? || (account.local? && account.user.nil?)
classes.join(' ')
end
end

View File

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

View File

@ -1,12 +1,14 @@
# frozen_string_literal: true
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
INVITE_FILTER = %i(available expired).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)
new_url = filtered_url_for(link_to_params)

View File

@ -23,7 +23,7 @@ module HomeHelper
else
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', 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 +
content_tag(:span, class: 'display-name') 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: '日本語',
ka: 'ქართული',
ko: '한국어',
ml: 'മലയാളം',
nl: 'Nederlands',
no: 'Norsk',
oc: 'Occitan',

View File

@ -34,12 +34,14 @@ module StreamEntriesHelper
end
end
def account_badge(account)
def account_badge(account, all: false)
if account.bot?
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
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')
elsif account.user_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']),
},
}).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(submitComposeSuccess({ ...response.data }));
@ -144,9 +150,7 @@ export function submitCompose(routerHistory) {
}
};
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
routerHistory.push('/timelines/direct');
} else if (response.data.visibility !== 'direct') {
if (response.data.visibility !== 'direct') {
insertIfOnline('home');
}

View File

@ -38,7 +38,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
const params = { max_id: 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 })

View File

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

View File

@ -30,6 +30,7 @@ export function fetchFavouritedStatuses() {
export function fetchFavouritedStatusesRequest() {
return {
type: FAVOURITED_STATUSES_FETCH_REQUEST,
skipLoading: true,
};
};
@ -38,6 +39,7 @@ export function fetchFavouritedStatusesSuccess(statuses, next) {
type: FAVOURITED_STATUSES_FETCH_SUCCESS,
statuses,
next,
skipLoading: true,
};
};
@ -45,6 +47,7 @@ export function fetchFavouritedStatusesFail(error) {
return {
type: FAVOURITED_STATUSES_FETCH_FAIL,
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_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) => {
if (getState().getIn(['lists', id])) {
return;
@ -316,3 +323,50 @@ export const removeFromListFail = (listId, accountId, error) => ({
accountId,
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,
} from './importer';
import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from '../utils/html';
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_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
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 excludeTypesFromFilter = filter => {
const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']);
return allTypes.filterNot(item => item === filter).toJS();
};
const noOp = () => {};
export function expandNotifications({ maxId } = {}, done = noOp) {
return (dispatch, getState) => {
const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
const notifications = getState().get('notifications');
const isLoadingMore = !!maxId;
if (notifications.get('isLoading')) {
done();
@ -101,14 +111,16 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
const params = {
max_id: maxId,
exclude_types: excludeTypesFromSettings(getState()),
exclude_types: activeFilter === 'all'
? excludeTypesFromSettings(getState())
: excludeTypesFromFilter(activeFilter),
};
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 => {
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(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);
done();
}).catch(error => {
dispatch(expandNotificationsFail(error));
dispatch(expandNotificationsFail(error, isLoadingMore));
done();
});
};
};
export function expandNotificationsRequest() {
export function expandNotificationsRequest(isLoadingMore) {
return {
type: NOTIFICATIONS_EXPAND_REQUEST,
skipLoading: !isLoadingMore,
};
};
export function expandNotificationsSuccess(notifications, next) {
export function expandNotificationsSuccess(notifications, next, isLoadingMore) {
return {
type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications,
next,
skipLoading: !isLoadingMore,
};
};
export function expandNotificationsFail(error) {
export function expandNotificationsFail(error, isLoadingMore) {
return {
type: NOTIFICATIONS_EXPAND_FAIL,
error,
skipLoading: !isLoadingMore,
};
};
@ -163,3 +178,14 @@ export function scrollTopNotifications(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';
export function showOnboardingOnce() {
return (dispatch, getState) => {
const alreadySeen = getState().getIn(['settings', 'onboarded']);
export const INTRODUCTION_VERSION = 20181216044202;
if (!alreadySeen) {
dispatch(openModal('ONBOARDING'));
dispatch(changeSetting(['onboarded'], true));
dispatch(saveSettings());
}
};
export const closeOnboarding = () => dispatch => {
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
dispatch(saveSettings());
};

View File

@ -12,7 +12,7 @@ import { getLocale } from '../locales';
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) => {
const locale = getState().getIn(['meta', 'locale']);
@ -24,7 +24,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
onReceive (data) {
switch(data.event) {
case 'update':
dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), accept));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
@ -51,6 +51,6 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
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 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 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_DELETE = 'TIMELINE_DELETE';
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
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 function updateTimeline(timeline, status) {
return (dispatch, getState) => {
const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : [];
export function updateTimeline(timeline, status, accept) {
return dispatch => {
if (typeof accept === 'function' && !accept(status)) {
return;
}
dispatch(importFetchedStatus(status));
@ -23,7 +26,6 @@ export function updateTimeline(timeline, status) {
type: TIMELINE_UPDATE,
timeline,
status,
references,
});
};
};
@ -44,11 +46,24 @@ export function deleteFromTimelines(id) {
};
};
export function clearTimeline(timeline) {
return (dispatch) => {
dispatch({ type: TIMELINE_CLEAR, timeline });
};
};
const noOp = () => {};
const parseTags = (tags = {}, mode) => {
return (tags[mode] || []).map((tag) => {
return tag.value;
});
};
export function expandTimeline(timelineId, path, params = {}, done = noOp) {
return (dispatch, getState) => {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
const isLoadingMore = !!params.max_id;
if (timeline.get('isLoading')) {
done();
@ -59,15 +74,17 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
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 => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
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();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error));
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
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 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 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 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 {
type: TIMELINE_EXPAND_REQUEST,
timeline,
skipLoading: !isLoadingMore,
};
};
export function expandTimelineSuccess(timeline, statuses, next, partial) {
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
return {
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses,
next,
partial,
isLoadingRecent,
skipLoading: !isLoadingMore,
};
};
export function expandTimelineFail(timeline, error) {
export function expandTimelineFail(timeline, error, isLoadingMore) {
return {
type: TIMELINE_EXPAND_FAIL,
timeline,
error,
skipLoading: !isLoadingMore,
};
};

View File

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

View File

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

View File

@ -37,6 +37,14 @@ class ColumnHeader extends React.PureComponent {
animating: false,
};
historyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/');
} else {
this.context.router.history.goBack();
}
}
handleToggleClick = (e) => {
e.stopPropagation();
this.setState({ collapsed: !this.state.collapsed, animating: true });
@ -55,16 +63,22 @@ class ColumnHeader extends React.PureComponent {
}
handleBackClick = () => {
if (window.history && window.history.length === 1) this.context.router.history.push('/');
else this.context.router.history.goBack();
this.historyBack();
}
handleTransitionEnd = () => {
this.setState({ animating: false });
}
handlePin = () => {
if (!this.props.pinned) {
this.historyBack();
}
this.props.onPin();
}
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 wrapperClassName = classNames('column-header__wrapper', {
@ -95,7 +109,7 @@ class ColumnHeader extends React.PureComponent {
}
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 = (
<div key='move-buttons' className='column-header__setting-arrows'>
@ -104,7 +118,7 @@ class ColumnHeader extends React.PureComponent {
</div>
);
} 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)) {

View File

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

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