mirror of
https://framagit.org/tykayn/mastodon.git
synced 2023-08-25 08:33:12 +02:00
Merge branch 'master' of http://github.com/tootsuite/mastodon
This commit is contained in:
commit
6c86d67a7b
@ -3,7 +3,7 @@ version: 2
|
|||||||
aliases:
|
aliases:
|
||||||
- &defaults
|
- &defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.5.1-stretch-node
|
- image: circleci/ruby:2.6.0-stretch-node
|
||||||
environment: &ruby_environment
|
environment: &ruby_environment
|
||||||
BUNDLE_APP_CONFIG: ./.bundle/
|
BUNDLE_APP_CONFIG: ./.bundle/
|
||||||
DB_HOST: localhost
|
DB_HOST: localhost
|
||||||
@ -98,21 +98,21 @@ jobs:
|
|||||||
<<: *defaults
|
<<: *defaults
|
||||||
<<: *install_steps
|
<<: *install_steps
|
||||||
|
|
||||||
|
install-ruby2.6:
|
||||||
|
<<: *defaults
|
||||||
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
install-ruby2.5:
|
install-ruby2.5:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
docker:
|
||||||
|
- image: circleci/ruby:2.5.3-stretch-node
|
||||||
|
environment: *ruby_environment
|
||||||
<<: *install_ruby_dependencies
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
install-ruby2.4:
|
install-ruby2.4:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.4.4-stretch-node
|
- image: circleci/ruby:2.4.5-stretch-node
|
||||||
environment: *ruby_environment
|
|
||||||
<<: *install_ruby_dependencies
|
|
||||||
|
|
||||||
install-ruby2.3:
|
|
||||||
<<: *defaults
|
|
||||||
docker:
|
|
||||||
- image: circleci/ruby:2.3.7-stretch-node
|
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
<<: *install_ruby_dependencies
|
<<: *install_ruby_dependencies
|
||||||
|
|
||||||
@ -128,43 +128,43 @@ jobs:
|
|||||||
- ./mastodon/public/assets
|
- ./mastodon/public/assets
|
||||||
- ./mastodon/public/packs-test/
|
- ./mastodon/public/packs-test/
|
||||||
|
|
||||||
|
test-ruby2.6:
|
||||||
|
<<: *defaults
|
||||||
|
docker:
|
||||||
|
- image: circleci/ruby:2.6.0-stretch-node
|
||||||
|
environment: *ruby_environment
|
||||||
|
- image: circleci/postgres:10.6-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: root
|
||||||
|
- image: circleci/redis:5.0.3-alpine3.8
|
||||||
|
<<: *test_steps
|
||||||
|
|
||||||
test-ruby2.5:
|
test-ruby2.5:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.5.1-stretch-node
|
- image: circleci/ruby:2.5.3-stretch-node
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
- image: circleci/postgres:10.3-alpine
|
- image: circleci/postgres:10.6-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: root
|
POSTGRES_USER: root
|
||||||
- image: circleci/redis:4.0.9-alpine
|
- image: circleci/redis:4.0.12-alpine
|
||||||
<<: *test_steps
|
<<: *test_steps
|
||||||
|
|
||||||
test-ruby2.4:
|
test-ruby2.4:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.4.4-stretch-node
|
- image: circleci/ruby:2.4.5-stretch-node
|
||||||
environment: *ruby_environment
|
environment: *ruby_environment
|
||||||
- image: circleci/postgres:10.3-alpine
|
- image: circleci/postgres:10.6-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: root
|
POSTGRES_USER: root
|
||||||
- image: circleci/redis:4.0.9-alpine
|
- image: circleci/redis:4.0.12-alpine
|
||||||
<<: *test_steps
|
|
||||||
|
|
||||||
test-ruby2.3:
|
|
||||||
<<: *defaults
|
|
||||||
docker:
|
|
||||||
- image: circleci/ruby:2.3.7-stretch-node
|
|
||||||
environment: *ruby_environment
|
|
||||||
- image: circleci/postgres:10.3-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: root
|
|
||||||
- image: circleci/redis:4.0.9-alpine
|
|
||||||
<<: *test_steps
|
<<: *test_steps
|
||||||
|
|
||||||
test-webui:
|
test-webui:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8.11.1-stretch
|
- image: circleci/node:8.15.0-stretch
|
||||||
steps:
|
steps:
|
||||||
- *attach_workspace
|
- *attach_workspace
|
||||||
- run: ./bin/retry yarn test:jest
|
- run: ./bin/retry yarn test:jest
|
||||||
@ -183,20 +183,24 @@ workflows:
|
|||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- install
|
- install
|
||||||
|
- install-ruby2.6:
|
||||||
|
requires:
|
||||||
|
- install
|
||||||
- install-ruby2.5:
|
- install-ruby2.5:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
|
- install-ruby2.6
|
||||||
- install-ruby2.4:
|
- install-ruby2.4:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
- install-ruby2.5
|
- install-ruby2.6
|
||||||
- install-ruby2.3:
|
|
||||||
requires:
|
|
||||||
- install
|
|
||||||
- install-ruby2.5
|
|
||||||
- build:
|
- build:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.5
|
- install-ruby2.6
|
||||||
|
- test-ruby2.6:
|
||||||
|
requires:
|
||||||
|
- install-ruby2.6
|
||||||
|
- build
|
||||||
- test-ruby2.5:
|
- test-ruby2.5:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.5
|
- install-ruby2.5
|
||||||
@ -205,13 +209,9 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- install-ruby2.4
|
- install-ruby2.4
|
||||||
- build
|
- build
|
||||||
- test-ruby2.3:
|
|
||||||
requires:
|
|
||||||
- install-ruby2.3
|
|
||||||
- build
|
|
||||||
- test-webui:
|
- test-webui:
|
||||||
requires:
|
requires:
|
||||||
- install
|
- install
|
||||||
- check-i18n:
|
- check-i18n:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.5
|
- install-ruby2.6
|
||||||
|
@ -27,7 +27,7 @@ plugins:
|
|||||||
enabled: true
|
enabled: true
|
||||||
eslint:
|
eslint:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: eslint-4
|
channel: eslint-5
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-0-54
|
channel: rubocop-0-54
|
||||||
|
@ -1,30 +1,13 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
/build/**
|
||||||
#
|
/coverage/**
|
||||||
# If you find yourself ignoring temporary files generated by your text editor
|
/db/**
|
||||||
# or operating system, you probably want to add a global ignore instead:
|
/lib/**
|
||||||
# git config --global core.excludesfile '~/.gitignore_global'
|
/log/**
|
||||||
|
/node_modules/**
|
||||||
# Ignore bundler config.
|
/nonobox/**
|
||||||
/.bundle
|
/public/**
|
||||||
|
!/public/embed.js
|
||||||
# Ignore the default SQLite database.
|
/spec/**
|
||||||
/db/*.sqlite3
|
/tmp/**
|
||||||
/db/*.sqlite3-journal
|
/vendor/**
|
||||||
|
!.eslintrc.js
|
||||||
# Ignore all logfiles and tempfiles.
|
|
||||||
/log/*
|
|
||||||
!/log/.keep
|
|
||||||
/tmp
|
|
||||||
coverage
|
|
||||||
public/system
|
|
||||||
public/assets
|
|
||||||
.env
|
|
||||||
.env.production
|
|
||||||
node_modules/
|
|
||||||
neo4j/
|
|
||||||
|
|
||||||
# Ignore Vagrant files
|
|
||||||
.vagrant/
|
|
||||||
|
|
||||||
# Ignore Capistrano customizations
|
|
||||||
config/deploy/*
|
|
||||||
|
204
.eslintrc.js
Normal file
204
.eslintrc.js
Normal file
@ -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',
|
||||||
|
},
|
||||||
|
};
|
170
.eslintrc.yml
170
.eslintrc.yml
@ -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
|
|
@ -1,9 +0,0 @@
|
|||||||
plugins:
|
|
||||||
postcss-smart-import: {}
|
|
||||||
precss: {}
|
|
||||||
autoprefixer:
|
|
||||||
browsers:
|
|
||||||
- last 2 versions
|
|
||||||
- IE >= 11
|
|
||||||
- iOS >= 9
|
|
||||||
postcss-object-fit-images: {}
|
|
@ -1 +1 @@
|
|||||||
2.5.3
|
2.6.1
|
||||||
|
387
AUTHORS.md
387
AUTHORS.md
@ -1,41 +1,44 @@
|
|||||||
|
Authors
|
||||||
|
=======
|
||||||
|
|
||||||
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
|
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
|
||||||
and provided thanks to the work of the following contributors:
|
and provided thanks to the work of the following contributors:
|
||||||
|
|
||||||
* [Gargron](https://github.com/Gargron)
|
* [Gargron](https://github.com/Gargron)
|
||||||
* [ykzts](https://github.com/ykzts)
|
* [ykzts](https://github.com/ykzts)
|
||||||
* [akihikodaki](https://github.com/akihikodaki)
|
* [akihikodaki](https://github.com/akihikodaki)
|
||||||
* [mjankowski](https://github.com/mjankowski)
|
|
||||||
* [ThibG](https://github.com/ThibG)
|
* [ThibG](https://github.com/ThibG)
|
||||||
|
* [mjankowski](https://github.com/mjankowski)
|
||||||
|
* [dependabot[bot]](https://github.com/apps/dependabot)
|
||||||
* [unarist](https://github.com/unarist)
|
* [unarist](https://github.com/unarist)
|
||||||
* [m4sk1n](https://github.com/m4sk1n)
|
* [m4sk1n](https://github.com/m4sk1n)
|
||||||
* [yiskah](https://github.com/yiskah)
|
* [yiskah](https://github.com/yiskah)
|
||||||
* [nolanlawson](https://github.com/nolanlawson)
|
* [nolanlawson](https://github.com/nolanlawson)
|
||||||
|
* [ysksn](https://github.com/ysksn)
|
||||||
* [sorin-davidoi](https://github.com/sorin-davidoi)
|
* [sorin-davidoi](https://github.com/sorin-davidoi)
|
||||||
* [abcang](https://github.com/abcang)
|
* [abcang](https://github.com/abcang)
|
||||||
* [lynlynlynx](https://github.com/lynlynlynx)
|
* [lynlynlynx](https://github.com/lynlynlynx)
|
||||||
* [dependabot[bot]](https://github.com/apps/dependabot)
|
* [mayaeh](https://github.com/mayaeh)
|
||||||
|
* [renatolond](https://github.com/renatolond)
|
||||||
* [alpaca-tc](https://github.com/alpaca-tc)
|
* [alpaca-tc](https://github.com/alpaca-tc)
|
||||||
* [nclm](https://github.com/nclm)
|
* [nclm](https://github.com/nclm)
|
||||||
* [ineffyble](https://github.com/ineffyble)
|
* [ineffyble](https://github.com/ineffyble)
|
||||||
* [renatolond](https://github.com/renatolond)
|
|
||||||
* [jeroenpraat](https://github.com/jeroenpraat)
|
* [jeroenpraat](https://github.com/jeroenpraat)
|
||||||
* [mayaeh](https://github.com/mayaeh)
|
|
||||||
* [blackle](https://github.com/blackle)
|
* [blackle](https://github.com/blackle)
|
||||||
* [Quent-in](https://github.com/Quent-in)
|
* [Quent-in](https://github.com/Quent-in)
|
||||||
* [JantsoP](https://github.com/JantsoP)
|
* [JantsoP](https://github.com/JantsoP)
|
||||||
|
* [mabkenar](https://github.com/mabkenar)
|
||||||
|
* [Kjwon15](https://github.com/Kjwon15)
|
||||||
* [nullkal](https://github.com/nullkal)
|
* [nullkal](https://github.com/nullkal)
|
||||||
* [yookoala](https://github.com/yookoala)
|
* [yookoala](https://github.com/yookoala)
|
||||||
* [mabkenar](https://github.com/mabkenar)
|
|
||||||
* [ysksn](https://github.com/ysksn)
|
|
||||||
* [shuheiktgw](https://github.com/shuheiktgw)
|
* [shuheiktgw](https://github.com/shuheiktgw)
|
||||||
* [ashfurrow](https://github.com/ashfurrow)
|
* [ashfurrow](https://github.com/ashfurrow)
|
||||||
* [Kjwon15](https://github.com/Kjwon15)
|
* [Quenty31](https://github.com/Quenty31)
|
||||||
* [zunda](https://github.com/zunda)
|
* [zunda](https://github.com/zunda)
|
||||||
* [eramdam](https://github.com/eramdam)
|
* [eramdam](https://github.com/eramdam)
|
||||||
* [masarakki](https://github.com/masarakki)
|
|
||||||
* [takayamaki](https://github.com/takayamaki)
|
* [takayamaki](https://github.com/takayamaki)
|
||||||
|
* [masarakki](https://github.com/masarakki)
|
||||||
* [ticky](https://github.com/ticky)
|
* [ticky](https://github.com/ticky)
|
||||||
* [Quenty31](https://github.com/Quenty31)
|
|
||||||
* [danhunsaker](https://github.com/danhunsaker)
|
* [danhunsaker](https://github.com/danhunsaker)
|
||||||
* [ThisIsMissEm](https://github.com/ThisIsMissEm)
|
* [ThisIsMissEm](https://github.com/ThisIsMissEm)
|
||||||
* [hcmiya](https://github.com/hcmiya)
|
* [hcmiya](https://github.com/hcmiya)
|
||||||
@ -45,16 +48,16 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [rkarabut](https://github.com/rkarabut)
|
* [rkarabut](https://github.com/rkarabut)
|
||||||
* [yukimochi](https://github.com/yukimochi)
|
* [yukimochi](https://github.com/yukimochi)
|
||||||
* [Artoria2e5](https://github.com/Artoria2e5)
|
* [Artoria2e5](https://github.com/Artoria2e5)
|
||||||
|
* [nightpool](https://github.com/nightpool)
|
||||||
* [marrus-sh](https://github.com/marrus-sh)
|
* [marrus-sh](https://github.com/marrus-sh)
|
||||||
* [krainboltgreene](https://github.com/krainboltgreene)
|
* [krainboltgreene](https://github.com/krainboltgreene)
|
||||||
* [patf](https://github.com/patf)
|
* [pfigel](https://github.com/pfigel)
|
||||||
* [Aldarone](https://github.com/Aldarone)
|
* [Aldarone](https://github.com/Aldarone)
|
||||||
* [BoFFire](https://github.com/BoFFire)
|
* [BoFFire](https://github.com/BoFFire)
|
||||||
* [clworld](https://github.com/clworld)
|
* [clworld](https://github.com/clworld)
|
||||||
* [dracos](https://github.com/dracos)
|
* [dracos](https://github.com/dracos)
|
||||||
* [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
|
* [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
|
||||||
* [Sylvhem](https://github.com/Sylvhem)
|
* [Sylvhem](https://github.com/Sylvhem)
|
||||||
* [nightpool](https://github.com/nightpool)
|
|
||||||
* [MasterGroosha](https://github.com/MasterGroosha)
|
* [MasterGroosha](https://github.com/MasterGroosha)
|
||||||
* [JeanGauthier](https://github.com/JeanGauthier)
|
* [JeanGauthier](https://github.com/JeanGauthier)
|
||||||
* [kschaper](https://github.com/kschaper)
|
* [kschaper](https://github.com/kschaper)
|
||||||
@ -74,11 +77,14 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [johnsudaar](https://github.com/johnsudaar)
|
* [johnsudaar](https://github.com/johnsudaar)
|
||||||
* [trebmuh](https://github.com/trebmuh)
|
* [trebmuh](https://github.com/trebmuh)
|
||||||
* [Rakib Hasan](mailto:rmhasan@gmail.com)
|
* [Rakib Hasan](mailto:rmhasan@gmail.com)
|
||||||
|
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
|
||||||
* [lindwurm](https://github.com/lindwurm)
|
* [lindwurm](https://github.com/lindwurm)
|
||||||
* [victorhck](mailto:victorhck@geeko.site)
|
* [victorhck](mailto:victorhck@geeko.site)
|
||||||
* [voidsatisfaction](https://github.com/voidsatisfaction)
|
* [voidsatisfaction](https://github.com/voidsatisfaction)
|
||||||
|
* [rinsuki](https://github.com/rinsuki)
|
||||||
* [hikari-no-yume](https://github.com/hikari-no-yume)
|
* [hikari-no-yume](https://github.com/hikari-no-yume)
|
||||||
* [angristan](https://github.com/angristan)
|
* [angristan](https://github.com/angristan)
|
||||||
|
* [hinaloe](https://github.com/hinaloe)
|
||||||
* [seefood](https://github.com/seefood)
|
* [seefood](https://github.com/seefood)
|
||||||
* [jackjennings](https://github.com/jackjennings)
|
* [jackjennings](https://github.com/jackjennings)
|
||||||
* [spla](mailto:spla@mastodont.cat)
|
* [spla](mailto:spla@mastodont.cat)
|
||||||
@ -88,28 +94,31 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [mistydemeo](https://github.com/mistydemeo)
|
* [mistydemeo](https://github.com/mistydemeo)
|
||||||
* [dunn](https://github.com/dunn)
|
* [dunn](https://github.com/dunn)
|
||||||
* [xqus](https://github.com/xqus)
|
* [xqus](https://github.com/xqus)
|
||||||
|
* [hugogameiro](https://github.com/hugogameiro)
|
||||||
|
* [ariasuni](https://github.com/ariasuni)
|
||||||
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
|
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
|
||||||
* [fakenine](https://github.com/fakenine)
|
* [fakenine](https://github.com/fakenine)
|
||||||
* [tsuwatch](https://github.com/tsuwatch)
|
* [tsuwatch](https://github.com/tsuwatch)
|
||||||
* [victorhck](https://github.com/victorhck)
|
* [victorhck](https://github.com/victorhck)
|
||||||
|
* [kedamaDQ](https://github.com/kedamaDQ)
|
||||||
* [puckipedia](https://github.com/puckipedia)
|
* [puckipedia](https://github.com/puckipedia)
|
||||||
* [fvh-P](https://github.com/fvh-P)
|
* [fvh-P](https://github.com/fvh-P)
|
||||||
* [contraexemplo](https://github.com/contraexemplo)
|
* [contraexemplo](https://github.com/contraexemplo)
|
||||||
* [hugogameiro](https://github.com/hugogameiro)
|
* [Aditoo17](https://github.com/Aditoo17)
|
||||||
* [kazu9su](https://github.com/kazu9su)
|
* [kazu9su](https://github.com/kazu9su)
|
||||||
* [Komic](https://github.com/Komic)
|
* [Komic](https://github.com/Komic)
|
||||||
|
* [lmorchard](https://github.com/lmorchard)
|
||||||
* [diomed](https://github.com/diomed)
|
* [diomed](https://github.com/diomed)
|
||||||
* [ariasuni](https://github.com/ariasuni)
|
|
||||||
* [Neetshin](mailto:neetshin@neetsh.in)
|
* [Neetshin](mailto:neetshin@neetsh.in)
|
||||||
* [rainyday](https://github.com/rainyday)
|
* [rainyday](https://github.com/rainyday)
|
||||||
* [ProgVal](https://github.com/ProgVal)
|
* [ProgVal](https://github.com/ProgVal)
|
||||||
* [valentin2105](https://github.com/valentin2105)
|
* [valentin2105](https://github.com/valentin2105)
|
||||||
* [yuntan](https://github.com/yuntan)
|
* [yuntan](https://github.com/yuntan)
|
||||||
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
|
|
||||||
* [goofy-bz](mailto:goofy@babelzilla.org)
|
* [goofy-bz](mailto:goofy@babelzilla.org)
|
||||||
* [kadiix](https://github.com/kadiix)
|
* [kadiix](https://github.com/kadiix)
|
||||||
* [kodacs](https://github.com/kodacs)
|
* [kodacs](https://github.com/kodacs)
|
||||||
* [rtucker](https://github.com/rtucker)
|
* [trwnh](https://github.com/trwnh)
|
||||||
|
* [JMendyk](https://github.com/JMendyk)
|
||||||
* [KScl](https://github.com/KScl)
|
* [KScl](https://github.com/KScl)
|
||||||
* [sterdev](https://github.com/sterdev)
|
* [sterdev](https://github.com/sterdev)
|
||||||
* [TheKinrar](https://github.com/TheKinrar)
|
* [TheKinrar](https://github.com/TheKinrar)
|
||||||
@ -119,18 +128,20 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [northerner](https://github.com/northerner)
|
* [northerner](https://github.com/northerner)
|
||||||
* [fhemberger](https://github.com/fhemberger)
|
* [fhemberger](https://github.com/fhemberger)
|
||||||
* [greysteil](https://github.com/greysteil)
|
* [greysteil](https://github.com/greysteil)
|
||||||
* [hnrysmth](https://github.com/hnrysmth)
|
* [hensmith](https://github.com/hensmith)
|
||||||
* [d6rkaiz](https://github.com/d6rkaiz)
|
* [d6rkaiz](https://github.com/d6rkaiz)
|
||||||
* [JMendyk](https://github.com/JMendyk)
|
* [Reverite](https://github.com/Reverite)
|
||||||
* [JohnD28](https://github.com/JohnD28)
|
* [JohnD28](https://github.com/JohnD28)
|
||||||
* [znz](https://github.com/znz)
|
* [znz](https://github.com/znz)
|
||||||
* [Naouak](https://github.com/Naouak)
|
* [Naouak](https://github.com/Naouak)
|
||||||
|
* [pawelngei](https://github.com/pawelngei)
|
||||||
|
* [rtucker](https://github.com/rtucker)
|
||||||
* [reneklacan](https://github.com/reneklacan)
|
* [reneklacan](https://github.com/reneklacan)
|
||||||
* [ekiru](https://github.com/ekiru)
|
* [ekiru](https://github.com/ekiru)
|
||||||
|
* [noellabo](https://github.com/noellabo)
|
||||||
* [tcitworld](https://github.com/tcitworld)
|
* [tcitworld](https://github.com/tcitworld)
|
||||||
* [geta6](https://github.com/geta6)
|
* [geta6](https://github.com/geta6)
|
||||||
* [happycoloredbanana](https://github.com/happycoloredbanana)
|
* [happycoloredbanana](https://github.com/happycoloredbanana)
|
||||||
* [kedamaDQ](https://github.com/kedamaDQ)
|
|
||||||
* [leopku](https://github.com/leopku)
|
* [leopku](https://github.com/leopku)
|
||||||
* [SansPseudoFix](https://github.com/SansPseudoFix)
|
* [SansPseudoFix](https://github.com/SansPseudoFix)
|
||||||
* [tomfhowe](https://github.com/tomfhowe)
|
* [tomfhowe](https://github.com/tomfhowe)
|
||||||
@ -138,18 +149,19 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [theboss](https://github.com/theboss)
|
* [theboss](https://github.com/theboss)
|
||||||
* [178inaba](https://github.com/178inaba)
|
* [178inaba](https://github.com/178inaba)
|
||||||
* [alyssais](https://github.com/alyssais)
|
* [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)
|
* [stalker314314](https://github.com/stalker314314)
|
||||||
* [huertanix](https://github.com/huertanix)
|
* [huertanix](https://github.com/huertanix)
|
||||||
* [genesixx](https://github.com/genesixx)
|
* [genesixx](https://github.com/genesixx)
|
||||||
* [halkeye](https://github.com/halkeye)
|
* [halkeye](https://github.com/halkeye)
|
||||||
* [hinaloe](https://github.com/hinaloe)
|
|
||||||
* [treby](https://github.com/treby)
|
* [treby](https://github.com/treby)
|
||||||
* [Reverite](https://github.com/Reverite)
|
|
||||||
* [jpdevries](https://github.com/jpdevries)
|
* [jpdevries](https://github.com/jpdevries)
|
||||||
* [H-C-F](https://github.com/H-C-F)
|
* [gdpelican](https://github.com/gdpelican)
|
||||||
|
* [kmichl](https://github.com/kmichl)
|
||||||
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
|
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
|
||||||
* [saper](https://github.com/saper)
|
* [saper](https://github.com/saper)
|
||||||
|
* [marek-lach](https://github.com/marek-lach)
|
||||||
* [nevillepark](https://github.com/nevillepark)
|
* [nevillepark](https://github.com/nevillepark)
|
||||||
* [ornithocoder](https://github.com/ornithocoder)
|
* [ornithocoder](https://github.com/ornithocoder)
|
||||||
* [pierreozoux](https://github.com/pierreozoux)
|
* [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)
|
* [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl)
|
||||||
* [R0ckweb](https://github.com/R0ckweb)
|
* [R0ckweb](https://github.com/R0ckweb)
|
||||||
* [caasi](https://github.com/caasi)
|
* [caasi](https://github.com/caasi)
|
||||||
|
* [chr-1x](https://github.com/chr-1x)
|
||||||
* [esetomo](https://github.com/esetomo)
|
* [esetomo](https://github.com/esetomo)
|
||||||
* [foxiehkins](https://github.com/foxiehkins)
|
* [foxiehkins](https://github.com/foxiehkins)
|
||||||
* [hoodie](mailto:hoodiekitten@outlook.com)
|
* [hoodie](mailto:hoodiekitten@outlook.com)
|
||||||
* [luzi82](https://github.com/luzi82)
|
* [luzi82](https://github.com/luzi82)
|
||||||
* [duxovni](https://github.com/duxovni)
|
* [duxovni](https://github.com/duxovni)
|
||||||
|
* [tmm576](https://github.com/tmm576)
|
||||||
* [unsmell](https://github.com/unsmell)
|
* [unsmell](https://github.com/unsmell)
|
||||||
|
* [valerauko](https://github.com/valerauko)
|
||||||
* [chriswmartin](https://github.com/chriswmartin)
|
* [chriswmartin](https://github.com/chriswmartin)
|
||||||
* [vahnj](https://github.com/vahnj)
|
* [vahnj](https://github.com/vahnj)
|
||||||
* [ikuradon](https://github.com/ikuradon)
|
* [ikuradon](https://github.com/ikuradon)
|
||||||
* [AndreLewin](https://github.com/AndreLewin)
|
* [AndreLewin](https://github.com/AndreLewin)
|
||||||
* [rinsuki](https://github.com/rinsuki)
|
* [0xflotus](https://github.com/0xflotus)
|
||||||
* [redtachyons](https://github.com/redtachyons)
|
* [redtachyons](https://github.com/redtachyons)
|
||||||
* [thurloat](https://github.com/thurloat)
|
* [thurloat](https://github.com/thurloat)
|
||||||
* [aaribaud](https://github.com/aaribaud)
|
* [aaribaud](https://github.com/aaribaud)
|
||||||
|
* [pointlessone](https://github.com/pointlessone)
|
||||||
* [Andrew](mailto:andrewlchronister@gmail.com)
|
* [Andrew](mailto:andrewlchronister@gmail.com)
|
||||||
* [estuans](https://github.com/estuans)
|
* [estuans](https://github.com/estuans)
|
||||||
* [dissolve](https://github.com/dissolve)
|
* [dissolve](https://github.com/dissolve)
|
||||||
@ -192,7 +208,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [cdutson](https://github.com/cdutson)
|
* [cdutson](https://github.com/cdutson)
|
||||||
* [farlistener](https://github.com/farlistener)
|
* [farlistener](https://github.com/farlistener)
|
||||||
* [DavidLibeau](https://github.com/DavidLibeau)
|
* [DavidLibeau](https://github.com/DavidLibeau)
|
||||||
* [SirCmpwn](https://github.com/SirCmpwn)
|
* [ddevault](https://github.com/ddevault)
|
||||||
* [Fjoerfoks](https://github.com/Fjoerfoks)
|
* [Fjoerfoks](https://github.com/Fjoerfoks)
|
||||||
* [fmauNeko](https://github.com/fmauNeko)
|
* [fmauNeko](https://github.com/fmauNeko)
|
||||||
* [gloaec](https://github.com/gloaec)
|
* [gloaec](https://github.com/gloaec)
|
||||||
@ -203,10 +219,12 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [ErikXXon](https://github.com/ErikXXon)
|
* [ErikXXon](https://github.com/ErikXXon)
|
||||||
* [ian-kelling](https://github.com/ian-kelling)
|
* [ian-kelling](https://github.com/ian-kelling)
|
||||||
* [immae](https://github.com/immae)
|
* [immae](https://github.com/immae)
|
||||||
|
* [J0WI](https://github.com/J0WI)
|
||||||
* [foozmeat](https://github.com/foozmeat)
|
* [foozmeat](https://github.com/foozmeat)
|
||||||
* [jasonrhodes](https://github.com/jasonrhodes)
|
* [jasonrhodes](https://github.com/jasonrhodes)
|
||||||
* [Jason Snell](mailto:jason@newrelic.com)
|
* [Jason Snell](mailto:jason@newrelic.com)
|
||||||
* [jviide](https://github.com/jviide)
|
* [jviide](https://github.com/jviide)
|
||||||
|
* [YuleZ](https://github.com/YuleZ)
|
||||||
* [crakaC](https://github.com/crakaC)
|
* [crakaC](https://github.com/crakaC)
|
||||||
* [tkbky](https://github.com/tkbky)
|
* [tkbky](https://github.com/tkbky)
|
||||||
* [Kaylee](mailto:kaylee@codethat.sucks)
|
* [Kaylee](mailto:kaylee@codethat.sucks)
|
||||||
@ -216,6 +234,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [Lorenz Diener](mailto:halcyon@icosahedron.website)
|
* [Lorenz Diener](mailto:halcyon@icosahedron.website)
|
||||||
* [alimony](https://github.com/alimony)
|
* [alimony](https://github.com/alimony)
|
||||||
* [mig5](https://github.com/mig5)
|
* [mig5](https://github.com/mig5)
|
||||||
|
* [moritzheiber](https://github.com/moritzheiber)
|
||||||
* [ndarville](https://github.com/ndarville)
|
* [ndarville](https://github.com/ndarville)
|
||||||
* [Abzol](https://github.com/Abzol)
|
* [Abzol](https://github.com/Abzol)
|
||||||
* [pwoolcoc](https://github.com/pwoolcoc)
|
* [pwoolcoc](https://github.com/pwoolcoc)
|
||||||
@ -223,10 +242,13 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [petzah](https://github.com/petzah)
|
* [petzah](https://github.com/petzah)
|
||||||
* [ignisf](https://github.com/ignisf)
|
* [ignisf](https://github.com/ignisf)
|
||||||
* [raymestalez](https://github.com/raymestalez)
|
* [raymestalez](https://github.com/raymestalez)
|
||||||
|
* [remram44](https://github.com/remram44)
|
||||||
|
* [sts10](https://github.com/sts10)
|
||||||
* [sascha-sl](https://github.com/sascha-sl)
|
* [sascha-sl](https://github.com/sascha-sl)
|
||||||
* [u1-liquid](https://github.com/u1-liquid)
|
* [u1-liquid](https://github.com/u1-liquid)
|
||||||
* [sim6](https://github.com/sim6)
|
* [sim6](https://github.com/sim6)
|
||||||
* [stemid](https://github.com/stemid)
|
* [stemid](https://github.com/stemid)
|
||||||
|
* [sumdog](https://github.com/sumdog)
|
||||||
* [ThomasLeister](https://github.com/ThomasLeister)
|
* [ThomasLeister](https://github.com/ThomasLeister)
|
||||||
* [mcat-ee](https://github.com/mcat-ee)
|
* [mcat-ee](https://github.com/mcat-ee)
|
||||||
* [tototoshi](https://github.com/tototoshi)
|
* [tototoshi](https://github.com/tototoshi)
|
||||||
@ -243,7 +265,6 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [aus-social](https://github.com/aus-social)
|
* [aus-social](https://github.com/aus-social)
|
||||||
* [imbsky](https://github.com/imbsky)
|
* [imbsky](https://github.com/imbsky)
|
||||||
* [bsky](mailto:me@imbsky.net)
|
* [bsky](mailto:me@imbsky.net)
|
||||||
* [chr-1x](https://github.com/chr-1x)
|
|
||||||
* [codl](https://github.com/codl)
|
* [codl](https://github.com/codl)
|
||||||
* [cpsdqs](https://github.com/cpsdqs)
|
* [cpsdqs](https://github.com/cpsdqs)
|
||||||
* [barzamin](https://github.com/barzamin)
|
* [barzamin](https://github.com/barzamin)
|
||||||
@ -252,6 +273,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [ik11235](https://github.com/ik11235)
|
* [ik11235](https://github.com/ik11235)
|
||||||
* [kawax](https://github.com/kawax)
|
* [kawax](https://github.com/kawax)
|
||||||
* [007lva](https://github.com/007lva)
|
* [007lva](https://github.com/007lva)
|
||||||
|
* [mbajur](https://github.com/mbajur)
|
||||||
* [matsurai25](https://github.com/matsurai25)
|
* [matsurai25](https://github.com/matsurai25)
|
||||||
* [mecab](https://github.com/mecab)
|
* [mecab](https://github.com/mecab)
|
||||||
* [nicobz25](https://github.com/nicobz25)
|
* [nicobz25](https://github.com/nicobz25)
|
||||||
@ -259,7 +281,6 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [pinfort](https://github.com/pinfort)
|
* [pinfort](https://github.com/pinfort)
|
||||||
* [rbaumert](https://github.com/rbaumert)
|
* [rbaumert](https://github.com/rbaumert)
|
||||||
* [rhoio](https://github.com/rhoio)
|
* [rhoio](https://github.com/rhoio)
|
||||||
* [trwnh](https://github.com/trwnh)
|
|
||||||
* [usagi-f](https://github.com/usagi-f)
|
* [usagi-f](https://github.com/usagi-f)
|
||||||
* [vidarlee](https://github.com/vidarlee)
|
* [vidarlee](https://github.com/vidarlee)
|
||||||
* [vjackson725](https://github.com/vjackson725)
|
* [vjackson725](https://github.com/vjackson725)
|
||||||
@ -269,11 +290,12 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [Awea](https://github.com/Awea)
|
* [Awea](https://github.com/Awea)
|
||||||
* [halcy](https://github.com/halcy)
|
* [halcy](https://github.com/halcy)
|
||||||
* [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf)
|
* [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf)
|
||||||
* [NecroTechno](https://github.com/NecroTechno)
|
|
||||||
* [8398a7](https://github.com/8398a7)
|
* [8398a7](https://github.com/8398a7)
|
||||||
* [857b](https://github.com/857b)
|
* [857b](https://github.com/857b)
|
||||||
* [insom](https://github.com/insom)
|
* [insom](https://github.com/insom)
|
||||||
* [Aditoo17](https://github.com/Aditoo17)
|
* [tachyons](https://github.com/tachyons)
|
||||||
|
* [acid-chicken](https://github.com/acid-chicken)
|
||||||
|
* [Esteth](https://github.com/Esteth)
|
||||||
* [unascribed](https://github.com/unascribed)
|
* [unascribed](https://github.com/unascribed)
|
||||||
* [Aguay-val](https://github.com/Aguay-val)
|
* [Aguay-val](https://github.com/Aguay-val)
|
||||||
* [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp)
|
* [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp)
|
||||||
@ -282,7 +304,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [unleashed](https://github.com/unleashed)
|
* [unleashed](https://github.com/unleashed)
|
||||||
* [alxrcs](https://github.com/alxrcs)
|
* [alxrcs](https://github.com/alxrcs)
|
||||||
* [console-cowboy](https://github.com/console-cowboy)
|
* [console-cowboy](https://github.com/console-cowboy)
|
||||||
* [pointlessone](https://github.com/pointlessone)
|
* [Alkarex](https://github.com/Alkarex)
|
||||||
* [a2](https://github.com/a2)
|
* [a2](https://github.com/a2)
|
||||||
* [0xa](https://github.com/0xa)
|
* [0xa](https://github.com/0xa)
|
||||||
* [palindromordnilap](https://github.com/palindromordnilap)
|
* [palindromordnilap](https://github.com/palindromordnilap)
|
||||||
@ -299,7 +321,6 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [ayumin](https://github.com/ayumin)
|
* [ayumin](https://github.com/ayumin)
|
||||||
* [BaptisteGelez](https://github.com/BaptisteGelez)
|
* [BaptisteGelez](https://github.com/BaptisteGelez)
|
||||||
* [bzg](https://github.com/bzg)
|
* [bzg](https://github.com/bzg)
|
||||||
* [BenLubar](https://github.com/BenLubar)
|
|
||||||
* [benediktg](https://github.com/benediktg)
|
* [benediktg](https://github.com/benediktg)
|
||||||
* [blakebarnett](https://github.com/blakebarnett)
|
* [blakebarnett](https://github.com/blakebarnett)
|
||||||
* [bradj](https://github.com/bradj)
|
* [bradj](https://github.com/bradj)
|
||||||
@ -314,6 +335,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [Motoma](https://github.com/Motoma)
|
* [Motoma](https://github.com/Motoma)
|
||||||
* [chriswk](https://github.com/chriswk)
|
* [chriswk](https://github.com/chriswk)
|
||||||
* [csu](https://github.com/csu)
|
* [csu](https://github.com/csu)
|
||||||
|
* [clarcharr](https://github.com/clarcharr)
|
||||||
* [kklleemm](https://github.com/kklleemm)
|
* [kklleemm](https://github.com/kklleemm)
|
||||||
* [colindean](https://github.com/colindean)
|
* [colindean](https://github.com/colindean)
|
||||||
* [dachinat](https://github.com/dachinat)
|
* [dachinat](https://github.com/dachinat)
|
||||||
@ -341,6 +363,8 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [espenronnevik](https://github.com/espenronnevik)
|
* [espenronnevik](https://github.com/espenronnevik)
|
||||||
* [Finariel](https://github.com/Finariel)
|
* [Finariel](https://github.com/Finariel)
|
||||||
* [siuying](https://github.com/siuying)
|
* [siuying](https://github.com/siuying)
|
||||||
|
* [zoc](https://github.com/zoc)
|
||||||
|
* [fwenzel](https://github.com/fwenzel)
|
||||||
* [GenbuHase](https://github.com/GenbuHase)
|
* [GenbuHase](https://github.com/GenbuHase)
|
||||||
* [hattori6789](https://github.com/hattori6789)
|
* [hattori6789](https://github.com/hattori6789)
|
||||||
* [algernon](https://github.com/algernon)
|
* [algernon](https://github.com/algernon)
|
||||||
@ -375,10 +399,9 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [jguerder](https://github.com/jguerder)
|
* [jguerder](https://github.com/jguerder)
|
||||||
* [Jehops](https://github.com/Jehops)
|
* [Jehops](https://github.com/Jehops)
|
||||||
* [joshuap](https://github.com/joshuap)
|
* [joshuap](https://github.com/joshuap)
|
||||||
* [YuleZ](https://github.com/YuleZ)
|
|
||||||
* [Tiwy57](https://github.com/Tiwy57)
|
* [Tiwy57](https://github.com/Tiwy57)
|
||||||
* [xuv](https://github.com/xuv)
|
* [xuv](https://github.com/xuv)
|
||||||
* [Jnsll](https://github.com/Jnsll)
|
* [June Sallou](mailto:jnsll@users.noreply.github.com)
|
||||||
* [j0k3r](https://github.com/j0k3r)
|
* [j0k3r](https://github.com/j0k3r)
|
||||||
* [KEINOS](https://github.com/KEINOS)
|
* [KEINOS](https://github.com/KEINOS)
|
||||||
* [futoase](https://github.com/futoase)
|
* [futoase](https://github.com/futoase)
|
||||||
@ -389,7 +412,6 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [k0ta0uchi](https://github.com/k0ta0uchi)
|
* [k0ta0uchi](https://github.com/k0ta0uchi)
|
||||||
* [KrzysiekJ](https://github.com/KrzysiekJ)
|
* [KrzysiekJ](https://github.com/KrzysiekJ)
|
||||||
* [leowzukw](https://github.com/leowzukw)
|
* [leowzukw](https://github.com/leowzukw)
|
||||||
* [lmorchard](https://github.com/lmorchard)
|
|
||||||
* [Tak](https://github.com/Tak)
|
* [Tak](https://github.com/Tak)
|
||||||
* [cacheflow](https://github.com/cacheflow)
|
* [cacheflow](https://github.com/cacheflow)
|
||||||
* [ldidry](https://github.com/ldidry)
|
* [ldidry](https://github.com/ldidry)
|
||||||
@ -402,6 +424,7 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [martymcguire](https://github.com/martymcguire)
|
* [martymcguire](https://github.com/martymcguire)
|
||||||
* [marvinkopf](https://github.com/marvinkopf)
|
* [marvinkopf](https://github.com/marvinkopf)
|
||||||
* [otsune](https://github.com/otsune)
|
* [otsune](https://github.com/otsune)
|
||||||
|
* [mbugowski](https://github.com/mbugowski)
|
||||||
* [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
|
* [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
|
||||||
* [matt-auckland](https://github.com/matt-auckland)
|
* [matt-auckland](https://github.com/matt-auckland)
|
||||||
* [webroo](https://github.com/webroo)
|
* [webroo](https://github.com/webroo)
|
||||||
@ -420,12 +443,12 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [premist](https://github.com/premist)
|
* [premist](https://github.com/premist)
|
||||||
* [Mnkai](https://github.com/Mnkai)
|
* [Mnkai](https://github.com/Mnkai)
|
||||||
* [mitchhentges](https://github.com/mitchhentges)
|
* [mitchhentges](https://github.com/mitchhentges)
|
||||||
* [moritzheiber](https://github.com/moritzheiber)
|
|
||||||
* [mouse-reeve](https://github.com/mouse-reeve)
|
* [mouse-reeve](https://github.com/mouse-reeve)
|
||||||
* [Mozinet-fr](https://github.com/Mozinet-fr)
|
* [Mozinet-fr](https://github.com/Mozinet-fr)
|
||||||
* [lae](https://github.com/lae)
|
* [lae](https://github.com/lae)
|
||||||
* [Nanamachi](https://github.com/Nanamachi)
|
* [Nanamachi](https://github.com/Nanamachi)
|
||||||
* [orinthe](https://github.com/orinthe)
|
* [orinthe](https://github.com/orinthe)
|
||||||
|
* [NecroTechno](https://github.com/NecroTechno)
|
||||||
* [Dar13](https://github.com/Dar13)
|
* [Dar13](https://github.com/Dar13)
|
||||||
* [ngerakines](https://github.com/ngerakines)
|
* [ngerakines](https://github.com/ngerakines)
|
||||||
* [vonneudeck](https://github.com/vonneudeck)
|
* [vonneudeck](https://github.com/vonneudeck)
|
||||||
@ -443,31 +466,34 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [Pangoraw](https://github.com/Pangoraw)
|
* [Pangoraw](https://github.com/Pangoraw)
|
||||||
* [peterkeen](https://github.com/peterkeen)
|
* [peterkeen](https://github.com/peterkeen)
|
||||||
* [pgate](https://github.com/pgate)
|
* [pgate](https://github.com/pgate)
|
||||||
* [remram44](https://github.com/remram44)
|
* [Reto Kromer](mailto:retokromer@users.noreply.github.com)
|
||||||
* [retokromer](https://github.com/retokromer)
|
* [Rey Tucker](mailto:git@reytucker.us)
|
||||||
* [rfwatson](https://github.com/rfwatson)
|
* [Rob Watson](mailto:rfwatson@users.noreply.github.com)
|
||||||
* [rfreebern](https://github.com/rfreebern)
|
* [Ryan Freebern](mailto:ryan@freebern.org)
|
||||||
* [Ryan Wade](mailto:ryan.wade@protonmail.com)
|
* [Ryan Wade](mailto:ryan.wade@protonmail.com)
|
||||||
* [sylph01](https://github.com/sylph01)
|
* [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info)
|
||||||
* [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS)
|
* [S.H](mailto:gamelinks007@gmail.com)
|
||||||
* [staticsafe](https://github.com/staticsafe)
|
* [Sadiq Saif](mailto:staticsafe@users.noreply.github.com)
|
||||||
* [snwh](https://github.com/snwh)
|
* [Sam Hewitt](mailto:hewittsamuel@gmail.com)
|
||||||
* [sts10](https://github.com/sts10)
|
* [Satoshi KOJIMA](mailto:skoji@mac.com)
|
||||||
* [skoji](https://github.com/skoji)
|
* [ScienJus](mailto:i@scienjus.com)
|
||||||
* [ScienJus](https://github.com/ScienJus)
|
* [Scott Larkin](mailto:scott@codeclimate.com)
|
||||||
* [larkinscott](https://github.com/larkinscott)
|
* [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
|
||||||
* [imolein](https://github.com/imolein)
|
* [Sebastian Morr](mailto:sebastian@morr.cc)
|
||||||
* [blinry](https://github.com/blinry)
|
* [Sergei Č](mailto:noiwex1911@gmail.com)
|
||||||
* [Noiwex](https://github.com/Noiwex)
|
* [Setuu](mailto:yuki764setuu@gmail.com)
|
||||||
* [yuki764](https://github.com/yuki764)
|
* [Shaun Gillies](mailto:me@shaungillies.net)
|
||||||
* [shnjp](https://github.com/shnjp)
|
* [Shin Adachi](mailto:shn@glucose.jp)
|
||||||
* [ernix](https://github.com/ernix)
|
* [Shin Kojima](mailto:shin@kojima.org)
|
||||||
* [rosylilly](https://github.com/rosylilly)
|
* [Sho Kusano](mailto:rosylilly@aduca.org)
|
||||||
* [shouko](https://github.com/shouko)
|
* [Shouko Yu](mailto:imshouko@gmail.com)
|
||||||
* [Sina Mashek](mailto:sina@mashek.xyz)
|
* [Sina Mashek](mailto:sina@mashek.xyz)
|
||||||
* [sossii](https://github.com/sossii)
|
* [Sir-Boops](mailto:admin@boops.me)
|
||||||
|
* [Soshi Kato](mailto:mail@sossii.com)
|
||||||
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
|
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
|
||||||
|
* [Stanislas](mailto:angristan@pm.me)
|
||||||
* [StefOfficiel](mailto:pichard.stephane@free.fr)
|
* [StefOfficiel](mailto:pichard.stephane@free.fr)
|
||||||
|
* [Steven Tappert](mailto:admin@dark-it.net)
|
||||||
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
|
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
|
||||||
* [Sébastien Santoro](mailto:dereckson@espace-win.org)
|
* [Sébastien Santoro](mailto:dereckson@espace-win.org)
|
||||||
* [Tad Thorley](mailto:phaedryx@users.noreply.github.com)
|
* [Tad Thorley](mailto:phaedryx@users.noreply.github.com)
|
||||||
@ -515,26 +541,30 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [fsubal](mailto:fsubal@users.noreply.github.com)
|
* [fsubal](mailto:fsubal@users.noreply.github.com)
|
||||||
* [fusshi-](mailto:dikky1218@users.noreply.github.com)
|
* [fusshi-](mailto:dikky1218@users.noreply.github.com)
|
||||||
* [gentaro](mailto:gentaroooo@gmail.com)
|
* [gentaro](mailto:gentaroooo@gmail.com)
|
||||||
|
* [gol-cha](mailto:info@mevo.xyz)
|
||||||
* [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
|
* [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
|
||||||
* [haosbvnker](mailto:github@chaosbunker.com)
|
* [haosbvnker](mailto:github@chaosbunker.com)
|
||||||
* [isati](mailto:phil@juchnowi.cz)
|
* [isati](mailto:phil@juchnowi.cz)
|
||||||
* [jacob](mailto:jacobherringtondeveloper@gmail.com)
|
* [jacob](mailto:jacobherringtondeveloper@gmail.com)
|
||||||
* [jenn kaplan](mailto:me@jkap.io)
|
* [jenn kaplan](mailto:me@jkap.io)
|
||||||
* [jirayudech](mailto:jirayudech@gmail.com)
|
* [jirayudech](mailto:jirayudech@gmail.com)
|
||||||
|
* [jomo](mailto:github@jomo.tv)
|
||||||
* [jooops](mailto:joops@autistici.org)
|
* [jooops](mailto:joops@autistici.org)
|
||||||
* [jukper](mailto:jukkaperanto@gmail.com)
|
* [jukper](mailto:jukkaperanto@gmail.com)
|
||||||
* [jumoru](mailto:jumoru@mailbox.org)
|
* [jumoru](mailto:jumoru@mailbox.org)
|
||||||
* [karlyeurl](mailto:karl.yeurl@gmail.com)
|
* [karlyeurl](mailto:karl.yeurl@gmail.com)
|
||||||
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
|
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
|
||||||
|
* [kodai](mailto:shirafuta.kodai@gmail.com)
|
||||||
* [kuro5hin](mailto:rusty@kuro5hin.org)
|
* [kuro5hin](mailto:rusty@kuro5hin.org)
|
||||||
* [luzpaz](mailto:luzpaz@users.noreply.github.com)
|
* [luzpaz](mailto:luzpaz@users.noreply.github.com)
|
||||||
* [maxypy](mailto:maxime@mpigou.fr)
|
* [maxypy](mailto:maxime@mpigou.fr)
|
||||||
* [mhe](mailto:mail@marcus-herrmann.com)
|
* [mhe](mailto:mail@marcus-herrmann.com)
|
||||||
|
* [mike castleman](mailto:m@mlcastle.net)
|
||||||
* [mimikun](mailto:dzdzble_effort_311@outlook.jp)
|
* [mimikun](mailto:dzdzble_effort_311@outlook.jp)
|
||||||
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
|
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
|
||||||
* [muan](mailto:muan@github.com)
|
* [muan](mailto:muan@github.com)
|
||||||
|
* [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
|
||||||
* [neetshin](mailto:neetshin@neetsh.in)
|
* [neetshin](mailto:neetshin@neetsh.in)
|
||||||
* [nightpool](mailto:nightpool@users.noreply.github.com)
|
|
||||||
* [rch850](mailto:rich850@gmail.com)
|
* [rch850](mailto:rich850@gmail.com)
|
||||||
* [roikale](mailto:roikale@users.noreply.github.com)
|
* [roikale](mailto:roikale@users.noreply.github.com)
|
||||||
* [rysiekpl](mailto:rysiek@hackerspace.pl)
|
* [rysiekpl](mailto:rysiek@hackerspace.pl)
|
||||||
@ -564,3 +594,248 @@ and provided thanks to the work of the following contributors:
|
|||||||
* [雨宮美羽](mailto:k737566@gmail.com)
|
* [雨宮美羽](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.
|
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
|
||||||
|
463
CHANGELOG.md
463
CHANGELOG.md
@ -3,199 +3,400 @@ Changelog
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [2.6.5] - 2018-12-01
|
## [2.7.3] - 2019-02-23
|
||||||
### Changed
|
### 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
|
### 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
|
## [2.6.4] - 2018-11-30
|
||||||
### Fixed
|
### 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
|
## [2.6.3] - 2018-11-30
|
||||||
### Added
|
### 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
|
### 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
|
### Fixed
|
||||||
|
|
||||||
- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer (#9368)
|
- 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 (#9379)
|
- 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 (#9358)
|
- Fix already queued deliveries still trying to reach inboxes marked as unavailable ([Gargron](https://github.com/tootsuite/mastodon/pull/9358))
|
||||||
|
|
||||||
### Security
|
### 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
|
## [2.6.2] - 2018-11-23
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add Page to whitelisted ActivityPub types (#9188)
|
- Add Page to whitelisted ActivityPub types ([mbajur](https://github.com/tootsuite/mastodon/pull/9188))
|
||||||
- Add 20px to column width in web UI (#9227)
|
- 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` (#9229, #9239, #9288)
|
- 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 (#9228)
|
- Add "Show thread" link to self-replies ([Gargron](https://github.com/tootsuite/mastodon/pull/9228))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change order of Atom and RSS links so Atom is first (#9302)
|
- 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 (#9310)
|
- Change Nginx configuration for Nanobox apps ([danhunsaker](https://github.com/tootsuite/mastodon/pull/9310))
|
||||||
- Change the follow action to appear instant in web UI (#9220)
|
- 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 (#9238)
|
- 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 (#9293)
|
- 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 (#9179)
|
- Change mime type comparison to ignore JSON-LD profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9179))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix web UI crash when conversation has no last status (#9207)
|
- 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 (#9230)
|
- 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 (#9235)
|
- Fix form validation flash message color and input borders ([Gargron](https://github.com/tootsuite/mastodon/pull/9235))
|
||||||
- Fix invalid twitter:player cards being displayed (#9254)
|
- Fix invalid twitter:player cards being displayed ([ThibG](https://github.com/tootsuite/mastodon/pull/9254))
|
||||||
- Fix emoji update date being processed incorrectly (#9255)
|
- 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 (#9270, #9275)
|
- 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 (#9272)
|
- 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 (#9291)
|
- 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 (#9303)
|
- Fix filter ID not being a string in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9303))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix multiple remote account deletions being able to deadlock the database (#9292)
|
- 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 (#9329)
|
- Fix HTTP connection timeout of 10s not being enforced ([Gargron](https://github.com/tootsuite/mastodon/pull/9329))
|
||||||
|
|
||||||
## [2.6.1] - 2018-10-30
|
## [2.6.1] - 2018-10-30
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix resolving resources by URL not working due to a regression in #9132 (#9171)
|
- 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 (#9173)
|
- 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
|
## [2.6.0] - 2018-10-30
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add link ownership verification (#8703)
|
- Add link ownership verification ([Gargron](https://github.com/tootsuite/mastodon/pull/8703))
|
||||||
- Add conversations API (#8832)
|
- 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 (#8807)
|
- 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 (#8766)
|
- 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 (#8950, #9093, #9150)
|
- 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 (#8830)
|
- 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 (#8762)
|
- 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 (#8569)
|
- 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 (#8655)
|
- Add `force_login` param to OAuth authorize page ([Gargron](https://github.com/tootsuite/mastodon/pull/8655))
|
||||||
- Add `tootctl accounts backup` (#8642, #8811)
|
- Add `tootctl accounts backup` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts create` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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` (#8642, #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 (#8736)
|
- 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 (#8893)
|
- 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 (#8906)
|
- 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 (#9024)
|
- Add button for disabling local account to report quick actions bar ([Gargron](https://github.com/tootsuite/mastodon/pull/9024))
|
||||||
- Add Czech language (#8594)
|
- Add Czech language ([Aditoo17](https://github.com/tootsuite/mastodon/pull/8594))
|
||||||
- Add `same-site` (`lax`) attribute to cookies (#8626)
|
- Add `same-site` (`lax`) attribute to cookies ([sorin-davidoi](https://github.com/tootsuite/mastodon/pull/8626))
|
||||||
- Add support for styled scrollbars in Firefox Nightly (#8653)
|
- 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 (#8673)
|
- 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 (#8689)
|
- 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 (#8688)
|
- 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 (#8770)
|
- Add nginx and systemd templates to `dist/` directory ([Gargron](https://github.com/tootsuite/mastodon/pull/8770))
|
||||||
- Add support for `/.well-known/change-password` (#8828)
|
- Add support for `/.well-known/change-password` ([Gargron](https://github.com/tootsuite/mastodon/pull/8828))
|
||||||
- Add option to override FFMPEG binary path (#8855)
|
- 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 (#8942)
|
- Add `dns-prefetch` tag when using different host for assets or uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/8942))
|
||||||
- Add `description` meta tag (#8941)
|
- Add `description` meta tag ([Gargron](https://github.com/tootsuite/mastodon/pull/8941))
|
||||||
- Add `Content-Security-Policy` header (#8957)
|
- Add `Content-Security-Policy` header ([ThibG](https://github.com/tootsuite/mastodon/pull/8957))
|
||||||
- Add cache for the instance info API (#8765)
|
- Add cache for the instance info API ([ykzts](https://github.com/tootsuite/mastodon/pull/8765))
|
||||||
- Add suggested follows to search screen in mobile layout (#9010)
|
- Add suggested follows to search screen in mobile layout ([Gargron](https://github.com/tootsuite/mastodon/pull/9010))
|
||||||
- Add CORS header to `/.well-known/*` routes (#9083)
|
- Add CORS header to `/.well-known/*` routes ([BenLubar](https://github.com/tootsuite/mastodon/pull/9083))
|
||||||
- Add `card` attribute to statuses returned from REST API (#9120)
|
- Add `card` attribute to statuses returned from REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
- Add in-stream link preview (#9120)
|
- Add in-stream link preview ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
- Add support for ActivityPub `Page` objects (#9121)
|
- Add support for ActivityPub `Page` objects ([mbajur](https://github.com/tootsuite/mastodon/pull/9121))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change forms design (#8703)
|
- Change forms design ([Gargron](https://github.com/tootsuite/mastodon/pull/8703))
|
||||||
- Change reports overview to group by target account (#8674)
|
- 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 (#8205)
|
- 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 (#8832, #9022)
|
- 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 (#8940)
|
- Change home timelines to exclude DMs ([Gargron](https://github.com/tootsuite/mastodon/pull/8940))
|
||||||
- Change list timelines to exclude all replies (#8683)
|
- Change list timelines to exclude all replies ([cbayerlein](https://github.com/tootsuite/mastodon/pull/8683))
|
||||||
- Change admin accounts UI default sort to most recent (#8813)
|
- Change admin accounts UI default sort to most recent ([Gargron](https://github.com/tootsuite/mastodon/pull/8813))
|
||||||
- Change documentation URL in the UI (#8898)
|
- Change documentation URL in the UI ([Gargron](https://github.com/tootsuite/mastodon/pull/8898))
|
||||||
- Change style of success and failure messages (#8973)
|
- Change style of success and failure messages ([Gargron](https://github.com/tootsuite/mastodon/pull/8973))
|
||||||
- Change DM filtering to always allow DMs from staff (#8993)
|
- 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 (#9003)
|
- 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 (#9055)
|
- 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 (#9100)
|
- 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 (#9152)
|
- 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 (#9147)
|
- 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 (#8547)
|
- Change CSS `detailed-status__wrapper` class actually wrap the detailed status ([trwnh](https://github.com/tootsuite/mastodon/pull/8547))
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
- `GET /api/v1/timelines/direct` → `GET /api/v1/conversations` (#8832)
|
- `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` (#8905)
|
- `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 (#9120)
|
- `GET /api/v1/statuses/:id/card` → `card` attributed included in status ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Remove "on this device" label in column push settings (#8704)
|
- 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 (#8675)
|
- Remove rake tasks in favour of tootctl commands ([Gargron](https://github.com/tootsuite/mastodon/pull/8675))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix remote statuses using instance's default locale if no language given (#8861)
|
- 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 (#9023)
|
- 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 (#8951)
|
- Fix network calls being performed in database transaction in ActivityPub handler ([Gargron](https://github.com/tootsuite/mastodon/pull/8951))
|
||||||
- Fix dropdown arrow position (#8637)
|
- Fix dropdown arrow position ([ThibG](https://github.com/tootsuite/mastodon/pull/8637))
|
||||||
- Fix first element of dropdowns being focused even if not using keyboard (#8679)
|
- 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 (#8619)
|
- Fix tootctl requiring `bundle exec` invocation ([abcang](https://github.com/tootsuite/mastodon/pull/8619))
|
||||||
- Fix public pages not using animation preference for avatars (#8614)
|
- 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 (#8669)
|
- 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 (#8597)
|
- 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 (#8678)
|
- 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 (#8758)
|
- Fix generated URLs of desktop notifications missing base URL ([GenbuHase](https://github.com/tootsuite/mastodon/pull/8758))
|
||||||
- Fix RTL styles (#8764, #8767, #8823, #8897, #9005, #9007, #9018, #9021, #9145, #9146)
|
- 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 (#8955)
|
- 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 (#8998)
|
- 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 (#9006)
|
- Fix some hotkeys not working on detailed status view ([ThibG](https://github.com/tootsuite/mastodon/pull/9006))
|
||||||
- Fix og:url on status pages (#9047)
|
- Fix og:url on status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9047))
|
||||||
- Fix upload option buttons only being visible on hover (#9074)
|
- 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 (#9094)
|
- 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 (#6934, #9158)
|
- 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 (#9109)
|
- 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 (#9124)
|
- 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 (#9126)
|
- 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 (#9119)
|
- 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 (#9163)
|
- 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 (#9162)
|
- Fix td instead of th in sessions table header ([Gargron](https://github.com/tootsuite/mastodon/pull/9162))
|
||||||
- Fix handling of content types with profile (#9132)
|
- Fix handling of content types with profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9132))
|
||||||
|
|
||||||
## [2.5.2] - 2018-10-12
|
## [2.5.2] - 2018-10-12
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix XSS vulnerability (#8959)
|
- Fix XSS vulnerability ([Gargron](https://github.com/tootsuite/mastodon/pull/8959))
|
||||||
|
|
||||||
## [2.5.1] - 2018-10-07
|
## [2.5.1] - 2018-10-07
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix database migrations for PostgreSQL below 9.5 (#8903)
|
- Fix database migrations for PostgreSQL below 9.5 ([Gargron](https://github.com/tootsuite/mastodon/pull/8903))
|
||||||
- Fix class autoloading issue in ActivityPub Create handler (#8820)
|
- 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 (#8831)
|
- 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 (#8883)
|
- Bump puma from 3.11.4 to 3.12.0 ([dependabot[bot]](https://github.com/tootsuite/mastodon/pull/8883))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix some local images not having their EXIF metadata stripped on upload (#8714)
|
- 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 (#8864)
|
- 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 (#8881)
|
- 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 (#8916)
|
- Fix being able to report statuses not belonging to the reported account ([ThibG](https://github.com/tootsuite/mastodon/pull/8916))
|
||||||
|
@ -10,6 +10,8 @@ You can contribute in the following ways:
|
|||||||
- Contributing code to Mastodon by fixing bugs or implementing features
|
- Contributing code to Mastodon by fixing bugs or implementing features
|
||||||
- Improving the documentation
|
- 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
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
184
Dockerfile
184
Dockerfile
@ -1,86 +1,126 @@
|
|||||||
FROM node:8.14.0-alpine as node
|
FROM ubuntu:18.04 as build-dep
|
||||||
FROM ruby:2.4.5-alpine3.8
|
|
||||||
|
|
||||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
# Use bash for the shell
|
||||||
description="Your self-hosted, globally interconnected microblogging community"
|
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 UID=991
|
||||||
ARG GID=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 \
|
# Copy over masto source from building and set permissions
|
||||||
RAILS_SERVE_STATIC_FILES=true \
|
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
||||||
RAILS_ENV=production \
|
|
||||||
NODE_ENV=production
|
|
||||||
|
|
||||||
ARG LIBICONV_VERSION=1.15
|
# Install masto runtime deps
|
||||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
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
|
# Run masto services in prod mode
|
||||||
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
ENV RAILS_ENV="production"
|
||||||
COPY --from=node /usr/local/bin/npm /usr/local/bin/npm
|
ENV NODE_ENV="production"
|
||||||
COPY --from=node /opt/yarn-* /opt/yarn
|
|
||||||
|
|
||||||
RUN apk -U upgrade \
|
# Tell rails to serve static files
|
||||||
&& apk add -t build-dependencies \
|
ENV RAILS_SERVE_STATIC_FILES="true"
|
||||||
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
|
|
||||||
|
|
||||||
|
# Set the run user
|
||||||
USER mastodon
|
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", "--"]
|
||||||
|
34
Gemfile
34
Gemfile
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.3.0', '< 2.6.0'
|
ruby '>= 2.4.0', '< 2.7.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.3'
|
gem 'pkg-config', '~> 1.3'
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ gem 'hamlit-rails', '~> 0.2'
|
|||||||
gem 'pg', '~> 1.1'
|
gem 'pg', '~> 1.1'
|
||||||
gem 'makara', '~> 0.4'
|
gem 'makara', '~> 0.4'
|
||||||
gem 'pghero', '~> 2.2'
|
gem 'pghero', '~> 2.2'
|
||||||
gem 'dotenv-rails', '~> 2.5'
|
gem 'dotenv-rails', '~> 2.7'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.30', require: false
|
gem 'aws-sdk-s3', '~> 1.30', require: false
|
||||||
gem 'fog-core', '<= 2.1.0'
|
gem 'fog-core', '<= 2.1.0'
|
||||||
@ -23,13 +23,13 @@ gem 'paperclip-av-transcoder', '~> 0.6'
|
|||||||
gem 'streamio-ffmpeg', '~> 3.0'
|
gem 'streamio-ffmpeg', '~> 3.0'
|
||||||
|
|
||||||
gem 'active_model_serializers', '~> 0.10'
|
gem 'active_model_serializers', '~> 0.10'
|
||||||
gem 'addressable', '~> 2.5'
|
gem 'addressable', '~> 2.6'
|
||||||
gem 'bootsnap', '~> 1.3', require: false
|
gem 'bootsnap', '~> 1.4', require: false
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.6'
|
gem 'charlock_holmes', '~> 0.7.6'
|
||||||
gem 'iso-639'
|
gem 'iso-639'
|
||||||
gem 'chewy', '~> 5.0'
|
gem 'chewy', '~> 5.0'
|
||||||
gem 'cld3', '~> 3.2.0'
|
gem 'cld3', '~> 3.2.3'
|
||||||
gem 'devise', '~> 4.5'
|
gem 'devise', '~> 4.5'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
|
|
||||||
@ -52,12 +52,12 @@ gem 'htmlentities', '~> 4.3'
|
|||||||
gem 'http', '~> 3.3'
|
gem 'http', '~> 3.3'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
|
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
|
||||||
gem 'httplog', '~> 1.1'
|
gem 'httplog', '~> 1.2'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.1'
|
gem 'kaminari', '~> 1.1'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
|
||||||
gem 'nokogiri', '~> 1.9'
|
gem 'nokogiri', '~> 1.10'
|
||||||
gem 'nsa', '~> 0.2'
|
gem 'nsa', '~> 0.2'
|
||||||
gem 'oj', '~> 3.7'
|
gem 'oj', '~> 3.7'
|
||||||
gem 'ostatus2', '~> 2.0'
|
gem 'ostatus2', '~> 2.0'
|
||||||
@ -75,7 +75,7 @@ gem 'rqrcode', '~> 0.10'
|
|||||||
gem 'sanitize', '~> 5.0'
|
gem 'sanitize', '~> 5.0'
|
||||||
gem 'sidekiq', '~> 5.2'
|
gem 'sidekiq', '~> 5.2'
|
||||||
gem 'sidekiq-scheduler', '~> 3.0'
|
gem 'sidekiq-scheduler', '~> 3.0'
|
||||||
gem 'sidekiq-unique-jobs', '~> 5.0'
|
gem 'sidekiq-unique-jobs', '~> 6.0'
|
||||||
gem 'sidekiq-bulk', '~>0.2.0'
|
gem 'sidekiq-bulk', '~>0.2.0'
|
||||||
gem 'simple-navigation', '~> 4.0'
|
gem 'simple-navigation', '~> 4.0'
|
||||||
gem 'simple_form', '~> 4.1'
|
gem 'simple_form', '~> 4.1'
|
||||||
@ -89,7 +89,7 @@ gem 'tzinfo-data', '~> 1.2018'
|
|||||||
gem 'webpacker', '~> 3.5'
|
gem 'webpacker', '~> 3.5'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
gem 'json-ld', '~> 2.2'
|
gem 'json-ld', '~> 3.0'
|
||||||
gem 'json-ld-preloaded', '~> 3.0'
|
gem 'json-ld-preloaded', '~> 3.0'
|
||||||
gem 'rdf-normalize', '~> 0.3'
|
gem 'rdf-normalize', '~> 0.3'
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ group :development, :test do
|
|||||||
gem 'fabrication', '~> 2.20'
|
gem 'fabrication', '~> 2.20'
|
||||||
gem 'fuubar', '~> 2.3'
|
gem 'fuubar', '~> 2.3'
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 0.9', require: false
|
||||||
gem 'pry-byebug', '~> 3.6'
|
gem 'pry-byebug', '~> 3.7'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 3.8'
|
gem 'rspec-rails', '~> 3.8'
|
||||||
end
|
end
|
||||||
@ -107,15 +107,15 @@ group :production, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.12'
|
gem 'capybara', '~> 3.14'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 1.9'
|
gem 'faker', '~> 1.9'
|
||||||
gem 'microformats', '~> 4.0'
|
gem 'microformats', '~> 4.1'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec-sidekiq', '~> 3.0'
|
gem 'rspec-sidekiq', '~> 3.0'
|
||||||
gem 'simplecov', '~> 0.16', require: false
|
gem 'simplecov', '~> 0.16', require: false
|
||||||
gem 'webmock', '~> 3.4'
|
gem 'webmock', '~> 3.5'
|
||||||
gem 'parallel_tests', '~> 2.27'
|
gem 'parallel_tests', '~> 2.28'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
@ -127,8 +127,8 @@ group :development do
|
|||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.3'
|
gem 'letter_opener_web', '~> 1.3'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.61', require: false
|
gem 'rubocop', '~> 0.65', require: false
|
||||||
gem 'brakeman', '~> 4.3', require: false
|
gem 'brakeman', '~> 4.4', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
gem 'scss_lint', '~> 0.57', require: false
|
gem 'scss_lint', '~> 0.57', require: false
|
||||||
|
|
||||||
@ -145,3 +145,5 @@ group :production do
|
|||||||
gem 'lograge', '~> 0.10'
|
gem 'lograge', '~> 0.10'
|
||||||
gem 'redis-rails', '~> 5.0'
|
gem 'redis-rails', '~> 5.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
gem 'concurrent-ruby', require: false
|
||||||
|
167
Gemfile.lock
167
Gemfile.lock
@ -38,7 +38,7 @@ GEM
|
|||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||||
active_model_serializers (0.10.8)
|
active_model_serializers (0.10.9)
|
||||||
actionpack (>= 4.1, < 6)
|
actionpack (>= 4.1, < 6)
|
||||||
activemodel (>= 4.1, < 6)
|
activemodel (>= 4.1, < 6)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
@ -62,7 +62,7 @@ GEM
|
|||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.2)
|
addressable (2.6.0)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
airbrussh (1.3.0)
|
airbrussh (1.3.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
@ -76,8 +76,8 @@ GEM
|
|||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-eventstream (1.0.1)
|
aws-eventstream (1.0.1)
|
||||||
aws-partitions (1.122.0)
|
aws-partitions (1.131.0)
|
||||||
aws-sdk-core (3.43.0)
|
aws-sdk-core (3.45.0)
|
||||||
aws-eventstream (~> 1.0)
|
aws-eventstream (~> 1.0)
|
||||||
aws-partitions (~> 1.0)
|
aws-partitions (~> 1.0)
|
||||||
aws-sigv4 (~> 1.0)
|
aws-sigv4 (~> 1.0)
|
||||||
@ -85,31 +85,31 @@ GEM
|
|||||||
aws-sdk-kms (1.13.0)
|
aws-sdk-kms (1.13.0)
|
||||||
aws-sdk-core (~> 3, >= 3.39.0)
|
aws-sdk-core (~> 3, >= 3.39.0)
|
||||||
aws-sigv4 (~> 1.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-core (~> 3, >= 3.39.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.0)
|
aws-sigv4 (~> 1.0)
|
||||||
aws-sigv4 (1.0.3)
|
aws-sigv4 (1.0.3)
|
||||||
bcrypt (3.1.12)
|
bcrypt (3.1.12)
|
||||||
benchmark-ips (2.7.2)
|
benchmark-ips (2.7.2)
|
||||||
better_errors (2.5.0)
|
better_errors (2.5.1)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.8.0)
|
binding_of_caller (0.8.0)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.3.2)
|
bootsnap (1.4.1)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.3.1)
|
brakeman (4.4.0)
|
||||||
browser (2.5.3)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.9.0)
|
bullet (5.9.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
bundler-audit (0.6.0)
|
bundler-audit (0.6.1)
|
||||||
bundler (~> 1.2)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
byebug (10.0.2)
|
byebug (11.0.0)
|
||||||
capistrano (3.11.0)
|
capistrano (3.11.0)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
@ -126,7 +126,7 @@ GEM
|
|||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.12.0)
|
capybara (3.14.0)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
@ -142,13 +142,13 @@ GEM
|
|||||||
elasticsearch (>= 2.0.0)
|
elasticsearch (>= 2.0.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
chunky_png (1.3.10)
|
chunky_png (1.3.10)
|
||||||
cld3 (3.2.2)
|
cld3 (3.2.3)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.10.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.2)
|
coderay (1.1.2)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.1.4)
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.2)
|
||||||
crack (0.4.3)
|
crack (0.4.3)
|
||||||
safe_yaml (~> 1.0.0)
|
safe_yaml (~> 1.0.0)
|
||||||
@ -185,10 +185,10 @@ GEM
|
|||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.0.2)
|
doorkeeper (5.0.2)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
dotenv (2.5.0)
|
dotenv (2.7.1)
|
||||||
dotenv-rails (2.5.0)
|
dotenv-rails (2.7.1)
|
||||||
dotenv (= 2.5.0)
|
dotenv (= 2.7.1)
|
||||||
railties (>= 3.2, < 6.0)
|
railties (>= 3.2, < 6.1)
|
||||||
elasticsearch (6.0.2)
|
elasticsearch (6.0.2)
|
||||||
elasticsearch-api (= 6.0.2)
|
elasticsearch-api (= 6.0.2)
|
||||||
elasticsearch-transport (= 6.0.2)
|
elasticsearch-transport (= 6.0.2)
|
||||||
@ -200,12 +200,12 @@ GEM
|
|||||||
multi_json
|
multi_json
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
equatable (0.5.0)
|
equatable (0.5.0)
|
||||||
erubi (1.7.1)
|
erubi (1.8.0)
|
||||||
et-orbi (1.1.6)
|
et-orbi (1.1.6)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.62.0)
|
excon (0.62.0)
|
||||||
fabrication (2.20.1)
|
fabrication (2.20.1)
|
||||||
faker (1.9.1)
|
faker (1.9.3)
|
||||||
i18n (>= 0.7)
|
i18n (>= 0.7)
|
||||||
faraday (0.15.0)
|
faraday (0.15.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
@ -231,7 +231,7 @@ GEM
|
|||||||
fuubar (2.3.2)
|
fuubar (2.3.2)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
get_process_mem (0.2.2)
|
get_process_mem (0.2.3)
|
||||||
globalid (0.4.1)
|
globalid (0.4.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
goldfinger (2.1.0)
|
goldfinger (2.1.0)
|
||||||
@ -239,11 +239,11 @@ GEM
|
|||||||
http (~> 3.0)
|
http (~> 3.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
oj (~> 3.0)
|
oj (~> 3.0)
|
||||||
hamlit (2.8.8)
|
hamlit (2.9.2)
|
||||||
temple (>= 0.8.0)
|
temple (>= 0.8.0)
|
||||||
thor
|
thor
|
||||||
tilt
|
tilt
|
||||||
hamlit-rails (0.2.0)
|
hamlit-rails (0.2.2)
|
||||||
actionpack (>= 4.0.1)
|
actionpack (>= 4.0.1)
|
||||||
activesupport (>= 4.0.1)
|
activesupport (>= 4.0.1)
|
||||||
hamlit (>= 1.2.0)
|
hamlit (>= 1.2.0)
|
||||||
@ -266,10 +266,10 @@ GEM
|
|||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (2.1.1)
|
http-form_data (2.1.1)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
httplog (1.1.1)
|
httplog (1.2.1)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.1.1)
|
i18n (1.5.3)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (0.9.28)
|
i18n-tasks (0.9.28)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
@ -284,14 +284,14 @@ GEM
|
|||||||
idn-ruby (0.1.0)
|
idn-ruby (0.1.0)
|
||||||
ipaddress (0.8.3)
|
ipaddress (0.8.3)
|
||||||
iso-639 (0.2.8)
|
iso-639 (0.2.8)
|
||||||
jaro_winkler (1.5.1)
|
jaro_winkler (1.5.2)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
json-ld (2.2.1)
|
json-ld (3.0.2)
|
||||||
multi_json (~> 1.12)
|
multi_json (~> 1.12)
|
||||||
rdf (>= 2.2.8, < 4.0)
|
rdf (>= 2.2.8, < 4.0)
|
||||||
json-ld-preloaded (3.0.0)
|
json-ld-preloaded (3.0.2)
|
||||||
json-ld (>= 2.2, < 4.0)
|
json-ld (~> 3.0)
|
||||||
multi_json (~> 1.12)
|
multi_json (~> 1.12)
|
||||||
rdf (~> 3.0)
|
rdf (~> 3.0)
|
||||||
jsonapi-renderer (0.2.0)
|
jsonapi-renderer (0.2.0)
|
||||||
@ -335,9 +335,9 @@ GEM
|
|||||||
redis (>= 3.0.5)
|
redis (>= 3.0.5)
|
||||||
memory_profiler (0.9.12)
|
memory_profiler (0.9.12)
|
||||||
method_source (0.9.2)
|
method_source (0.9.2)
|
||||||
microformats (4.0.7)
|
microformats (4.1.0)
|
||||||
json
|
json (~> 2.1)
|
||||||
nokogiri
|
nokogiri (~> 1.8, >= 1.8.3)
|
||||||
mime-types (3.2.2)
|
mime-types (3.2.2)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2018.0812)
|
mime-types-data (3.2018.0812)
|
||||||
@ -345,7 +345,7 @@ GEM
|
|||||||
mini_mime (1.0.1)
|
mini_mime (1.0.1)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
msgpack (1.2.4)
|
msgpack (1.2.6)
|
||||||
multi_json (1.13.1)
|
multi_json (1.13.1)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
necromancer (0.4.0)
|
necromancer (0.4.0)
|
||||||
@ -354,16 +354,16 @@ GEM
|
|||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (5.0.2)
|
net-ssh (5.0.2)
|
||||||
nio4r (2.3.1)
|
nio4r (2.3.1)
|
||||||
nokogiri (1.9.1)
|
nokogiri (1.10.1)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
nokogumbo (2.0.0)
|
nokogumbo (2.0.0)
|
||||||
nokogiri (~> 1.8, >= 1.8.4)
|
nokogiri (~> 1.8, >= 1.8.4)
|
||||||
nsa (0.2.4)
|
nsa (0.2.7)
|
||||||
activesupport (>= 4.2, < 6)
|
activesupport (>= 4.2, < 6)
|
||||||
concurrent-ruby (~> 1.0.0)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (>= 3.5.0)
|
sidekiq (>= 3.5)
|
||||||
statsd-ruby (~> 1.2.0)
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.7.4)
|
oj (3.7.9)
|
||||||
omniauth (1.9.0)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.7.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
@ -389,18 +389,18 @@ GEM
|
|||||||
paperclip-av-transcoder (0.6.4)
|
paperclip-av-transcoder (0.6.4)
|
||||||
av (~> 0.9.0)
|
av (~> 0.9.0)
|
||||||
paperclip (>= 2.5.2)
|
paperclip (>= 2.5.2)
|
||||||
parallel (1.12.1)
|
parallel (1.13.0)
|
||||||
parallel_tests (2.27.0)
|
parallel_tests (2.28.0)
|
||||||
parallel
|
parallel
|
||||||
parser (2.5.3.0)
|
parser (2.6.0.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
pastel (0.7.2)
|
pastel (0.7.2)
|
||||||
equatable (~> 0.5.0)
|
equatable (~> 0.5.0)
|
||||||
tty-color (~> 0.4.0)
|
tty-color (~> 0.4.0)
|
||||||
pg (1.1.3)
|
pg (1.1.4)
|
||||||
pghero (2.2.0)
|
pghero (2.2.0)
|
||||||
activerecord
|
activerecord
|
||||||
pkg-config (1.3.2)
|
pkg-config (1.3.4)
|
||||||
powerpack (0.1.2)
|
powerpack (0.1.2)
|
||||||
premailer (1.11.1)
|
premailer (1.11.1)
|
||||||
addressable
|
addressable
|
||||||
@ -413,21 +413,22 @@ GEM
|
|||||||
pry (0.12.2)
|
pry (0.12.2)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.9.0)
|
method_source (~> 0.9.0)
|
||||||
pry-byebug (3.6.0)
|
pry-byebug (3.7.0)
|
||||||
byebug (~> 10.0)
|
byebug (~> 11.0)
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.8)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
|
psych (3.1.0)
|
||||||
public_suffix (3.0.3)
|
public_suffix (3.0.3)
|
||||||
puma (3.12.0)
|
puma (3.12.0)
|
||||||
pundit (2.0.0)
|
pundit (2.0.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.1.6)
|
raabro (1.1.6)
|
||||||
rack (2.0.6)
|
rack (2.0.6)
|
||||||
rack-attack (5.4.2)
|
rack-attack (5.4.2)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.0.2)
|
rack-cors (1.0.2)
|
||||||
rack-protection (2.0.4)
|
rack-protection (2.0.5)
|
||||||
rack
|
rack
|
||||||
rack-proxy (0.6.4)
|
rack-proxy (0.6.4)
|
||||||
rack
|
rack
|
||||||
@ -455,7 +456,7 @@ GEM
|
|||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.0.4)
|
rails-html-sanitizer (1.0.4)
|
||||||
loofah (~> 2.2, >= 2.2.2)
|
loofah (~> 2.2, >= 2.2.2)
|
||||||
rails-i18n (5.1.2)
|
rails-i18n (5.1.3)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 5.0, < 6)
|
railties (>= 5.0, < 6)
|
||||||
rails-settings-cached (0.6.6)
|
rails-settings-cached (0.6.6)
|
||||||
@ -467,11 +468,11 @@ GEM
|
|||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.19.0, < 2.0)
|
thor (>= 0.19.0, < 2.0)
|
||||||
rainbow (3.0.0)
|
rainbow (3.0.0)
|
||||||
rake (12.3.1)
|
rake (12.3.2)
|
||||||
rb-fsevent (0.10.3)
|
rb-fsevent (0.10.3)
|
||||||
rb-inotify (0.9.10)
|
rb-inotify (0.9.10)
|
||||||
ffi (>= 0.5.0, < 2)
|
ffi (>= 0.5.0, < 2)
|
||||||
rdf (3.0.7)
|
rdf (3.0.9)
|
||||||
hamster (~> 3.0)
|
hamster (~> 3.0)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.3.3)
|
rdf-normalize (0.3.3)
|
||||||
@ -513,7 +514,7 @@ GEM
|
|||||||
rspec-mocks (3.8.0)
|
rspec-mocks (3.8.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.8.0)
|
rspec-support (~> 3.8.0)
|
||||||
rspec-rails (3.8.1)
|
rspec-rails (3.8.2)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
@ -525,11 +526,12 @@ GEM
|
|||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.8.0)
|
rspec-support (3.8.0)
|
||||||
rubocop (0.61.1)
|
rubocop (0.65.0)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.5, != 2.5.1.1)
|
parser (>= 2.5, != 2.5.1.1)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
|
psych (>= 3.1.0)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (~> 1.4.0)
|
unicode-display_width (~> 1.4.0)
|
||||||
@ -551,8 +553,9 @@ GEM
|
|||||||
scss_lint (0.57.1)
|
scss_lint (0.57.1)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.5, >= 3.5.5)
|
sass (~> 3.5, >= 3.5.5)
|
||||||
sidekiq (5.2.3)
|
sidekiq (5.2.5)
|
||||||
connection_pool (~> 2.2, >= 2.2.2)
|
connection_pool (~> 2.2, >= 2.2.2)
|
||||||
|
rack (>= 1.5.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
redis (>= 3.3.5, < 5)
|
redis (>= 3.3.5, < 5)
|
||||||
sidekiq-bulk (0.2.0)
|
sidekiq-bulk (0.2.0)
|
||||||
@ -562,8 +565,9 @@ GEM
|
|||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (5.0.10)
|
sidekiq-unique-jobs (6.0.12)
|
||||||
sidekiq (>= 4.0, <= 6.0)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
|
sidekiq (>= 4.0, < 7.0)
|
||||||
thor (~> 0)
|
thor (~> 0)
|
||||||
simple-navigation (4.0.5)
|
simple-navigation (4.0.5)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
@ -586,7 +590,7 @@ GEM
|
|||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
stackprof (0.2.12)
|
stackprof (0.2.12)
|
||||||
statsd-ruby (1.2.1)
|
statsd-ruby (1.4.0)
|
||||||
stoplight (2.1.3)
|
stoplight (2.1.3)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
@ -599,13 +603,13 @@ GEM
|
|||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
thor (0.20.3)
|
thor (0.20.3)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.8)
|
tilt (2.0.9)
|
||||||
timers (4.2.0)
|
timers (4.2.0)
|
||||||
tty-color (0.4.3)
|
tty-color (0.4.3)
|
||||||
tty-command (0.8.2)
|
tty-command (0.8.2)
|
||||||
pastel (~> 0.7.0)
|
pastel (~> 0.7.0)
|
||||||
tty-cursor (0.6.0)
|
tty-cursor (0.6.0)
|
||||||
tty-prompt (0.18.0)
|
tty-prompt (0.18.1)
|
||||||
necromancer (~> 0.4.0)
|
necromancer (~> 0.4.0)
|
||||||
pastel (~> 0.7.0)
|
pastel (~> 0.7.0)
|
||||||
timers (~> 4.0)
|
timers (~> 4.0)
|
||||||
@ -620,16 +624,16 @@ GEM
|
|||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.5)
|
tzinfo (1.2.5)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2018.7)
|
tzinfo-data (1.2018.9)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.5)
|
unf_ext (0.0.7.5)
|
||||||
unicode-display_width (1.4.0)
|
unicode-display_width (1.4.1)
|
||||||
uniform_notifier (1.12.1)
|
uniform_notifier (1.12.1)
|
||||||
warden (1.2.7)
|
warden (1.2.7)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
webmock (3.4.2)
|
webmock (3.5.1)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
@ -637,7 +641,7 @@ GEM
|
|||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
webpush (0.3.4)
|
webpush (0.3.6)
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
websocket-driver (0.7.0)
|
websocket-driver (0.7.0)
|
||||||
@ -653,13 +657,13 @@ PLATFORMS
|
|||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
active_model_serializers (~> 0.10)
|
||||||
active_record_query_trace (~> 1.5)
|
active_record_query_trace (~> 1.5)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.6)
|
||||||
annotate (~> 2.7)
|
annotate (~> 2.7)
|
||||||
aws-sdk-s3 (~> 1.30)
|
aws-sdk-s3 (~> 1.30)
|
||||||
better_errors (~> 2.5)
|
better_errors (~> 2.5)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
bootsnap (~> 1.3)
|
bootsnap (~> 1.4)
|
||||||
brakeman (~> 4.3)
|
brakeman (~> 4.4)
|
||||||
browser
|
browser
|
||||||
bullet (~> 5.9)
|
bullet (~> 5.9)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
@ -667,17 +671,18 @@ DEPENDENCIES
|
|||||||
capistrano-rails (~> 1.4)
|
capistrano-rails (~> 1.4)
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 3.12)
|
capybara (~> 3.14)
|
||||||
charlock_holmes (~> 0.7.6)
|
charlock_holmes (~> 0.7.6)
|
||||||
chewy (~> 5.0)
|
chewy (~> 5.0)
|
||||||
cld3 (~> 3.2.0)
|
cld3 (~> 3.2.3)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
|
concurrent-ruby
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
devise (~> 4.5)
|
devise (~> 4.5)
|
||||||
devise-two-factor (~> 3.0)
|
devise-two-factor (~> 3.0)
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
doorkeeper (~> 5.0)
|
doorkeeper (~> 5.0)
|
||||||
dotenv-rails (~> 2.5)
|
dotenv-rails (~> 2.7)
|
||||||
fabrication (~> 2.20)
|
fabrication (~> 2.20)
|
||||||
faker (~> 1.9)
|
faker (~> 1.9)
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
@ -692,11 +697,11 @@ DEPENDENCIES
|
|||||||
http (~> 3.3)
|
http (~> 3.3)
|
||||||
http_accept_language (~> 2.1)
|
http_accept_language (~> 2.1)
|
||||||
http_parser.rb (~> 0.6)!
|
http_parser.rb (~> 0.6)!
|
||||||
httplog (~> 1.1)
|
httplog (~> 1.2)
|
||||||
i18n-tasks (~> 0.9)
|
i18n-tasks (~> 0.9)
|
||||||
idn-ruby
|
idn-ruby
|
||||||
iso-639
|
iso-639
|
||||||
json-ld (~> 2.2)
|
json-ld (~> 3.0)
|
||||||
json-ld-preloaded (~> 3.0)
|
json-ld-preloaded (~> 3.0)
|
||||||
kaminari (~> 1.1)
|
kaminari (~> 1.1)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
@ -706,10 +711,10 @@ DEPENDENCIES
|
|||||||
makara (~> 0.4)
|
makara (~> 0.4)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
microformats (~> 4.0)
|
microformats (~> 4.1)
|
||||||
mime-types (~> 3.2)
|
mime-types (~> 3.2)
|
||||||
net-ldap (~> 0.10)
|
net-ldap (~> 0.10)
|
||||||
nokogiri (~> 1.9)
|
nokogiri (~> 1.10)
|
||||||
nsa (~> 0.2)
|
nsa (~> 0.2)
|
||||||
oj (~> 3.7)
|
oj (~> 3.7)
|
||||||
omniauth (~> 1.9)
|
omniauth (~> 1.9)
|
||||||
@ -719,14 +724,14 @@ DEPENDENCIES
|
|||||||
ox (~> 2.10)
|
ox (~> 2.10)
|
||||||
paperclip (~> 6.0)
|
paperclip (~> 6.0)
|
||||||
paperclip-av-transcoder (~> 0.6)
|
paperclip-av-transcoder (~> 0.6)
|
||||||
parallel_tests (~> 2.27)
|
parallel_tests (~> 2.28)
|
||||||
pg (~> 1.1)
|
pg (~> 1.1)
|
||||||
pghero (~> 2.2)
|
pghero (~> 2.2)
|
||||||
pkg-config (~> 1.3)
|
pkg-config (~> 1.3)
|
||||||
posix-spawn!
|
posix-spawn!
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
pry-byebug (~> 3.6)
|
pry-byebug (~> 3.7)
|
||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 3.12)
|
puma (~> 3.12)
|
||||||
pundit (~> 2.0)
|
pundit (~> 2.0)
|
||||||
@ -743,13 +748,13 @@ DEPENDENCIES
|
|||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.8)
|
rspec-rails (~> 3.8)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop (~> 0.61)
|
rubocop (~> 0.65)
|
||||||
sanitize (~> 5.0)
|
sanitize (~> 5.0)
|
||||||
scss_lint (~> 0.57)
|
scss_lint (~> 0.57)
|
||||||
sidekiq (~> 5.2)
|
sidekiq (~> 5.2)
|
||||||
sidekiq-bulk (~> 0.2.0)
|
sidekiq-bulk (~> 0.2.0)
|
||||||
sidekiq-scheduler (~> 3.0)
|
sidekiq-scheduler (~> 3.0)
|
||||||
sidekiq-unique-jobs (~> 5.0)
|
sidekiq-unique-jobs (~> 6.0)
|
||||||
simple-navigation (~> 4.0)
|
simple-navigation (~> 4.0)
|
||||||
simple_form (~> 4.1)
|
simple_form (~> 4.1)
|
||||||
simplecov (~> 0.16)
|
simplecov (~> 0.16)
|
||||||
@ -763,12 +768,12 @@ DEPENDENCIES
|
|||||||
tty-prompt (~> 0.18)
|
tty-prompt (~> 0.18)
|
||||||
twitter-text (~> 1.14)
|
twitter-text (~> 1.14)
|
||||||
tzinfo-data (~> 1.2018)
|
tzinfo-data (~> 1.2018)
|
||||||
webmock (~> 3.4)
|
webmock (~> 3.5)
|
||||||
webpacker (~> 3.5)
|
webpacker (~> 3.5)
|
||||||
webpush
|
webpush
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.5.3p105
|
ruby 2.6.1p33
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.6
|
1.17.3
|
||||||
|
@ -80,13 +80,13 @@ A **Vagrant** configuration is included for development purposes.
|
|||||||
|
|
||||||
Mastodon is **free, open source software** licensed under **AGPLv3**.
|
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
|
**IRC channel**: #mastodon on irc.freenode.net
|
||||||
|
|
||||||
## License
|
## 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.
|
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.
|
||||||
|
|
||||||
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@ -44,7 +44,7 @@ sudo apt-get install \
|
|||||||
|
|
||||||
# Install rvm
|
# Install rvm
|
||||||
read RUBY_VERSION < .ruby-version
|
read RUBY_VERSION < .ruby-version
|
||||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
|
||||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||||
source /home/vagrant/.rvm/scripts/rvm
|
source /home/vagrant/.rvm/scripts/rvm
|
||||||
|
|
||||||
|
@ -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|
|
crutch :mentions do |collection|
|
||||||
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||||
@ -48,14 +48,14 @@ class StatusesIndex < Chewy::Index
|
|||||||
end
|
end
|
||||||
|
|
||||||
root date_detection: false do
|
root date_detection: false do
|
||||||
|
field :id, type: 'long'
|
||||||
field :account_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'
|
field :stemmed, type: 'text', analyzer: 'content'
|
||||||
end
|
end
|
||||||
|
|
||||||
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
|
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
|
||||||
field :created_at, type: 'date'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,11 +52,12 @@ class AccountsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def show_pinned_statuses?
|
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
|
end
|
||||||
|
|
||||||
def filtered_statuses
|
def filtered_statuses
|
||||||
default_statuses.tap do |statuses|
|
default_statuses.tap do |statuses|
|
||||||
|
statuses.merge!(hashtag_scope) if tag_requested?
|
||||||
statuses.merge!(only_media_scope) if media_requested?
|
statuses.merge!(only_media_scope) if media_requested?
|
||||||
statuses.merge!(no_replies_scope) unless replies_requested?
|
statuses.merge!(no_replies_scope) unless replies_requested?
|
||||||
end
|
end
|
||||||
@ -78,12 +79,15 @@ class AccountsController < ApplicationController
|
|||||||
Status.without_replies
|
Status.without_replies
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hashtag_scope
|
||||||
|
Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id)
|
||||||
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find_local!(params[:username])
|
@account = Account.find_local!(params[:username])
|
||||||
end
|
end
|
||||||
|
|
||||||
def older_url
|
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)
|
pagination_url(max_id: @statuses.last.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,7 +96,9 @@ class AccountsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def pagination_url(max_id: nil, min_id: nil)
|
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)
|
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
||||||
elsif replies_requested?
|
elsif replies_requested?
|
||||||
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
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')
|
request.path.ends_with?('/with_replies')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tag_requested?
|
||||||
|
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_status_page(params)
|
def filtered_status_page(params)
|
||||||
if params[:min_id].present?
|
if params[:min_id].present?
|
||||||
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
||||||
|
@ -17,7 +17,7 @@ module Admin
|
|||||||
account_action.save!
|
account_action.save!
|
||||||
|
|
||||||
if account_action.with_report?
|
if account_action.with_report?
|
||||||
redirect_to admin_report_path(account_action.report)
|
redirect_to admin_reports_path
|
||||||
else
|
else
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class AccountsController < BaseController
|
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_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||||
before_action :require_local_account!, only: [:enable, :memorialize]
|
before_action :require_local_account!, only: [:enable, :memorialize]
|
||||||
|
|
||||||
@ -62,9 +62,8 @@ module Admin
|
|||||||
def redownload
|
def redownload
|
||||||
authorize @account, :redownload?
|
authorize @account, :redownload?
|
||||||
|
|
||||||
@account.reset_avatar!
|
@account.update!(last_webfingered_at: nil)
|
||||||
@account.reset_header!
|
ResolveAccountService.new.call(@account)
|
||||||
@account.save!
|
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
@ -5,6 +5,9 @@ module Admin
|
|||||||
before_action :set_custom_emoji, except: [:index, :new, :create]
|
before_action :set_custom_emoji, except: [:index, :new, :create]
|
||||||
before_action :set_filter_params
|
before_action :set_filter_params
|
||||||
|
|
||||||
|
include ObfuscateFilename
|
||||||
|
obfuscate_filename [:custom_emoji, :image]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :custom_emoji, :index?
|
authorize :custom_emoji, :index?
|
||||||
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
||||||
|
@ -4,14 +4,9 @@ module Admin
|
|||||||
class DomainBlocksController < BaseController
|
class DomainBlocksController < BaseController
|
||||||
before_action :set_domain_block, only: [:show, :destroy]
|
before_action :set_domain_block, only: [:show, :destroy]
|
||||||
|
|
||||||
def index
|
|
||||||
authorize :domain_block, :index?
|
|
||||||
@domain_blocks = DomainBlock.page(params[:page])
|
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
def new
|
||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
@domain_block = DomainBlock.new
|
@domain_block = DomainBlock.new(domain: params[:_domain])
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@ -22,7 +17,7 @@ module Admin
|
|||||||
if @domain_block.save
|
if @domain_block.save
|
||||||
DomainBlockWorker.perform_async(@domain_block.id)
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
log_action :create, @domain_block
|
log_action :create, @domain_block
|
||||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg')
|
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||||
else
|
else
|
||||||
render :new
|
render :new
|
||||||
end
|
end
|
||||||
@ -36,7 +31,7 @@ module Admin
|
|||||||
authorize @domain_block, :destroy?
|
authorize @domain_block, :destroy?
|
||||||
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
||||||
log_action :destroy, @domain_block
|
log_action :destroy, @domain_block
|
||||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
18
app/controllers/admin/followers_controller.rb
Normal file
18
app/controllers/admin/followers_controller.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class FollowersController < BaseController
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
PER_PAGE = 40
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize :account, :index?
|
||||||
|
@followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -4,14 +4,21 @@ module Admin
|
|||||||
class InstancesController < BaseController
|
class InstancesController < BaseController
|
||||||
def index
|
def index
|
||||||
authorize :instance, :index?
|
authorize :instance, :index?
|
||||||
|
|
||||||
@instances = ordered_instances
|
@instances = ordered_instances
|
||||||
end
|
end
|
||||||
|
|
||||||
def resubscribe
|
def show
|
||||||
authorize :instance, :resubscribe?
|
authorize :instance, :show?
|
||||||
params.require(:by_domain)
|
|
||||||
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
|
@instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
|
||||||
redirect_to admin_instances_path
|
@following_count = Follow.where(account: Account.where(domain: params[:id])).count
|
||||||
|
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
|
||||||
|
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
|
||||||
|
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
|
||||||
|
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
|
||||||
|
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
|
||||||
|
@domain_block = DomainBlock.find_by(domain: params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -27,17 +34,11 @@ module Admin
|
|||||||
helper_method :paginated_instances
|
helper_method :paginated_instances
|
||||||
|
|
||||||
def ordered_instances
|
def ordered_instances
|
||||||
paginated_instances.map { |account| Instance.new(account) }
|
paginated_instances.map { |resource| Instance.new(resource) }
|
||||||
end
|
|
||||||
|
|
||||||
def subscribeable_accounts
|
|
||||||
Account.remote.where(protocol: :ostatus).where(domain: params[:by_domain])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.permit(
|
params.permit(:limited, :by_domain)
|
||||||
:domain_name
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,6 +10,10 @@ module Admin
|
|||||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
@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
|
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)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,6 +22,15 @@ module Admin
|
|||||||
@form = Form::StatusBatch.new
|
@form = Form::StatusBatch.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize :status, :index?
|
||||||
|
|
||||||
|
@statuses = @account.statuses.where(id: params[:id])
|
||||||
|
authorize @statuses.first, :show?
|
||||||
|
|
||||||
|
@form = Form::StatusBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
authorize :status, :update?
|
authorize :status, :update?
|
||||||
|
|
||||||
|
@ -68,12 +68,14 @@ class Api::BaseController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def require_user!
|
def require_user!
|
||||||
if current_user && !current_user.disabled?
|
if !current_user
|
||||||
set_user_activity
|
|
||||||
elsif current_user
|
|
||||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
|
||||||
else
|
|
||||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||||
|
elsif current_user.disabled?
|
||||||
|
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||||
|
elsif !current_user.confirmed?
|
||||||
|
render json: { error: 'Email confirmation is not completed' }, status: 403
|
||||||
|
else
|
||||||
|
set_user_activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@ class Api::V1::Accounts::SearchController < Api::BaseController
|
|||||||
def account_search
|
def account_search
|
||||||
AccountSearchService.new.call(
|
AccountSearchService.new.call(
|
||||||
params[:q],
|
params[:q],
|
||||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
|
||||||
current_account,
|
current_account,
|
||||||
|
limit: limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
resolve: truthy_param?(:resolve),
|
resolve: truthy_param?(:resolve),
|
||||||
following: truthy_param?(:following)
|
following: truthy_param?(:following),
|
||||||
|
offset: params[:offset]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -28,13 +28,12 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||||||
|
|
||||||
def account_statuses
|
def account_statuses
|
||||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||||
statuses = statuses.paginate_by_id(
|
statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||||
|
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||||
|
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||||
|
|
||||||
statuses
|
statuses
|
||||||
end
|
end
|
||||||
@ -52,7 +51,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||||||
# Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
|
# 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
|
# 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.
|
# 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)
|
@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])
|
.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||||
.reorder(id: :desc).distinct(:id).pluck(:id)
|
.reorder(id: :desc).distinct(:id).pluck(:id)
|
||||||
end
|
end
|
||||||
@ -65,6 +64,14 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||||||
Status.without_replies
|
Status.without_replies
|
||||||
end
|
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)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < Api::BaseController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
|
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
|
||||||
|
|
||||||
before_action :require_user!, except: [:show]
|
before_action :require_user!, except: [:show, :create]
|
||||||
before_action :set_account
|
before_action :set_account, except: [:create]
|
||||||
before_action :check_account_suspension, only: [:show]
|
before_action :check_account_suspension, only: [:show]
|
||||||
|
before_action :check_enabled_registrations, only: [:create]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
@ -16,6 +18,16 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
render json: @account, serializer: REST::AccountSerializer
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
token = AppSignUpService.new.call(doorkeeper_token.application, account_params)
|
||||||
|
response = Doorkeeper::OAuth::TokenResponse.new(token)
|
||||||
|
|
||||||
|
headers.merge!(response.headers)
|
||||||
|
|
||||||
|
self.response_body = Oj.dump(response.body)
|
||||||
|
self.status = response.status
|
||||||
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
||||||
|
|
||||||
@ -62,4 +74,12 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
gone if @account.suspended?
|
gone if @account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.permit(:username, :email, :password, :agreement, :locale)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_enabled_registrations
|
||||||
|
forbidden if single_user_mode? || !Setting.open_registrations
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,6 +6,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
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
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,8 @@ class Api::V1::CustomEmojisController < Api::BaseController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer
|
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
||||||
|
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
29
app/controllers/api/v1/polls/votes_controller.rb
Normal file
29
app/controllers/api/v1/polls/votes_controller.rb
Normal file
@ -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
|
13
app/controllers/api/v1/polls_controller.rb
Normal file
13
app/controllers/api/v1/polls_controller.rb
Normal file
@ -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
|
77
app/controllers/api/v1/scheduled_statuses_controller.rb
Normal file
77
app/controllers/api/v1/scheduled_statuses_controller.rb
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::ScheduledStatusesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
|
||||||
|
|
||||||
|
before_action :set_statuses, only: :index
|
||||||
|
before_action :set_status, except: :index
|
||||||
|
|
||||||
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @statuses, each_serializer: REST::ScheduledStatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @status, serializer: REST::ScheduledStatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@status.update!(scheduled_status_params)
|
||||||
|
render json: @status, serializer: REST::ScheduledStatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@status.destroy!
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_statuses
|
||||||
|
@statuses = current_account.scheduled_statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = current_account.scheduled_statuses.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def scheduled_status_params
|
||||||
|
params.permit(:scheduled_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.slice(:limit).permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @statuses.empty?
|
||||||
|
api_v1_scheduled_statuses_url pagination_params(min_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@statuses.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
end
|
@ -3,7 +3,7 @@
|
|||||||
class Api::V1::SearchController < Api::BaseController
|
class Api::V1::SearchController < Api::BaseController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
RESULTS_LIMIT = 5
|
RESULTS_LIMIT = 20
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
@ -11,30 +11,22 @@ class Api::V1::SearchController < Api::BaseController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search)
|
@search = Search.new(search_results)
|
||||||
render json: @search, serializer: REST::SearchSerializer
|
render json: @search, serializer: REST::SearchSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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
|
def search_results
|
||||||
SearchService.new.call(
|
SearchService.new.call(
|
||||||
params[:q],
|
params[:q],
|
||||||
RESULTS_LIMIT,
|
current_account,
|
||||||
truthy_param?(:resolve),
|
limit_param(RESULTS_LIMIT),
|
||||||
current_account
|
search_params.merge(resolve: truthy_param?(:resolve))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_params
|
||||||
|
params.permit(:type, :offset, :min_id, :max_id, :account_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -25,7 +25,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def paginated_statuses
|
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),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params[:max_id],
|
params[:max_id],
|
||||||
params[:since_id]
|
params[:since_id]
|
||||||
|
@ -45,16 +45,18 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@status = PostStatusService.new.call(current_user.account,
|
@status = PostStatusService.new.call(current_user.account,
|
||||||
status_params[:status],
|
text: status_params[:status],
|
||||||
status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
|
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
|
||||||
media_ids: status_params[:media_ids],
|
media_ids: status_params[:media_ids],
|
||||||
sensitive: status_params[:sensitive],
|
sensitive: status_params[:sensitive],
|
||||||
spoiler_text: status_params[:spoiler_text],
|
spoiler_text: status_params[:spoiler_text],
|
||||||
visibility: status_params[:visibility],
|
visibility: status_params[:visibility],
|
||||||
|
scheduled_at: status_params[:scheduled_at],
|
||||||
application: doorkeeper_token.application,
|
application: doorkeeper_token.application,
|
||||||
|
poll: status_params[:poll],
|
||||||
idempotency: request.headers['Idempotency-Key'])
|
idempotency: request.headers['Idempotency-Key'])
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@ -72,12 +74,25 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
@status = Status.find(params[:id])
|
@status = Status.find(params[:id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
# Reraise in order to get a 404 instead of a 403 error code
|
|
||||||
raise ActiveRecord::RecordNotFound
|
raise ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_params
|
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
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class Api::V2::SearchController < Api::V1::SearchController
|
class Api::V2::SearchController < Api::V1::SearchController
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search)
|
@search = Search.new(search_results)
|
||||||
render json: @search, serializer: REST::V2::SearchSerializer
|
render json: @search, serializer: REST::V2::SearchSerializer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,9 +6,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :set_user, only: [:finish_signup]
|
before_action :set_user, only: [:finish_signup]
|
||||||
|
|
||||||
# GET/PATCH /users/:id/finish_signup
|
|
||||||
def finish_signup
|
def finish_signup
|
||||||
return unless request.patch? && params[:user]
|
return unless request.patch? && params[:user]
|
||||||
|
|
||||||
if @user.update(user_params)
|
if @user.update(user_params)
|
||||||
@user.skip_reconfirmation!
|
@user.skip_reconfirmation!
|
||||||
bypass_sign_in(@user)
|
bypass_sign_in(@user)
|
||||||
@ -31,4 +31,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(:email)
|
params.require(:user).permit(:email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_confirmation_path_for(_resource_name, user)
|
||||||
|
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||||
|
user.created_by_application.redirect_uri
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -26,7 +26,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
|
|
||||||
resource.locale = I18n.locale
|
resource.locale = I18n.locale
|
||||||
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
||||||
|
resource.agreement = true
|
||||||
|
|
||||||
|
resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil?
|
||||||
resource.build_account if resource.account.nil?
|
resource.build_account if resource.account.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ module SignatureVerification
|
|||||||
.with_fallback { nil }
|
.with_fallback { nil }
|
||||||
.with_threshold(1)
|
.with_threshold(1)
|
||||||
.with_cool_off_time(5.minutes.seconds)
|
.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
|
account = account_stoplight.run
|
||||||
|
|
||||||
@ -59,24 +60,27 @@ module SignatureVerification
|
|||||||
signature = Base64.decode64(signature_params['signature'])
|
signature = Base64.decode64(signature_params['signature'])
|
||||||
compare_signed_string = build_signed_string(signature_params['headers'])
|
compare_signed_string = build_signed_string(signature_params['headers'])
|
||||||
|
|
||||||
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
|
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||||
@signed_request_account = account
|
|
||||||
@signed_request_account
|
account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
|
||||||
elsif account.possibly_stale?
|
.with_fallback { nil }
|
||||||
account = account.refresh!
|
.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?
|
||||||
|
|
||||||
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}"
|
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
|
||||||
@signed_request_account = nil
|
@signed_request_account = nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
|
|
||||||
@signed_request_account = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def request_body
|
def request_body
|
||||||
@request_body ||= request.raw_post
|
@request_body ||= request.raw_post
|
||||||
@ -84,6 +88,15 @@ module SignatureVerification
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def verify_signature(account, signature, compare_signed_string)
|
||||||
|
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
|
||||||
|
@signed_request_account = account
|
||||||
|
@signed_request_account
|
||||||
|
end
|
||||||
|
rescue OpenSSL::PKey::RSAError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def build_signed_string(signed_headers)
|
def build_signed_string(signed_headers)
|
||||||
signed_headers = 'date' if signed_headers.blank?
|
signed_headers = 'date' if signed_headers.blank?
|
||||||
|
|
||||||
@ -130,4 +143,9 @@ module SignatureVerification
|
|||||||
account
|
account
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_refresh_key(account)
|
||||||
|
return if account.local? || !account.activitypub?
|
||||||
|
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ class DirectoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_tags
|
def set_tags
|
||||||
@tags = Tag.discoverable.limit(30)
|
@tags = Tag.discoverable.limit(30).reject { |tag| tag.cached_sample_accounts.empty? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_accounts
|
def set_accounts
|
||||||
|
@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||||||
|
|
||||||
before_action :store_current_location
|
before_action :store_current_location
|
||||||
before_action :authenticate_resource_owner!
|
before_action :authenticate_resource_owner!
|
||||||
|
before_action :set_body_classes
|
||||||
|
|
||||||
include Localized
|
include Localized
|
||||||
|
|
||||||
@ -15,6 +16,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_body_classes
|
||||||
|
@body_classes = 'admin'
|
||||||
|
end
|
||||||
|
|
||||||
def store_current_location
|
def store_current_location
|
||||||
store_location_for(:user, request.url)
|
store_location_for(:user, request.url)
|
||||||
end
|
end
|
||||||
|
@ -5,6 +5,7 @@ class RemoteInteractionController < ApplicationController
|
|||||||
|
|
||||||
layout 'modal'
|
layout 'modal'
|
||||||
|
|
||||||
|
before_action :set_interaction_type
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
|
||||||
@ -45,4 +46,8 @@ class RemoteInteractionController < ApplicationController
|
|||||||
@body_classes = 'modal-layout'
|
@body_classes = 'modal-layout'
|
||||||
@hide_header = true
|
@hide_header = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_interaction_type
|
||||||
|
@interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -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
|
19
app/controllers/settings/exports/lists_controller.rb
Normal file
19
app/controllers/settings/exports/lists_controller.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
module Exports
|
||||||
|
class ListsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def export_data
|
||||||
|
@export.to_lists_csv
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
51
app/controllers/settings/featured_tags_controller.rb
Normal file
51
app/controllers/settings/featured_tags_controller.rb
Normal file
@ -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
|
@ -48,6 +48,7 @@ class Settings::PreferencesController < Settings::BaseController
|
|||||||
:setting_theme,
|
:setting_theme,
|
||||||
:setting_hide_network,
|
:setting_hide_network,
|
||||||
:setting_aggregate_reblogs,
|
:setting_aggregate_reblogs,
|
||||||
|
:setting_show_application,
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest report),
|
notification_emails: %i(follow follow_request reblog favourite mention digest report),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following)
|
||||||
)
|
)
|
||||||
|
@ -32,6 +32,6 @@ class Settings::ProfilesController < Settings::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = current_user.account
|
@account = current_account
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Settings::SessionsController < Settings::BaseController
|
class Settings::SessionsController < Settings::BaseController
|
||||||
|
before_action :authenticate_user!
|
||||||
before_action :set_session, only: :destroy
|
before_action :set_session, only: :destroy
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -18,6 +18,7 @@ class StatusesController < ApplicationController
|
|||||||
before_action :redirect_to_original, only: [:show]
|
before_action :redirect_to_original, only: [:show]
|
||||||
before_action :set_referrer_policy_header, only: [:show]
|
before_action :set_referrer_policy_header, only: [:show]
|
||||||
before_action :set_cache_headers
|
before_action :set_cache_headers
|
||||||
|
before_action :set_replies, only: [:replies]
|
||||||
|
|
||||||
content_security_policy only: :embed do |p|
|
content_security_policy only: :embed do |p|
|
||||||
p.frame_ancestors(false)
|
p.frame_ancestors(false)
|
||||||
@ -63,8 +64,37 @@ class StatusesController < ApplicationController
|
|||||||
render 'stream_entries/embed', layout: 'embedded'
|
render 'stream_entries/embed', layout: 'embedded'
|
||||||
end
|
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
|
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)
|
def create_descendant_thread(starting_depth, statuses)
|
||||||
depth = starting_depth + statuses.size
|
depth = starting_depth + statuses.size
|
||||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||||
@ -174,4 +204,27 @@ class StatusesController < ApplicationController
|
|||||||
return if @status.public_visibility? || @status.unlisted_visibility?
|
return if @status.public_visibility? || @status.unlisted_visibility?
|
||||||
response.headers['Referrer-Policy'] = 'origin'
|
response.headers['Referrer-Policy'] = 'origin'
|
||||||
end
|
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
|
end
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
class TagsController < ApplicationController
|
class TagsController < ApplicationController
|
||||||
PAGE_SIZE = 20
|
PAGE_SIZE = 20
|
||||||
|
|
||||||
|
layout 'public'
|
||||||
|
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
|
@ -6,8 +6,9 @@ module Admin::FilterHelper
|
|||||||
INVITE_FILTER = %i(available expired).freeze
|
INVITE_FILTER = %i(available expired).freeze
|
||||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||||
TAGS_FILTERS = %i(hidden).freeze
|
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)
|
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||||
new_url = filtered_url_for(link_to_params)
|
new_url = filtered_url_for(link_to_params)
|
||||||
|
@ -69,8 +69,12 @@ module ApplicationHelper
|
|||||||
tag(:meta, content: content, property: property)
|
tag(:meta, content: content, property: property)
|
||||||
end
|
end
|
||||||
|
|
||||||
def react_component(name, props = {})
|
def react_component(name, props = {}, &block)
|
||||||
|
if block.nil?
|
||||||
content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) })
|
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
|
end
|
||||||
|
|
||||||
def body_classes
|
def body_classes
|
||||||
|
@ -68,6 +68,7 @@ module JsonLdHelper
|
|||||||
return body_to_json(response.body_with_limit) if response.code == 200
|
return body_to_json(response.body_with_limit) if response.code == 200
|
||||||
end
|
end
|
||||||
# If request failed, retry without doing it on behalf of a user
|
# If request failed, retry without doing it on behalf of a user
|
||||||
|
return if on_behalf_of.nil?
|
||||||
build_request(uri).perform do |response|
|
build_request(uri).perform do |response|
|
||||||
response.code == 200 ? body_to_json(response.body_with_limit) : nil
|
response.code == 200 ? body_to_json(response.body_with_limit) : nil
|
||||||
end
|
end
|
||||||
|
@ -4,7 +4,7 @@ module SettingsHelper
|
|||||||
HUMAN_LOCALES = {
|
HUMAN_LOCALES = {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
ar: 'العربية',
|
ar: 'العربية',
|
||||||
ast: 'l\'asturianu',
|
ast: 'Asturianu',
|
||||||
bg: 'Български',
|
bg: 'Български',
|
||||||
ca: 'Català',
|
ca: 'Català',
|
||||||
co: 'Corsu',
|
co: 'Corsu',
|
||||||
@ -29,23 +29,29 @@ module SettingsHelper
|
|||||||
it: 'Italiano',
|
it: 'Italiano',
|
||||||
ja: '日本語',
|
ja: '日本語',
|
||||||
ka: 'ქართული',
|
ka: 'ქართული',
|
||||||
|
kk: 'Қазақша',
|
||||||
ko: '한국어',
|
ko: '한국어',
|
||||||
|
lt: 'Lietuvių',
|
||||||
|
lv: 'Latviešu',
|
||||||
|
ml: 'മലയാളം',
|
||||||
|
ms: 'Bahasa Melayu',
|
||||||
nl: 'Nederlands',
|
nl: 'Nederlands',
|
||||||
no: 'Norsk',
|
no: 'Norsk',
|
||||||
oc: 'Occitan',
|
oc: 'Occitan',
|
||||||
pl: 'Polszczyzna',
|
pl: 'Polski',
|
||||||
pt: 'Português',
|
pt: 'Português',
|
||||||
'pt-BR': 'Português do Brasil',
|
'pt-BR': 'Português do Brasil',
|
||||||
ro: 'Limba română',
|
ro: 'Română',
|
||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
sk: 'Slovenčina',
|
sk: 'Slovenčina',
|
||||||
sl: 'Slovenščina',
|
sl: 'Slovenščina',
|
||||||
|
sq: 'Shqip',
|
||||||
sr: 'Српски',
|
sr: 'Српски',
|
||||||
'sr-Latn': 'Srpski (latinica)',
|
'sr-Latn': 'Srpski (latinica)',
|
||||||
sv: 'Svenska',
|
sv: 'Svenska',
|
||||||
ta: 'தமிழ்',
|
ta: 'தமிழ்',
|
||||||
te: 'తెలుగు',
|
te: 'తెలుగు',
|
||||||
th: 'ภาษาไทย',
|
th: 'ไทย',
|
||||||
tr: 'Türkçe',
|
tr: 'Türkçe',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
zh: '中文',
|
zh: '中文',
|
||||||
|
@ -170,7 +170,7 @@ module StreamEntriesHelper
|
|||||||
when 'public'
|
when 'public'
|
||||||
fa_icon 'globe fw'
|
fa_icon 'globe fw'
|
||||||
when 'unlisted'
|
when 'unlisted'
|
||||||
fa_icon 'unlock-alt fw'
|
fa_icon 'unlock fw'
|
||||||
when 'private'
|
when 'private'
|
||||||
fa_icon 'lock fw'
|
fa_icon 'lock fw'
|
||||||
when 'direct'
|
when 'direct'
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 27 KiB |
@ -22,7 +22,7 @@ export function clearAlert() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function showAlert(title, message) {
|
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) {
|
||||||
return {
|
return {
|
||||||
type: ALERT_SHOW,
|
type: ALERT_SHOW,
|
||||||
title,
|
title,
|
||||||
@ -44,6 +44,6 @@ export function showAlertForError(error) {
|
|||||||
return showAlert(title, message);
|
return showAlert(title, message);
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return showAlert(messages.unexpectedTitle, messages.unexpectedMessage);
|
return showAlert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image';
|
|||||||
import { importFetchedAccounts } from './importer';
|
import { importFetchedAccounts } from './importer';
|
||||||
import { updateTimeline } from './timelines';
|
import { updateTimeline } from './timelines';
|
||||||
import { showAlertForError } from './alerts';
|
import { showAlertForError } from './alerts';
|
||||||
|
import { showAlert } from './alerts';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
let cancelFetchComposeSuggestionsAccounts;
|
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_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
||||||
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
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) {
|
export function changeCompose(text) {
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_CHANGE,
|
type: COMPOSE_CHANGE,
|
||||||
@ -130,6 +136,12 @@ export function submitCompose(routerHistory) {
|
|||||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||||
},
|
},
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
|
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
||||||
|
routerHistory.push('/timelines/direct');
|
||||||
|
} else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
|
||||||
|
routerHistory.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...response.data }));
|
||||||
|
|
||||||
@ -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') {
|
if (response.data.visibility !== 'direct') {
|
||||||
insertIfOnline('home');
|
insertIfOnline('home');
|
||||||
}
|
}
|
||||||
@ -184,22 +190,34 @@ export function submitComposeFail(error) {
|
|||||||
|
|
||||||
export function uploadCompose(files) {
|
export function uploadCompose(files) {
|
||||||
return function (dispatch, getState) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(uploadComposeRequest());
|
dispatch(uploadComposeRequest());
|
||||||
|
|
||||||
resizeImage(files[0]).then(file => {
|
for (const [i, f] of Array.from(files).entries()) {
|
||||||
|
if (media.size + i > 3) break;
|
||||||
|
|
||||||
|
resizeImage(f).then(file => {
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
|
||||||
return api(getState).post('/api/v1/media', data, {
|
return api(getState).post('/api/v1/media', data, {
|
||||||
onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)),
|
onUploadProgress: function({ loaded }){
|
||||||
|
progress[i] = loaded;
|
||||||
|
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||||
|
},
|
||||||
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
}).then(({ data }) => dispatch(uploadComposeSuccess(data)));
|
||||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function changeUploadCompose(id, params) {
|
export function changeUploadCompose(id, params) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
@ -41,13 +41,15 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
|
|||||||
params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']);
|
params.since_id = getState().getIn(['conversations', 'items', 0, 'last_status']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLoadingRecent = !!params.since_id;
|
||||||
|
|
||||||
api(getState).get('/api/v1/conversations', { params })
|
api(getState).get('/api/v1/conversations', { params })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
|
dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
|
||||||
dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x)));
|
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)));
|
.catch(err => dispatch(expandConversationsFail(err)));
|
||||||
};
|
};
|
||||||
@ -56,10 +58,11 @@ export const expandConversationsRequest = () => ({
|
|||||||
type: CONVERSATIONS_FETCH_REQUEST,
|
type: CONVERSATIONS_FETCH_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const expandConversationsSuccess = (conversations, next) => ({
|
export const expandConversationsSuccess = (conversations, next, isLoadingRecent) => ({
|
||||||
type: CONVERSATIONS_FETCH_SUCCESS,
|
type: CONVERSATIONS_FETCH_SUCCESS,
|
||||||
conversations,
|
conversations,
|
||||||
next,
|
next,
|
||||||
|
isLoadingRecent,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const expandConversationsFail = error => ({
|
export const expandConversationsFail = error => ({
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
// import { autoPlayGif } from '../../initial_state';
|
|
||||||
// import { putAccounts, putStatuses } from '../../storage/modifier';
|
|
||||||
import { normalizeAccount, normalizeStatus } from './normalizer';
|
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 ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
|
||||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||||
|
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||||
|
|
||||||
function pushUnique(array, object) {
|
function pushUnique(array, object) {
|
||||||
if (array.every(element => element.id !== object.id)) {
|
if (array.every(element => element.id !== object.id)) {
|
||||||
@ -29,6 +28,10 @@ export function importStatuses(statuses) {
|
|||||||
return { type: STATUSES_IMPORT, statuses };
|
return { type: STATUSES_IMPORT, statuses };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function importPolls(polls) {
|
||||||
|
return { type: POLLS_IMPORT, polls };
|
||||||
|
}
|
||||||
|
|
||||||
export function importFetchedAccount(account) {
|
export function importFetchedAccount(account) {
|
||||||
return importFetchedAccounts([account]);
|
return importFetchedAccounts([account]);
|
||||||
}
|
}
|
||||||
@ -45,7 +48,6 @@ export function importFetchedAccounts(accounts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
accounts.forEach(processAccount);
|
accounts.forEach(processAccount);
|
||||||
//putAccounts(normalAccounts, !autoPlayGif);
|
|
||||||
|
|
||||||
return importAccounts(normalAccounts);
|
return importAccounts(normalAccounts);
|
||||||
}
|
}
|
||||||
@ -58,6 +60,7 @@ export function importFetchedStatuses(statuses) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const accounts = [];
|
const accounts = [];
|
||||||
const normalStatuses = [];
|
const normalStatuses = [];
|
||||||
|
const polls = [];
|
||||||
|
|
||||||
function processStatus(status) {
|
function processStatus(status) {
|
||||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
|
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
|
||||||
@ -66,11 +69,15 @@ export function importFetchedStatuses(statuses) {
|
|||||||
if (status.reblog && status.reblog.id) {
|
if (status.reblog && status.reblog.id) {
|
||||||
processStatus(status.reblog);
|
processStatus(status.reblog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.poll && status.poll.id) {
|
||||||
|
pushUnique(polls, status.poll);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses.forEach(processStatus);
|
statuses.forEach(processStatus);
|
||||||
//putStatuses(normalStatuses);
|
|
||||||
|
|
||||||
|
dispatch(importPolls(polls));
|
||||||
dispatch(importFetchedAccounts(accounts));
|
dispatch(importFetchedAccounts(accounts));
|
||||||
dispatch(importStatuses(normalStatuses));
|
dispatch(importStatuses(normalStatuses));
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
normalStatus.reblog = status.reblog.id;
|
normalStatus.reblog = status.reblog.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.poll && status.poll.id) {
|
||||||
|
normalStatus.poll = status.poll.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Only calculate these values when status first encountered
|
// Only calculate these values when status first encountered
|
||||||
// Otherwise keep the ones already in the reducer
|
// Otherwise keep the ones already in the reducer
|
||||||
if (normalOldStatus) {
|
if (normalOldStatus) {
|
||||||
|
53
app/javascript/mastodon/actions/polls.js
Normal file
53
app/javascript/mastodon/actions/polls.js
Normal file
@ -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,
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import LinkHeader from 'http-link-header';
|
||||||
import ready from './ready';
|
import ready from './ready';
|
||||||
import LinkHeader from './link_header';
|
|
||||||
|
|
||||||
export const getLinks = response => {
|
export const getLinks = response => {
|
||||||
const value = response.headers.link;
|
const value = response.headers.link;
|
||||||
|
@ -4,5 +4,9 @@ export function start() {
|
|||||||
require('font-awesome/css/font-awesome.css');
|
require('font-awesome/css/font-awesome.css');
|
||||||
require.context('../images/', true);
|
require.context('../images/', true);
|
||||||
|
|
||||||
|
try {
|
||||||
Rails.start();
|
Rails.start();
|
||||||
|
} catch (e) {
|
||||||
|
// If called twice
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -68,10 +68,10 @@ class Account extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
{account.get('display_name')}
|
{account.get('display_name')}
|
||||||
{account.get('username')}
|
{account.get('username')}
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class Account extends ImmutablePureComponent {
|
|||||||
if (requested) {
|
if (requested) {
|
||||||
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
||||||
} else if (blocking) {
|
} else if (blocking) {
|
||||||
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||||
} else if (muting) {
|
} else if (muting) {
|
||||||
let hidingNotificationsButton;
|
let hidingNotificationsButton;
|
||||||
if (account.getIn(['relationship', 'muting_notifications'])) {
|
if (account.getIn(['relationship', 'muting_notifications'])) {
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={attachment.get('id')}>
|
<li key={attachment.get('id')}>
|
||||||
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
|
<a href={displayUrl} target='_blank' rel='noopener'><Icon id='link' /> {filename(displayUrl)}</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -36,7 +37,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='attachment-list'>
|
<div className='attachment-list'>
|
||||||
<div className='attachment-list__icon'>
|
<div className='attachment-list__icon'>
|
||||||
<i className='fa fa-link' />
|
<Icon id='link' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className='attachment-list__list'>
|
<ul className='attachment-list__list'>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class ColumnBackButton extends React.PureComponent {
|
export default class ColumnBackButton extends React.PureComponent {
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ export default class ColumnBackButton extends React.PureComponent {
|
|||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<button onClick={this.handleClick} className='column-back-button'>
|
<button onClick={this.handleClick} className='column-back-button'>
|
||||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ColumnBackButton from './column_back_button';
|
import ColumnBackButton from './column_back_button';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class ColumnBackButtonSlim extends ColumnBackButton {
|
export default class ColumnBackButtonSlim extends ColumnBackButton {
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton {
|
|||||||
return (
|
return (
|
||||||
<div className='column-back-button--slim'>
|
<div className='column-back-button--slim'>
|
||||||
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
|
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
|
||||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||||
@ -109,22 +110,22 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (multiColumn && pinned) {
|
if (multiColumn && pinned) {
|
||||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
|
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
|
||||||
|
|
||||||
moveButtons = (
|
moveButtons = (
|
||||||
<div key='move-buttons' className='column-header__setting-arrows'>
|
<div key='move-buttons' className='column-header__setting-arrows'>
|
||||||
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
|
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
|
||||||
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
|
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (multiColumn) {
|
} else if (multiColumn) {
|
||||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pinned && (multiColumn || showBackButton)) {
|
if (!pinned && (multiColumn || showBackButton)) {
|
||||||
backButton = (
|
backButton = (
|
||||||
<button onClick={this.handleBackClick} className='column-header__back-button'>
|
<button onClick={this.handleBackClick} className='column-header__back-button'>
|
||||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@ -140,7 +141,7 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (children || multiColumn) {
|
if (children || multiColumn) {
|
||||||
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
|
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasTitle = icon && title;
|
const hasTitle = icon && title;
|
||||||
@ -150,7 +151,7 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
<h1 className={buttonClassName}>
|
<h1 className={buttonClassName}>
|
||||||
{hasTitle && (
|
{hasTitle && (
|
||||||
<button onClick={this.handleTitleClick}>
|
<button onClick={this.handleTitleClick}>
|
||||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
<Icon id={icon} fixedWidth className='column-header__icon' />
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
@ -1,28 +1,46 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default class DisplayName extends React.PureComponent {
|
export default class DisplayName extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
others: ImmutablePropTypes.list,
|
others: ImmutablePropTypes.list,
|
||||||
|
localDomain: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, others } = this.props;
|
const { others, localDomain } = this.props;
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
|
||||||
|
|
||||||
let suffix;
|
let displayName, suffix, account;
|
||||||
|
|
||||||
if (others && others.size > 1) {
|
if (others && others.size > 1) {
|
||||||
suffix = `+${others.size}`;
|
displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
|
||||||
|
|
||||||
|
if (others.size - 2 > 0) {
|
||||||
|
suffix = `+${others.size - 2}`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
suffix = <span className='display-name__account'>@{account.get('acct')}</span>;
|
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 = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
|
||||||
|
suffix = <span className='display-name__account'>@{acct}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className='display-name'>
|
<span className='display-name'>
|
||||||
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {suffix}
|
{displayName} {suffix}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class Account extends ImmutablePureComponent {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className='domain__buttons'>
|
<div className='domain__buttons'>
|
||||||
<IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
|
<IconButton active icon='unlock' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import Permalink from './permalink';
|
||||||
import { shortNumberFormat } from '../utils/numbers';
|
import { shortNumberFormat } from '../utils/numbers';
|
||||||
|
|
||||||
const Hashtag = ({ hashtag }) => (
|
const Hashtag = ({ hashtag }) => (
|
||||||
<div className='trends__item'>
|
<div className='trends__item'>
|
||||||
<div className='trends__item__name'>
|
<div className='trends__item__name'>
|
||||||
<Link to={`/timelines/tag/${hashtag.get('name')}`}>
|
<Permalink href={hashtag.get('url')} to={`/timelines/tag/${hashtag.get('name')}`}>
|
||||||
#<span>{hashtag.get('name')}</span>
|
#<span>{hashtag.get('name')}</span>
|
||||||
</Link>
|
</Permalink>
|
||||||
|
|
||||||
<FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
|
<FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
|
||||||
</div>
|
</div>
|
||||||
|
21
app/javascript/mastodon/components/icon.js
Normal file
21
app/javascript/mastodon/components/icon.js
Normal file
@ -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 (
|
||||||
|
<i role='img' className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ import Motion from '../features/ui/util/optional_motion';
|
|||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class IconButton extends React.PureComponent {
|
export default class IconButton extends React.PureComponent {
|
||||||
|
|
||||||
@ -86,7 +87,7 @@ export default class IconButton extends React.PureComponent {
|
|||||||
style={style}
|
style={style}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
>
|
>
|
||||||
<i className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
<Icon id={icon} fixedWidth aria-hidden='true' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -104,7 +105,7 @@ export default class IconButton extends React.PureComponent {
|
|||||||
style={style}
|
style={style}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
>
|
>
|
||||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
<Icon id={icon} style={{ transform: `rotate(${rotate}deg)` }} fixedWidth aria-hidden='true' />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Motion>
|
</Motion>
|
||||||
|
@ -65,7 +65,7 @@ export default class IntersectionObserverArticle extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateStateAfterIntersection = (prevState) => {
|
updateStateAfterIntersection = (prevState) => {
|
||||||
if (prevState.isIntersecting && !this.entry.isIntersecting) {
|
if (prevState.isIntersecting !== false && !this.entry.isIntersecting) {
|
||||||
scheduleIdleTask(this.hideIfNotIntersecting);
|
scheduleIdleTask(this.hideIfNotIntersecting);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||||
@ -25,7 +26,7 @@ class LoadGap extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
|
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
|
||||||
<i className='fa fa-ellipsis-h' />
|
<Icon id='ellipsis-h' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,10 @@ class Item extends React.PureComponent {
|
|||||||
const { index, onClick } = this.props;
|
const { index, onClick } = this.props;
|
||||||
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
if (this.hoverToPlay()) {
|
||||||
|
e.target.pause();
|
||||||
|
e.target.currentTime = 0;
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick(index);
|
onClick(index);
|
||||||
}
|
}
|
||||||
@ -231,6 +235,8 @@ class MediaGallery extends React.PureComponent {
|
|||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
defaultWidth: PropTypes.number,
|
||||||
|
cacheWidth: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -239,6 +245,7 @@ class MediaGallery extends React.PureComponent {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
|
visible: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
|
||||||
|
width: this.props.defaultWidth,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@ -258,6 +265,7 @@ class MediaGallery extends React.PureComponent {
|
|||||||
handleRef = (node) => {
|
handleRef = (node) => {
|
||||||
if (node /*&& this.isStandaloneEligible()*/) {
|
if (node /*&& this.isStandaloneEligible()*/) {
|
||||||
// offsetWidth triggers a layout, so only calculate when we need to
|
// offsetWidth triggers a layout, so only calculate when we need to
|
||||||
|
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
|
||||||
this.setState({
|
this.setState({
|
||||||
width: node.offsetWidth,
|
width: node.offsetWidth,
|
||||||
});
|
});
|
||||||
@ -270,8 +278,10 @@ class MediaGallery extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, intl, sensitive, height } = this.props;
|
const { media, intl, sensitive, height, defaultWidth } = this.props;
|
||||||
const { width, visible } = this.state;
|
const { visible } = this.state;
|
||||||
|
|
||||||
|
const width = this.state.width || defaultWidth;
|
||||||
|
|
||||||
let children;
|
let children;
|
||||||
|
|
||||||
|
154
app/javascript/mastodon/components/poll.js
Normal file
154
app/javascript/mastodon/components/poll.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { vote, fetchPoll } from 'mastodon/actions/polls';
|
||||||
|
import Motion from 'mastodon/features/ui/util/optional_motion';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
moments: { id: 'time_remaining.moments', defaultMessage: 'Moments remaining' },
|
||||||
|
seconds: { id: 'time_remaining.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} left' },
|
||||||
|
minutes: { id: 'time_remaining.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} left' },
|
||||||
|
hours: { id: 'time_remaining.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} left' },
|
||||||
|
days: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const SECOND = 1000;
|
||||||
|
const MINUTE = 1000 * 60;
|
||||||
|
const HOUR = 1000 * 60 * 60;
|
||||||
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
|
const timeRemainingString = (intl, date, now) => {
|
||||||
|
const delta = date.getTime() - now;
|
||||||
|
|
||||||
|
let relativeTime;
|
||||||
|
|
||||||
|
if (delta < 10 * SECOND) {
|
||||||
|
relativeTime = intl.formatMessage(messages.moments);
|
||||||
|
} else if (delta < MINUTE) {
|
||||||
|
relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) });
|
||||||
|
} else if (delta < HOUR) {
|
||||||
|
relativeTime = intl.formatMessage(messages.minutes, { number: Math.floor(delta / MINUTE) });
|
||||||
|
} else if (delta < DAY) {
|
||||||
|
relativeTime = intl.formatMessage(messages.hours, { number: Math.floor(delta / HOUR) });
|
||||||
|
} else {
|
||||||
|
relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return relativeTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
poll: ImmutablePropTypes.map,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selected: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOptionChange = e => {
|
||||||
|
const { target: { value } } = e;
|
||||||
|
|
||||||
|
if (this.props.poll.get('multiple')) {
|
||||||
|
const tmp = { ...this.state.selected };
|
||||||
|
if (tmp[value]) {
|
||||||
|
delete tmp[value];
|
||||||
|
} else {
|
||||||
|
tmp[value] = true;
|
||||||
|
}
|
||||||
|
this.setState({ selected: tmp });
|
||||||
|
} else {
|
||||||
|
const tmp = {};
|
||||||
|
tmp[value] = true;
|
||||||
|
this.setState({ selected: tmp });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleVote = () => {
|
||||||
|
if (this.props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRefresh = () => {
|
||||||
|
if (this.props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(fetchPoll(this.props.poll.get('id')));
|
||||||
|
};
|
||||||
|
|
||||||
|
renderOption (option, optionIndex) {
|
||||||
|
const { poll, disabled } = this.props;
|
||||||
|
const percent = (option.get('votes_count') / poll.get('votes_count')) * 100;
|
||||||
|
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
|
||||||
|
const active = !!this.state.selected[`${optionIndex}`];
|
||||||
|
const showResults = poll.get('voted') || poll.get('expired');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={option.get('title')}>
|
||||||
|
{showResults && (
|
||||||
|
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
|
||||||
|
{({ width }) =>
|
||||||
|
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
|
||||||
|
}
|
||||||
|
</Motion>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<label className={classNames('poll__text', { selectable: !showResults })}>
|
||||||
|
<input
|
||||||
|
name='vote-options'
|
||||||
|
type={poll.get('multiple') ? 'checkbox' : 'radio'}
|
||||||
|
value={optionIndex}
|
||||||
|
checked={active}
|
||||||
|
onChange={this.handleOptionChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
|
||||||
|
{showResults && <span className='poll__number'>{Math.floor(percent)}%</span>}
|
||||||
|
|
||||||
|
{option.get('title')}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { poll, intl } = this.props;
|
||||||
|
|
||||||
|
if (!poll) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeRemaining = timeRemainingString(intl, new Date(poll.get('expires_at')), intl.now());
|
||||||
|
const showResults = poll.get('voted') || poll.get('expired');
|
||||||
|
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='poll'>
|
||||||
|
<ul>
|
||||||
|
{poll.get('options').map((option, i) => this.renderOption(option, i))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className='poll__footer'>
|
||||||
|
{!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
|
||||||
|
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
|
||||||
|
<FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} /> · {timeRemaining}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -40,6 +40,7 @@ export default class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
fullscreen: null,
|
fullscreen: null,
|
||||||
|
cachedMediaWidth: 250, // Default media/card width using default Mastodon theme
|
||||||
};
|
};
|
||||||
|
|
||||||
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
||||||
@ -130,6 +131,20 @@ export default class ScrollableList extends PureComponent {
|
|||||||
this.handleScroll();
|
this.handleScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScrollPosition = () => {
|
||||||
|
if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
|
||||||
|
return { height: this.node.scrollHeight, top: this.node.scrollTop };
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScrollBottom = (snapshot) => {
|
||||||
|
const newScrollTop = this.node.scrollHeight - snapshot;
|
||||||
|
|
||||||
|
this.setScrollTop(newScrollTop);
|
||||||
|
}
|
||||||
|
|
||||||
getSnapshotBeforeUpdate (prevProps) {
|
getSnapshotBeforeUpdate (prevProps) {
|
||||||
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
||||||
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
||||||
@ -150,6 +165,12 @@ export default class ScrollableList extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheMediaWidth = (width) => {
|
||||||
|
if (width && this.state.cachedMediaWidth !== width) {
|
||||||
|
this.setState({ cachedMediaWidth: width });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.clearMouseIdleTimer();
|
this.clearMouseIdleTimer();
|
||||||
this.detachScrollListener();
|
this.detachScrollListener();
|
||||||
@ -239,7 +260,12 @@ export default class ScrollableList extends PureComponent {
|
|||||||
intersectionObserverWrapper={this.intersectionObserverWrapper}
|
intersectionObserverWrapper={this.intersectionObserverWrapper}
|
||||||
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
|
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
|
||||||
>
|
>
|
||||||
{child}
|
{React.cloneElement(child, {
|
||||||
|
getScrollPosition: this.getScrollPosition,
|
||||||
|
updateScrollBottom: this.updateScrollBottom,
|
||||||
|
cachedMediaWidth: this.state.cachedMediaWidth,
|
||||||
|
cacheMediaWidth: this.cacheMediaWidth,
|
||||||
|
})}
|
||||||
</IntersectionObserverArticleContainer>
|
</IntersectionObserverArticleContainer>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||||||
import { MediaGallery, Video } from '../features/ui/util/async-components';
|
import { MediaGallery, Video } from '../features/ui/util/async-components';
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import PollContainer from 'mastodon/containers/poll_container';
|
||||||
|
|
||||||
// We use the component (and not the container) since we do not want
|
// We use the component (and not the container) since we do not want
|
||||||
// to use the progress bar to show download progress
|
// to use the progress bar to show download progress
|
||||||
@ -68,6 +70,10 @@ class Status extends ImmutablePureComponent {
|
|||||||
onMoveUp: PropTypes.func,
|
onMoveUp: PropTypes.func,
|
||||||
onMoveDown: PropTypes.func,
|
onMoveDown: PropTypes.func,
|
||||||
showThread: PropTypes.bool,
|
showThread: PropTypes.bool,
|
||||||
|
getScrollPosition: PropTypes.func,
|
||||||
|
updateScrollBottom: PropTypes.func,
|
||||||
|
cacheMediaWidth: PropTypes.func,
|
||||||
|
cachedMediaWidth: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
@ -77,7 +83,44 @@ class Status extends ImmutablePureComponent {
|
|||||||
'account',
|
'account',
|
||||||
'muted',
|
'muted',
|
||||||
'hidden',
|
'hidden',
|
||||||
]
|
];
|
||||||
|
|
||||||
|
// Track height changes we know about to compensate scrolling
|
||||||
|
componentDidMount () {
|
||||||
|
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
|
||||||
|
}
|
||||||
|
|
||||||
|
getSnapshotBeforeUpdate () {
|
||||||
|
if (this.props.getScrollPosition) {
|
||||||
|
return this.props.getScrollPosition();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compensate height changes
|
||||||
|
componentDidUpdate (prevProps, prevState, snapshot) {
|
||||||
|
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
|
||||||
|
if (doShowCard && !this.didShowCard) {
|
||||||
|
this.didShowCard = true;
|
||||||
|
if (snapshot !== null && this.props.updateScrollBottom) {
|
||||||
|
if (this.node && this.node.offsetTop < snapshot.top) {
|
||||||
|
this.props.updateScrollBottom(snapshot.height - snapshot.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.node && this.props.getScrollPosition) {
|
||||||
|
const position = this.props.getScrollPosition();
|
||||||
|
if (position !== null && this.node.offsetTop < position.top) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.props.updateScrollBottom(position.height - position.top);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
@ -165,6 +208,10 @@ class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let media = null;
|
let media = null;
|
||||||
let statusAvatar, prepend, rebloggedByText;
|
let statusAvatar, prepend, rebloggedByText;
|
||||||
@ -179,7 +226,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div ref={this.handleRef}>
|
||||||
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||||
{status.get('content')}
|
{status.get('content')}
|
||||||
</div>
|
</div>
|
||||||
@ -194,7 +241,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={minHandlers}>
|
<HotKeys handlers={minHandlers}>
|
||||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0'>
|
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
|
||||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
|
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
@ -204,7 +251,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
if (featured) {
|
if (featured) {
|
||||||
prepend = (
|
prepend = (
|
||||||
<div className='status__prepend'>
|
<div className='status__prepend'>
|
||||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
|
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div>
|
||||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
|
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -213,7 +260,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
prepend = (
|
prepend = (
|
||||||
<div className='status__prepend'>
|
<div className='status__prepend'>
|
||||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
|
||||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -224,7 +271,9 @@ class Status extends ImmutablePureComponent {
|
|||||||
status = status.get('reblog');
|
status = status.get('reblog');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('poll')) {
|
||||||
|
media = <PollContainer pollId={status.get('poll')} />;
|
||||||
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||||
media = (
|
media = (
|
||||||
<AttachmentList
|
<AttachmentList
|
||||||
@ -242,11 +291,12 @@ class Status extends ImmutablePureComponent {
|
|||||||
preview={video.get('preview_url')}
|
preview={video.get('preview_url')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
width={239}
|
width={this.props.cachedMediaWidth}
|
||||||
height={110}
|
height={110}
|
||||||
inline
|
inline
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
onOpenVideo={this.handleOpenVideo}
|
onOpenVideo={this.handleOpenVideo}
|
||||||
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Bundle>
|
</Bundle>
|
||||||
@ -254,7 +304,16 @@ class Status extends ImmutablePureComponent {
|
|||||||
} else {
|
} else {
|
||||||
media = (
|
media = (
|
||||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||||
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
|
{Component => (
|
||||||
|
<Component
|
||||||
|
media={status.get('media_attachments')}
|
||||||
|
sensitive={status.get('sensitive')}
|
||||||
|
height={110}
|
||||||
|
onOpenMedia={this.props.onOpenMedia}
|
||||||
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Bundle>
|
</Bundle>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -264,11 +323,13 @@ class Status extends ImmutablePureComponent {
|
|||||||
onOpenMedia={this.props.onOpenMedia}
|
onOpenMedia={this.props.onOpenMedia}
|
||||||
card={status.get('card')}
|
card={status.get('card')}
|
||||||
compact
|
compact
|
||||||
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherAccounts) {
|
if (otherAccounts && otherAccounts.size > 0) {
|
||||||
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
|
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
|
||||||
} else if (account === undefined || account === null) {
|
} else if (account === undefined || account === null) {
|
||||||
statusAvatar = <Avatar account={status.get('account')} size={48} />;
|
statusAvatar = <Avatar account={status.get('account')} size={48} />;
|
||||||
@ -290,7 +351,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}>
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))} ref={this.handleRef}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
||||||
|
@ -5,7 +5,7 @@ import IconButton from './icon_button';
|
|||||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { me } from '../initial_state';
|
import { me, isStaff } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
@ -30,6 +30,9 @@ const messages = defineMessages({
|
|||||||
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||||
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||||
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
|
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
||||||
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const obfuscatedCount = count => {
|
const obfuscatedCount = count => {
|
||||||
@ -75,7 +78,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
]
|
]
|
||||||
|
|
||||||
handleReplyClick = () => {
|
handleReplyClick = () => {
|
||||||
|
if (me) {
|
||||||
this.props.onReply(this.props.status, this.context.router.history);
|
this.props.onReply(this.props.status, this.context.router.history);
|
||||||
|
} else {
|
||||||
|
this._openInteractionDialog('reply');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShareClick = () => {
|
handleShareClick = () => {
|
||||||
@ -88,11 +95,23 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFavouriteClick = () => {
|
handleFavouriteClick = () => {
|
||||||
|
if (me) {
|
||||||
this.props.onFavourite(this.props.status);
|
this.props.onFavourite(this.props.status);
|
||||||
|
} else {
|
||||||
|
this._openInteractionDialog('favourite');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReblogClick = (e) => {
|
handleReblogClick = e => {
|
||||||
|
if (me) {
|
||||||
this.props.onReblog(this.props.status, e);
|
this.props.onReblog(this.props.status, e);
|
||||||
|
} else {
|
||||||
|
this._openInteractionDialog('reblog');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_openInteractionDialog = type => {
|
||||||
|
window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
@ -139,6 +158,25 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
this.props.onMuteConversation(this.props.status);
|
this.props.onMuteConversation(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCopy = () => {
|
||||||
|
const url = this.props.status.get('url');
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
|
||||||
|
textarea.textContent = url;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
|
try {
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, intl, withDismiss } = this.props;
|
const { status, intl, withDismiss } = this.props;
|
||||||
|
|
||||||
@ -148,11 +186,13 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
let replyIcon;
|
||||||
let replyTitle;
|
let replyTitle;
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||||
|
|
||||||
if (publicStatus) {
|
if (publicStatus) {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +221,12 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
|
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
|
||||||
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||||
|
|
||||||
|
if (isStaff) {
|
||||||
|
menu.push(null);
|
||||||
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||||
|
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('visibility') === 'direct') {
|
if (status.get('visibility') === 'direct') {
|
||||||
@ -190,8 +236,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('in_reply_to_id', null) === null) {
|
if (status.get('in_reply_to_id', null) === null) {
|
||||||
|
replyIcon = 'reply';
|
||||||
replyTitle = intl.formatMessage(messages.reply);
|
replyTitle = intl.formatMessage(messages.reply);
|
||||||
} else {
|
} else {
|
||||||
|
replyIcon = 'reply-all';
|
||||||
replyTitle = intl.formatMessage(messages.replyAll);
|
replyTitle = intl.formatMessage(messages.replyAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,9 +249,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='status__action-bar'>
|
<div className='status__action-bar'>
|
||||||
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon='reply' onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
|
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
|
||||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
<IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||||
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
||||||
<div className='status__action-bar-dropdown'>
|
<div className='status__action-bar-dropdown'>
|
||||||
|
@ -5,6 +5,7 @@ import { isRtl } from '../rtl';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Permalink from './permalink';
|
import Permalink from './permalink';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ export default class StatusContent extends React.PureComponent {
|
|||||||
|
|
||||||
const readMoreButton = (
|
const readMoreButton = (
|
||||||
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
|
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
|
||||||
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
|
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
|
|||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
import Compose from '../features/standalone/compose';
|
import Compose from '../features/standalone/compose';
|
||||||
import initialState from '../initial_state';
|
import initialState from '../initial_state';
|
||||||
|
import { fetchCustomEmojis } from '../actions/custom_emojis';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
@ -17,6 +18,8 @@ if (initialState) {
|
|||||||
store.dispatch(hydrateStore(initialState));
|
store.dispatch(hydrateStore(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.dispatch(fetchCustomEmojis());
|
||||||
|
|
||||||
export default class TimelineContainer extends React.PureComponent {
|
export default class TimelineContainer extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -10,8 +10,7 @@ const messages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, { }) => ({
|
const mapStateToProps = () => ({});
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { getLocale } from '../locales';
|
|||||||
import MediaGallery from '../components/media_gallery';
|
import MediaGallery from '../components/media_gallery';
|
||||||
import Video from '../features/video';
|
import Video from '../features/video';
|
||||||
import Card from '../features/status/components/card';
|
import Card from '../features/status/components/card';
|
||||||
|
import Poll from 'mastodon/components/poll';
|
||||||
import ModalRoot from '../components/modal_root';
|
import ModalRoot from '../components/modal_root';
|
||||||
import MediaModal from '../features/ui/components/media_modal';
|
import MediaModal from '../features/ui/components/media_modal';
|
||||||
import { List as ImmutableList, fromJS } from 'immutable';
|
import { List as ImmutableList, fromJS } from 'immutable';
|
||||||
@ -13,7 +14,7 @@ import { List as ImmutableList, fromJS } from 'immutable';
|
|||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
|
||||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card };
|
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll };
|
||||||
|
|
||||||
export default class MediaContainer extends PureComponent {
|
export default class MediaContainer extends PureComponent {
|
||||||
|
|
||||||
@ -54,11 +55,12 @@ export default class MediaContainer extends PureComponent {
|
|||||||
{[].map.call(components, (component, i) => {
|
{[].map.call(components, (component, i) => {
|
||||||
const componentName = component.getAttribute('data-component');
|
const componentName = component.getAttribute('data-component');
|
||||||
const Component = MEDIA_COMPONENTS[componentName];
|
const Component = MEDIA_COMPONENTS[componentName];
|
||||||
const { media, card, ...props } = JSON.parse(component.getAttribute('data-props'));
|
const { media, card, poll, ...props } = JSON.parse(component.getAttribute('data-props'));
|
||||||
|
|
||||||
Object.assign(props, {
|
Object.assign(props, {
|
||||||
...(media ? { media: fromJS(media) } : {}),
|
...(media ? { media: fromJS(media) } : {}),
|
||||||
...(card ? { card: fromJS(card) } : {}),
|
...(card ? { card: fromJS(card) } : {}),
|
||||||
|
...(poll ? { poll: fromJS(poll) } : {}),
|
||||||
|
|
||||||
...(componentName === 'Video' ? {
|
...(componentName === 'Video' ? {
|
||||||
onOpenVideo: this.handleOpenVideo,
|
onOpenVideo: this.handleOpenVideo,
|
||||||
|
8
app/javascript/mastodon/containers/poll_container.js
Normal file
8
app/javascript/mastodon/containers/poll_container.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Poll from 'mastodon/components/poll';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { pollId }) => ({
|
||||||
|
poll: state.getIn(['polls', pollId]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Poll);
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { me } from '../../../initial_state';
|
import { me, isStaff } from '../../../initial_state';
|
||||||
import { shortNumberFormat } from '../../../utils/numbers';
|
import { shortNumberFormat } from '../../../utils/numbers';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -35,6 +35,7 @@ const messages = defineMessages({
|
|||||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||||
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
@ -151,6 +152,11 @@ class ActionBar extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (account.get('id') !== me && isStaff) {
|
||||||
|
menu.push(null);
|
||||||
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{extraInfo}
|
{extraInfo}
|
||||||
|
@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { autoPlayGif, me } from '../../../initial_state';
|
import { autoPlayGif, me } from '../../../initial_state';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
@ -132,7 +133,7 @@ class Header extends ImmutablePureComponent {
|
|||||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||||
actionBtn = (
|
actionBtn = (
|
||||||
<div className='account--action-button'>
|
<div className='account--action-button'>
|
||||||
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
|
<IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -149,7 +150,7 @@ class Header extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (account.get('locked')) {
|
if (account.get('locked')) {
|
||||||
lockedIcon = <i className='fa fa-lock' title={intl.formatMessage(messages.account_locked)} />;
|
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = { __html: account.get('note_emojified') };
|
const content = { __html: account.get('note_emojified') };
|
||||||
@ -176,7 +177,7 @@ class Header extends ImmutablePureComponent {
|
|||||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||||
|
|
||||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
||||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><i className='fa fa-check verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
))}
|
))}
|
||||||
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Permalink from '../../../components/permalink';
|
import Permalink from '../../../components/permalink';
|
||||||
import { displayMedia } from '../../../initial_state';
|
import { displayMedia } from '../../../initial_state';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class MediaItem extends ImmutablePureComponent {
|
export default class MediaItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
|||||||
} else {
|
} else {
|
||||||
icon = (
|
icon = (
|
||||||
<span className='account-gallery__item__icons'>
|
<span className='account-gallery__item__icons'>
|
||||||
<i className='fa fa-eye-slash' />
|
<Icon id='eye-slash' />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import AvatarOverlay from '../../../components/avatar_overlay';
|
import AvatarOverlay from '../../../components/avatar_overlay';
|
||||||
import DisplayName from '../../../components/display_name';
|
import DisplayName from '../../../components/display_name';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class MovedNote extends ImmutablePureComponent {
|
export default class MovedNote extends ImmutablePureComponent {
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ export default class MovedNote extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='account__moved-note'>
|
<div className='account__moved-note'>
|
||||||
<div className='account__moved-note__message'>
|
<div className='account__moved-note__message'>
|
||||||
<div className='account__moved-note__icon-wrapper'><i className='fa fa-fw fa-suitcase account__moved-note__icon' /></div>
|
<div className='account__moved-note__icon-wrapper'><Icon id='suitcase' className='account__moved-note__icon' fixedWidth /></div>
|
||||||
<FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
|
<FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
|
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
|
||||||
|
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@ -29,6 +30,7 @@ class Blocks extends ImmutablePureComponent {
|
|||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
shouldUpdateScroll: PropTypes.func,
|
shouldUpdateScroll: PropTypes.func,
|
||||||
accountIds: ImmutablePropTypes.list,
|
accountIds: ImmutablePropTypes.list,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ class Blocks extends ImmutablePureComponent {
|
|||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, accountIds, shouldUpdateScroll } = this.props;
|
const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
@ -59,6 +61,7 @@ class Blocks extends ImmutablePureComponent {
|
|||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='blocks'
|
scrollKey='blocks'
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
>
|
>
|
||||||
|
@ -17,6 +17,7 @@ import { isMobile } from '../../../is_mobile';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import { countableText } from '../util/counter';
|
import { countableText } from '../util/counter';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||||
|
|
||||||
@ -165,7 +166,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
let publishText = '';
|
let publishText = '';
|
||||||
|
|
||||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||||
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
|
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||||
} else {
|
} else {
|
||||||
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||||
}
|
}
|
||||||
@ -179,7 +180,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
||||||
<label>
|
<label>
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
|
||||||
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
|
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} tabIndex={this.props.spoiler ? 0 : -1} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import Motion from '../../ui/util/optional_motion';
|
|||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import detectPassiveEvents from 'detect-passive-events';
|
import detectPassiveEvents from 'detect-passive-events';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
@ -132,7 +133,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
|||||||
{items.map(item => (
|
{items.map(item => (
|
||||||
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
|
||||||
<div className='privacy-dropdown__option__icon'>
|
<div className='privacy-dropdown__option__icon'>
|
||||||
<i className={`fa fa-fw fa-${item.icon}`} />
|
<Icon id={item.icon} fixedWidth />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='privacy-dropdown__option__content'>
|
<div className='privacy-dropdown__option__content'>
|
||||||
@ -214,7 +215,7 @@ class PrivacyDropdown extends React.PureComponent {
|
|||||||
|
|
||||||
this.options = [
|
this.options = [
|
||||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||||
{ icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||||
];
|
];
|
||||||
|
@ -5,6 +5,7 @@ import Overlay from 'react-overlays/lib/Overlay';
|
|||||||
import Motion from '../../ui/util/optional_motion';
|
import Motion from '../../ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import { searchEnabled } from '../../../initial_state';
|
import { searchEnabled } from '../../../initial_state';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
||||||
@ -116,8 +117,8 @@ class Search extends React.PureComponent {
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
||||||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
|
<Icon id='search' className={hasValue ? '' : 'active'} />
|
||||||
<i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
|
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
|
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
|
||||||
|
@ -6,6 +6,7 @@ import AccountContainer from '../../../containers/account_container';
|
|||||||
import StatusContainer from '../../../containers/status_container';
|
import StatusContainer from '../../../containers/status_container';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Hashtag from '../../../components/hashtag';
|
import Hashtag from '../../../components/hashtag';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
||||||
@ -34,7 +35,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||||||
<div className='search-results'>
|
<div className='search-results'>
|
||||||
<div className='trends'>
|
<div className='trends'>
|
||||||
<div className='trends__header'>
|
<div className='trends__header'>
|
||||||
<i className='fa fa-user-plus fa-fw' />
|
<Icon id='user-plus' fixedWidth />
|
||||||
<FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
|
<FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||||||
count += results.get('accounts').size;
|
count += results.get('accounts').size;
|
||||||
accounts = (
|
accounts = (
|
||||||
<div className='search-results__section'>
|
<div className='search-results__section'>
|
||||||
<h5><i className='fa fa-fw fa-users' /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
|
<h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
|
||||||
|
|
||||||
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
|
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +71,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||||||
count += results.get('statuses').size;
|
count += results.get('statuses').size;
|
||||||
statuses = (
|
statuses = (
|
||||||
<div className='search-results__section'>
|
<div className='search-results__section'>
|
||||||
<h5><i className='fa fa-fw fa-quote-right' /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||||
|
|
||||||
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
|
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +82,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||||||
count += results.get('hashtags').size;
|
count += results.get('hashtags').size;
|
||||||
hashtags = (
|
hashtags = (
|
||||||
<div className='search-results__section'>
|
<div className='search-results__section'>
|
||||||
<h5><i className='fa fa-fw fa-hashtag' /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
|
<h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
|
||||||
|
|
||||||
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
|
{results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
|
||||||
</div>
|
</div>
|
||||||
@ -91,7 +92,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='search-results'>
|
<div className='search-results'>
|
||||||
<div className='search-results__header'>
|
<div className='search-results__header'>
|
||||||
<i className='fa fa-search fa-fw' />
|
<Icon id='search' fixedWidth />
|
||||||
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
|
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import spring from 'react-motion/lib/spring';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
|
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
|
||||||
@ -99,17 +100,16 @@ class Upload extends ImmutablePureComponent {
|
|||||||
{({ scale }) => (
|
{({ scale }) => (
|
||||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||||
<div className={classNames('compose-form__upload__actions', { active })}>
|
<div className={classNames('compose-form__upload__actions', { active })}>
|
||||||
<button className='icon-button' onClick={this.handleUndoClick}><i className='fa fa-times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
||||||
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><i className='fa fa-crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
|
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classNames('compose-form__upload-description', { active })}>
|
<div className={classNames('compose-form__upload-description', { active })}>
|
||||||
<label>
|
<label>
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
|
||||||
|
|
||||||
<input
|
<textarea
|
||||||
placeholder={intl.formatMessage(messages.description)}
|
placeholder={intl.formatMessage(messages.description)}
|
||||||
type='text'
|
|
||||||
value={description}
|
value={description}
|
||||||
maxLength={420}
|
maxLength={420}
|
||||||
onFocus={this.handleInputFocus}
|
onFocus={this.handleInputFocus}
|
||||||
|
@ -63,7 +63,7 @@ class UploadButton extends ImmutablePureComponent {
|
|||||||
key={resetFileKey}
|
key={resetFileKey}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
type='file'
|
type='file'
|
||||||
multiple={false}
|
multiple
|
||||||
accept={acceptContentTypes.toArray().join(',')}
|
accept={acceptContentTypes.toArray().join(',')}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import Motion from '../../ui/util/optional_motion';
|
import Motion from '../../ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
export default class UploadProgress extends React.PureComponent {
|
export default class UploadProgress extends React.PureComponent {
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ export default class UploadProgress extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='upload-progress'>
|
<div className='upload-progress'>
|
||||||
<div className='upload-progress__icon'>
|
<div className='upload-progress__icon'>
|
||||||
<i className='fa fa-upload' />
|
<Icon id='upload' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='upload-progress__message'>
|
<div className='upload-progress__message'>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user