diff --git a/.circleci/config.yml b/.circleci/config.yml index 1161e4076..e8bfde299 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/.codeclimate.yml b/.codeclimate.yml index 58f6b3de4..a70459764 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -27,7 +27,7 @@ plugins: enabled: true eslint: enabled: true - channel: eslint-4 + channel: eslint-5 rubocop: enabled: true channel: rubocop-0-54 diff --git a/.eslintignore b/.eslintignore index 6d540c413..d4930e1f5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..177496d3a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,204 @@ +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)$', + ], + 'import/resolver': { + node: { + paths: ['app/javascript'], + }, + }, + }, + + 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', + }, +}; diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index fbda26549..000000000 --- a/.eslintrc.yml +++ /dev/null @@ -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 diff --git a/.postcssrc.yml b/.postcssrc.yml deleted file mode 100644 index efffb39ba..000000000 --- a/.postcssrc.yml +++ /dev/null @@ -1,9 +0,0 @@ -plugins: - postcss-smart-import: {} - precss: {} - autoprefixer: - browsers: - - last 2 versions - - IE >= 11 - - iOS >= 9 - postcss-object-fit-images: {} diff --git a/.ruby-version b/.ruby-version index aedc15bb0..6a6a3d8e3 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.5.3 +2.6.1 diff --git a/AUTHORS.md b/AUTHORS.md index b81b6d245..3171214e0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,41 +1,44 @@ +Authors +======= + Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon) 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) +* [dependabot[bot]](https://github.com/apps/dependabot) * [unarist](https://github.com/unarist) * [m4sk1n](https://github.com/m4sk1n) * [yiskah](https://github.com/yiskah) * [nolanlawson](https://github.com/nolanlawson) +* [ysksn](https://github.com/ysksn) * [sorin-davidoi](https://github.com/sorin-davidoi) * [abcang](https://github.com/abcang) * [lynlynlynx](https://github.com/lynlynlynx) -* [dependabot[bot]](https://github.com/apps/dependabot) +* [mayaeh](https://github.com/mayaeh) +* [renatolond](https://github.com/renatolond) * [alpaca-tc](https://github.com/alpaca-tc) * [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) +* [Kjwon15](https://github.com/Kjwon15) * [nullkal](https://github.com/nullkal) * [yookoala](https://github.com/yookoala) -* [mabkenar](https://github.com/mabkenar) -* [ysksn](https://github.com/ysksn) * [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) @@ -45,16 +48,16 @@ and provided thanks to the work of the following contributors: * [rkarabut](https://github.com/rkarabut) * [yukimochi](https://github.com/yukimochi) * [Artoria2e5](https://github.com/Artoria2e5) +* [nightpool](https://github.com/nightpool) * [marrus-sh](https://github.com/marrus-sh) * [krainboltgreene](https://github.com/krainboltgreene) -* [patf](https://github.com/patf) +* [pfigel](https://github.com/pfigel) * [Aldarone](https://github.com/Aldarone) * [BoFFire](https://github.com/BoFFire) * [clworld](https://github.com/clworld) * [dracos](https://github.com/dracos) * [SerCom_KC](mailto:sercom-kc@users.noreply.github.com) * [Sylvhem](https://github.com/Sylvhem) -* [nightpool](https://github.com/nightpool) * [MasterGroosha](https://github.com/MasterGroosha) * [JeanGauthier](https://github.com/JeanGauthier) * [kschaper](https://github.com/kschaper) @@ -74,11 +77,14 @@ and provided thanks to the work of the following contributors: * [johnsudaar](https://github.com/johnsudaar) * [trebmuh](https://github.com/trebmuh) * [Rakib Hasan](mailto:rmhasan@gmail.com) +* [ashleyhull-versent](https://github.com/ashleyhull-versent) * [lindwurm](https://github.com/lindwurm) * [victorhck](mailto:victorhck@geeko.site) * [voidsatisfaction](https://github.com/voidsatisfaction) +* [rinsuki](https://github.com/rinsuki) * [hikari-no-yume](https://github.com/hikari-no-yume) * [angristan](https://github.com/angristan) +* [hinaloe](https://github.com/hinaloe) * [seefood](https://github.com/seefood) * [jackjennings](https://github.com/jackjennings) * [spla](mailto:spla@mastodont.cat) @@ -88,28 +94,31 @@ 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) +* [ariasuni](https://github.com/ariasuni) * [pfm-eyesightjp](https://github.com/pfm-eyesightjp) * [fakenine](https://github.com/fakenine) * [tsuwatch](https://github.com/tsuwatch) * [victorhck](https://github.com/victorhck) +* [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) +* [Aditoo17](https://github.com/Aditoo17) * [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) * [rainyday](https://github.com/rainyday) * [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) -* [rtucker](https://github.com/rtucker) +* [trwnh](https://github.com/trwnh) +* [JMendyk](https://github.com/JMendyk) * [KScl](https://github.com/KScl) * [sterdev](https://github.com/sterdev) * [TheKinrar](https://github.com/TheKinrar) @@ -119,18 +128,20 @@ 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) * [d6rkaiz](https://github.com/d6rkaiz) -* [JMendyk](https://github.com/JMendyk) +* [Reverite](https://github.com/Reverite) * [JohnD28](https://github.com/JohnD28) * [znz](https://github.com/znz) * [Naouak](https://github.com/Naouak) +* [pawelngei](https://github.com/pawelngei) +* [rtucker](https://github.com/rtucker) * [reneklacan](https://github.com/reneklacan) * [ekiru](https://github.com/ekiru) +* [noellabo](https://github.com/noellabo) * [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) @@ -138,18 +149,19 @@ and provided thanks to the work of the following contributors: * [theboss](https://github.com/theboss) * [178inaba](https://github.com/178inaba) * [alyssais](https://github.com/alyssais) -* [kodnaplakal](https://github.com/kodnaplakal) +* [hiphref](https://github.com/hiphref) +* [BenLubar](https://github.com/BenLubar) * [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) +* [marek-lach](https://github.com/marek-lach) * [nevillepark](https://github.com/nevillepark) * [ornithocoder](https://github.com/ornithocoder) * [pierreozoux](https://github.com/pierreozoux) @@ -165,20 +177,24 @@ 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) +* [tmm576](https://github.com/tmm576) * [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) +* [pointlessone](https://github.com/pointlessone) * [Andrew](mailto:andrewlchronister@gmail.com) * [estuans](https://github.com/estuans) * [dissolve](https://github.com/dissolve) @@ -192,7 +208,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) @@ -203,10 +219,12 @@ and provided thanks to the work of the following contributors: * [ErikXXon](https://github.com/ErikXXon) * [ian-kelling](https://github.com/ian-kelling) * [immae](https://github.com/immae) +* [J0WI](https://github.com/J0WI) * [foozmeat](https://github.com/foozmeat) * [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) @@ -216,6 +234,7 @@ and provided thanks to the work of the following contributors: * [Lorenz Diener](mailto:halcyon@icosahedron.website) * [alimony](https://github.com/alimony) * [mig5](https://github.com/mig5) +* [moritzheiber](https://github.com/moritzheiber) * [ndarville](https://github.com/ndarville) * [Abzol](https://github.com/Abzol) * [pwoolcoc](https://github.com/pwoolcoc) @@ -223,10 +242,13 @@ 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) +* [sts10](https://github.com/sts10) * [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 +265,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 +273,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 +281,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 +290,12 @@ 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) +* [acid-chicken](https://github.com/acid-chicken) +* [Esteth](https://github.com/Esteth) * [unascribed](https://github.com/unascribed) * [Aguay-val](https://github.com/Aguay-val) * [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp) @@ -282,7 +304,7 @@ and provided thanks to the work of the following contributors: * [unleashed](https://github.com/unleashed) * [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 +321,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) @@ -314,6 +335,7 @@ and provided thanks to the work of the following contributors: * [Motoma](https://github.com/Motoma) * [chriswk](https://github.com/chriswk) * [csu](https://github.com/csu) +* [clarcharr](https://github.com/clarcharr) * [kklleemm](https://github.com/kklleemm) * [colindean](https://github.com/colindean) * [dachinat](https://github.com/dachinat) @@ -341,6 +363,8 @@ 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) +* [zoc](https://github.com/zoc) +* [fwenzel](https://github.com/fwenzel) * [GenbuHase](https://github.com/GenbuHase) * [hattori6789](https://github.com/hattori6789) * [algernon](https://github.com/algernon) @@ -375,10 +399,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 +412,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) @@ -402,6 +424,7 @@ and provided thanks to the work of the following contributors: * [martymcguire](https://github.com/martymcguire) * [marvinkopf](https://github.com/marvinkopf) * [otsune](https://github.com/otsune) +* [mbugowski](https://github.com/mbugowski) * [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com) * [matt-auckland](https://github.com/matt-auckland) * [webroo](https://github.com/webroo) @@ -420,12 +443,12 @@ and provided thanks to the work of the following contributors: * [premist](https://github.com/premist) * [Mnkai](https://github.com/Mnkai) * [mitchhentges](https://github.com/mitchhentges) -* [moritzheiber](https://github.com/moritzheiber) * [mouse-reeve](https://github.com/mouse-reeve) * [Mozinet-fr](https://github.com/Mozinet-fr) * [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,31 +466,34 @@ 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) +* [Reto Kromer](mailto:retokromer@users.noreply.github.com) +* [Rey Tucker](mailto:git@reytucker.us) +* [Rob Watson](mailto:rfwatson@users.noreply.github.com) +* [Ryan Freebern](mailto:ryan@freebern.org) * [Ryan Wade](mailto:ryan.wade@protonmail.com) -* [sylph01](https://github.com/sylph01) -* [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS) -* [staticsafe](https://github.com/staticsafe) -* [snwh](https://github.com/snwh) -* [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) +* [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info) +* [S.H](mailto:gamelinks007@gmail.com) +* [Sadiq Saif](mailto:staticsafe@users.noreply.github.com) +* [Sam Hewitt](mailto:hewittsamuel@gmail.com) +* [Satoshi KOJIMA](mailto:skoji@mac.com) +* [ScienJus](mailto:i@scienjus.com) +* [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) +* [Stanislas](mailto:angristan@pm.me) * [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) @@ -515,26 +541,30 @@ and provided thanks to the work of the following contributors: * [fsubal](mailto:fsubal@users.noreply.github.com) * [fusshi-](mailto:dikky1218@users.noreply.github.com) * [gentaro](mailto:gentaroooo@gmail.com) +* [gol-cha](mailto:info@mevo.xyz) * [hakoai](mailto:hk--76@qa2.so-net.ne.jp) * [haosbvnker](mailto:github@chaosbunker.com) * [isati](mailto:phil@juchnowi.cz) * [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) * [mhe](mailto:mail@marcus-herrmann.com) +* [mike castleman](mailto:m@mlcastle.net) * [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) * [roikale](mailto:roikale@users.noreply.github.com) * [rysiekpl](mailto:rysiek@hackerspace.pl) @@ -564,3 +594,248 @@ and provided thanks to the work of the following contributors: * [雨宮美羽](mailto:k737566@gmail.com) This document is provided for informational purposes only. Since it is only updated once per release, the version you are looking at may be currently out of date. To see the full list of contributors, consider looking at the [git history](https://github.com/tootsuite/mastodon/graphs/contributors) instead. + +## Translators + +Following people have contributed to translation of Mastodon: + +- **Arabic** + - ButterflyOfFire +- **Asturian** + - ButterflyOfFire + - Enol P. +- **Basque** + - Aitzol + - ButterflyOfFire + - Gorka Azkarate + - Osoitz + - Peru Iparragirre +- **Bulgarian** + - ButterflyOfFire +- **Catalan** + - ButterflyOfFire + - Joan Montané + - Jose Luis + - spla +- **Chinese (Hong Kong)** + - ButterflyOfFire + - Luzi Leung +- **Chinese (Simplified)** + - Allen Zhong + - ButterflyOfFire + - SerCom_KC +- **Chinese (Traditional)** + - ButterflyOfFire + - James58899 + - Jeff Huang + - S1ttidoe477 + - SHA265 +- **Corsican** + - Alix D. R. + - ButterflyOfFire +- **Croatian** + - ButterflyOfFire +- **Czech** + - ButterflyOfFire + - Lorem Ipsum + - Marek Ľach +- **Danish** + - ButterflyOfFire + - Rasmus Sæderup +- **Dutch** + - ButterflyOfFire + - Jelv + - jeroenpraat + - rscmbbng +- **English** + - ButterflyOfFire + - Renato "Lond" Cerqueira +- **Esperanto** + - ButterflyOfFire + - Jeong Arm + - Martin Bodin + - Mélanie Chauvel + - Vanege + - tuxayo/Victor Grousset +- **Finnish** + - ButterflyOfFire + - Jonne Arjoranta + - S Heija + - Taru Luojola +- **French** + - Alda Marteau-Hardi + - Alix D. R. + - Baptiste Jonglez + - ButterflyOfFire + - Franck Paul + - Jean-Baptiste Holcroft + - Jonathan Chan + - Letiteuf55 + - Martin Bodin + - Mélanie Chauvel + - Olivier Humbert + - Paul Marques Mota + - Sylvhem + - Technowix + - Thibaut Girka + - Théodore + - azenet + - codl +- **Galician** + - ButterflyOfFire + - Xose M. + - manequim +- **Georgian** + - ButterflyOfFire +- **German** + - Benedikt Geißler + - ButterflyOfFire + - Daniel + - Eugen Rochko + - Koyu Berteon + - Patrick Figel + - Weblate Admin + - averageunicorn + - ePirat + - koyu + - larsreineke + - lilo +- **Greek** + - Antonis + - ButterflyOfFire + - Dimitris Maroulidis + - Konstantinos Grevenitis +- **Hebrew** + - ButterflyOfFire + - Ira + - Yaron Shahrabani +- **Hungarian** + - Adam Paszternak + - ButterflyOfFire + - Tibike Miklós +- **Ido** + - ButterflyOfFire +- **Indonesian** + - Alfiana Sibuea + - ButterflyOfFire + - Dito Kurnia Pratama + - Eirworks + - afachri + - se7entime +- **Italian** + - Alessandro Levati + - ButterflyOfFire + - Giuseppe Pignataro + - Stefano +- **Japanese** + - ButterflyOfFire + - Kumasun Morino + - Yamagishi Kazutoshi + - mayaeh + - osapon + - unarist + - 小鳥遊まりあ + - 森の子リスのミーコの大冒険 +- **Korean** + - ButterflyOfFire + - Jeong Arm + - Minori Hiraoka + - Yamagishi Kazutoshi +- **Malay** + - ButterflyOfFire + - Muhammad Nur Hidayat (MNH48) +- **Norwegian (old code)** + - ButterflyOfFire + - Espen Rønnevik + - Tale +- **Occitan** + - ButterflyOfFire + - Maxenç + - Quenti2 + - Quentí +- **Persian** + - ButterflyOfFire + - Masoud Abkenar +- **Polish** + - ButterflyOfFire + - Jakub Mendyk + - Marcin Mikołajczak + - Marek Ľach + - Stasiek Michalski + - krkk +- **Portuguese** + - ButterflyOfFire + - Hugo Gameiro + - manequim +- **Portuguese (Brazil)** + - André Andrade + - Anna e só + - ButterflyOfFire + - Renato "Lond" Cerqueira +- **Romanian** + - ButterflyOfFire + - adrianbblk +- **Russian** + - Andrew Zyabin + - ButterflyOfFire + - Evgeny Petrov + - Yaron Shahrabani +- **Serbian** + - Branko Kokanovic + - Burekz Finezt + - ButterflyOfFire +- **Serbian (latin)** + - ButterflyOfFire +- **Slovak** + - ButterflyOfFire + - Ivan Pleva + - Lorem Ipsum + - Marek Ľach + - Peter +- **Slovenian** + - ButterflyOfFire + - Kristijan Tkalec +- **Spanish** + - Angeles Broullón + - Antón López + - ButterflyOfFire + - Carlos Mondragon + - David Charte + - Emmanuel + - Lothar Wolf + - Pablo de la Concepción Sanz +- **Swedish** + - ButterflyOfFire + - Elias Mårtenson + - Isak Holmström + - Shellkr + - Stefan Midjich + - Tim Stahel +- **Telugu** + - ButterflyOfFire + - Joseph Nuthalapati + - Ranjith Tellakula + - avndp +- **Thai** + - ButterflyOfFire +- **Turkish** + - ButterflyOfFire +- **Ukrainian** + - ButterflyOfFire + - Ivan Verchenko + - alexcleac +- **Welsh** + - ButterflyOfFire + - Jaz-Michael King + - Kevin Beynon + - Owain Rhys Lewis + - Renato "Lond" Cerqueira + - Rhoslyn Prys + - carl morris +- **Armenian** + - ButterflyOfFire +- **Latvian** + - ButterflyOfFire +- **Tamil** + - ButterflyOfFire + - Prasanna Venkadesh diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e24df451..b7040f924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,199 +3,400 @@ Changelog All notable changes to this project will be documented in this file. -## [2.6.5] - 2018-12-01 -### Changed +## [2.7.3] - 2019-02-23 +### Added -- Change lists to display replies to others on the list and list owner (#9324) +- Add domain filter to the admin federation page ([ThibG](https://github.com/tootsuite/mastodon/pull/10071)) +- Add quick link from admin account view to block/unblock instance ([ThibG](https://github.com/tootsuite/mastodon/pull/10073)) ### Fixed -- Fix failures caused by commonly-used JSON-LD contexts being unavailable (#9412) +- Fix video player width not being updated to fit container width ([ThibG](https://github.com/tootsuite/mastodon/pull/10069)) +- Fix domain filter being shown in admin page when local filter is active ([ThibG](https://github.com/tootsuite/mastodon/pull/10074)) +- Fix crash when conversations have no valid participants ([ThibG](https://github.com/tootsuite/mastodon/pull/10078)) +- Fix error when performing admin actions on no statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10094)) + +### Changed + +- Change custom emojis to randomize stored file name ([hinaloe](https://github.com/tootsuite/mastodon/pull/10090)) + +## [2.7.2] - 2019-02-17 +### Added + +- Add support for IPv6 in e-mail validation ([zoc](https://github.com/tootsuite/mastodon/pull/10009)) +- Add record of IP address used for signing up ([ThibG](https://github.com/tootsuite/mastodon/pull/10026)) +- Add tight rate-limit for API deletions (30 per 30 minutes) ([Gargron](https://github.com/tootsuite/mastodon/pull/10042)) +- Add support for embedded `Announce` objects attributed to the same actor ([ThibG](https://github.com/tootsuite/mastodon/pull/9998), [Gargron](https://github.com/tootsuite/mastodon/pull/10065)) +- Add spam filter for `Create` and `Announce` activities ([Gargron](https://github.com/tootsuite/mastodon/pull/10005), [Gargron](https://github.com/tootsuite/mastodon/pull/10041), [Gargron](https://github.com/tootsuite/mastodon/pull/10062)) +- Add `registrations` attribute to `GET /api/v1/instance` ([Gargron](https://github.com/tootsuite/mastodon/pull/10060)) +- Add `vapid_key` to `POST /api/v1/apps` and `GET /api/v1/apps/verify_credentials` ([Gargron](https://github.com/tootsuite/mastodon/pull/10058)) + +### Fixed + +- Fix link color and add link underlines in high-contrast theme ([Gargron](https://github.com/tootsuite/mastodon/pull/9949), [Gargron](https://github.com/tootsuite/mastodon/pull/10028)) +- Fix unicode characters in URLs not being linkified ([JMendyk](https://github.com/tootsuite/mastodon/pull/8447), [hinaloe](https://github.com/tootsuite/mastodon/pull/9991)) +- Fix URLs linkifier grabbing ending quotation as part of the link ([Gargron](https://github.com/tootsuite/mastodon/pull/9997)) +- Fix authorized applications page design ([rinsuki](https://github.com/tootsuite/mastodon/pull/9969)) +- Fix custom emojis not showing up in share page emoji picker ([rinsuki](https://github.com/tootsuite/mastodon/pull/9970)) +- Fix too liberal application of whitespace in toots ([trwnh](https://github.com/tootsuite/mastodon/pull/9968)) +- Fix misleading e-mail hint being displayed in admin view ([ThibG](https://github.com/tootsuite/mastodon/pull/9973)) +- Fix tombstones not being cleared out ([abcang](https://github.com/tootsuite/mastodon/pull/9978)) +- Fix some timeline jumps ([ThibG](https://github.com/tootsuite/mastodon/pull/9982), [ThibG](https://github.com/tootsuite/mastodon/pull/10001), [rinsuki](https://github.com/tootsuite/mastodon/pull/10046)) +- Fix content warning input taking keyboard focus even when hidden ([hinaloe](https://github.com/tootsuite/mastodon/pull/10017)) +- Fix hashtags select styling in default and high-contrast themes ([Gargron](https://github.com/tootsuite/mastodon/pull/10029)) +- Fix style regressions on landing page ([Gargron](https://github.com/tootsuite/mastodon/pull/10030)) +- Fix hashtag column not subscribing to stream on mount ([Gargron](https://github.com/tootsuite/mastodon/pull/10040)) +- Fix relay enabling/disabling not resetting inbox availability status ([Gargron](https://github.com/tootsuite/mastodon/pull/10048)) +- Fix mutes, blocks, domain blocks and follow requests not paginating ([Gargron](https://github.com/tootsuite/mastodon/pull/10057)) +- Fix crash on public hashtag pages when streaming fails ([ThibG](https://github.com/tootsuite/mastodon/pull/10061)) + +### Changed + +- Change icon for unlisted visibility level ([clarcharr](https://github.com/tootsuite/mastodon/pull/9952)) +- Change queue of actor deletes from push to pull for non-follower recipients ([ThibG](https://github.com/tootsuite/mastodon/pull/10016)) +- Change robots.txt to exclude media proxy URLs ([nightpool](https://github.com/tootsuite/mastodon/pull/10038)) +- Change upload description input to allow line breaks ([BenLubar](https://github.com/tootsuite/mastodon/pull/10036)) +- Change `dist/mastodon-streaming.service` to recommend running node without intermediary npm command ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10032)) +- Change conversations to always show names of other participants ([Gargron](https://github.com/tootsuite/mastodon/pull/10047)) +- Change buttons on timeline preview to open the interaction dialog ([Gargron](https://github.com/tootsuite/mastodon/pull/10054)) +- Change error graphic to hover-to-play ([Gargron](https://github.com/tootsuite/mastodon/pull/10055)) + +## [2.7.1] - 2019-01-28 +### Fixed + +- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/tootsuite/mastodon/pull/9915)) +- Fix slow fallback of CopyAccountStats migration setting stats to 0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9930)) +- Fix wrong command in migration error message ([angristan](https://github.com/tootsuite/mastodon/pull/9877)) +- Fix initial value of volume slider in video player and handle volume changes ([ThibG](https://github.com/tootsuite/mastodon/pull/9929)) +- Fix missing hotkeys for notifications ([ThibG](https://github.com/tootsuite/mastodon/pull/9927)) +- Fix being able to attach unattached media created by other users ([ThibG](https://github.com/tootsuite/mastodon/pull/9921)) +- Fix unrescued SSL error during link verification ([renatolond](https://github.com/tootsuite/mastodon/pull/9914)) +- Fix Firefox scrollbar color regression ([trwnh](https://github.com/tootsuite/mastodon/pull/9908)) +- Fix scheduled status with media immediately creating a status ([ThibG](https://github.com/tootsuite/mastodon/pull/9894)) +- Fix missing strong style for landing page description ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9892)) + +## [2.7.0] - 2019-01-20 +### Added + +- Add link for adding a user to a list from their profile ([namelessGonbai](https://github.com/tootsuite/mastodon/pull/9062)) +- Add joining several hashtags in a single column ([gdpelican](https://github.com/tootsuite/mastodon/pull/8904)) +- Add volume sliders for videos ([sumdog](https://github.com/tootsuite/mastodon/pull/9366)) +- Add a tooltip explaining what a locked account is ([pawelngei](https://github.com/tootsuite/mastodon/pull/9403)) +- Add preloaded cache for common JSON-LD contexts ([ThibG](https://github.com/tootsuite/mastodon/pull/9412)) +- Add profile directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9427)) +- Add setting to not group reblogs in home feed ([ThibG](https://github.com/tootsuite/mastodon/pull/9248)) +- Add admin ability to remove a user's header image ([ThibG](https://github.com/tootsuite/mastodon/pull/9495)) +- Add account hashtags to ActivityPub actor JSON ([Gargron](https://github.com/tootsuite/mastodon/pull/9450)) +- Add error message for avatar image that's too large ([sumdog](https://github.com/tootsuite/mastodon/pull/9518)) +- Add notification quick-filter bar ([pawelngei](https://github.com/tootsuite/mastodon/pull/9399)) +- Add new first-time tutorial ([Gargron](https://github.com/tootsuite/mastodon/pull/9531)) +- Add moderation warnings ([Gargron](https://github.com/tootsuite/mastodon/pull/9519)) +- Add emoji codepoint mappings for v11.0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9618)) +- Add REST API for creating an account ([Gargron](https://github.com/tootsuite/mastodon/pull/9572)) +- Add support for Malayalam in language filter ([tachyons](https://github.com/tootsuite/mastodon/pull/9624)) +- Add exclude_reblogs option to account statuses API ([Gargron](https://github.com/tootsuite/mastodon/pull/9640)) +- Add local followers page to admin account UI ([chr-1x](https://github.com/tootsuite/mastodon/pull/9610)) +- Add healthcheck commands to docker-compose.yml ([BenLubar](https://github.com/tootsuite/mastodon/pull/9143)) +- Add handler for Move activity to migrate followers ([Gargron](https://github.com/tootsuite/mastodon/pull/9629)) +- Add CSV export for lists and domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/9677)) +- Add `tootctl accounts follow ACCT` ([Gargron](https://github.com/tootsuite/mastodon/pull/9414)) +- Add scheduled statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9706)) +- Add immutable caching for S3 objects ([nolanlawson](https://github.com/tootsuite/mastodon/pull/9722)) +- Add cache to custom emojis API ([Gargron](https://github.com/tootsuite/mastodon/pull/9732)) +- Add preview cards to non-detailed statuses on public pages ([Gargron](https://github.com/tootsuite/mastodon/pull/9714)) +- Add `mod` and `moderator` to list of default reserved usernames ([Gargron](https://github.com/tootsuite/mastodon/pull/9713)) +- Add quick links to the admin interface in the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/8545)) +- Add `tootctl domains crawl` ([Gargron](https://github.com/tootsuite/mastodon/pull/9809)) +- Add attachment list fallback to public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9780)) +- Add `tootctl --version` ([Gargron](https://github.com/tootsuite/mastodon/pull/9835)) +- Add information about how to opt-in to the directory on the directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9834)) +- Add timeouts for S3 ([Gargron](https://github.com/tootsuite/mastodon/pull/9842)) +- Add support for non-public reblogs from ActivityPub ([Gargron](https://github.com/tootsuite/mastodon/pull/9841)) +- Add sending of `Reject` activity when sending a `Block` activity ([ThibG](https://github.com/tootsuite/mastodon/pull/9811)) + +### Changed + +- Temporarily pause timeline if mouse moved recently ([lmorchard](https://github.com/tootsuite/mastodon/pull/9200)) +- Change the password form order ([mayaeh](https://github.com/tootsuite/mastodon/pull/9267)) +- Redesign admin UI for accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/9340), [Gargron](https://github.com/tootsuite/mastodon/pull/9643)) +- Redesign admin UI for instances/domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/9645)) +- Swap avatar and header input fields in profile page ([ThibG](https://github.com/tootsuite/mastodon/pull/9271)) +- When posting in mobile mode, go back to previous history location ([ThibG](https://github.com/tootsuite/mastodon/pull/9502)) +- Split out is_changing_upload from is_submitting ([ThibG](https://github.com/tootsuite/mastodon/pull/9536)) +- Back to the getting-started when pins the timeline. ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9561)) +- Allow unauthenticated REST API access to GET /api/v1/accounts/:id/statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9573)) +- Limit maximum visibility of local silenced users to unlisted ([ThibG](https://github.com/tootsuite/mastodon/pull/9583)) +- Change API error message for unconfirmed accounts ([noellabo](https://github.com/tootsuite/mastodon/pull/9625)) +- Change the icon to "reply-all" when it's a reply to other accounts ([mayaeh](https://github.com/tootsuite/mastodon/pull/9378)) +- Do not ignore federated reports targetting already-reported accounts ([ThibG](https://github.com/tootsuite/mastodon/pull/9534)) +- Upgrade default Ruby version to 2.6.0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9688)) +- Change e-mail digest frequency ([Gargron](https://github.com/tootsuite/mastodon/pull/9689)) +- Change Docker images for Tor support in docker-compose.yml ([Sir-Boops](https://github.com/tootsuite/mastodon/pull/9438)) +- Display fallback link card thumbnail when none is given ([Gargron](https://github.com/tootsuite/mastodon/pull/9715)) +- Change account bio length validation to ignore mention domains and URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/9717)) +- Use configured contact user for "anonymous" federation activities ([yukimochi](https://github.com/tootsuite/mastodon/pull/9661)) +- Change remote interaction dialog to use specific actions instead of generic "interact" ([Gargron](https://github.com/tootsuite/mastodon/pull/9743)) +- Always re-fetch public key when signature verification fails to support blind key rotation ([ThibG](https://github.com/tootsuite/mastodon/pull/9667)) +- Make replies to boosts impossible, connect reply to original status instead ([valerauko](https://github.com/tootsuite/mastodon/pull/9129)) +- Change e-mail MX validation to check both A and MX records against blacklist ([Gargron](https://github.com/tootsuite/mastodon/pull/9489)) +- Hide floating action button on search and getting started pages ([tmm576](https://github.com/tootsuite/mastodon/pull/9826)) +- Redesign public hashtag page to use a masonry layout ([Gargron](https://github.com/tootsuite/mastodon/pull/9822)) +- Use `summary` as summary instead of content warning for converted ActivityPub objects ([Gargron](https://github.com/tootsuite/mastodon/pull/9823)) +- Display a double reply arrow on public pages for toots that are replies ([ThibG](https://github.com/tootsuite/mastodon/pull/9808)) +- Change admin UI right panel size to be wider ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9768)) + +### Removed + +- Remove links to bridge.joinmastodon.org (non-functional) ([Gargron](https://github.com/tootsuite/mastodon/pull/9608)) +- Remove LD-Signatures from activities that do not need them ([ThibG](https://github.com/tootsuite/mastodon/pull/9659)) + +### Fixed + +- Remove unused computation of reblog references from updateTimeline ([ThibG](https://github.com/tootsuite/mastodon/pull/9244)) +- Fix loaded embeds resetting if a status arrives from API again ([ThibG](https://github.com/tootsuite/mastodon/pull/9270)) +- Fix race condition causing shallow status with only a "favourited" attribute ([ThibG](https://github.com/tootsuite/mastodon/pull/9272)) +- Remove intermediary arrays when creating hash maps from results ([Gargron](https://github.com/tootsuite/mastodon/pull/9291)) +- Extract counters from accounts table to account_stats table to improve performance ([Gargron](https://github.com/tootsuite/mastodon/pull/9295)) +- Change identities id column to a bigint ([Gargron](https://github.com/tootsuite/mastodon/pull/9371)) +- Fix conversations API pagination ([ThibG](https://github.com/tootsuite/mastodon/pull/9407)) +- Improve account suspension speed and completeness ([Gargron](https://github.com/tootsuite/mastodon/pull/9290)) +- Fix thread depth computation in statuses_controller ([ThibG](https://github.com/tootsuite/mastodon/pull/9426)) +- Fix database deadlocks by moving account stats update outside transaction ([ThibG](https://github.com/tootsuite/mastodon/pull/9437)) +- Escape HTML in profile name preview in profile settings ([pawelngei](https://github.com/tootsuite/mastodon/pull/9446)) +- Use same CORS policy for /@:username and /users/:username ([ThibG](https://github.com/tootsuite/mastodon/pull/9485)) +- Make custom emoji domains case insensitive ([Esteth](https://github.com/tootsuite/mastodon/pull/9474)) +- Various fixes to scrollable lists and media gallery ([ThibG](https://github.com/tootsuite/mastodon/pull/9501)) +- Fix bootsnap cache directory being declared relatively ([Gargron](https://github.com/tootsuite/mastodon/pull/9511)) +- Fix timeline pagination in the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9516)) +- Fix padding on dropdown elements in preferences ([ThibG](https://github.com/tootsuite/mastodon/pull/9517)) +- Make avatar and headers respect GIF autoplay settings ([ThibG](https://github.com/tootsuite/mastodon/pull/9515)) +- Do no retry Web Push workers if the server returns a 4xx response ([Gargron](https://github.com/tootsuite/mastodon/pull/9434)) +- Minor scrollable list fixes ([ThibG](https://github.com/tootsuite/mastodon/pull/9551)) +- Ignore low-confidence CharlockHolmes guesses when parsing link cards ([ThibG](https://github.com/tootsuite/mastodon/pull/9510)) +- Fix `tootctl accounts rotate` not updating public keys ([Gargron](https://github.com/tootsuite/mastodon/pull/9556)) +- Fix CSP / X-Frame-Options for media players ([jomo](https://github.com/tootsuite/mastodon/pull/9558)) +- Fix unnecessary loadMore calls when the end of a timeline has been reached ([ThibG](https://github.com/tootsuite/mastodon/pull/9581)) +- Skip mailer job retries when a record no longer exists ([Gargron](https://github.com/tootsuite/mastodon/pull/9590)) +- Fix composer not getting focus after reply confirmation dialog ([ThibG](https://github.com/tootsuite/mastodon/pull/9602)) +- Fix signature verification stoplight triggering on non-timeout errors ([Gargron](https://github.com/tootsuite/mastodon/pull/9617)) +- Fix ThreadResolveWorker getting queued with invalid URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/9628)) +- Fix crash when clearing uninitialized timeline ([ThibG](https://github.com/tootsuite/mastodon/pull/9662)) +- Avoid duplicate work by merging ReplyDistributionWorker into DistributionWorker ([ThibG](https://github.com/tootsuite/mastodon/pull/9660)) +- Skip full text search if it fails, instead of erroring out completely ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9654)) +- Fix profile metadata links not verifying correctly sometimes ([shrft](https://github.com/tootsuite/mastodon/pull/9673)) +- Ensure blocked user unfollows blocker if Block/Undo-Block activities are processed out of order ([ThibG](https://github.com/tootsuite/mastodon/pull/9687)) +- Fix unreadable text color in report modal for some statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9716)) +- Stop GIFV timeline preview explicitly when it's opened in modal ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9749)) +- Fix scrollbar width compensation ([ThibG](https://github.com/tootsuite/mastodon/pull/9824)) +- Fix race conditions when processing deleted toots ([ThibG](https://github.com/tootsuite/mastodon/pull/9815)) +- Fix SSO issues on WebKit browsers by disabling Same-Site cookie again ([moritzheiber](https://github.com/tootsuite/mastodon/pull/9819)) +- Fix empty OEmbed error ([renatolond](https://github.com/tootsuite/mastodon/pull/9807)) +- Fix drag & drop modal not disappearing sometimes ([hinaloe](https://github.com/tootsuite/mastodon/pull/9797)) +- Fix statuses with content warnings being displayed in web push notifications sometimes ([ThibG](https://github.com/tootsuite/mastodon/pull/9778)) +- Fix scroll-to-detailed status not working on public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9773)) +- Fix media modal loading indicator ([ThibG](https://github.com/tootsuite/mastodon/pull/9771)) +- Fix hashtag search results not having a permalink fallback in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9810)) +- Fix slightly cropped font on settings page dropdowns when using system font ([ariasuni](https://github.com/tootsuite/mastodon/pull/9839)) +- Fix not being able to drag & drop text into forms ([tmm576](https://github.com/tootsuite/mastodon/pull/9840)) + +### Security + +- Sanitize and sandbox toot embeds in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9552)) +- Add tombstones for remote statuses to prevent replay attacks ([ThibG](https://github.com/tootsuite/mastodon/pull/9830)) + +## [2.6.5] - 2018-12-01 +### Changed + +- Change lists to display replies to others on the list and list owner ([ThibG](https://github.com/tootsuite/mastodon/pull/9324)) + +### Fixed + +- Fix failures caused by commonly-used JSON-LD contexts being unavailable ([ThibG](https://github.com/tootsuite/mastodon/pull/9412)) ## [2.6.4] - 2018-11-30 ### Fixed -- Fix yarn dependencies not installing due to yanked event-stream package (#9401) +- Fix yarn dependencies not installing due to yanked event-stream package ([Gargron](https://github.com/tootsuite/mastodon/pull/9401)) ## [2.6.3] - 2018-11-30 ### Added -- Add hyphen to characters allowed in remote usernames (#9345) +- Add hyphen to characters allowed in remote usernames ([ThibG](https://github.com/tootsuite/mastodon/pull/9345)) ### Changed -- Change server user count to exclude suspended accounts (#9380) +- Change server user count to exclude suspended accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/9380)) ### Fixed -- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer (#9368) -- Fix missing DNS records raising the wrong kind of exception (#9379) -- Fix already queued deliveries still trying to reach inboxes marked as unavailable (#9358) +- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer ([hugogameiro](https://github.com/tootsuite/mastodon/pull/9368)) +- Fix missing DNS records raising the wrong kind of exception ([Gargron](https://github.com/tootsuite/mastodon/pull/9379)) +- Fix already queued deliveries still trying to reach inboxes marked as unavailable ([Gargron](https://github.com/tootsuite/mastodon/pull/9358)) ### Security -- Fix TLS handshake timeout not being enforced (#9381) +- Fix TLS handshake timeout not being enforced ([Gargron](https://github.com/tootsuite/mastodon/pull/9381)) ## [2.6.2] - 2018-11-23 ### Added -- Add Page to whitelisted ActivityPub types (#9188) -- Add 20px to column width in web UI (#9227) -- Add amount of freed disk space in `tootctl media remove` (#9229, #9239, #9288) -- Add "Show thread" link to self-replies (#9228) +- Add Page to whitelisted ActivityPub types ([mbajur](https://github.com/tootsuite/mastodon/pull/9188)) +- Add 20px to column width in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9227)) +- Add amount of freed disk space in `tootctl media remove` ([Gargron](https://github.com/tootsuite/mastodon/pull/9229), [Gargron](https://github.com/tootsuite/mastodon/pull/9239), [mayaeh](https://github.com/tootsuite/mastodon/pull/9288)) +- Add "Show thread" link to self-replies ([Gargron](https://github.com/tootsuite/mastodon/pull/9228)) ### Changed -- Change order of Atom and RSS links so Atom is first (#9302) -- Change Nginx configuration for Nanobox apps (#9310) -- Change the follow action to appear instant in web UI (#9220) -- Change how the ActiveRecord connection is instantiated in on_worker_boot (#9238) -- Change `tootctl accounts cull` to always touch accounts so they can be skipped (#9293) -- Change mime type comparison to ignore JSON-LD profile (#9179) +- Change order of Atom and RSS links so Atom is first ([Alkarex](https://github.com/tootsuite/mastodon/pull/9302)) +- Change Nginx configuration for Nanobox apps ([danhunsaker](https://github.com/tootsuite/mastodon/pull/9310)) +- Change the follow action to appear instant in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9220)) +- Change how the ActiveRecord connection is instantiated in on_worker_boot ([Gargron](https://github.com/tootsuite/mastodon/pull/9238)) +- Change `tootctl accounts cull` to always touch accounts so they can be skipped ([renatolond](https://github.com/tootsuite/mastodon/pull/9293)) +- Change mime type comparison to ignore JSON-LD profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9179)) ### Fixed -- Fix web UI crash when conversation has no last status (#9207) -- Fix follow limit validator reporting lower number past threshold (#9230) -- Fix form validation flash message color and input borders (#9235) -- Fix invalid twitter:player cards being displayed (#9254) -- Fix emoji update date being processed incorrectly (#9255) -- Fix playing embed resetting if status is reloaded in web UI (#9270, #9275) -- Fix web UI crash when favouriting a deleted status (#9272) -- Fix intermediary arrays being created for hash maps (#9291) -- Fix filter ID not being a string in REST API (#9303) +- Fix web UI crash when conversation has no last status ([sammy8806](https://github.com/tootsuite/mastodon/pull/9207)) +- Fix follow limit validator reporting lower number past threshold ([Gargron](https://github.com/tootsuite/mastodon/pull/9230)) +- Fix form validation flash message color and input borders ([Gargron](https://github.com/tootsuite/mastodon/pull/9235)) +- Fix invalid twitter:player cards being displayed ([ThibG](https://github.com/tootsuite/mastodon/pull/9254)) +- Fix emoji update date being processed incorrectly ([ThibG](https://github.com/tootsuite/mastodon/pull/9255)) +- Fix playing embed resetting if status is reloaded in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9270), [Gargron](https://github.com/tootsuite/mastodon/pull/9275)) +- Fix web UI crash when favouriting a deleted status ([ThibG](https://github.com/tootsuite/mastodon/pull/9272)) +- Fix intermediary arrays being created for hash maps ([Gargron](https://github.com/tootsuite/mastodon/pull/9291)) +- Fix filter ID not being a string in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9303)) ### Security -- Fix multiple remote account deletions being able to deadlock the database (#9292) -- Fix HTTP connection timeout of 10s not being enforced (#9329) +- Fix multiple remote account deletions being able to deadlock the database ([Gargron](https://github.com/tootsuite/mastodon/pull/9292)) +- Fix HTTP connection timeout of 10s not being enforced ([Gargron](https://github.com/tootsuite/mastodon/pull/9329)) ## [2.6.1] - 2018-10-30 ### Fixed -- Fix resolving resources by URL not working due to a regression in #9132 (#9171) -- Fix reducer error in web UI when a conversation has no last status (#9173) +- Fix resolving resources by URL not working due to a regression in [valerauko](https://github.com/tootsuite/mastodon/pull/9132) ([Gargron](https://github.com/tootsuite/mastodon/pull/9171)) +- Fix reducer error in web UI when a conversation has no last status ([Gargron](https://github.com/tootsuite/mastodon/pull/9173)) ## [2.6.0] - 2018-10-30 ### Added -- Add link ownership verification (#8703) -- Add conversations API (#8832) -- Add limit for the number of people that can be followed from one account (#8807) -- Add admin setting to customize mascot (#8766) -- Add support for more granular ActivityPub audiences from other software, i.e. circles (#8950, #9093, #9150) -- Add option to block all reports from a domain (#8830) -- Add user preference to always expand toots marked with content warnings (#8762) -- Add user preference to always hide all media (#8569) -- Add `force_login` param to OAuth authorize page (#8655) -- Add `tootctl accounts backup` (#8642, #8811) -- Add `tootctl accounts create` (#8642, #8811) -- Add `tootctl accounts cull` (#8642, #8811) -- Add `tootctl accounts delete` (#8642, #8811) -- Add `tootctl accounts modify` (#8642, #8811) -- Add `tootctl accounts refresh` (#8642, #8811) -- Add `tootctl feeds build` (#8642, #8811) -- Add `tootctl feeds clear` (#8642, #8811) -- Add `tootctl settings registrations open` (#8642, #8811) -- Add `tootctl settings registrations close` (#8642, #8811) -- Add `min_id` param to REST API to support backwards pagination (#8736) -- Add a confirmation dialog when hitting reply and the compose box isn't empty (#8893) -- Add PostgreSQL disk space growth tracking in PGHero (#8906) -- Add button for disabling local account to report quick actions bar (#9024) -- Add Czech language (#8594) -- Add `same-site` (`lax`) attribute to cookies (#8626) -- Add support for styled scrollbars in Firefox Nightly (#8653) -- Add highlight to the active tab in web UI profiles (#8673) -- Add auto-focus for comment textarea in report modal (#8689) -- Add auto-focus for emoji picker's search field (#8688) -- Add nginx and systemd templates to `dist/` directory (#8770) -- Add support for `/.well-known/change-password` (#8828) -- Add option to override FFMPEG binary path (#8855) -- Add `dns-prefetch` tag when using different host for assets or uploads (#8942) -- Add `description` meta tag (#8941) -- Add `Content-Security-Policy` header (#8957) -- Add cache for the instance info API (#8765) -- Add suggested follows to search screen in mobile layout (#9010) -- Add CORS header to `/.well-known/*` routes (#9083) -- Add `card` attribute to statuses returned from REST API (#9120) -- Add in-stream link preview (#9120) -- Add support for ActivityPub `Page` objects (#9121) +- Add link ownership verification ([Gargron](https://github.com/tootsuite/mastodon/pull/8703)) +- Add conversations API ([Gargron](https://github.com/tootsuite/mastodon/pull/8832)) +- Add limit for the number of people that can be followed from one account ([Gargron](https://github.com/tootsuite/mastodon/pull/8807)) +- Add admin setting to customize mascot ([ashleyhull-versent](https://github.com/tootsuite/mastodon/pull/8766)) +- Add support for more granular ActivityPub audiences from other software, i.e. circles ([Gargron](https://github.com/tootsuite/mastodon/pull/8950), [Gargron](https://github.com/tootsuite/mastodon/pull/9093), [Gargron](https://github.com/tootsuite/mastodon/pull/9150)) +- Add option to block all reports from a domain ([Gargron](https://github.com/tootsuite/mastodon/pull/8830)) +- Add user preference to always expand toots marked with content warnings ([webroo](https://github.com/tootsuite/mastodon/pull/8762)) +- Add user preference to always hide all media ([fvh-P](https://github.com/tootsuite/mastodon/pull/8569)) +- Add `force_login` param to OAuth authorize page ([Gargron](https://github.com/tootsuite/mastodon/pull/8655)) +- Add `tootctl accounts backup` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl accounts create` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl accounts cull` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl accounts delete` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl accounts modify` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl accounts refresh` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl feeds build` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl feeds clear` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl settings registrations open` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `tootctl settings registrations close` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811)) +- Add `min_id` param to REST API to support backwards pagination ([Gargron](https://github.com/tootsuite/mastodon/pull/8736)) +- Add a confirmation dialog when hitting reply and the compose box isn't empty ([ThibG](https://github.com/tootsuite/mastodon/pull/8893)) +- Add PostgreSQL disk space growth tracking in PGHero ([Gargron](https://github.com/tootsuite/mastodon/pull/8906)) +- Add button for disabling local account to report quick actions bar ([Gargron](https://github.com/tootsuite/mastodon/pull/9024)) +- Add Czech language ([Aditoo17](https://github.com/tootsuite/mastodon/pull/8594)) +- Add `same-site` (`lax`) attribute to cookies ([sorin-davidoi](https://github.com/tootsuite/mastodon/pull/8626)) +- Add support for styled scrollbars in Firefox Nightly ([sorin-davidoi](https://github.com/tootsuite/mastodon/pull/8653)) +- Add highlight to the active tab in web UI profiles ([rhoio](https://github.com/tootsuite/mastodon/pull/8673)) +- Add auto-focus for comment textarea in report modal ([ThibG](https://github.com/tootsuite/mastodon/pull/8689)) +- Add auto-focus for emoji picker's search field ([ThibG](https://github.com/tootsuite/mastodon/pull/8688)) +- Add nginx and systemd templates to `dist/` directory ([Gargron](https://github.com/tootsuite/mastodon/pull/8770)) +- Add support for `/.well-known/change-password` ([Gargron](https://github.com/tootsuite/mastodon/pull/8828)) +- Add option to override FFMPEG binary path ([sascha-sl](https://github.com/tootsuite/mastodon/pull/8855)) +- Add `dns-prefetch` tag when using different host for assets or uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/8942)) +- Add `description` meta tag ([Gargron](https://github.com/tootsuite/mastodon/pull/8941)) +- Add `Content-Security-Policy` header ([ThibG](https://github.com/tootsuite/mastodon/pull/8957)) +- Add cache for the instance info API ([ykzts](https://github.com/tootsuite/mastodon/pull/8765)) +- Add suggested follows to search screen in mobile layout ([Gargron](https://github.com/tootsuite/mastodon/pull/9010)) +- Add CORS header to `/.well-known/*` routes ([BenLubar](https://github.com/tootsuite/mastodon/pull/9083)) +- Add `card` attribute to statuses returned from REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9120)) +- Add in-stream link preview ([Gargron](https://github.com/tootsuite/mastodon/pull/9120)) +- Add support for ActivityPub `Page` objects ([mbajur](https://github.com/tootsuite/mastodon/pull/9121)) ### Changed -- Change forms design (#8703) -- Change reports overview to group by target account (#8674) -- Change web UI to show "read more" link on overly long in-stream statuses (#8205) -- Change design of direct messages column (#8832, #9022) -- Change home timelines to exclude DMs (#8940) -- Change list timelines to exclude all replies (#8683) -- Change admin accounts UI default sort to most recent (#8813) -- Change documentation URL in the UI (#8898) -- Change style of success and failure messages (#8973) -- Change DM filtering to always allow DMs from staff (#8993) -- Change recommended Ruby version to 2.5.3 (#9003) -- Change docker-compose default to persist volumes in current directory (#9055) -- Change character counters on edit profile page to input length limit (#9100) -- Change notification filtering to always let through messages from staff (#9152) -- Change "hide boosts from user" function also hiding notifications about boosts (#9147) -- Change CSS `detailed-status__wrapper` class actually wrap the detailed status (#8547) +- Change forms design ([Gargron](https://github.com/tootsuite/mastodon/pull/8703)) +- Change reports overview to group by target account ([Gargron](https://github.com/tootsuite/mastodon/pull/8674)) +- Change web UI to show "read more" link on overly long in-stream statuses ([lanodan](https://github.com/tootsuite/mastodon/pull/8205)) +- Change design of direct messages column ([Gargron](https://github.com/tootsuite/mastodon/pull/8832), [Gargron](https://github.com/tootsuite/mastodon/pull/9022)) +- Change home timelines to exclude DMs ([Gargron](https://github.com/tootsuite/mastodon/pull/8940)) +- Change list timelines to exclude all replies ([cbayerlein](https://github.com/tootsuite/mastodon/pull/8683)) +- Change admin accounts UI default sort to most recent ([Gargron](https://github.com/tootsuite/mastodon/pull/8813)) +- Change documentation URL in the UI ([Gargron](https://github.com/tootsuite/mastodon/pull/8898)) +- Change style of success and failure messages ([Gargron](https://github.com/tootsuite/mastodon/pull/8973)) +- Change DM filtering to always allow DMs from staff ([qguv](https://github.com/tootsuite/mastodon/pull/8993)) +- Change recommended Ruby version to 2.5.3 ([zunda](https://github.com/tootsuite/mastodon/pull/9003)) +- Change docker-compose default to persist volumes in current directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9055)) +- Change character counters on edit profile page to input length limit ([Gargron](https://github.com/tootsuite/mastodon/pull/9100)) +- Change notification filtering to always let through messages from staff ([Gargron](https://github.com/tootsuite/mastodon/pull/9152)) +- Change "hide boosts from user" function also hiding notifications about boosts ([ThibG](https://github.com/tootsuite/mastodon/pull/9147)) +- Change CSS `detailed-status__wrapper` class actually wrap the detailed status ([trwnh](https://github.com/tootsuite/mastodon/pull/8547)) ### Deprecated -- `GET /api/v1/timelines/direct` → `GET /api/v1/conversations` (#8832) -- `POST /api/v1/notifications/dismiss` → `POST /api/v1/notifications/:id/dismiss` (#8905) -- `GET /api/v1/statuses/:id/card` → `card` attributed included in status (#9120) +- `GET /api/v1/timelines/direct` → `GET /api/v1/conversations` ([Gargron](https://github.com/tootsuite/mastodon/pull/8832)) +- `POST /api/v1/notifications/dismiss` → `POST /api/v1/notifications/:id/dismiss` ([Gargron](https://github.com/tootsuite/mastodon/pull/8905)) +- `GET /api/v1/statuses/:id/card` → `card` attributed included in status ([Gargron](https://github.com/tootsuite/mastodon/pull/9120)) ### Removed -- Remove "on this device" label in column push settings (#8704) -- Remove rake tasks in favour of tootctl commands (#8675) +- Remove "on this device" label in column push settings ([rhoio](https://github.com/tootsuite/mastodon/pull/8704)) +- Remove rake tasks in favour of tootctl commands ([Gargron](https://github.com/tootsuite/mastodon/pull/8675)) ### Fixed -- Fix remote statuses using instance's default locale if no language given (#8861) -- Fix streaming API not exiting when port or socket is unavailable (#9023) -- Fix network calls being performed in database transaction in ActivityPub handler (#8951) -- Fix dropdown arrow position (#8637) -- Fix first element of dropdowns being focused even if not using keyboard (#8679) -- Fix tootctl requiring `bundle exec` invocation (#8619) -- Fix public pages not using animation preference for avatars (#8614) -- Fix OEmbed/OpenGraph cards not understanding relative URLs (#8669) -- Fix some dark emojis not having a white outline (#8597) -- Fix media description not being displayed in various media modals (#8678) -- Fix generated URLs of desktop notifications missing base URL (#8758) -- Fix RTL styles (#8764, #8767, #8823, #8897, #9005, #9007, #9018, #9021, #9145, #9146) -- Fix crash in streaming API when tag param missing (#8955) -- Fix hotkeys not working when no element is focused (#8998) -- Fix some hotkeys not working on detailed status view (#9006) -- Fix og:url on status pages (#9047) -- Fix upload option buttons only being visible on hover (#9074) -- Fix tootctl not returning exit code 1 on wrong arguments (#9094) -- Fix preview cards for appearing for profiles mentioned in toot (#6934, #9158) -- Fix local accounts sometimes being duplicated as faux-remote (#9109) -- Fix emoji search when the shortcode has multiple separators (#9124) -- Fix dropdowns sometimes being partially obscured by other elements (#9126) -- Fix cache not updating when reply/boost/favourite counters or media sensitivity update (#9119) -- Fix empty display name precedence over username in web UI (#9163) -- Fix td instead of th in sessions table header (#9162) -- Fix handling of content types with profile (#9132) +- Fix remote statuses using instance's default locale if no language given ([Kjwon15](https://github.com/tootsuite/mastodon/pull/8861)) +- Fix streaming API not exiting when port or socket is unavailable ([Gargron](https://github.com/tootsuite/mastodon/pull/9023)) +- Fix network calls being performed in database transaction in ActivityPub handler ([Gargron](https://github.com/tootsuite/mastodon/pull/8951)) +- Fix dropdown arrow position ([ThibG](https://github.com/tootsuite/mastodon/pull/8637)) +- Fix first element of dropdowns being focused even if not using keyboard ([ThibG](https://github.com/tootsuite/mastodon/pull/8679)) +- Fix tootctl requiring `bundle exec` invocation ([abcang](https://github.com/tootsuite/mastodon/pull/8619)) +- Fix public pages not using animation preference for avatars ([renatolond](https://github.com/tootsuite/mastodon/pull/8614)) +- Fix OEmbed/OpenGraph cards not understanding relative URLs ([ThibG](https://github.com/tootsuite/mastodon/pull/8669)) +- Fix some dark emojis not having a white outline ([ThibG](https://github.com/tootsuite/mastodon/pull/8597)) +- Fix media description not being displayed in various media modals ([ThibG](https://github.com/tootsuite/mastodon/pull/8678)) +- Fix generated URLs of desktop notifications missing base URL ([GenbuHase](https://github.com/tootsuite/mastodon/pull/8758)) +- Fix RTL styles ([mabkenar](https://github.com/tootsuite/mastodon/pull/8764), [mabkenar](https://github.com/tootsuite/mastodon/pull/8767), [mabkenar](https://github.com/tootsuite/mastodon/pull/8823), [mabkenar](https://github.com/tootsuite/mastodon/pull/8897), [mabkenar](https://github.com/tootsuite/mastodon/pull/9005), [mabkenar](https://github.com/tootsuite/mastodon/pull/9007), [mabkenar](https://github.com/tootsuite/mastodon/pull/9018), [mabkenar](https://github.com/tootsuite/mastodon/pull/9021), [mabkenar](https://github.com/tootsuite/mastodon/pull/9145), [mabkenar](https://github.com/tootsuite/mastodon/pull/9146)) +- Fix crash in streaming API when tag param missing ([Gargron](https://github.com/tootsuite/mastodon/pull/8955)) +- Fix hotkeys not working when no element is focused ([ThibG](https://github.com/tootsuite/mastodon/pull/8998)) +- Fix some hotkeys not working on detailed status view ([ThibG](https://github.com/tootsuite/mastodon/pull/9006)) +- Fix og:url on status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9047)) +- Fix upload option buttons only being visible on hover ([Gargron](https://github.com/tootsuite/mastodon/pull/9074)) +- Fix tootctl not returning exit code 1 on wrong arguments ([sascha-sl](https://github.com/tootsuite/mastodon/pull/9094)) +- Fix preview cards for appearing for profiles mentioned in toot ([ThibG](https://github.com/tootsuite/mastodon/pull/6934), [ThibG](https://github.com/tootsuite/mastodon/pull/9158)) +- Fix local accounts sometimes being duplicated as faux-remote ([Gargron](https://github.com/tootsuite/mastodon/pull/9109)) +- Fix emoji search when the shortcode has multiple separators ([ThibG](https://github.com/tootsuite/mastodon/pull/9124)) +- Fix dropdowns sometimes being partially obscured by other elements ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9126)) +- Fix cache not updating when reply/boost/favourite counters or media sensitivity update ([Gargron](https://github.com/tootsuite/mastodon/pull/9119)) +- Fix empty display name precedence over username in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9163)) +- Fix td instead of th in sessions table header ([Gargron](https://github.com/tootsuite/mastodon/pull/9162)) +- Fix handling of content types with profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9132)) ## [2.5.2] - 2018-10-12 ### Security -- Fix XSS vulnerability (#8959) +- Fix XSS vulnerability ([Gargron](https://github.com/tootsuite/mastodon/pull/8959)) ## [2.5.1] - 2018-10-07 ### Fixed -- Fix database migrations for PostgreSQL below 9.5 (#8903) -- Fix class autoloading issue in ActivityPub Create handler (#8820) -- Fix cache statistics not being sent via statsd when statsd enabled (#8831) -- Bump puma from 3.11.4 to 3.12.0 (#8883) +- Fix database migrations for PostgreSQL below 9.5 ([Gargron](https://github.com/tootsuite/mastodon/pull/8903)) +- Fix class autoloading issue in ActivityPub Create handler ([Gargron](https://github.com/tootsuite/mastodon/pull/8820)) +- Fix cache statistics not being sent via statsd when statsd enabled ([ykzts](https://github.com/tootsuite/mastodon/pull/8831)) +- Bump puma from 3.11.4 to 3.12.0 ([dependabot[bot]](https://github.com/tootsuite/mastodon/pull/8883)) ### Security -- Fix some local images not having their EXIF metadata stripped on upload (#8714) -- Fix being able to enable a disabled relay via ActivityPub Accept handler (#8864) -- Bump nokogiri from 1.8.4 to 1.8.5 (#8881) -- Fix being able to report statuses not belonging to the reported account (#8916) +- Fix some local images not having their EXIF metadata stripped on upload ([ThibG](https://github.com/tootsuite/mastodon/pull/8714)) +- Fix being able to enable a disabled relay via ActivityPub Accept handler ([ThibG](https://github.com/tootsuite/mastodon/pull/8864)) +- Bump nokogiri from 1.8.4 to 1.8.5 ([dependabot[bot]](https://github.com/tootsuite/mastodon/pull/8881)) +- Fix being able to report statuses not belonging to the reported account ([ThibG](https://github.com/tootsuite/mastodon/pull/8916)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b55729a9b..65366be5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Contributing ============ -Thank you for considering contributing to Mastodon 🐘 +Thank you for considering contributing to Mastodon 🐘 You can contribute in the following ways: @@ -10,6 +10,8 @@ You can contribute in the following ways: - Contributing code to Mastodon by fixing bugs or implementing features - Improving the documentation +If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). + ## Bug reports Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles. diff --git a/Dockerfile b/Dockerfile index 11fc17d36..1263efc93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,86 +1,126 @@ -FROM node:8.14.0-alpine as node -FROM ruby:2.4.5-alpine3.8 +FROM ubuntu:18.04 as build-dep -LABEL maintainer="https://github.com/tootsuite/mastodon" \ - description="Your self-hosted, globally interconnected microblogging community" +# Use bash for the shell +SHELL ["bash", "-c"] +# Install Node +ENV NODE_VER="8.15.0" +RUN echo "Etc/UTC" > /etc/localtime && \ + apt update && \ + apt -y dist-upgrade && \ + apt -y install wget make gcc g++ python && \ + cd ~ && \ + wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \ + tar xf node-v$NODE_VER.tar.gz && \ + cd node-v$NODE_VER && \ + ./configure --prefix=/opt/node && \ + make -j$(nproc) > /dev/null && \ + make install + +# Install jemalloc +ENV JE_VER="5.1.0" +RUN apt -y install autoconf && \ + cd ~ && \ + wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \ + tar xf $JE_VER.tar.gz && \ + cd jemalloc-$JE_VER && \ + ./autogen.sh && \ + ./configure --prefix=/opt/jemalloc && \ + make -j$(nproc) > /dev/null && \ + make install_bin install_include install_lib + +# Install ruby +ENV RUBY_VER="2.6.1" +ENV CPPFLAGS="-I/opt/jemalloc/include" +ENV LDFLAGS="-L/opt/jemalloc/lib/" +RUN apt -y install build-essential \ + bison libyaml-dev libgdbm-dev libreadline-dev \ + libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \ + cd ~ && \ + wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \ + tar xf ruby-$RUBY_VER.tar.gz && \ + cd ruby-$RUBY_VER && \ + ./configure --prefix=/opt/ruby \ + --with-jemalloc \ + --with-shared \ + --disable-install-doc && \ + ln -s /opt/jemalloc/lib/* /usr/lib/ && \ + make -j$(nproc) > /dev/null && \ + make install + +ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin" + +RUN npm install -g yarn && \ + gem install bundler + +COPY . /opt/mastodon + +RUN apt -y install git libicu-dev libidn11-dev \ + libpq-dev libprotobuf-dev protobuf-compiler && \ + cd /opt/mastodon && \ + bundle install -j$(nproc) --deployment --without development test && \ + yarn install --pure-lockfile + +FROM ubuntu:18.04 + +# Copy over all the langs needed for runtime +COPY --from=build-dep /opt/node /opt/node +COPY --from=build-dep /opt/ruby /opt/ruby +COPY --from=build-dep /opt/jemalloc /opt/jemalloc + +# Add more PATHs to the PATH +ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin" + +# Create the mastodon user ARG UID=991 ARG GID=991 +RUN apt update && \ + echo "Etc/UTC" > /etc/localtime && \ + ln -s /opt/jemalloc/lib/* /usr/lib/ && \ + apt -y dist-upgrade && \ + apt install -y whois wget && \ + addgroup --gid $GID mastodon && \ + useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \ + echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd -ENV PATH=/mastodon/bin:$PATH \ - RAILS_SERVE_STATIC_FILES=true \ - RAILS_ENV=production \ - NODE_ENV=production +# Copy over masto source from building and set permissions +COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon -ARG LIBICONV_VERSION=1.15 -ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178 +# Install masto runtime deps +RUN apt -y --no-install-recommends install \ + libssl1.1 libpq5 imagemagick ffmpeg \ + libicu60 libprotobuf10 libidn11 libyaml-0-2 \ + file ca-certificates tzdata libreadline7 && \ + apt -y install gcc && \ + ln -s /opt/mastodon /mastodon && \ + gem install bundler -EXPOSE 3000 4000 +# Clean up more dirs +RUN rm -rf /var/cache && \ + rm -rf /var/apt -WORKDIR /mastodon +# Add tini +ENV TINI_VERSION="0.18.0" +ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855" +ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini +RUN echo "$TINI_SUM tini" | sha256sum -c - +RUN chmod +x /tini -COPY --from=node /usr/local/bin/node /usr/local/bin/node -COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules -COPY --from=node /usr/local/bin/npm /usr/local/bin/npm -COPY --from=node /opt/yarn-* /opt/yarn +# Run masto services in prod mode +ENV RAILS_ENV="production" +ENV NODE_ENV="production" -RUN apk -U upgrade \ - && apk add -t build-dependencies \ - build-base \ - icu-dev \ - libidn-dev \ - libressl \ - libtool \ - postgresql-dev \ - protobuf-dev \ - python \ - && apk add \ - ca-certificates \ - ffmpeg \ - file \ - git \ - icu-libs \ - imagemagick \ - libidn \ - libpq \ - protobuf \ - tini \ - tzdata \ - && update-ca-certificates \ - && ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \ - && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg \ - && mkdir -p /tmp/src /opt \ - && wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \ - && echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \ - && tar -xzf libiconv.tar.gz -C /tmp/src \ - && rm libiconv.tar.gz \ - && cd /tmp/src/libiconv-$LIBICONV_VERSION \ - && ./configure --prefix=/usr/local \ - && make -j$(getconf _NPROCESSORS_ONLN)\ - && make install \ - && libtool --finish /usr/local/lib \ - && cd /mastodon \ - && rm -rf /tmp/* /var/cache/apk/* - -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 \ - && bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \ - && yarn install --pure-lockfile --ignore-engines \ - && yarn cache clean - -RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \ - && mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \ - && chown -R mastodon:mastodon /mastodon/public - -COPY . /mastodon - -RUN chown -R mastodon:mastodon /mastodon - -VOLUME /mastodon/public/system +# Tell rails to serve static files +ENV RAILS_SERVE_STATIC_FILES="true" +# Set the run user USER mastodon -RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder bundle exec rails assets:precompile +# Precompile assets +RUN cd ~ && \ + OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \ + yarn cache clean -ENTRYPOINT ["/sbin/tini", "--"] +# Set the work dir and the container entry point +WORKDIR /opt/mastodon +ENTRYPOINT ["/tini", "--"] diff --git a/Gemfile b/Gemfile index 3d78b1dfe..46d77f662 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ # 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' @@ -13,7 +13,7 @@ gem 'hamlit-rails', '~> 0.2' gem 'pg', '~> 1.1' gem 'makara', '~> 0.4' gem 'pghero', '~> 2.2' -gem 'dotenv-rails', '~> 2.5' +gem 'dotenv-rails', '~> 2.7' gem 'aws-sdk-s3', '~> 1.30', require: false gem 'fog-core', '<= 2.1.0' @@ -23,13 +23,13 @@ gem 'paperclip-av-transcoder', '~> 0.6' gem 'streamio-ffmpeg', '~> 3.0' gem 'active_model_serializers', '~> 0.10' -gem 'addressable', '~> 2.5' -gem 'bootsnap', '~> 1.3', require: false +gem 'addressable', '~> 2.6' +gem 'bootsnap', '~> 1.4', require: false 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' @@ -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.9' +gem 'nokogiri', '~> 1.10' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.7' gem 'ostatus2', '~> 2.0' @@ -75,7 +75,7 @@ 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-unique-jobs', '~> 6.0' gem 'sidekiq-bulk', '~>0.2.0' gem 'simple-navigation', '~> 4.0' gem 'simple_form', '~> 4.1' @@ -89,7 +89,7 @@ gem 'tzinfo-data', '~> 1.2018' gem 'webpacker', '~> 3.5' gem 'webpush' -gem 'json-ld', '~> 2.2' +gem 'json-ld', '~> 3.0' gem 'json-ld-preloaded', '~> 3.0' gem 'rdf-normalize', '~> 0.3' @@ -97,7 +97,7 @@ group :development, :test do gem 'fabrication', '~> 2.20' gem 'fuubar', '~> 2.3' gem 'i18n-tasks', '~> 0.9', require: false - gem 'pry-byebug', '~> 3.6' + gem 'pry-byebug', '~> 3.7' gem 'pry-rails', '~> 0.3' gem 'rspec-rails', '~> 3.8' end @@ -107,15 +107,15 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.12' + gem 'capybara', '~> 3.14' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' - gem 'microformats', '~> 4.0' + gem 'microformats', '~> 4.1' 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.27' + gem 'webmock', '~> 3.5' + gem 'parallel_tests', '~> 2.28' end group :development do @@ -127,8 +127,8 @@ group :development do gem 'letter_opener', '~> 1.7' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', '~> 0.61', require: false - gem 'brakeman', '~> 4.3', require: false + gem 'rubocop', '~> 0.65', require: false + gem 'brakeman', '~> 4.4', require: false gem 'bundler-audit', '~> 0.6', require: false gem 'scss_lint', '~> 0.57', require: false @@ -145,3 +145,5 @@ group :production do gem 'lograge', '~> 0.10' gem 'redis-rails', '~> 5.0' end + +gem 'concurrent-ruby', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 36a3a65f6..782fccba2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,7 +38,7 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_model_serializers (0.10.8) + active_model_serializers (0.10.9) actionpack (>= 4.1, < 6) activemodel (>= 4.1, < 6) case_transform (>= 0.2) @@ -62,7 +62,7 @@ GEM i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.5.2) + addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) @@ -76,8 +76,8 @@ GEM av (0.9.0) cocaine (~> 0.5.3) aws-eventstream (1.0.1) - aws-partitions (1.122.0) - aws-sdk-core (3.43.0) + aws-partitions (1.131.0) + aws-sdk-core (3.45.0) aws-eventstream (~> 1.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) @@ -85,31 +85,31 @@ GEM aws-sdk-kms (1.13.0) aws-sdk-core (~> 3, >= 3.39.0) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.30.0) + aws-sdk-s3 (1.30.1) aws-sdk-core (~> 3, >= 3.39.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) aws-sigv4 (1.0.3) bcrypt (3.1.12) benchmark-ips (2.7.2) - better_errors (2.5.0) + better_errors (2.5.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.3.2) + bootsnap (1.4.1) msgpack (~> 1.0) - brakeman (4.3.1) + brakeman (4.4.0) browser (2.5.3) builder (3.2.3) bullet (5.9.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - bundler-audit (0.6.0) - bundler (~> 1.2) + bundler-audit (0.6.1) + bundler (>= 1.2.0, < 3) thor (~> 0.18) - byebug (10.0.2) + byebug (11.0.0) capistrano (3.11.0) airbrussh (>= 1.0.0) i18n @@ -126,7 +126,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.12.0) + capybara (3.14.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -142,13 +142,13 @@ 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) climate_control (>= 0.0.3, < 1.0) coderay (1.1.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -185,10 +185,10 @@ GEM unf (>= 0.0.5, < 1.0.0) doorkeeper (5.0.2) railties (>= 4.2) - dotenv (2.5.0) - dotenv-rails (2.5.0) - dotenv (= 2.5.0) - railties (>= 3.2, < 6.0) + dotenv (2.7.1) + dotenv-rails (2.7.1) + dotenv (= 2.7.1) + railties (>= 3.2, < 6.1) elasticsearch (6.0.2) elasticsearch-api (= 6.0.2) elasticsearch-transport (= 6.0.2) @@ -200,12 +200,12 @@ GEM multi_json encryptor (3.0.0) equatable (0.5.0) - erubi (1.7.1) + erubi (1.8.0) et-orbi (1.1.6) tzinfo excon (0.62.0) fabrication (2.20.1) - faker (1.9.1) + faker (1.9.3) i18n (>= 0.7) faraday (0.15.0) multipart-post (>= 1.2, < 3) @@ -231,7 +231,7 @@ GEM fuubar (2.3.2) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - get_process_mem (0.2.2) + get_process_mem (0.2.3) globalid (0.4.1) activesupport (>= 4.2.0) goldfinger (2.1.0) @@ -239,11 +239,11 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.8.8) + hamlit (2.9.2) temple (>= 0.8.0) thor tilt - hamlit-rails (0.2.0) + hamlit-rails (0.2.2) actionpack (>= 4.0.1) activesupport (>= 4.0.1) hamlit (>= 1.2.0) @@ -266,10 +266,10 @@ GEM domain_name (~> 0.5) http-form_data (2.1.1) http_accept_language (2.1.1) - httplog (1.1.1) + httplog (1.2.1) rack (>= 1.0) rainbow (>= 2.0.0) - i18n (1.1.1) + i18n (1.5.3) concurrent-ruby (~> 1.0) i18n-tasks (0.9.28) activesupport (>= 4.0.2) @@ -284,14 +284,14 @@ GEM idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) - jaro_winkler (1.5.1) + jaro_winkler (1.5.2) jmespath (1.4.0) json (2.1.0) - json-ld (2.2.1) + json-ld (3.0.2) multi_json (~> 1.12) rdf (>= 2.2.8, < 4.0) - json-ld-preloaded (3.0.0) - json-ld (>= 2.2, < 4.0) + json-ld-preloaded (3.0.2) + json-ld (~> 3.0) multi_json (~> 1.12) rdf (~> 3.0) jsonapi-renderer (0.2.0) @@ -335,9 +335,9 @@ GEM redis (>= 3.0.5) memory_profiler (0.9.12) method_source (0.9.2) - microformats (4.0.7) - json - nokogiri + microformats (4.1.0) + json (~> 2.1) + nokogiri (~> 1.8, >= 1.8.3) mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) @@ -345,7 +345,7 @@ GEM mini_mime (1.0.1) mini_portile2 (2.4.0) minitest (5.11.3) - msgpack (1.2.4) + msgpack (1.2.6) multi_json (1.13.1) multipart-post (2.0.0) necromancer (0.4.0) @@ -354,16 +354,16 @@ GEM net-ssh (>= 2.6.5) net-ssh (5.0.2) nio4r (2.3.1) - nokogiri (1.9.1) + nokogiri (1.10.1) mini_portile2 (~> 2.4.0) nokogumbo (2.0.0) nokogiri (~> 1.8, >= 1.8.4) - nsa (0.2.4) + nsa (0.2.7) activesupport (>= 4.2, < 6) - concurrent-ruby (~> 1.0.0) - sidekiq (>= 3.5.0) - statsd-ruby (~> 1.2.0) - oj (3.7.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + sidekiq (>= 3.5) + statsd-ruby (~> 1.4, >= 1.4.0) + oj (3.7.9) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -389,18 +389,18 @@ GEM paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) - parallel (1.12.1) - parallel_tests (2.27.0) + parallel (1.13.0) + parallel_tests (2.28.0) parallel - parser (2.5.3.0) + parser (2.6.0.0) ast (~> 2.4.0) pastel (0.7.2) equatable (~> 0.5.0) tty-color (~> 0.4.0) - pg (1.1.3) + pg (1.1.4) pghero (2.2.0) activerecord - pkg-config (1.3.2) + pkg-config (1.3.4) powerpack (0.1.2) premailer (1.11.1) addressable @@ -413,21 +413,22 @@ GEM pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.6.0) - byebug (~> 10.0) + pry-byebug (3.7.0) + byebug (~> 11.0) pry (~> 0.10) - pry-rails (0.3.8) + pry-rails (0.3.9) pry (>= 0.10.4) + psych (3.1.0) public_suffix (3.0.3) puma (3.12.0) - pundit (2.0.0) + pundit (2.0.1) activesupport (>= 3.0.0) raabro (1.1.6) rack (2.0.6) rack-attack (5.4.2) rack (>= 1.0, < 3) rack-cors (1.0.2) - rack-protection (2.0.4) + rack-protection (2.0.5) rack rack-proxy (0.6.4) rack @@ -455,7 +456,7 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - rails-i18n (5.1.2) + rails-i18n (5.1.3) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) rails-settings-cached (0.6.6) @@ -467,11 +468,11 @@ GEM rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) rainbow (3.0.0) - rake (12.3.1) + rake (12.3.2) rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rdf (3.0.7) + rdf (3.0.9) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.3) @@ -513,7 +514,7 @@ GEM rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) - rspec-rails (3.8.1) + rspec-rails (3.8.2) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -525,11 +526,12 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.8.0) - rubocop (0.61.1) + rubocop (0.65.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) powerpack (~> 0.1) + psych (>= 3.1.0) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.4.0) @@ -551,8 +553,9 @@ GEM scss_lint (0.57.1) rake (>= 0.9, < 13) sass (~> 3.5, >= 3.5.5) - sidekiq (5.2.3) + sidekiq (5.2.5) connection_pool (~> 2.2, >= 2.2.2) + rack (>= 1.5.0) rack-protection (>= 1.5.0) redis (>= 3.3.5, < 5) sidekiq-bulk (0.2.0) @@ -562,8 +565,9 @@ GEM rufus-scheduler (~> 3.2) sidekiq (>= 3) tilt (>= 1.4.0) - sidekiq-unique-jobs (5.0.10) - sidekiq (>= 4.0, <= 6.0) + sidekiq-unique-jobs (6.0.12) + concurrent-ruby (~> 1.0, >= 1.0.5) + sidekiq (>= 4.0, < 7.0) thor (~> 0) simple-navigation (4.0.5) activesupport (>= 2.3.2) @@ -586,7 +590,7 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.12) - statsd-ruby (1.2.1) + statsd-ruby (1.4.0) stoplight (2.1.3) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) @@ -599,13 +603,13 @@ GEM climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) - tilt (2.0.8) + tilt (2.0.9) 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.18.0) + tty-prompt (0.18.1) necromancer (~> 0.4.0) pastel (~> 0.7.0) timers (~> 4.0) @@ -620,16 +624,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) + 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 @@ -637,7 +641,7 @@ GEM activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - webpush (0.3.4) + webpush (0.3.6) hkdf (~> 0.2) jwt (~> 2.0) websocket-driver (0.7.0) @@ -653,13 +657,13 @@ PLATFORMS DEPENDENCIES active_model_serializers (~> 0.10) active_record_query_trace (~> 1.5) - addressable (~> 2.5) + addressable (~> 2.6) annotate (~> 2.7) aws-sdk-s3 (~> 1.30) better_errors (~> 2.5) binding_of_caller (~> 0.7) - bootsnap (~> 1.3) - brakeman (~> 4.3) + bootsnap (~> 1.4) + brakeman (~> 4.4) browser bullet (~> 5.9) bundler-audit (~> 0.6) @@ -667,17 +671,18 @@ DEPENDENCIES capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 3.12) + capybara (~> 3.14) charlock_holmes (~> 0.7.6) chewy (~> 5.0) - cld3 (~> 3.2.0) + cld3 (~> 3.2.3) climate_control (~> 0.2) + concurrent-ruby derailed_benchmarks devise (~> 4.5) devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.2) doorkeeper (~> 5.0) - dotenv-rails (~> 2.5) + dotenv-rails (~> 2.7) fabrication (~> 2.20) faker (~> 1.9) fast_blank (~> 1.0) @@ -692,11 +697,11 @@ 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 (~> 3.0) json-ld-preloaded (~> 3.0) kaminari (~> 1.1) letter_opener (~> 1.7) @@ -706,10 +711,10 @@ DEPENDENCIES makara (~> 0.4) mario-redis-lock (~> 1.2) memory_profiler - microformats (~> 4.0) + microformats (~> 4.1) mime-types (~> 3.2) net-ldap (~> 0.10) - nokogiri (~> 1.9) + nokogiri (~> 1.10) nsa (~> 0.2) oj (~> 3.7) omniauth (~> 1.9) @@ -719,14 +724,14 @@ DEPENDENCIES ox (~> 2.10) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.27) + parallel_tests (~> 2.28) pg (~> 1.1) pghero (~> 2.2) pkg-config (~> 1.3) posix-spawn! premailer-rails private_address_check (~> 0.5) - pry-byebug (~> 3.6) + pry-byebug (~> 3.7) pry-rails (~> 0.3) puma (~> 3.12) pundit (~> 2.0) @@ -743,13 +748,13 @@ DEPENDENCIES rqrcode (~> 0.10) rspec-rails (~> 3.8) rspec-sidekiq (~> 3.0) - rubocop (~> 0.61) + rubocop (~> 0.65) sanitize (~> 5.0) scss_lint (~> 0.57) sidekiq (~> 5.2) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 3.0) - sidekiq-unique-jobs (~> 5.0) + sidekiq-unique-jobs (~> 6.0) simple-navigation (~> 4.0) simple_form (~> 4.1) simplecov (~> 0.16) @@ -763,12 +768,12 @@ DEPENDENCIES tty-prompt (~> 0.18) twitter-text (~> 1.14) tzinfo-data (~> 1.2018) - webmock (~> 3.4) + webmock (~> 3.5) webpacker (~> 3.5) webpush RUBY VERSION - ruby 2.5.3p105 + ruby 2.6.1p33 BUNDLED WITH - 1.16.6 + 1.17.3 diff --git a/README.md b/README.md index 6c4918e6f..03c8472b9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Click below to **learn more** in a video: [youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE -## Navigation +## Navigation - [Project homepage 🐘](https://joinmastodon.org) - [Support the development via Patreon][patreon] @@ -80,13 +80,13 @@ A **Vagrant** configuration is included for development purposes. Mastodon is **free, open source software** licensed under **AGPLv3**. -You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md) +You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). **IRC channel**: #mastodon on irc.freenode.net ## License -Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) +Copyright (C) 2016-2019 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. diff --git a/Vagrantfile b/Vagrantfile index 5e25efb80..f9839fffb 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -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 diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index d3104172c..8ce413f8a 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -31,7 +31,7 @@ class StatusesIndex < Chewy::Index }, } - define_type ::Status.unscoped.without_reblogs do + define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do crutch :mentions do |collection| data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id) data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } @@ -48,14 +48,14 @@ class StatusesIndex < Chewy::Index end root date_detection: false do + field :id, type: 'long' field :account_id, type: 'long' - field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do + field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do field :stemmed, type: 'text', analyzer: 'content' end field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) } - field :created_at, type: 'date' end end end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index f788a9078..cad2ecf3f 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -52,11 +52,12 @@ class AccountsController < ApplicationController private def show_pinned_statuses? - [replies_requested?, media_requested?, params[:max_id].present?, params[:min_id].present?].none? + [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? end def filtered_statuses default_statuses.tap do |statuses| + statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(only_media_scope) if media_requested? statuses.merge!(no_replies_scope) unless replies_requested? end @@ -78,12 +79,15 @@ class AccountsController < ApplicationController Status.without_replies end + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id) + end + def set_account @account = Account.find_local!(params[:username]) end def older_url - ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}") pagination_url(max_id: @statuses.last.id) end @@ -92,7 +96,9 @@ class AccountsController < ApplicationController end def pagination_url(max_id: nil, min_id: nil) - if media_requested? + if tag_requested? + short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id) + elsif media_requested? short_account_media_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) @@ -109,6 +115,10 @@ class AccountsController < ApplicationController request.path.ends_with?('/with_replies') end + def tag_requested? + request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) + end + def filtered_status_page(params) if params[:min_id].present? filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb index e847495f1..a2cea461e 100644 --- a/app/controllers/admin/account_actions_controller.rb +++ b/app/controllers/admin/account_actions_controller.rb @@ -17,7 +17,7 @@ module Admin account_action.save! if account_action.with_report? - redirect_to admin_report_path(account_action.report) + redirect_to admin_reports_path else redirect_to admin_account_path(@account.id) end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 10abd1e6a..562fba996 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,7 +2,7 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :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, :memorialize] @@ -62,9 +62,8 @@ module Admin 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 diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index d61bafdf0..f77699166 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -5,6 +5,9 @@ module Admin before_action :set_custom_emoji, except: [:index, :new, :create] before_action :set_filter_params + include ObfuscateFilename + obfuscate_filename [:custom_emoji, :image] + def index authorize :custom_emoji, :index? @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 90c70275a..5f307ddee 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -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 diff --git a/app/controllers/admin/followers_controller.rb b/app/controllers/admin/followers_controller.rb new file mode 100644 index 000000000..d826f47c5 --- /dev/null +++ b/app/controllers/admin/followers_controller.rb @@ -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 diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 6f8eaf65c..6dd659a30 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -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.remote.where(protocol: :ostatus).where(domain: params[:by_domain]) + paginated_instances.map { |resource| Instance.new(resource) } end def filter_params - params.permit( - :domain_name - ) + params.permit(:limited, :by_domain) end end end diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index d3c2f5e9e..3ba9f5df2 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -10,6 +10,10 @@ module Admin @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save + redirect_to admin_report_path(@report) + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.statuses.no_status_selected') + redirect_to admin_report_path(@report) end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index a69f12084..650195034 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -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? diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index ac8de5fc0..a1dd30918 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -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 diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb index 91c9f1547..4217b527a 100644 --- a/app/controllers/api/v1/accounts/search_controller.rb +++ b/app/controllers/api/v1/accounts/search_controller.rb @@ -16,10 +16,11 @@ class Api::V1::Accounts::SearchController < Api::BaseController def account_search AccountSearchService.new.call( params[:q], - limit_param(DEFAULT_ACCOUNTS_LIMIT), current_account, + limit: limit_param(DEFAULT_ACCOUNTS_LIMIT), resolve: truthy_param?(:resolve), - following: truthy_param?(:following) + following: truthy_param?(:following), + offset: params[:offset] ) end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index d3f1197f8..ed10f3f6a 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -28,13 +28,12 @@ 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.merge!(hashtag_scope) if params[:tagged].present? statuses end @@ -52,9 +51,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController # Also, Avoid getting slow by not narrowing down by `statuses.account_id`. # When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used # and the table will be joined by `Merge Semi Join`, so the query will be slow. - Status.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account) - .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) - .reorder(id: :desc).distinct(:id).pluck(:id) + @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account) + .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) + .reorder(id: :desc).distinct(:id).pluck(:id) end def pinned_scope @@ -65,6 +64,14 @@ class Api::V1::Accounts::StatusesController < Api::BaseController Status.without_replies end + def no_reblogs_scope + Status.without_reblogs + end + + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tagged])&.id) + end + def pagination_params(core_params) params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index f711c4676..2ccbc3cbb 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -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 diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index e469c7d21..8b63d0490 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -6,6 +6,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController respond_to :json def show - render json: doorkeeper_token.application, serializer: REST::StatusSerializer::ApplicationSerializer + render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key) end end diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index f8cd64455..7bac27da4 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -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 diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb new file mode 100644 index 000000000..3fa0b6a76 --- /dev/null +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Api::V1::Polls::VotesController < Api::BaseController + include Authorization + + before_action -> { doorkeeper_authorize! :write, :'write:statuses' } + before_action :require_user! + before_action :set_poll + + respond_to :json + + def create + VoteService.new.call(current_account, @poll, vote_params[:choices]) + render json: @poll, serializer: REST::PollSerializer + end + + private + + def set_poll + @poll = Poll.attached.find(params[:poll_id]) + authorize @poll.status, :show? + rescue Mastodon::NotPermittedError + raise ActiveRecord::RecordNotFound + end + + def vote_params + params.permit(choices: []) + end +end diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb new file mode 100644 index 000000000..4f4a6858d --- /dev/null +++ b/app/controllers/api/v1/polls_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Api::V1::PollsController < Api::BaseController + before_action -> { authorize_if_got_token! :read, :'read:statuses' }, only: :show + + respond_to :json + + def show + @poll = Poll.attached.find(params[:id]) + ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale? + render json: @poll, serializer: REST::PollSerializer, include_results: true + end +end diff --git a/app/controllers/api/v1/scheduled_statuses_controller.rb b/app/controllers/api/v1/scheduled_statuses_controller.rb new file mode 100644 index 000000000..9950296f3 --- /dev/null +++ b/app/controllers/api/v1/scheduled_statuses_controller.rb @@ -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 diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index dc1a37599..6131cbbb6 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -3,7 +3,7 @@ class Api::V1::SearchController < Api::BaseController include Authorization - RESULTS_LIMIT = 5 + RESULTS_LIMIT = 20 before_action -> { doorkeeper_authorize! :read, :'read:search' } before_action :require_user! @@ -11,30 +11,22 @@ class Api::V1::SearchController < Api::BaseController respond_to :json def index - @search = Search.new(search) + @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer end private - def search - search_results.tap do |search| - search[:statuses].keep_if do |status| - begin - authorize status, :show? - rescue Mastodon::NotPermittedError - false - end - end - end - end - def search_results SearchService.new.call( params[:q], - RESULTS_LIMIT, - truthy_param?(:resolve), - current_account + current_account, + limit_param(RESULTS_LIMIT), + search_params.merge(resolve: truthy_param?(:resolve)) ) end + + def search_params + params.permit(:type, :offset, :min_id, :max_id, :account_id) + end end diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 4315b0283..6851099f6 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -25,7 +25,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController end def paginated_statuses - Status.where(reblog_of_id: @status.id).paginate_by_max_id( + Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id( limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id] diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 49a52f7a6..f9506971a 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -45,16 +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, + poll: status_params[:poll], idempotency: request.headers['Idempotency-Key']) - render json: @status, serializer: REST::StatusSerializer + render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer end def destroy @@ -72,12 +74,25 @@ class Api::V1::StatusesController < Api::BaseController @status = Status.find(params[:id]) authorize @status, :show? rescue Mastodon::NotPermittedError - # Reraise in order to get a 404 instead of a 403 error code raise ActiveRecord::RecordNotFound end def status_params - params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: []) + params.permit( + :status, + :in_reply_to_id, + :sensitive, + :spoiler_text, + :visibility, + :scheduled_at, + media_ids: [], + poll: [ + :multiple, + :hide_totals, + :expires_in, + options: [], + ] + ) end def pagination_params(core_params) diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index 2e91d68ee..9aa6edc69 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -2,7 +2,7 @@ class Api::V2::SearchController < Api::V1::SearchController def index - @search = Search.new(search) + @search = Search.new(search_results) render json: @search, serializer: REST::V2::SearchSerializer end end diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 7af9cbe81..c28c7471c 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -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 diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 088832be3..ad7b1859f 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -26,7 +26,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController resource.locale = I18n.locale resource.invite_code = params[:invite_code] if resource.invite_code.blank? + resource.agreement = true + resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil? resource.build_account if resource.account.nil? end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 7e491641b..91566c4fa 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -47,6 +47,7 @@ module SignatureVerification .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 @@ -59,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 @@ -84,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? @@ -130,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 diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb index 96fe4c1c6..ff7ff4a42 100644 --- a/app/controllers/directories_controller.rb +++ b/app/controllers/directories_controller.rb @@ -28,7 +28,7 @@ class DirectoriesController < ApplicationController end def set_tags - @tags = Tag.discoverable.limit(30) + @tags = Tag.discoverable.limit(30).reject { |tag| tag.cached_sample_accounts.empty? } end def set_accounts diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 0c28d194b..f3d235366 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :store_current_location before_action :authenticate_resource_owner! + before_action :set_body_classes include Localized @@ -15,6 +16,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio private + def set_body_classes + @body_classes = 'admin' + end + def store_current_location store_location_for(:user, request.url) end diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb index 6299a1e13..cc6993c52 100644 --- a/app/controllers/remote_interaction_controller.rb +++ b/app/controllers/remote_interaction_controller.rb @@ -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 diff --git a/app/controllers/settings/exports/blocked_domains_controller.rb b/app/controllers/settings/exports/blocked_domains_controller.rb new file mode 100644 index 000000000..6676ce340 --- /dev/null +++ b/app/controllers/settings/exports/blocked_domains_controller.rb @@ -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 diff --git a/app/controllers/settings/exports/lists_controller.rb b/app/controllers/settings/exports/lists_controller.rb new file mode 100644 index 000000000..cf5a9de44 --- /dev/null +++ b/app/controllers/settings/exports/lists_controller.rb @@ -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 diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb new file mode 100644 index 000000000..3a3241425 --- /dev/null +++ b/app/controllers/settings/featured_tags_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Settings::FeaturedTagsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + before_action :set_featured_tags, only: :index + before_action :set_featured_tag, except: [:index, :create] + before_action :set_most_used_tags, only: :index + + def index + @featured_tag = FeaturedTag.new + end + + def create + @featured_tag = current_account.featured_tags.new(featured_tag_params) + @featured_tag.reset_data + + if @featured_tag.save + redirect_to settings_featured_tags_path + else + set_featured_tags + set_most_used_tags + + render :index + end + end + + def destroy + @featured_tag.destroy! + redirect_to settings_featured_tags_path + end + + private + + def set_featured_tag + @featured_tag = current_account.featured_tags.find(params[:id]) + end + + def set_featured_tags + @featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?) + end + + def set_most_used_tags + @most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) + end + + def featured_tag_params + params.require(:featured_tag).permit(:name) + end +end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 41df3bde2..90967635d 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -48,6 +48,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_theme, :setting_hide_network, :setting_aggregate_reblogs, + :setting_show_application, notification_emails: %i(follow follow_request reblog favourite mention digest report), interactions: %i(must_be_follower must_be_following) ) diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index db9081fdf..8b640cdca 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -32,6 +32,6 @@ class Settings::ProfilesController < Settings::BaseController end def set_account - @account = current_user.account + @account = current_account end end diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb index 11b150ffd..84ebb21f2 100644 --- a/app/controllers/settings/sessions_controller.rb +++ b/app/controllers/settings/sessions_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Settings::SessionsController < Settings::BaseController + before_action :authenticate_user! before_action :set_session, only: :destroy def destroy diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 15d59fd89..3686bd9fd 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -18,6 +18,7 @@ class StatusesController < ApplicationController before_action :redirect_to_original, only: [:show] before_action :set_referrer_policy_header, only: [:show] before_action :set_cache_headers + before_action :set_replies, only: [:replies] content_security_policy only: :embed do |p| p.frame_ancestors(false) @@ -63,8 +64,37 @@ class StatusesController < ApplicationController render 'stream_entries/embed', layout: 'embedded' end + def replies + skip_session! + + render json: replies_collection_presenter, + serializer: ActivityPub::CollectionSerializer, + adapter: ActivityPub::Adapter, + content_type: 'application/activity+json', + skip_activities: true + end + private + def replies_collection_presenter + page = ActivityPub::CollectionPresenter.new( + id: replies_account_status_url(@account, @status, page_params), + type: :unordered, + part_of: replies_account_status_url(@account, @status), + next: next_page, + items: @replies.map { |status| status.local ? status : status.id } + ) + if page_requested? + page + else + ActivityPub::CollectionPresenter.new( + id: replies_account_status_url(@account, @status), + type: :unordered, + first: page + ) + end + end + def create_descendant_thread(starting_depth, statuses) depth = starting_depth + statuses.size if depth < DESCENDANTS_DEPTH_LIMIT @@ -174,4 +204,27 @@ class StatusesController < ApplicationController return if @status.public_visibility? || @status.unlisted_visibility? response.headers['Referrer-Policy'] = 'origin' end + + def page_requested? + params[:page] == 'true' + end + + def set_replies + @replies = page_params[:other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses + @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) + @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) + end + + def next_page + last_reply = @replies.last + return if last_reply.nil? + same_account = last_reply.account_id == @account.id + return unless same_account || @replies.size == DESCENDANTS_LIMIT + same_account = false unless @replies.size == DESCENDANTS_LIMIT + replies_account_status_url(@account, @status, page: true, min_id: last_reply.id, other_accounts: !same_account) + end + + def page_params + { page: true, other_accounts: params[:other_accounts], min_id: params[:min_id] }.compact + end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 8e4051834..4694c823a 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -3,6 +3,8 @@ class TagsController < ApplicationController PAGE_SIZE = 20 + layout 'public' + before_action :set_body_classes before_action :set_instance_presenter diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 8807cc784..275b5f2fe 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -6,8 +6,9 @@ module Admin::FilterHelper 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 by_domain).freeze - FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_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) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c33975cac..5097a0953 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -69,8 +69,12 @@ module ApplicationHelper tag(:meta, content: content, property: property) end - def react_component(name, props = {}) - content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) }) + def react_component(name, props = {}, &block) + if block.nil? + content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) }) + else + content_tag(:div, data: { component: name.to_s.camelcase, props: Oj.dump(props) }, &block) + end end def body_classes diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index 532397272..59e4ae685 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -68,6 +68,7 @@ module JsonLdHelper return body_to_json(response.body_with_limit) if response.code == 200 end # If request failed, retry without doing it on behalf of a user + return if on_behalf_of.nil? build_request(uri).perform do |response| response.code == 200 ? body_to_json(response.body_with_limit) : nil end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index ed873ceed..241addb83 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -4,7 +4,7 @@ module SettingsHelper HUMAN_LOCALES = { en: 'English', ar: 'العربية', - ast: 'l\'asturianu', + ast: 'Asturianu', bg: 'Български', ca: 'Català', co: 'Corsu', @@ -29,23 +29,29 @@ module SettingsHelper it: 'Italiano', ja: '日本語', ka: 'ქართული', + kk: 'Қазақша', ko: '한국어', + lt: 'Lietuvių', + lv: 'Latviešu', + ml: 'മലയാളം', + ms: 'Bahasa Melayu', nl: 'Nederlands', no: 'Norsk', oc: 'Occitan', - pl: 'Polszczyzna', + pl: 'Polski', pt: 'Português', 'pt-BR': 'Português do Brasil', - ro: 'Limba română', + ro: 'Română', ru: 'Русский', sk: 'Slovenčina', sl: 'Slovenščina', + sq: 'Shqip', sr: 'Српски', 'sr-Latn': 'Srpski (latinica)', sv: 'Svenska', ta: 'தமிழ்', te: 'తెలుగు', - th: 'ภาษาไทย', + th: 'ไทย', tr: 'Türkçe', uk: 'Українська', zh: '中文', diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 033d435c4..7a74c0b7d 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -170,7 +170,7 @@ module StreamEntriesHelper when 'public' fa_icon 'globe fw' when 'unlisted' - fa_icon 'unlock-alt fw' + fa_icon 'unlock fw' when 'private' fa_icon 'lock fw' when 'direct' diff --git a/app/javascript/images/screen_interactions.svg b/app/javascript/images/screen_interactions.svg index 41873371a..66a36f978 100644 --- a/app/javascript/images/screen_interactions.svg +++ b/app/javascript/images/screen_interactions.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 3f5d7ef46..50cd48a9e 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -22,7 +22,7 @@ export function clearAlert() { }; }; -export function showAlert(title, message) { +export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) { return { type: ALERT_SHOW, title, @@ -44,6 +44,6 @@ export function showAlertForError(error) { return showAlert(title, message); } else { console.error(error); - return showAlert(messages.unexpectedTitle, messages.unexpectedMessage); + return showAlert(); } } diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 1c7f14b5e..0be2a5cd4 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; import { showAlertForError } from './alerts'; +import { showAlert } from './alerts'; +import { defineMessages } from 'react-intl'; let cancelFetchComposeSuggestionsAccounts; @@ -49,6 +51,10 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; +const messages = defineMessages({ + uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, +}); + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -130,6 +136,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 })); @@ -142,12 +154,6 @@ export function submitCompose(routerHistory) { } }; - 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(); - } - if (response.data.visibility !== 'direct') { insertIfOnline('home'); } @@ -184,20 +190,32 @@ export function submitComposeFail(error) { export function uploadCompose(files) { return function (dispatch, getState) { - if (getState().getIn(['compose', 'media_attachments']).size > 3) { + const uploadLimit = 4; + const media = getState().getIn(['compose', 'media_attachments']); + const total = Array.from(files).reduce((a, v) => a + v.size, 0); + const progress = new Array(files.length).fill(0); + + if (files.length + media.size > uploadLimit) { + dispatch(showAlert(undefined, messages.uploadErrorLimit)); return; } - dispatch(uploadComposeRequest()); - resizeImage(files[0]).then(file => { - const data = new FormData(); - data.append('file', file); + for (const [i, f] of Array.from(files).entries()) { + if (media.size + i > 3) break; - return api(getState).post('/api/v1/media', data, { - onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)), - }).then(({ data }) => dispatch(uploadComposeSuccess(data))); - }).catch(error => dispatch(uploadComposeFail(error))); + resizeImage(f).then(file => { + const data = new FormData(); + data.append('file', file); + + return api(getState).post('/api/v1/media', data, { + onUploadProgress: function({ loaded }){ + progress[i] = loaded; + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); + }, + }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).catch(error => dispatch(uploadComposeFail(error))); + }; }; }; diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js index 3c2ea9680..c6e062ef7 100644 --- a/app/javascript/mastodon/actions/conversations.js +++ b/app/javascript/mastodon/actions/conversations.js @@ -41,13 +41,15 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']); } + const isLoadingRecent = !!params.since_id; + api(getState).get('/api/v1/conversations', { params }) .then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), []))); dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x))); - dispatch(expandConversationsSuccess(response.data, next ? next.uri : null)); + dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent)); }) .catch(err => dispatch(expandConversationsFail(err))); }; @@ -56,10 +58,11 @@ export const expandConversationsRequest = () => ({ type: CONVERSATIONS_FETCH_REQUEST, }); -export const expandConversationsSuccess = (conversations, next) => ({ +export const expandConversationsSuccess = (conversations, next, isLoadingRecent) => ({ type: CONVERSATIONS_FETCH_SUCCESS, conversations, next, + isLoadingRecent, }); export const expandConversationsFail = error => ({ diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index 931711f4b..abadee817 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -1,11 +1,10 @@ -// import { autoPlayGif } from '../../initial_state'; -// import { putAccounts, putStatuses } from '../../storage/modifier'; import { normalizeAccount, normalizeStatus } from './normalizer'; -export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; +export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT'; -export const STATUS_IMPORT = 'STATUS_IMPORT'; +export const STATUS_IMPORT = 'STATUS_IMPORT'; export const STATUSES_IMPORT = 'STATUSES_IMPORT'; +export const POLLS_IMPORT = 'POLLS_IMPORT'; function pushUnique(array, object) { if (array.every(element => element.id !== object.id)) { @@ -29,6 +28,10 @@ export function importStatuses(statuses) { return { type: STATUSES_IMPORT, statuses }; } +export function importPolls(polls) { + return { type: POLLS_IMPORT, polls }; +} + export function importFetchedAccount(account) { return importFetchedAccounts([account]); } @@ -45,7 +48,6 @@ export function importFetchedAccounts(accounts) { } accounts.forEach(processAccount); - //putAccounts(normalAccounts, !autoPlayGif); return importAccounts(normalAccounts); } @@ -58,6 +60,7 @@ export function importFetchedStatuses(statuses) { return (dispatch, getState) => { const accounts = []; const normalStatuses = []; + const polls = []; function processStatus(status) { pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]))); @@ -66,11 +69,15 @@ export function importFetchedStatuses(statuses) { if (status.reblog && status.reblog.id) { processStatus(status.reblog); } + + if (status.poll && status.poll.id) { + pushUnique(polls, status.poll); + } } statuses.forEach(processStatus); - //putStatuses(normalStatuses); + dispatch(importPolls(polls)); dispatch(importFetchedAccounts(accounts)); dispatch(importStatuses(normalStatuses)); }; diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 34a4150fa..3085cd537 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -43,6 +43,10 @@ export function normalizeStatus(status, normalOldStatus) { normalStatus.reblog = status.reblog.id; } + if (status.poll && status.poll.id) { + normalStatus.poll = status.poll.id; + } + // Only calculate these values when status first encountered // Otherwise keep the ones already in the reducer if (normalOldStatus) { diff --git a/app/javascript/mastodon/actions/polls.js b/app/javascript/mastodon/actions/polls.js new file mode 100644 index 000000000..bee4c48a6 --- /dev/null +++ b/app/javascript/mastodon/actions/polls.js @@ -0,0 +1,53 @@ +import api from '../api'; + +export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST'; +export const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS'; +export const POLL_VOTE_FAIL = 'POLL_VOTE_FAIL'; + +export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST'; +export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS'; +export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL'; + +export const vote = (pollId, choices) => (dispatch, getState) => { + dispatch(voteRequest()); + + api(getState).post(`/api/v1/polls/${pollId}/votes`, { choices }) + .then(({ data }) => dispatch(voteSuccess(data))) + .catch(err => dispatch(voteFail(err))); +}; + +export const fetchPoll = pollId => (dispatch, getState) => { + dispatch(fetchPollRequest()); + + api(getState).get(`/api/v1/polls/${pollId}`) + .then(({ data }) => dispatch(fetchPollSuccess(data))) + .catch(err => dispatch(fetchPollFail(err))); +}; + +export const voteRequest = () => ({ + type: POLL_VOTE_REQUEST, +}); + +export const voteSuccess = poll => ({ + type: POLL_VOTE_SUCCESS, + poll, +}); + +export const voteFail = error => ({ + type: POLL_VOTE_FAIL, + error, +}); + +export const fetchPollRequest = () => ({ + type: POLL_FETCH_REQUEST, +}); + +export const fetchPollSuccess = poll => ({ + type: POLL_FETCH_SUCCESS, + poll, +}); + +export const fetchPollFail = error => ({ + type: POLL_FETCH_FAIL, + error, +}); diff --git a/app/javascript/mastodon/api.js b/app/javascript/mastodon/api.js index 0be08d7fd..4be3eadb0 100644 --- a/app/javascript/mastodon/api.js +++ b/app/javascript/mastodon/api.js @@ -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; diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js index 2b10b8c30..fba21316a 100644 --- a/app/javascript/mastodon/common.js +++ b/app/javascript/mastodon/common.js @@ -4,5 +4,9 @@ export function start() { require('font-awesome/css/font-awesome.css'); require.context('../images/', true); - Rails.start(); + try { + Rails.start(); + } catch (e) { + // If called twice + } }; diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 2bcea8b67..2705a6001 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -68,10 +68,10 @@ class Account extends ImmutablePureComponent { if (hidden) { return ( -
+ {account.get('display_name')} {account.get('username')} -
+ ); } @@ -88,7 +88,7 @@ class Account extends ImmutablePureComponent { if (requested) { buttons = ; } else if (blocking) { - buttons = ; + buttons = ; } else if (muting) { let hidingNotificationsButton; if (account.getIn(['relationship', 'muting_notifications'])) { diff --git a/app/javascript/mastodon/components/attachment_list.js b/app/javascript/mastodon/components/attachment_list.js index 8e5bb0e0b..5dfa1464c 100644 --- a/app/javascript/mastodon/components/attachment_list.js +++ b/app/javascript/mastodon/components/attachment_list.js @@ -2,6 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'mastodon/components/icon'; const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; @@ -24,7 +25,7 @@ export default class AttachmentList extends ImmutablePureComponent { return (
  • - {filename(displayUrl)} + {filename(displayUrl)}
  • ); })} @@ -36,7 +37,7 @@ export default class AttachmentList extends ImmutablePureComponent { return (
    - +
      diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js index 8a60c4192..f41045787 100644 --- a/app/javascript/mastodon/components/column_back_button.js +++ b/app/javascript/mastodon/components/column_back_button.js @@ -1,6 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; +import Icon from 'mastodon/components/icon'; export default class ColumnBackButton extends React.PureComponent { @@ -19,7 +20,7 @@ export default class ColumnBackButton extends React.PureComponent { render () { return ( ); diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js index 964c100be..cc8bfb151 100644 --- a/app/javascript/mastodon/components/column_back_button_slim.js +++ b/app/javascript/mastodon/components/column_back_button_slim.js @@ -1,6 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import ColumnBackButton from './column_back_button'; +import Icon from 'mastodon/components/icon'; export default class ColumnBackButtonSlim extends ColumnBackButton { @@ -8,7 +9,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton { return (
      - +
      diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index f68e4155e..f33c689e7 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; +import Icon from 'mastodon/components/icon'; const messages = defineMessages({ show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, @@ -109,22 +110,22 @@ class ColumnHeader extends React.PureComponent { } if (multiColumn && pinned) { - pinButton = ; + pinButton = ; moveButtons = (
      - - + +
      ); } else if (multiColumn) { - pinButton = ; + pinButton = ; } if (!pinned && (multiColumn || showBackButton)) { backButton = ( ); @@ -140,7 +141,7 @@ class ColumnHeader extends React.PureComponent { } if (children || multiColumn) { - collapseButton = ; + collapseButton = ; } const hasTitle = icon && title; @@ -150,7 +151,7 @@ class ColumnHeader extends React.PureComponent {

      {hasTitle && ( )} diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js index c2c40cb3f..6b9dd6f81 100644 --- a/app/javascript/mastodon/components/display_name.js +++ b/app/javascript/mastodon/components/display_name.js @@ -1,28 +1,46 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; export default class DisplayName extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, others: ImmutablePropTypes.list, + localDomain: PropTypes.string, }; render () { - const { account, others } = this.props; - const displayNameHtml = { __html: account.get('display_name_html') }; + const { others, localDomain } = this.props; - let suffix; + let displayName, suffix, account; if (others && others.size > 1) { - suffix = `+${others.size}`; + displayName = others.take(2).map(a => ).reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + suffix = `+${others.size - 2}`; + } } else { - suffix = @{account.get('acct')}; + if (others && others.size > 0) { + account = others.first(); + } else { + account = this.props.account; + } + + let acct = account.get('acct'); + + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; + } + + displayName = ; + suffix = @{acct}; } return ( - {suffix} + {displayName} {suffix} ); } diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js index 24f80e788..85729ca94 100644 --- a/app/javascript/mastodon/components/domain.js +++ b/app/javascript/mastodon/components/domain.js @@ -32,7 +32,7 @@ class Account extends ImmutablePureComponent {
      - +

    diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js index a407df31e..f091d7893 100644 --- a/app/javascript/mastodon/components/hashtag.js +++ b/app/javascript/mastodon/components/hashtag.js @@ -1,16 +1,16 @@ import React from 'react'; import { Sparklines, SparklinesCurve } from 'react-sparklines'; -import { Link } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import Permalink from './permalink'; import { shortNumberFormat } from '../utils/numbers'; const Hashtag = ({ hashtag }) => (
    - + #{hashtag.get('name')} - + {shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))} }} />
    diff --git a/app/javascript/mastodon/components/icon.js b/app/javascript/mastodon/components/icon.js new file mode 100644 index 000000000..d8a17722f --- /dev/null +++ b/app/javascript/mastodon/components/icon.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export default class Icon extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + className: PropTypes.string, + fixedWidth: PropTypes.bool, + }; + + render () { + const { id, className, fixedWidth, ...other } = this.props; + + return ( + + ); + } + +} diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index b96e48fd0..fbb42f78f 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -3,6 +3,7 @@ import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import Icon from 'mastodon/components/icon'; export default class IconButton extends React.PureComponent { @@ -86,7 +87,7 @@ export default class IconButton extends React.PureComponent { style={style} tabIndex={tabIndex} > -