From 1840a352f5cdf676284c1d8d32bef7776fbb536d Mon Sep 17 00:00:00 2001 From: abcang Date: Sat, 17 Jun 2017 05:38:26 +0900 Subject: [PATCH 001/382] Fix ogp url (#3802) --- app/views/about/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index fd21c2a7f..d15b04163 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -10,7 +10,7 @@ %meta{ property: 'og:type', content: 'website' }/ %meta{ property: 'og:title', content: site_hostname }/ %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.presence || t('about.about_mastodon')) }/ - %meta{ property: 'og:image', content: asset_pack_path('mastodon_small.jpg') }/ + %meta{ property: 'og:image', content: asset_pack_path('mastodon_small.jpg', protocol: :request) }/ %meta{ property: 'og:image:width', content: '400' }/ %meta{ property: 'og:image:height', content: '400' }/ %meta{ property: 'twitter:card', content: 'summary' }/ From 2356580cee9d485d6e33d7b51ea2d83632cf791e Mon Sep 17 00:00:00 2001 From: unarist Date: Sat, 17 Jun 2017 08:14:42 +0900 Subject: [PATCH 002/382] Use cross-env for npm run on Windows (#3789) --- package.json | 9 +++++---- yarn.lock | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 462772e18..a6aa1ce1c 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "license": "AGPL-3.0", "scripts": { "postversion": "git push --tags", - "build:development": "NODE_ENV=development yarn webpack -- --config config/webpack/development.js", - "build:production": "NODE_ENV=production yarn webpack -- --config config/webpack/production.js", + "build:development": "cross-env NODE_ENV=development yarn webpack -- --config config/webpack/development.js", + "build:production": "cross-env NODE_ENV=production yarn webpack -- --config config/webpack/production.js", "manage:translations": "node ./config/webpack/translationRunner.js", "start": "rimraf ./tmp/streaming && babel ./streaming/index.js --out-dir ./tmp && node ./tmp/streaming/index.js", - "storybook": "NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook", + "storybook": "cross-env NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook", "test": "npm run test:lint && npm run test:mocha", "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ storybook/ streaming/", - "test:mocha": "NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js", + "test:mocha": "cross-env NODE_ENV=test mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.js", "postinstall": "npm rebuild node-sass" }, "repository": { @@ -40,6 +40,7 @@ "babel-preset-react": "^6.24.1", "classnames": "^2.2.5", "compression-webpack-plugin": "^0.4.0", + "cross-env": "^5.0.1", "css-loader": "^0.28.4", "dotenv": "^4.0.0", "emojione": "^2.2.7", diff --git a/yarn.lock b/yarn.lock index c11b7718a..960c2ca02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1894,6 +1894,13 @@ create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.5 loose-envify "^1.3.1" object-assign "^4.1.1" +cross-env@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.0.1.tgz#ff4e72ea43b47da2486b43a7f2043b2609e44913" + dependencies: + cross-spawn "^5.1.0" + is-windows "^1.0.0" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -1908,6 +1915,14 @@ cross-spawn@^4.0.0: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -3668,6 +3683,10 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +is-windows@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -6201,6 +6220,16 @@ shallowequal@0.2.x, shallowequal@^0.2.2: dependencies: lodash.keys "^3.1.2" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + shelljs@^0.7.5, shelljs@^0.7.7: version "0.7.8" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" From 74d10b9b9d60426ed44e5c6d361db7a44996236a Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Sat, 17 Jun 2017 08:15:00 +0900 Subject: [PATCH 003/382] Spec UserMailer (#3757) --- spec/mailers/user_mailer_spec.rb | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 spec/mailers/user_mailer_spec.rb diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 000000000..1f6d44015 --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe UserMailer, type: :mailer do + let(:receiver) { Fabricate(:user) } + + shared_examples 'localized subject' do |*args, **kwrest| + it 'renders subject localized for the locale of the receiver' do + locale = I18n.available_locales.sample + receiver.update!(locale: locale) + expect(mail.subject).to eq I18n.t(*args, kwrest.merge(locale: locale)) + end + + it 'renders subject localized for the default locale if the locale of the receiver is unavailable' do + receiver.update!(locale: nil) + expect(mail.subject).to eq I18n.t(*args, kwrest.merge(locale: I18n.default_locale)) + end + end + + describe 'confirmation_instructions' do + let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } + + it 'renders confirmation instructions' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include receiver.email + expect(mail.body.encoded).to include 'spec' + expect(mail.body.encoded).to include Rails.configuration.x.local_domain + end + + include_examples 'localized subject', + 'devise.mailer.confirmation_instructions.subject', + instance: Rails.configuration.x.local_domain + end + + describe 'reset_password_instructions' do + let(:mail) { UserMailer.reset_password_instructions(receiver, 'spec') } + + it 'renders reset password instructions' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include receiver.email + expect(mail.body.encoded).to include 'spec' + end + + include_examples 'localized subject', + 'devise.mailer.reset_password_instructions.subject' + end + + describe 'password_change' do + let(:mail) { UserMailer.password_change(receiver) } + + it 'renders password change notification' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include receiver.email + end + + include_examples 'localized subject', + 'devise.mailer.password_change.subject' + end +end From 8fd931dc126d0f90417a6614bd21bb945543e4f4 Mon Sep 17 00:00:00 2001 From: Quent-in Date: Sat, 17 Jun 2017 20:21:25 +0200 Subject: [PATCH 004/382] l10n update for account deletion (#3820) * l10n update for acount deletion * l10n little change * l10n little changes Less passive voice, more natural. * Update oc.yml Correction, " added --- config/locales/devise.oc.yml | 2 +- config/locales/oc.yml | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml index 2913078ff..77740f230 100644 --- a/config/locales/devise.oc.yml +++ b/config/locales/devise.oc.yml @@ -35,7 +35,7 @@ oc: updated_not_active: Vòstre senhal es ben estat cambiat. registrations: destroyed: Adiu ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu. - signed_up: Benvengut ! Sètz ben marcat al malhum. + signed_up: La benvenguda ! Sètz ben marcat al malhum. signed_up_but_inactive: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara validat. signed_up_but_locked: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara blocat. signed_up_but_unconfirmed: Un messatge amb un ligam de confirmacion es estat enviat a vòstra adreça de corrièl. Clicatz sul ligam per activar vòstre compte. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables. diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 709f6ddb4..3770c0671 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -195,6 +195,8 @@ oc: invalid_url: L’URL donada es invalida auth: change_password: Cambiar lo senhal + delete_account: Suprimir lo compte + delete_account_html: Se volètz suprimir vòstre compte, podètz o far aquí. Vos demandarem que confirmetz. didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ? forgot_password: Senhal oblidat ? login: Se connectar @@ -281,6 +283,14 @@ oc: x_minutes: Fa %{count} minutas x_months: Fa %{count} meses x_seconds: Fa %{count} segondas + deletes: + bad_password_msg: Ben ensajat pirata ! Senhal incorrècte + confirm_password: Picatz vòstre senhal acutal per verificar vòstra identitat + description_html: Aquò suprimirà definitivament e sens possibilitat de retorn lo contengut de vòstre compte e lo desactivarà. Lo nom d’utilizaire serà gardat per evitar una futura impostura. + proceed: Delete account + success_msg: Your account was successfully deleted + warning_html: La supression del contengut d’aquesta instància es sola assegurada. Lo contengut fòrça partejat daissarà problablament de traças. Los servidors fòra-linha e los que vos sègon pas mai auràn pas la mesa a jorn de lor basa de donada. + warning_title: Disponibilitat del contengut difusat errors: '404': La pagina que recercatz existís pas. '410': La pagina que cercatz existís pas mai. @@ -342,13 +352,13 @@ oc: subject: "%{name} a mes vòstre estatut en favorit" follow: body: "%{name} vos sèc ara !" - subject: "%{name} es a vos sègre ara" + subject: "%{name} vos sèc ara" follow_request: - body: "%{name} a demandar a vos sègre" + body: "%{name} a demandat a vos sègre" subject: 'Demanda d’abonament : %{name}' mention: - body: 'Sètz estat mencionat per %{name} dins :' - subject: Sètz estat mencionat per %{name} + body: "%{name} vos a mencionat dins :" + subject: "%{name} vos a mencionat" reblog: body: "%{name} a tornat partejar vòstre estatut :" subject: "%{name} a tornat partejar vòstre estatut" @@ -364,6 +374,7 @@ oc: settings: authorized_apps: Aplicacions autorizadas back: Tornar a Mastodon + delete: Supression de compte edit_profile: Modificar lo perfil export: Export donadas followers: Seguidors autorizats @@ -383,7 +394,7 @@ oc: unlisted: Pas listat unlisted_long: Tot lo mond pòt veire mai serà pas visible sul flux public stream_entries: - click_to_show: Clic per afichar + click_to_show: Clicatz per afichar reblogged: a partejat sensitive_content: Contengut sensible time: From 94d0e012dea89058b9c059636fb6d42f6565e534 Mon Sep 17 00:00:00 2001 From: nightpool Date: Sat, 17 Jun 2017 14:26:05 -0400 Subject: [PATCH 005/382] Whitelist allowed classes for federated statuses (#3810) * Whitelist allowed classes for federated statuses Allowed classes are currently: - Any microformats class (h/p/u/dt/e-*) - the classes mention, hashtag, ellipses and invisible. this last one is somewhat suspect, but Mastodon currently uses it to render hidden link text. resolved #3790 * Fix code style --- app/lib/sanitize_config.rb | 21 ++++++++++++++++++++- spec/lib/formatter_spec.rb | 8 ++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb index 9cf9b3db0..f09288fcd 100644 --- a/app/lib/sanitize_config.rb +++ b/app/lib/sanitize_config.rb @@ -4,6 +4,21 @@ class Sanitize module Config HTTP_PROTOCOLS ||= ['http', 'https', :relative].freeze + CLASS_WHITELIST_TRANSFORMER = lambda do |env| + node = env[:node] + class_list = node['class']&.split(' ') + + return unless class_list + + class_list.keep_if do |e| + return true if e =~ /^(h|p|u|dt|e)-/ # microformats classes + return true if e =~ /^(mention|hashtag)$/ # semantic classes + return true if e =~ /^(ellipsis|invisible)$/ # link formatting classes + end + + node['class'] = class_list.join(' ') + end + MASTODON_STRICT ||= freeze_config( elements: %w(p br span a), @@ -21,7 +36,11 @@ class Sanitize protocols: { 'a' => { 'href' => HTTP_PROTOCOLS }, - } + }, + + transformers: [ + CLASS_WHITELIST_TRANSFORMER, + ] ) MASTODON_OEMBED ||= freeze_config merge( diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index cc32f7fd6..dfe1d8b8f 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -204,6 +204,14 @@ RSpec.describe Formatter do is_expected.to_not include '' end end + + context 'contains malicious classes' do + let(:text) { 'Show more' } + + it 'strips malicious classes' do + is_expected.to_not include 'status__content__spoiler-link' + end + end end describe '#plaintext' do From 53e42bf91ef2c705168f8d04b9374c349ec14074 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 18 Jun 2017 09:57:09 +0900 Subject: [PATCH 006/382] Upgrade Webpacker to version 2.0 (#3729) --- .gitignore | 1 + .travis.yml | 2 +- Gemfile | 2 +- Gemfile.lock | 4 +- bin/webpack | 25 ++-- bin/webpack-dev-server | 30 +++-- bin/yarn | 11 -- config/webpack/configuration.js | 35 +++-- config/webpack/development.js | 16 +++ config/webpack/development.server.js | 20 --- config/webpack/development.server.yml | 17 --- config/webpack/loaders/sass.js | 3 +- config/webpack/production.js | 15 ++- config/webpack/shared.js | 32 +++-- config/{webpack/paths.yml => webpacker.yml} | 17 ++- package.json | 5 +- yarn.lock | 134 +++++++++++++++++++- 17 files changed, 241 insertions(+), 128 deletions(-) delete mode 100755 bin/yarn delete mode 100644 config/webpack/development.server.js delete mode 100644 config/webpack/development.server.yml rename config/{webpack/paths.yml => webpacker.yml} (61%) diff --git a/.gitignore b/.gitignore index 31743fccf..38ebc934f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ coverage public/system public/assets public/packs +public/packs-test .env .env.production node_modules/ diff --git a/.travis.yml b/.travis.yml index a855aa31c..8bef9c4ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ cache: directories: - node_modules - public/assets - - public/packs + - public/packs-test dist: trusty sudo: false diff --git a/Gemfile b/Gemfile index b014ba03c..77fffe7a6 100644 --- a/Gemfile +++ b/Gemfile @@ -59,7 +59,7 @@ gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'statsd-instrument', '~> 2.1' gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2017' -gem 'webpacker', '~> 1.2' +gem 'webpacker', '~> 2.0' group :development, :test do gem 'fabrication', '~> 2.16' diff --git a/Gemfile.lock b/Gemfile.lock index ef7e40376..00ce84556 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -461,7 +461,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (1.2) + webpacker (2.0) activesupport (>= 4.2) multi_json (~> 1.2) railties (>= 4.2) @@ -558,7 +558,7 @@ DEPENDENCIES tzinfo-data (~> 1.2017) uglifier (~> 3.2) webmock (~> 3.0) - webpacker (~> 1.2) + webpacker (~> 2.0) RUBY VERSION ruby 2.4.1p111 diff --git a/bin/webpack b/bin/webpack index a871ce77b..867550eb8 100755 --- a/bin/webpack +++ b/bin/webpack @@ -5,29 +5,24 @@ require "shellwords" require "yaml" ENV["RAILS_ENV"] ||= "development" -RAILS_ENV = ENV["RAILS_ENV"] +RAILS_ENV = ENV["RAILS_ENV"] ENV["NODE_ENV"] ||= RAILS_ENV -NODE_ENV = ENV["NODE_ENV"] +NODE_ENV = ENV["NODE_ENV"] -APP_PATH = File.expand_path("../", __dir__) -CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") +APP_PATH = File.expand_path("../", __dir__) +NODE_MODULES_PATH = File.join(APP_PATH, "node_modules") +WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js") -begin - paths = YAML.load(File.read(CONFIG_PATH))[NODE_ENV] - - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, paths["node_modules"]) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, paths["config"]) -rescue Errno::ENOENT, NoMethodError - puts "Configuration not found in config/webpack/paths.yml" +unless File.exist?(WEBPACK_CONFIG) + puts "Webpack configuration not found." puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end -WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack" -WEBPACK_CONFIG = "#{WEBPACK_CONFIG_PATH}/#{NODE_ENV}.js" +newenv = { "NODE_PATH" => NODE_MODULES_PATH.shellescape } +cmdline = ["yarn", "run", "webpack", "--", "--config", WEBPACK_CONFIG] + ARGV Dir.chdir(APP_PATH) do - exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --config #{WEBPACK_CONFIG}" \ - " #{ARGV.join(" ")}" + exec newenv, *cmdline end diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server index c2a61ff5e..a867f2c01 100755 --- a/bin/webpack-dev-server +++ b/bin/webpack-dev-server @@ -10,24 +10,34 @@ RAILS_ENV = ENV["RAILS_ENV"] ENV["NODE_ENV"] ||= RAILS_ENV NODE_ENV = ENV["NODE_ENV"] -APP_PATH = File.expand_path("../", __dir__) -CONFIG_PATH = File.join(APP_PATH, "config/webpack/paths.yml") +APP_PATH = File.expand_path("../", __dir__) +CONFIG_FILE = File.join(APP_PATH, "config/webpacker.yml") +NODE_MODULES_PATH = File.join(APP_PATH, "node_modules") +WEBPACK_CONFIG = File.join(APP_PATH, "config/webpack/development.js") + +def args(key) + index = ARGV.index(key) + index ? ARGV[index + 1] : nil +end begin - paths = YAML.load(File.read(CONFIG_PATH))[NODE_ENV] + dev_server = YAML.load_file(CONFIG_FILE)["development"]["dev_server"] - NODE_MODULES_PATH = File.join(APP_PATH.shellescape, paths["node_modules"]) - WEBPACK_CONFIG_PATH = File.join(APP_PATH.shellescape, paths["config"]) + DEV_SERVER_HOST = "http#{"s" if args('--https') || dev_server["https"]}://#{args('--host') || dev_server["host"]}:#{args('--port') || dev_server["port"]}" - WEBPACK_BIN = "#{NODE_MODULES_PATH}/.bin/webpack-dev-server" - DEV_SERVER_CONFIG = "#{WEBPACK_CONFIG_PATH}/development.server.js" rescue Errno::ENOENT, NoMethodError - puts "Configuration not found in config/webpacker/paths.yml." + puts "Webpack dev_server configuration not found in #{CONFIG_FILE}." puts "Please run bundle exec rails webpacker:install to install webpacker" exit! end +newenv = { + "NODE_PATH" => NODE_MODULES_PATH.shellescape, + "ASSET_HOST" => DEV_SERVER_HOST.shellescape +}.freeze + +cmdline = ["yarn", "run", "webpack-dev-server", "--", "--progress", "--color", "--config", WEBPACK_CONFIG] + ARGV + Dir.chdir(APP_PATH) do - exec "NODE_PATH=#{NODE_MODULES_PATH} #{WEBPACK_BIN} --progress --color " \ - "--config #{DEV_SERVER_CONFIG} #{ARGV.join(" ")}" + exec newenv, *cmdline end diff --git a/bin/yarn b/bin/yarn deleted file mode 100755 index c2bacef83..000000000 --- a/bin/yarn +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env ruby -VENDOR_PATH = File.expand_path('..', __dir__) -Dir.chdir(VENDOR_PATH) do - begin - exec "yarnpkg #{ARGV.join(" ")}" - rescue Errno::ENOENT - $stderr.puts "Yarn executable was not detected in the system." - $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" - exit 1 - end -end diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js index 2a54080cf..6ef484c3a 100644 --- a/config/webpack/configuration.js +++ b/config/webpack/configuration.js @@ -1,26 +1,35 @@ -// Common configuration for webpacker loaded from config/webpack/paths.yml +// Common configuration for webpacker loaded from config/webpacker.yml const { join, resolve } = require('path'); const { env } = require('process'); const { safeLoad } = require('js-yaml'); const { readFileSync } = require('fs'); -const configPath = resolve('config', 'webpack'); +const configPath = resolve('config', 'webpacker.yml'); const loadersDir = join(__dirname, 'loaders'); -const paths = safeLoad(readFileSync(join(configPath, 'paths.yml'), 'utf8'))[env.NODE_ENV || 'development']; -const devServer = safeLoad(readFileSync(join(configPath, 'development.server.yml'), 'utf8'))[env.NODE_ENV || 'development']; +const settings = safeLoad(readFileSync(configPath), 'utf8')[env.NODE_ENV]; -// Compute public path based on environment and CDN_HOST in production -const ifHasCDN = env.CDN_HOST !== undefined && env.NODE_ENV === 'production'; -const devServerUrl = `http://${env.LOCAL_DOMAIN || devServer.host}:${devServer.port}/${paths.entry}/`; -const publicUrl = ifHasCDN ? `${env.CDN_HOST}/${paths.entry}/` : `/${paths.entry}/`; -const publicPath = env.NODE_ENV !== 'production' ? devServerUrl : publicUrl; +function removeOuterSlashes(string) { + return string.replace(/^\/*/, '').replace(/\/*$/, ''); +} + +function formatPublicPath(host = '', path = '') { + let formattedHost = removeOuterSlashes(host); + if (formattedHost && !/^http/i.test(formattedHost)) { + formattedHost = `//${formattedHost}`; + } + const formattedPath = removeOuterSlashes(path); + return `${formattedHost}/${formattedPath}/`; +} + +const output = { + path: resolve('public', settings.public_output_path), + publicPath: formatPublicPath(env.ASSET_HOST, settings.public_output_path), +}; module.exports = { - devServer, + settings, env, - paths, loadersDir, - publicUrl, - publicPath, + output, }; diff --git a/config/webpack/development.js b/config/webpack/development.js index f6235f42e..7fce7e3f9 100644 --- a/config/webpack/development.js +++ b/config/webpack/development.js @@ -2,6 +2,7 @@ const merge = require('webpack-merge'); const sharedConfig = require('./shared.js'); +const { settings, output } = require('./configuration.js'); module.exports = merge(sharedConfig, { devtool: 'cheap-module-eval-source-map', @@ -13,4 +14,19 @@ module.exports = merge(sharedConfig, { output: { pathinfo: true, }, + + devServer: { + clientLogLevel: 'none', + https: settings.dev_server.https, + host: settings.dev_server.host, + port: settings.dev_server.port, + contentBase: output.path, + publicPath: output.publicPath, + compress: true, + headers: { 'Access-Control-Allow-Origin': '*' }, + historyApiFallback: true, + watchOptions: { + ignored: /node_modules/, + }, + }, }); diff --git a/config/webpack/development.server.js b/config/webpack/development.server.js deleted file mode 100644 index 8e8a399e8..000000000 --- a/config/webpack/development.server.js +++ /dev/null @@ -1,20 +0,0 @@ -// Note: You must restart bin/webpack-dev-server for changes to take effect - -const { resolve } = require('path'); -const { env } = require('process'); -const merge = require('webpack-merge'); -const devConfig = require('./development.js'); -const { devServer, publicPath, paths } = require('./configuration.js'); - -module.exports = merge(devConfig, { - devServer: { - host: env.LOCAL_DOMAIN ? '0.0.0.0' : devServer.host, - port: devServer.port, - headers: { 'Access-Control-Allow-Origin': '*' }, - compress: true, - historyApiFallback: true, - contentBase: resolve(paths.output, paths.entry), - publicPath, - disableHostCheck: true, - }, -}); diff --git a/config/webpack/development.server.yml b/config/webpack/development.server.yml deleted file mode 100644 index ee588a888..000000000 --- a/config/webpack/development.server.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Note: You must restart bin/webpack-dev-server for changes to take effect - -default: &default - enabled: true - host: localhost - port: 8080 - -development: - <<: *default - -test: - <<: *default - enabled: false - -production: - <<: *default - enabled: false diff --git a/config/webpack/loaders/sass.js b/config/webpack/loaders/sass.js index 24abf1685..88d94c684 100644 --- a/config/webpack/loaders/sass.js +++ b/config/webpack/loaders/sass.js @@ -7,7 +7,8 @@ module.exports = { fallback: 'style-loader', use: [ { loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } }, - 'postcss-loader', + { loader: 'postcss-loader', options: { sourceMap: true } }, + 'resolve-url-loader', 'sass-loader', ], }), diff --git a/config/webpack/production.js b/config/webpack/production.js index 755464061..303fca81b 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -7,26 +7,27 @@ const sharedConfig = require('./shared.js'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = merge(sharedConfig, { - - devtool: 'source-map', // separate sourcemap file, suitable for production - output: { filename: '[name]-[chunkhash].js' }, + devtool: 'source-map', // separate sourcemap file, suitable for production + stats: 'normal', plugins: [ new webpack.optimize.UglifyJsPlugin({ - compress: true, + sourceMap: true, mangle: true, + compress: { + warnings: false, + }, + output: { comments: false, }, - - sourceMap: true, }), new CompressionPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', - test: /\.(js|css|svg|eot|ttf|woff|woff2)$/, + test: /\.(js|css|html|json|ico|svg|eot|otf|ttf)$/, }), new BundleAnalyzerPlugin({ // generates report.html and stats.json analyzerMode: 'static', diff --git a/config/webpack/shared.js b/config/webpack/shared.js index de57ecb68..4d865b816 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -7,21 +7,22 @@ const { sync } = require('glob'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const extname = require('path-complete-extname'); -const { env, paths, publicPath, loadersDir } = require('./configuration.js'); +const { env, settings, output, loadersDir } = require('./configuration.js'); const localePackPaths = require('./generateLocalePacks'); -const extensionGlob = `**/*{${paths.extensions.join(',')}}*`; -const packPaths = sync(join(paths.source, paths.entry, extensionGlob)); -const entryPacks = [].concat(packPaths).concat(localePackPaths).filter(path => path !== join(paths.source, paths.entry, 'custom.js')); +const extensionGlob = `**/*{${settings.extensions.join(',')}}*`; +const entryPath = join(settings.source_path, settings.source_entry_path); +const packPaths = sync(join(entryPath, extensionGlob)); +const entryPacks = [...packPaths, ...localePackPaths].filter(path => path !== join(entryPath, 'custom.js')); -const customApplicationStyle = resolve(join(paths.source, 'styles/custom.scss')); -const originalApplicationStyle = resolve(join(paths.source, 'styles/application.scss')); +const customApplicationStyle = resolve(join(settings.source_path, 'styles/custom.scss')); +const originalApplicationStyle = resolve(join(settings.source_path, 'styles/application.scss')); module.exports = { entry: entryPacks.reduce( (map, entry) => { const localMap = map; - let namespace = relative(join(paths.source, paths.entry), dirname(entry)); + let namespace = relative(join(entryPath), dirname(entry)); if (namespace === join('..', '..', '..', 'tmp', 'packs')) { namespace = ''; // generated by generateLocalePacks.js } @@ -33,8 +34,8 @@ module.exports = { output: { filename: '[name].js', chunkFilename: '[name]-[chunkhash].js', - path: resolve(paths.output, paths.entry), - publicPath, + path: output.path, + publicPath: output.publicPath, }, module: { @@ -44,7 +45,10 @@ module.exports = { plugins: [ new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))), new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'), - new ManifestPlugin({ fileName: paths.manifest, publicPath, writeToFileEmit: true }), + new ManifestPlugin({ + publicPath: output.publicPath, + writeToFileEmit: true, + }), new webpack.optimize.CommonsChunkPlugin({ name: 'common', minChunks: (module, count) => { @@ -67,15 +71,15 @@ module.exports = { 'mastodon-application-style': existsSync(customApplicationStyle) ? customApplicationStyle : originalApplicationStyle, }, - extensions: paths.extensions, + extensions: settings.extensions, modules: [ - resolve(paths.source), - resolve(paths.node_modules), + resolve(settings.source_path), + 'node_modules', ], }, resolveLoader: { - modules: [paths.node_modules], + modules: ['node_modules'], }, node: { diff --git a/config/webpack/paths.yml b/config/webpacker.yml similarity index 61% rename from config/webpack/paths.yml rename to config/webpacker.yml index fdae76141..c1cd6e93b 100644 --- a/config/webpack/paths.yml +++ b/config/webpacker.yml @@ -1,12 +1,9 @@ # Note: You must restart bin/webpack-dev-server for changes to take effect default: &default - config: config/webpack - entry: packs - output: public - manifest: manifest.json - node_modules: node_modules - source: app/javascript + source_path: app/javascript + source_entry_path: packs + public_output_path: packs extensions: - .js - .sass @@ -21,9 +18,15 @@ default: &default development: <<: *default + dev_server: + host: 0.0.0.0 + port: 8080 + https: false + test: <<: *default - manifest: manifest-test.json + + public_output_path: packs-test production: <<: *default diff --git a/package.json b/package.json index a6aa1ce1c..0abea3235 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "license": "AGPL-3.0", "scripts": { "postversion": "git push --tags", - "build:development": "cross-env NODE_ENV=development yarn webpack -- --config config/webpack/development.js", - "build:production": "cross-env NODE_ENV=production yarn webpack -- --config config/webpack/production.js", + "build:development": "cross-env RAILS_ENV=development ASSET_HOST=http://0.0.0.0:8080 ./bin/webpack", + "build:production": "cross-env RAILS_ENV=production ./bin/webpack", "manage:translations": "node ./config/webpack/translationRunner.js", "start": "rimraf ./tmp/streaming && babel ./streaming/index.js --out-dir ./tmp && node ./tmp/streaming/index.js", "storybook": "cross-env NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook", @@ -97,6 +97,7 @@ "redux-thunk": "^2.2.0", "requestidlecallback": "^0.3.0", "reselect": "^3.0.1", + "resolve-url-loader": "^2.0.2", "rimraf": "^2.6.1", "sass-loader": "^6.0.5", "stringz": "^0.2.1", diff --git a/yarn.lock b/yarn.lock index 960c2ca02..5532efa6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -163,6 +163,18 @@ acorn@^5.0.0, acorn@^5.0.1, acorn@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" +adjust-sourcemap-loader@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.1.0.tgz#412d92404eb61e4113635012cba53a33d008e0e2" + dependencies: + assert "^1.3.0" + camelcase "^1.2.1" + loader-utils "^1.0.2" + lodash.assign "^4.0.1" + lodash.defaults "^3.1.2" + object-path "^0.9.2" + regex-parser "^2.2.1" + airbnb-js-shims@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-1.1.1.tgz#27224f0030f244e6570442ed1020772c1434aec2" @@ -342,7 +354,7 @@ assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" -assert@^1.1.1: +assert@^1.1.1, assert@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" dependencies: @@ -386,6 +398,10 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +atob@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" + autoprefixer@^6.3.1: version "6.7.7" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" @@ -1488,7 +1504,7 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" -camelcase@^1.0.2: +camelcase@^1.0.2, camelcase@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" @@ -1500,7 +1516,7 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camelcase@^4.1.0: +camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -1823,7 +1839,11 @@ content-type@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" -convert-source-map@^1.1.0: +convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" + +convert-source-map@^1.1.0, convert-source-map@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -2007,6 +2027,15 @@ css-what@2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" +css@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" + dependencies: + inherits "^2.0.1" + source-map "^0.1.38" + source-map-resolve "^0.3.0" + urix "^0.1.0" + cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" @@ -3914,7 +3943,7 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.x: +loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.x: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -3948,6 +3977,18 @@ lodash._basecreate@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + +lodash._createassigner@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" + dependencies: + lodash._bindcallback "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash.restparam "^3.0.0" + lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" @@ -3956,7 +3997,15 @@ lodash._isiterateecall@^3.0.0: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" -lodash.assign@^4.2.0: +lodash.assign@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa" + dependencies: + lodash._baseassign "^3.0.0" + lodash._createassigner "^3.0.0" + lodash.keys "^3.0.0" + +lodash.assign@^4.0.1, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -3984,7 +4033,14 @@ lodash.create@3.1.1: lodash._basecreate "^3.0.0" lodash._isiterateecall "^3.0.0" -lodash.defaults@^4.0.1: +lodash.defaults@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c" + dependencies: + lodash.assign "^3.0.0" + lodash.restparam "^3.0.0" + +lodash.defaults@^4.0.0, lodash.defaults@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -4044,6 +4100,10 @@ lodash.reject@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" @@ -4516,6 +4576,10 @@ object-keys@^1.0.10, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-path@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.9.2.tgz#0fd9a74fc5fad1ae3968b586bda5c632bd6c05a5" + object.assign@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" @@ -5897,6 +5961,10 @@ regex-cache@^0.4.2: is-equal-shallow "^0.1.3" is-primitive "^2.0.0" +regex-parser@^2.2.1: + version "2.2.7" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.7.tgz#bd090e09181849acc45457e765f7be2a63f50ef1" + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -6017,6 +6085,24 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve-url-loader@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.2.tgz#c465e97ea0a4791f3961f766cea775ff2e3ceb8c" + dependencies: + adjust-sourcemap-loader "^1.1.0" + camelcase "^4.0.0" + convert-source-map "^1.1.1" + loader-utils "^1.0.0" + lodash.defaults "^4.0.0" + rework "^1.0.1" + rework-visit "^1.0.0" + source-map "^0.5.6" + urix "^0.1.0" + +resolve-url@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + resolve@^1.1.6, resolve@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" @@ -6030,6 +6116,17 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" +rework-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" + +rework@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + rgb@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5" @@ -6315,12 +6412,31 @@ source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-map-resolve@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" + dependencies: + atob "~1.1.0" + resolve-url "~0.2.1" + source-map-url "~0.3.0" + urix "~0.1.0" + source-map-support@^0.4.2: version "0.4.15" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" dependencies: source-map "^0.5.6" +source-map-url@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" + +source-map@^0.1.38: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + source-map@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" @@ -6780,6 +6896,10 @@ unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +urix@^0.1.0, urix@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + url-loader@^0.5.8: version "0.5.8" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.8.tgz#b9183b1801e0f847718673673040bc9dc1c715c5" From 1d9f9352a66e24f21efef4e61fa4f997219bbea3 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 17 Jun 2017 17:57:41 -0700 Subject: [PATCH 007/382] handle resize in a debounce() (#3834) --- app/javascript/mastodon/features/ui/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index fe775b434..9f31b5443 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -13,8 +13,6 @@ import { refreshNotifications } from '../../actions/notifications'; import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; -const noOp = () => false; - class UI extends React.PureComponent { static propTypes = { @@ -27,9 +25,11 @@ class UI extends React.PureComponent { draggingOver: false, }; - handleResize = () => { + handleResize = debounce(() => { this.setState({ width: window.innerWidth }); - } + }, 500, { + trailing: true, + }); handleDragEnter = (e) => { e.preventDefault(); From 1f2abd8d672288939232cbe1e2ce063f3ab4f0b4 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 17 Jun 2017 17:59:29 -0700 Subject: [PATCH 008/382] Fix jittery scrolling for Chromium browsers (#3776) (#3832) --- .../mastodon/components/status_list.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index b5a853589..b73a73d1b 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -6,6 +6,7 @@ import StatusContainer from '../containers/status_container'; import LoadMore from './load_more'; import ImmutablePureComponent from 'react-immutable-pure-component'; import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; +import { debounce } from 'lodash'; class StatusList extends ImmutablePureComponent { @@ -29,7 +30,7 @@ class StatusList extends ImmutablePureComponent { intersectionObserverWrapper = new IntersectionObserverWrapper(); - handleScroll = (e) => { + handleScroll = debounce((e) => { const { scrollTop, scrollHeight, clientHeight } = e.target; const offset = scrollHeight - scrollTop - clientHeight; this._oldScrollPosition = scrollHeight - scrollTop; @@ -41,7 +42,9 @@ class StatusList extends ImmutablePureComponent { } else if (this.props.onScroll) { this.props.onScroll(); } - } + }, 200, { + trailing: true, + }); componentDidMount () { this.attachScrollListener(); @@ -49,8 +52,16 @@ class StatusList extends ImmutablePureComponent { } componentDidUpdate (prevProps) { - if ((prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition) && this.node.scrollTop > 0) { - this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; + // Reset the scroll position when a new toot comes in in order not to + // jerk the scrollbar around if you're already scrolled down the page. + if (prevProps.statusIds.size < this.props.statusIds.size && + prevProps.statusIds.first() !== this.props.statusIds.first() && + this._oldScrollPosition && + this.node.scrollTop > 0) { + let newScrollTop = this.node.scrollHeight - this._oldScrollPosition; + if (this.node.scrollTop !== newScrollTop) { + this.node.scrollTop = newScrollTop; + } } } From b51945f09649a4c010b8f4a8279f6f7856c832dd Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Sun, 18 Jun 2017 09:59:49 +0900 Subject: [PATCH 009/382] Spec AccountDomainBlock (#3816) --- spec/models/account_domain_block_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/models/account_domain_block_spec.rb b/spec/models/account_domain_block_spec.rb index bd64e10fb..469bc05cb 100644 --- a/spec/models/account_domain_block_spec.rb +++ b/spec/models/account_domain_block_spec.rb @@ -1,5 +1,22 @@ require 'rails_helper' RSpec.describe AccountDomainBlock, type: :model do + it 'removes blocking cache after creation' do + account = Fabricate(:account) + Rails.cache.write("exclude_domains_for:#{account.id}", 'a.domain.already.blocked') + AccountDomainBlock.create!(account: account, domain: 'a.domain.blocked.later') + + expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to eq false + end + + it 'removes blocking cache after destruction' do + account = Fabricate(:account) + block = AccountDomainBlock.create!(account: account, domain: 'domain') + Rails.cache.write("exclude_domains_for:#{account.id}", 'domain') + + block.destroy! + + expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to eq false + end end From 05e4728de781209d13405240eca0dd4f3ef6baa8 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 19 Jun 2017 08:38:50 +0900 Subject: [PATCH 010/382] Cover Favourite more (#3841) --- spec/models/favourite_spec.rb | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/spec/models/favourite_spec.rb b/spec/models/favourite_spec.rb index 5b7126506..ba1410a45 100644 --- a/spec/models/favourite_spec.rb +++ b/spec/models/favourite_spec.rb @@ -1,9 +1,29 @@ require 'rails_helper' RSpec.describe Favourite, type: :model do - let(:alice) { Fabricate(:account, username: 'alice') } - let(:bob) { Fabricate(:account, username: 'bob') } - let(:status) { Fabricate(:status, account: bob) } + let(:account) { Fabricate(:account) } - subject { Favourite.new(account: alice, status: status) } + context 'when status is a reblog' do + let(:reblog) { Fabricate(:status, reblog: nil) } + let(:status) { Fabricate(:status, reblog: reblog) } + + it 'invalidates if the reblogged status is already a favourite' do + Favourite.create!(account: account, status: reblog) + expect(Favourite.new(account: account, status: status).valid?).to eq false + end + + it 'replaces status with the reblogged one if it is a reblog' do + favourite = Favourite.create!(account: account, status: status) + expect(favourite.status).to eq reblog + end + end + + context 'when status is not a reblog' do + let(:status) { Fabricate(:status, reblog: nil) } + + it 'saves with the specified status' do + favourite = Favourite.create!(account: account, status: status) + expect(favourite.status).to eq status + end + end end From aebebdc5d1967e2110a9caafc3238c8b8ec055c4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Jun 2017 01:50:56 +0200 Subject: [PATCH 011/382] Debounce autosuggestions (#3836) * Debounce autosuggestions * Remove duplicate import --- .../mastodon/features/compose/components/compose_form.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 621ec43ab..8da3f8046 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -74,9 +74,9 @@ class ComposeForm extends ImmutablePureComponent { this.props.onClearSuggestions(); } - onSuggestionsFetchRequested = (token) => { + onSuggestionsFetchRequested = debounce((token) => { this.props.onFetchSuggestions(token); - } + }, 500, { trailing: true }) onSuggestionSelected = (tokenStart, token, value) => { this._restoreCaret = null; From f3be6052867343ab15cc84bc91edcaf0c1d70115 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 19 Jun 2017 01:51:04 +0200 Subject: [PATCH 012/382] Rename FollowRemoteAccountService to ResolveRemoteAccountService (#3847) Rename Activitypub to ActivityPub --- app/controllers/api/activitypub/activities_controller.rb | 2 +- app/controllers/api/activitypub/notes_controller.rb | 2 +- app/controllers/api/activitypub/outbox_controller.rb | 2 +- app/controllers/authorize_follows_controller.rb | 2 +- app/services/account_search_service.rb | 2 +- app/services/concerns/author_extractor.rb | 2 +- app/services/follow_service.rb | 2 +- app/services/process_mentions_service.rb | 2 +- ...e_account_service.rb => resolve_remote_account_service.rb} | 2 +- app/views/activitypub/types/collection.activitystreams2.rabl | 2 +- .../types/ordered_collection_page.activitystreams2.rabl | 2 +- app/workers/import_worker.rb | 4 ++-- config/initializers/inflections.rb | 1 + .../controllers/api/activitypub/activities_controller_spec.rb | 2 +- spec/controllers/api/activitypub/notes_controller_spec.rb | 2 +- spec/controllers/api/activitypub/outbox_controller_spec.rb | 2 +- spec/controllers/authorize_follows_controller_spec.rb | 4 ++-- spec/controllers/settings/imports_controller_spec.rb | 4 ++-- spec/services/account_search_service_spec.rb | 4 ++-- ...service_spec.rb => resolve_remote_account_service_spec.rb} | 4 ++-- 20 files changed, 25 insertions(+), 24 deletions(-) rename app/services/{follow_remote_account_service.rb => resolve_remote_account_service.rb} (98%) rename spec/services/{follow_remote_account_service_spec.rb => resolve_remote_account_service_spec.rb} (97%) diff --git a/app/controllers/api/activitypub/activities_controller.rb b/app/controllers/api/activitypub/activities_controller.rb index 740c8589a..a880ee92f 100644 --- a/app/controllers/api/activitypub/activities_controller.rb +++ b/app/controllers/api/activitypub/activities_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::Activitypub::ActivitiesController < Api::BaseController +class Api::ActivityPub::ActivitiesController < Api::BaseController include Authorization # before_action :set_follow, only: [:show_follow] diff --git a/app/controllers/api/activitypub/notes_controller.rb b/app/controllers/api/activitypub/notes_controller.rb index 783c1c4ed..96652b879 100644 --- a/app/controllers/api/activitypub/notes_controller.rb +++ b/app/controllers/api/activitypub/notes_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::Activitypub::NotesController < Api::BaseController +class Api::ActivityPub::NotesController < Api::BaseController include Authorization before_action :set_status diff --git a/app/controllers/api/activitypub/outbox_controller.rb b/app/controllers/api/activitypub/outbox_controller.rb index 0738d7dee..1af04cb54 100644 --- a/app/controllers/api/activitypub/outbox_controller.rb +++ b/app/controllers/api/activitypub/outbox_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::Activitypub::OutboxController < Api::BaseController +class Api::ActivityPub::OutboxController < Api::BaseController before_action :set_account respond_to :activitystreams2 diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb index b15883d58..da4ef022a 100644 --- a/app/controllers/authorize_follows_controller.rb +++ b/app/controllers/authorize_follows_controller.rb @@ -40,7 +40,7 @@ class AuthorizeFollowsController < ApplicationController end def account_from_remote_follow - FollowRemoteAccountService.new.call(acct_without_prefix) + ResolveRemoteAccountService.new.call(acct_without_prefix) end def acct_param_is_url? diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 2b77ad7c6..c266494f0 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -18,7 +18,7 @@ class AccountSearchService < BaseService return [] if query_blank_or_hashtag? || limit < 1 if resolving_non_matching_remote_account? - [FollowRemoteAccountService.new.call("#{query_username}@#{query_domain}")] + [ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")] else search_results_and_exact_match.compact.uniq.slice(0, limit) end diff --git a/app/services/concerns/author_extractor.rb b/app/services/concerns/author_extractor.rb index ae32eebbb..00fe1c663 100644 --- a/app/services/concerns/author_extractor.rb +++ b/app/services/concerns/author_extractor.rb @@ -18,6 +18,6 @@ module AuthorExtractor acct = "#{username}@#{domain}" end - FollowRemoteAccountService.new.call(acct, update_profile) + ResolveRemoteAccountService.new.call(acct, update_profile) end end diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 4de75e98d..e54ff7d0f 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -7,7 +7,7 @@ class FollowService < BaseService # @param [Account] source_account From which to follow # @param [String] uri User URI to follow in the form of username@domain def call(source_account, uri) - target_account = FollowRemoteAccountService.new.call(uri) + target_account = ResolveRemoteAccountService.new.call(uri) raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index aa0a4d71b..438033d22 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -41,6 +41,6 @@ class ProcessMentionsService < BaseService private def follow_remote_account_service - @follow_remote_account_service ||= FollowRemoteAccountService.new + @follow_remote_account_service ||= ResolveRemoteAccountService.new end end diff --git a/app/services/follow_remote_account_service.rb b/app/services/resolve_remote_account_service.rb similarity index 98% rename from app/services/follow_remote_account_service.rb rename to app/services/resolve_remote_account_service.rb index 30ba7bc75..362d0df98 100644 --- a/app/services/follow_remote_account_service.rb +++ b/app/services/resolve_remote_account_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class FollowRemoteAccountService < BaseService +class ResolveRemoteAccountService < BaseService include OStatus2::MagicKey include HttpHelper diff --git a/app/views/activitypub/types/collection.activitystreams2.rabl b/app/views/activitypub/types/collection.activitystreams2.rabl index d3f8ddcac..cc0e532b7 100644 --- a/app/views/activitypub/types/collection.activitystreams2.rabl +++ b/app/views/activitypub/types/collection.activitystreams2.rabl @@ -1,3 +1,3 @@ extends 'activitypub/intransient.activitystreams2.rabl' -node(:type) { 'Collection' } +node(:type) { 'Collection' } diff --git a/app/views/activitypub/types/ordered_collection_page.activitystreams2.rabl b/app/views/activitypub/types/ordered_collection_page.activitystreams2.rabl index c821fa928..9937d11e9 100644 --- a/app/views/activitypub/types/ordered_collection_page.activitystreams2.rabl +++ b/app/views/activitypub/types/ordered_collection_page.activitystreams2.rabl @@ -1,3 +1,3 @@ extends 'activitypub/types/ordered_collection.activitystreams2.rabl' -node(:type) { 'OrderedCollectionPage' } +node(:type) { 'OrderedCollectionPage' } diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb index e93fa33cf..90a226206 100644 --- a/app/workers/import_worker.rb +++ b/app/workers/import_worker.rb @@ -41,7 +41,7 @@ class ImportWorker def process_mutes import_rows.each do |row| begin - target_account = FollowRemoteAccountService.new.call(row.first) + target_account = ResolveRemoteAccountService.new.call(row.first) next if target_account.nil? MuteService.new.call(from_account, target_account) rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError @@ -53,7 +53,7 @@ class ImportWorker def process_blocks import_rows.each do |row| begin - target_account = FollowRemoteAccountService.new.call(row.first) + target_account = ResolveRemoteAccountService.new.call(row.first) next if target_account.nil? BlockService.new.call(from_account, target_account) rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index b5e43e705..a7b1ef690 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -13,4 +13,5 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'StatsD' inflect.acronym 'OEmbed' + inflect.acronym 'ActivityPub' end diff --git a/spec/controllers/api/activitypub/activities_controller_spec.rb b/spec/controllers/api/activitypub/activities_controller_spec.rb index 2c966a45a..07df28ac2 100644 --- a/spec/controllers/api/activitypub/activities_controller_spec.rb +++ b/spec/controllers/api/activitypub/activities_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Api::Activitypub::ActivitiesController, type: :controller do +RSpec.describe Api::ActivityPub::ActivitiesController, type: :controller do render_views let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } diff --git a/spec/controllers/api/activitypub/notes_controller_spec.rb b/spec/controllers/api/activitypub/notes_controller_spec.rb index 39ddec03e..a0f05dc65 100644 --- a/spec/controllers/api/activitypub/notes_controller_spec.rb +++ b/spec/controllers/api/activitypub/notes_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Api::Activitypub::NotesController, type: :controller do +RSpec.describe Api::ActivityPub::NotesController, type: :controller do render_views let(:user_alice) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } diff --git a/spec/controllers/api/activitypub/outbox_controller_spec.rb b/spec/controllers/api/activitypub/outbox_controller_spec.rb index 99797c582..049cf451d 100644 --- a/spec/controllers/api/activitypub/outbox_controller_spec.rb +++ b/spec/controllers/api/activitypub/outbox_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Api::Activitypub::OutboxController, type: :controller do +RSpec.describe Api::ActivityPub::OutboxController, type: :controller do render_views let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } diff --git a/spec/controllers/authorize_follows_controller_spec.rb b/spec/controllers/authorize_follows_controller_spec.rb index 1bc4eb6b7..b801aa661 100644 --- a/spec/controllers/authorize_follows_controller_spec.rb +++ b/spec/controllers/authorize_follows_controller_spec.rb @@ -30,7 +30,7 @@ describe AuthorizeFollowsController do it 'renders error when account cant be found' do service = double - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) allow(service).to receive(:call).with('missing@hostname').and_return(nil) get :show, params: { acct: 'acct:missing@hostname' } @@ -54,7 +54,7 @@ describe AuthorizeFollowsController do it 'sets account from acct uri' do account = Account.new service = double - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) allow(service).to receive(:call).with('found@hostname').and_return(account) get :show, params: { acct: 'acct:found@hostname' } diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 8bf6aec2b..4810be701 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Settings::ImportsController, type: :controller do describe 'POST #create' do it 'redirects to settings path with successful following import' do service = double(call: nil) - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) post :create, params: { import: { type: 'following', @@ -30,7 +30,7 @@ RSpec.describe Settings::ImportsController, type: :controller do it 'redirects to settings path with successful blocking import' do service = double(call: nil) - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) post :create, params: { import: { type: 'blocking', diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 697ac8f23..25bb56bca 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -123,7 +123,7 @@ describe AccountSearchService do describe 'when there is a domain but no exact match' do it 'follows the remote account when resolve is true' do service = double(call: nil) - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) results = subject.call('newuser@remote.com', 10, true) expect(service).to have_received(:call).with('newuser@remote.com') @@ -131,7 +131,7 @@ describe AccountSearchService do it 'does not follow the remote account when resolve is false' do service = double(call: nil) - allow(FollowRemoteAccountService).to receive(:new).and_return(service) + allow(ResolveRemoteAccountService).to receive(:new).and_return(service) results = subject.call('newuser@remote.com', 10, false) expect(service).not_to have_received(:call) diff --git a/spec/services/follow_remote_account_service_spec.rb b/spec/services/resolve_remote_account_service_spec.rb similarity index 97% rename from spec/services/follow_remote_account_service_spec.rb rename to spec/services/resolve_remote_account_service_spec.rb index 9ae9ff0ce..ad4d436ba 100644 --- a/spec/services/follow_remote_account_service_spec.rb +++ b/spec/services/resolve_remote_account_service_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' -RSpec.describe FollowRemoteAccountService do - subject { FollowRemoteAccountService.new } +RSpec.describe ResolveRemoteAccountService do + subject { ResolveRemoteAccountService.new } before do stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt')) From 8d2b3ada801c347f58343adf614a0b1658523506 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Mon, 19 Jun 2017 17:28:35 +0900 Subject: [PATCH 013/382] Fixes streaming callbacks of HashtagTimeline (#3849) --- app/javascript/mastodon/features/hashtag_timeline/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index ba6dbe6e5..3b2f1ba93 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -60,7 +60,7 @@ class HashtagTimeline extends React.PureComponent { received (data) { switch(data.event) { case 'update': - dispatch(updateTimeline('tag', JSON.parse(data.payload))); + dispatch(updateTimeline(`hashtag:${id}`, JSON.parse(data.payload))); break; case 'delete': dispatch(deleteFromTimelines(data.payload)); From 436744328726d5ba32a90dbdaf60e57272c788db Mon Sep 17 00:00:00 2001 From: spla Date: Mon, 19 Jun 2017 10:29:18 +0200 Subject: [PATCH 014/382] Added new Catalan strings (#3843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Catalan language * Add Catalan language * Update ca.json * Update ca.json * Update ca.json * Update ca.json * Update ca.json * Update ca.json * Update settings_helper.rb * Update mastodon.js * Update index.js * Update application.rb * Update ca.yml * removed extra spaces at line 225 * Catalan translation update added activerecord.ca.yml * Update activerecord.ca.yml Done * Updated activerecord.ca.yml * Catalan language updated * Catalan language updated * Catalan language updated * Catalan language updated * Catalan language updated * Update ca.json Removed : <<<<<<< HEAD "getting_started.support": "{faq} • {userguide} • {apps}", ======= >>>>>>> upstream/master --- config/locales/ca.yml | 24 +++++++++++++++++++++--- config/locales/simple_form.ca.yml | 13 +++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 73d6efb3d..24fc5690d 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -4,7 +4,7 @@ ca: about_mastodon: Mastodon és un servidor de xarxa social lliure i de codi obert. Una alternativa descentralitzada a plataformes comercials, que evita el risc que una única companyia monopolitzi la teva comunicació. Qualsevol pot executar Mastodon i participar sense problemes en la xarxa social. about_this: Sobre aquesta instància apps: Apps - business_email: 'Correu de negocis:' + business_email: 'Adreça de contacte:' closed_registrations: Els registres estan actualment tancats en aquesta instància. contact: Contacte description_headline: Què es %{domain}? @@ -39,6 +39,7 @@ ca: people_who_follow: Usuaris que segueixn a %{name} posts: Toots remote_follow: Seguir + reserved_username: El nom d'usuari està reservat unfollow: Deixar de seguir activitypub: activity: @@ -85,8 +86,10 @@ ca: profile_url: URL del perfil public: Públic push_subscription_expires: La subscripció PuSH expira + redownload: Refrescar avatar reset: Reajustar reset_password: Restablir la contrasenya + resubscribe: Resubscribir salmon_url: URL Salmon search: Cerca show: @@ -95,9 +98,11 @@ ca: targeted_reports: Informes realitzats sobre aquest compte silence: Silenci statuses: Estats + subscribe: Subscribir title: Comptes undo_silenced: Desfer silenci undo_suspension: Desfer suspensió + unsubscribe: Donar-se de baixa username: Nom d'usuari web: Web domain_blocks: @@ -135,6 +140,7 @@ ca: domain_name: Domini title: Instàncies conegudes reports: + action_taken_by: Mesures adoptades per are_you_sure: Estàs segur? comment: label: Comentari @@ -195,8 +201,10 @@ ca: invalid_url: La URL proporcionada es incorrecte auth: change_password: Canviar contrasenya - didnt_get_confirmation: "¿No vas rebre el correu de confirmació?" - forgot_password: "¿Has oblidat la contrassenya?" + delete_account: Esborrar el compte + delete_account_html: Si vols esborrar el teu compte pots fer-ho aquí. S'et demanarà confirmació. + didnt_get_confirmation: "No vas rebre el correu de confirmació?" + forgot_password: "Has oblidat la contrasenya?" login: Iniciar sessió logout: Tancar sessió register: Enregistrarse @@ -222,6 +230,14 @@ ca: x_minutes: "%{count}m" x_months: "%{count}m" x_seconds: "%{count}s" + deletes: + bad_password_msg: Bon intent hackers! Contrasenya incorrecta + confirm_password: Introdueix la contrasenya actual per verificar la teva identitat + description_html: Això eliminarà de forma irreversible i permanent el contingut del teu compte i el desactivarà. El teu nom d'usuari romandrà reservat per evitar que algú volgués fer-se passar per tu. + proceed: Esborrar el compte + success_msg: El teu compte s'ha eliminat correctament + warning_html: Només està garantida l'eliminació d'aquesta particular instància. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades. + warning_title: Disponibilitat de contingut disseminat errors: '403': No tens permís per veure aquesta pàgina. '404': La pàgina que estàs buscant no existeix. @@ -230,6 +246,7 @@ ca: content: La verificació de seguretat ha fallat. Bloquejes les galetes? title: La verificació de seguretat ha fallat '429': Estrangulat + noscript: Per utilitzar Mastodon si us plau activa JavaScript. exports: blocks: Persones que has bloquejat csv: CSV @@ -306,6 +323,7 @@ ca: settings: authorized_apps: Aplicacions autoritzades back: Tornar al inici + delete: Eliminació del compte edit_profile: Editar perfil export: Exportar informació followers: Seguidors autoritzats diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 7940ed37d..7bcc21e66 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -4,12 +4,20 @@ ca: hints: defaults: avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px - display_name: Màxim 30 caràcters + display_name: + one: 1 character left + other: %{count} characters left header: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 700x335px locked: Requereix que aprovis manualment seguidors i les publicacions seran mostrades només als teus seguidors - note: Màxim 160 caràcters + note: + one: 1 character left + other: %{count} characters left imports: data: Arxiu CSV exportat desde una altra instància de Mastodon + sessions: + otp: Introdueix el codi de dos factors des del teu telèfon o utilitza un dels teus codis de recuperació. + user: + filtered_languages: Els idiomes seleccionats seran eliminats de les línies de temps públiques. labels: defaults: avatar: Avatar @@ -29,6 +37,7 @@ ca: setting_auto_play_gif: Auto-reproducció de GIFs animats setting_boost_modal: Mostrar finestra de confirmació abans d'un Retoot setting_default_privacy: Privacitat de publicacions + setting_delete_modal: Mostrar finestra de confirmació abans d'esborrar un toot severity: Severitat type: Importar tipus username: Nom d´usuari From cf6fe4f8cbafeb2f4e3a92bac4f3909cfc327edf Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Mon, 19 Jun 2017 18:29:57 +0900 Subject: [PATCH 015/382] Unobserve status on unmount (#3851) --- app/javascript/mastodon/components/status.js | 10 ++++++++++ .../features/ui/util/intersection_observer_wrapper.js | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 7ca898c39..3b8f88bf7 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -88,6 +88,16 @@ class Status extends ImmutablePureComponent { ); } + componentWillUnmount () { + if (!this.props.intersectionObserverWrapper) { + // TODO: enable IntersectionObserver optimization for notification statuses. + // These are managed in notifications/index.js rather than status_list.js + return; + } + + this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node); + } + handleIntersection = (entry) => { // Edge 15 doesn't support isIntersecting, but we can infer it // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/ diff --git a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js index 0e959f9ae..b15f8b9ef 100644 --- a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js +++ b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js @@ -37,6 +37,13 @@ class IntersectionObserverWrapper { } } + unobserve (id, node) { + if (this.observer) { + delete this.callbacks[id]; + this.observer.unobserve(node); + } + } + disconnect () { if (this.observer) { this.observer.disconnect(); From d55f207274648369cba30ff001aa3e354fa30dca Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 19 Jun 2017 18:30:27 +0900 Subject: [PATCH 016/382] Cover Export more (#3840) --- .../media_attachment_fabricator.rb | 1 + spec/models/export_spec.rb | 49 ++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb index dc91d708f..c5dfe12e5 100644 --- a/spec/fabricators/media_attachment_fabricator.rb +++ b/spec/fabricators/media_attachment_fabricator.rb @@ -1,3 +1,4 @@ Fabricator(:media_attachment) do account + file { [attachment_fixture(['attachment.gif', 'attachment.jpg', 'attachment.webm'].sample), nil].sample } end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 3ee042fb6..6daa46145 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -1,17 +1,16 @@ require 'rails_helper' describe Export do + let(:account) { Fabricate(:account) } + let(:target_accounts) do + [ {}, { username: 'one', domain: 'local.host' } ].map(&method(:Fabricate).curry(2).call(:account)) + end + describe 'to_csv' do - before do - one = Account.new(username: 'one', domain: 'local.host') - two = Account.new(username: 'two', domain: 'local.host') - accounts = [one, two] - - @account = double(blocking: accounts, muting: accounts, following: accounts) - end - it 'returns a csv of the blocked accounts' do - export = Export.new(@account).to_blocked_accounts_csv + target_accounts.each(&account.method(:block!)) + + export = Export.new(account).to_blocked_accounts_csv results = export.strip.split expect(results.size).to eq 2 @@ -19,7 +18,9 @@ describe Export do end it 'returns a csv of the muted accounts' do - export = Export.new(@account).to_muted_accounts_csv + target_accounts.each(&account.method(:mute!)) + + export = Export.new(account).to_muted_accounts_csv results = export.strip.split expect(results.size).to eq 2 @@ -27,11 +28,37 @@ describe Export do end it 'returns a csv of the following accounts' do - export = Export.new(@account).to_following_accounts_csv + target_accounts.each(&account.method(:follow!)) + + export = Export.new(account).to_following_accounts_csv results = export.strip.split expect(results.size).to eq 2 expect(results.first).to eq 'one@local.host' end end + + describe 'total_storage' do + it 'returns the total size of the media attachments' do + media_attachment = Fabricate(:media_attachment, account: account) + expect(Export.new(account).total_storage).to eq media_attachment.file_file_size || 0 + end + end + + describe 'total_follows' do + it 'returns the total number of the followed accounts' do + target_accounts.each(&account.method(:follow!)) + expect(Export.new(account).total_follows).to eq 2 + end + + it 'returns the total number of the blocked accounts' do + target_accounts.each(&account.method(:block!)) + expect(Export.new(account).total_blocks).to eq 2 + end + + it 'returns the total number of the muted accounts' do + target_accounts.each(&account.method(:mute!)) + expect(Export.new(account).total_mutes).to eq 2 + end + end end From 29a22691d2a9eefbc2eb886659843f87d187c98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B4=20Shoemake?= Date: Mon, 19 Jun 2017 02:31:14 -0700 Subject: [PATCH 017/382] Fix character/grapheme count stuff (#3839) * Bring Toot button in line with counter Both should use stringz I guess * Use grapheme_length for character count --- .../mastodon/features/compose/components/compose_form.js | 3 ++- app/validators/status_length_validator.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 8da3f8046..0ee45c978 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -18,6 +18,7 @@ import UploadFormContainer from '../containers/upload_form_container'; import TextIconButton from './text_icon_button'; import WarningContainer from '../containers/warning_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { length } from 'stringz'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, @@ -193,7 +194,7 @@ class ComposeForm extends ImmutablePureComponent {
-
+
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb index 55135a598..3f3e422d9 100644 --- a/app/validators/status_length_validator.rb +++ b/app/validators/status_length_validator.rb @@ -5,6 +5,6 @@ class StatusLengthValidator < ActiveModel::Validator def validate(status) return unless status.local? && !status.reblog? - status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if [status.text, status.spoiler_text].join.length > MAX_CHARS + status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if [status.text, status.spoiler_text].join.mb_chars.grapheme_length > MAX_CHARS end end From 6eefccdacc96161d06ce38070002b7d0ce582aee Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 19 Jun 2017 18:31:27 +0900 Subject: [PATCH 018/382] Cover DomainBlock more (#3838) --- spec/models/domain_block_spec.rb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb index b19c8083e..89cadccfe 100644 --- a/spec/models/domain_block_spec.rb +++ b/spec/models/domain_block_spec.rb @@ -13,11 +13,27 @@ RSpec.describe DomainBlock, type: :model do expect(domain_block).to model_have_error_on_field(:domain) end - it 'is invalid if the domain already exists' do - domain_block_1 = Fabricate(:domain_block, domain: 'dalek.com') - domain_block_2 = Fabricate.build(:domain_block, domain: 'dalek.com') + it 'is invalid if the same normalized domain already exists' do + domain_block_1 = Fabricate(:domain_block, domain: 'にゃん') + domain_block_2 = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b') domain_block_2.valid? expect(domain_block_2).to model_have_error_on_field(:domain) end end + + describe 'blocked?' do + it 'returns true if the domain is suspended' do + Fabricate(:domain_block, domain: 'domain', severity: :suspend) + expect(DomainBlock.blocked?('domain')).to eq true + end + + it 'returns false even if the domain is silenced' do + Fabricate(:domain_block, domain: 'domain', severity: :silence) + expect(DomainBlock.blocked?('domain')).to eq false + end + + it 'returns false if the domain is not suspended nor silenced' do + expect(DomainBlock.blocked?('domain')).to eq false + end + end end From 3d13f6ea0c6cf0022c9fa57c570f93462647dd12 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 19 Jun 2017 18:31:37 +0900 Subject: [PATCH 019/382] Cover Block more (#3837) --- spec/models/block_spec.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/models/block_spec.rb b/spec/models/block_spec.rb index cabb41c3e..acbdc77f5 100644 --- a/spec/models/block_spec.rb +++ b/spec/models/block_spec.rb @@ -19,4 +19,29 @@ RSpec.describe Block, type: :model do expect(block).to model_have_error_on_field(:target_account) end end + + it 'removes blocking cache after creation' do + account = Fabricate(:account) + target_account = Fabricate(:account) + Rails.cache.write("exclude_account_ids_for:#{account.id}", []) + Rails.cache.write("exclude_account_ids_for:#{target_account.id}", []) + + Block.create!(account: account, target_account: target_account) + + expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to eq false + expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to eq false + end + + it 'removes blocking cache after destruction' do + account = Fabricate(:account) + target_account = Fabricate(:account) + block = Block.create!(account: account, target_account: target_account) + Rails.cache.write("exclude_account_ids_for:#{account.id}", [target_account.id]) + Rails.cache.write("exclude_account_ids_for:#{target_account.id}", [account.id]) + + block.destroy! + + expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to eq false + expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to eq false + end end From 5bd3715a4c11d533d9b859dadce45adc99e97373 Mon Sep 17 00:00:00 2001 From: Daigo 3 Dango Date: Sun, 18 Jun 2017 23:32:28 -1000 Subject: [PATCH 020/382] Link to /about from public page on a single user instance (#3814) There was no link for visitors to follow to see the about page. --- app/views/layouts/public.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml index 09e12b691..83e92b938 100644 --- a/app/views/layouts/public.html.haml +++ b/app/views/layouts/public.html.haml @@ -8,7 +8,9 @@ %span.single-user-login = link_to t('auth.login'), new_user_session_path — - %span.domain= link_to site_hostname, root_path + %span.domain= link_to site_hostname, about_path + - else + %span.domain= link_to site_hostname, root_path %span.powered-by != t('generic.powered_by', link: link_to('Mastodon', 'https://joinmastodon.org')) From 500e28442f8e6657de766fbdb4a4cfdb164b2b92 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 19 Jun 2017 21:01:31 +0900 Subject: [PATCH 021/382] Re-add disableHostCheck (regression #3729) (#3854) ref #2790 --- config/webpack/development.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/webpack/development.js b/config/webpack/development.js index 7fce7e3f9..830183c0d 100644 --- a/config/webpack/development.js +++ b/config/webpack/development.js @@ -25,6 +25,7 @@ module.exports = merge(sharedConfig, { compress: true, headers: { 'Access-Control-Allow-Origin': '*' }, historyApiFallback: true, + disableHostCheck: true, watchOptions: { ignored: /node_modules/, }, From ff142eb64d2d1251f9df1baa0b302fd532ec30ed Mon Sep 17 00:00:00 2001 From: masarakki Date: Mon, 19 Jun 2017 22:12:31 +0900 Subject: [PATCH 022/382] setting-for-account-deletable (#3852) --- app/controllers/settings/deletes_controller.rb | 5 +++++ app/helpers/application_helper.rb | 4 ++++ app/views/auth/registrations/edit.html.haml | 7 ++++--- config/settings.yml | 1 + .../settings/deletes_controller_spec.rb | 14 ++++++++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb index dd18b4c2f..80002b995 100644 --- a/app/controllers/settings/deletes_controller.rb +++ b/app/controllers/settings/deletes_controller.rb @@ -3,6 +3,7 @@ class Settings::DeletesController < ApplicationController layout 'admin' + before_action :check_enabled_deletion before_action :authenticate_user! def show @@ -21,6 +22,10 @@ class Settings::DeletesController < ApplicationController private + def check_enabled_deletion + redirect_to root_path unless Setting.open_deletion + end + def delete_params params.require(:form_delete_confirmation).permit(:password) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 42f6ab3db..36c37fae0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -13,6 +13,10 @@ module ApplicationHelper Setting.open_registrations end + def open_deletion? + Setting.open_deletion + end + def add_rtl_body_class(other_classes) other_classes = "#{other_classes} rtl" if [:ar, :fa, :he].include?(I18n.locale) other_classes diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index cbaa75ae0..38d4349cb 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -12,7 +12,8 @@ .actions = f.button :button, t('generic.save_changes'), type: :submit -%hr/ +- if open_deletion? + %hr/ -%h6= t('auth.delete_account') -%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) + %h6= t('auth.delete_account') + %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/config/settings.yml b/config/settings.yml index bffb7052a..7b78b6cdb 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -14,6 +14,7 @@ defaults: &defaults site_contact_email: '' open_registrations: true closed_registrations_message: '' + open_deletion: true boost_modal: false auto_play_gif: true delete_modal: true diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index b9c7c3068..9b55090df 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -68,5 +68,19 @@ describe Settings::DeletesController do expect(response).to redirect_to '/auth/sign_in' end end + + context do + around do |example| + open_deletion = Setting.open_deletion + example.run + Setting.open_deletion = open_deletion + end + + it 'redirects' do + Setting.open_deletion = false + delete :destroy + expect(response).to redirect_to root_path + end + end end end From 5cc7cd8518eea864ce29228a8c04a65f9cdbb87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AE=E3=82=89?= Date: Mon, 19 Jun 2017 22:12:41 +0900 Subject: [PATCH 023/382] Add Japanese translation "Action taken by" (#3850) --- config/locales/ja.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 840fdca3f..94f02e940 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -140,6 +140,7 @@ ja: domain_name: ドメイン名 title: 既知のインスタンス reports: + action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? comment: label: コメント From aa58cca040f332eefc6ef0e98752dd0f1a770c6e Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 20 Jun 2017 01:27:07 +0900 Subject: [PATCH 024/382] Set cursor:pointer only when necessary (#3857) --- app/javascript/mastodon/components/status_content.js | 6 +++--- app/javascript/styles/components.scss | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 89031b3dc..4e435b2c4 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -136,7 +136,7 @@ class StatusContent extends React.PureComponent { } return ( -
+
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 4b5802cee..1cd0eba74 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -436,14 +436,10 @@ margin-right: 5px; } -.status__content { +.status__content--with-action { cursor: pointer; } -.status__content--no-action { - cursor: default; -} - .status__content, .reply-indicator__content { font-size: 15px; From bf8c2c43483650bc046a3b3dd114f1cdfa49d2b4 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 20 Jun 2017 08:34:10 +0900 Subject: [PATCH 025/382] Clicking on the CW text should expand the status (#3855) --- app/javascript/mastodon/components/status_content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 4e435b2c4..0a71cbba7 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -81,7 +81,7 @@ class StatusContent extends React.PureComponent { const [ startX, startY ] = this.startXY; const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; - if (e.target.localName === 'button' || e.target.localName === 'span' || e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) { + if (e.target.localName === 'button' || e.target.localName === 'a' || (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) { return; } From 94ad0706f514e25c58c0a8f3201f96c1d1ccbbd8 Mon Sep 17 00:00:00 2001 From: unarist Date: Tue, 20 Jun 2017 11:12:51 +0900 Subject: [PATCH 026/382] Don't attach IntersectionObserver for wrapped statuses (#3863) This fixes a bug that sometimes boosted statuses being hidden on scrolling. Previously, we've attached IntersectionObserver twice for boosted statuses: wrapper Status and wrapped Status. This will call intersection handler twice, so this may results race condition...probably. --- app/javascript/mastodon/components/status.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 3b8f88bf7..17f2eb9d3 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -154,7 +154,10 @@ class Status extends ImmutablePureComponent { render () { let media = null; let statusAvatar; - const { status, account, ...other } = this.props; + + // Exclude intersectionObserverWrapper from `other` variable + // because intersection is managed in here. + const { status, account, intersectionObserverWrapper, ...other } = this.props; const { isExpanded, isIntersecting, isHidden } = this.state; if (status === null) { From da6fa029f66d50f38f2b6c85687994793f7766aa Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Tue, 20 Jun 2017 18:34:27 +0200 Subject: [PATCH 027/382] i18n: Fixed typo in Polish translation (#3864) --- config/locales/pl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 78f78a893..7376c3e2b 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -355,7 +355,7 @@ pl: default: "%b %d, %Y, %H:%M" two_factor_authentication: code_hint: Aby kontynuować, wprowadź kod wyświetlany przez aplikację uwierzytelniającą - description_html: Jeśli włączysz uwierzytelnianie dwuetapowe, logowanie się będzie wymagało podania tokenu wyświetlonego na Twoim telefone. + description_html: Jeśli włączysz uwierzytelnianie dwuetapowe, logowanie się będzie wymagało podania tokenu wyświetlonego na Twoim telefonie. disable: Wyłącz enable: Włącz enabled_success: Pomyślnie aktywowano uwierzytelnianie dwuetapowe From b16b69350eb4ded2e1011931433b51dac5e34b53 Mon Sep 17 00:00:00 2001 From: unarist Date: Wed, 21 Jun 2017 01:45:09 +0900 Subject: [PATCH 028/382] Fix RTL detection on Ruby side (#3867) This fixes below bugs: * pipe characters being counted as RTL character * only first word being checked --- app/helpers/stream_entries_helper.rb | 10 +++++----- spec/helpers/stream_entries_helper_spec.rb | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index a17b02128..4ef7cffb0 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -53,11 +53,11 @@ module StreamEntriesHelper def rtl?(text) text = simplified_text(text) - rtl_characters = /[\p{Hebrew}|\p{Arabic}|\p{Syriac}|\p{Thaana}|\p{Nko}]+/m.match(text) + rtl_words = text.scan(/[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}]+/m) - if rtl_characters.present? + if rtl_words.present? total_size = text.size.to_f - rtl_size(rtl_characters.to_a) / total_size > 0.3 + rtl_size(rtl_words) / total_size > 0.3 else false end @@ -77,8 +77,8 @@ module StreamEntriesHelper end end - def rtl_size(characters) - characters.reduce(0) { |acc, elem| acc + elem.size }.to_f + def rtl_size(words) + words.reduce(0) { |acc, elem| acc + elem.size }.to_f end def embedded_view? diff --git a/spec/helpers/stream_entries_helper_spec.rb b/spec/helpers/stream_entries_helper_spec.rb index 1ef49a3ab..2c0d7b239 100644 --- a/spec/helpers/stream_entries_helper_spec.rb +++ b/spec/helpers/stream_entries_helper_spec.rb @@ -217,7 +217,7 @@ RSpec.describe StreamEntriesHelper, type: :helper do end it 'is true if right to left characters are greater than 1/3 of total text' do - expect(helper).to be_rtl 'aaݟ' + expect(helper).to be_rtl 'aaݟaaݟ' end end end From eb832e88f44d661a504a091defc051e052eb1252 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 21 Jun 2017 02:43:09 +0900 Subject: [PATCH 029/382] Replace TextIconButton for SensitiveButton to IconButton (#3759) * Replace TextIconButton for SensitiveButton to IconButton * line-height --- .../containers/sensitive_button_container.js | 28 +++++++++++++++---- app/javascript/styles/components.scss | 12 ++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js index 761ae8c08..63c0e8ae4 100644 --- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js @@ -1,7 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import TextIconButton from '../components/text_icon_button'; +import classNames from 'classnames'; +import IconButton from '../../../components/icon_button'; import { changeComposeSensitivity } from '../../../actions/compose'; import Motion from 'react-motion/lib/Motion'; import spring from 'react-motion/lib/spring'; @@ -38,11 +39,26 @@ class SensitiveButton extends React.PureComponent { return ( - {({ scale }) => -
- -
- } + {({ scale }) => { + const icon = active ? 'eye-slash' : 'eye'; + const className = classNames('compose-form__sensitive-button', { + 'compose-form__sensitive-button--visible': visible, + }); + return ( +
+ +
+ ); + }}
); } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 1cd0eba74..ecfc186eb 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -306,6 +306,18 @@ line-height: 27px; } +.compose-form__sensitive-button { + display: none; + + &.compose-form__sensitive-button--visible { + display: block; + } + + .compose-form__sensitive-button__icon { + line-height: 27px; + } +} + .compose-form__upload-wrapper { overflow: hidden; } From 1fc6cb499742c1a872ad717a689c58a80aeb714d Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 21 Jun 2017 03:37:09 +0900 Subject: [PATCH 030/382] Do not call setState from unmounted component (#3853) Stop an executing task if the component already unmounted. --- app/javascript/mastodon/components/status.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 17f2eb9d3..8c4443967 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -86,6 +86,8 @@ class Status extends ImmutablePureComponent { this.node, this.handleIntersection ); + + this.componentMounted = true; } componentWillUnmount () { @@ -96,6 +98,8 @@ class Status extends ImmutablePureComponent { } this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node); + + this.componentMounted = false; } handleIntersection = (entry) => { @@ -116,6 +120,10 @@ class Status extends ImmutablePureComponent { } hideIfNotIntersecting = () => { + if (!this.componentMounted) { + return; + } + // When the browser gets a chance, test if we're still not intersecting, // and if so, set our isHidden to true to trigger an unrender. The point of // this is to save DOM nodes and avoid using up too much memory. From 8f03fdce7fa16fcaa829a40f395cefc85eb957d5 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Tue, 20 Jun 2017 20:40:03 +0200 Subject: [PATCH 031/382] Upgrade React Router (#3677) * chore(yarn): Remove react-router * chore(yarn): Remove react-router-scroll * chore(yarn): Remove history * chore(yarn): Add react-router-dom * chore: Remove usages of react-router-scroll * refactor: Upgrade to react-router-web * refactor: Use fork of react-router-scroll This reverts commit 2ddea9a6c8d39fc64b7d0b587f3fbda7a45a7fa2. * fix: Issues mentions in the PR feedback --- .../mastodon/components/column_back_button.js | 4 +- .../components/column_back_button_slim.js | 4 +- .../mastodon/components/column_header.js | 4 +- .../mastodon/components/dropdown_menu.js | 2 +- .../mastodon/components/permalink.js | 2 +- app/javascript/mastodon/components/status.js | 4 +- .../mastodon/components/status_action_bar.js | 8 +- .../mastodon/components/status_content.js | 4 +- .../mastodon/containers/mastodon.js | 70 ++------------ .../features/account/components/action_bar.js | 2 +- .../account_timeline/components/header.js | 4 +- .../compose/components/navigation_bar.js | 2 +- .../compose/components/reply_indicator.js | 2 +- .../compose/components/search_results.js | 2 +- .../mastodon/features/compose/index.js | 2 +- .../features/follow_requests/index.js | 1 + .../features/getting_started/index.js | 2 +- .../mastodon/features/home_timeline/index.js | 2 +- .../mastodon/features/report/index.js | 4 +- .../features/status/components/action_bar.js | 4 +- .../status/components/detailed_status.js | 4 +- .../mastodon/features/status/index.js | 2 +- .../features/ui/components/boost_modal.js | 2 +- .../features/ui/components/column_link.js | 2 +- .../features/ui/components/tabs_bar.js | 14 +-- app/javascript/mastodon/features/ui/index.js | 95 ++++++++++++++++++- package.json | 5 +- yarn.lock | 63 +++++++----- 28 files changed, 186 insertions(+), 130 deletions(-) diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js index 3add65968..191c0f597 100644 --- a/app/javascript/mastodon/components/column_back_button.js +++ b/app/javascript/mastodon/components/column_back_button.js @@ -9,8 +9,8 @@ class ColumnBackButton extends React.PureComponent { }; handleClick = () => { - if (window.history && window.history.length === 1) this.context.router.push('/'); - else this.context.router.goBack(); + if (window.history && window.history.length === 1) this.context.router.history.push('/'); + else this.context.router.history.goBack(); } render () { diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js index 2d3f1b57a..ffb05fa02 100644 --- a/app/javascript/mastodon/components/column_back_button_slim.js +++ b/app/javascript/mastodon/components/column_back_button_slim.js @@ -9,8 +9,8 @@ class ColumnBackButtonSlim extends React.PureComponent { }; handleClick = () => { - if (window.history && window.history.length === 1) this.context.router.push('/'); - else this.context.router.goBack(); + if (window.history && window.history.length === 1) this.context.router.history.push('/'); + else this.context.router.history.goBack(); } render () { diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 175ee800f..076f1cdad 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -45,8 +45,8 @@ class ColumnHeader extends React.PureComponent { } handleBackClick = () => { - if (window.history && window.history.length === 1) this.context.router.push('/'); - else this.context.router.goBack(); + if (window.history && window.history.length === 1) this.context.router.history.push('/'); + else this.context.router.history.goBack(); } handleTransitionEnd = () => { diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 5712cffab..6e394a95d 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -41,7 +41,7 @@ class DropdownMenu extends React.PureComponent { action(); } else if (to) { e.preventDefault(); - this.context.router.push(to); + this.context.router.history.push(to); } this.dropdown.hide(); diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js index b45969d85..7149e8380 100644 --- a/app/javascript/mastodon/components/permalink.js +++ b/app/javascript/mastodon/components/permalink.js @@ -17,7 +17,7 @@ class Permalink extends React.PureComponent { handleClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.push(this.props.to); + this.context.router.history.push(this.props.to); } } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 8c4443967..b2214f72a 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -144,14 +144,14 @@ class Status extends ImmutablePureComponent { handleClick = () => { const { status } = this.props; - this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); + this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); } handleAccountClick = (e) => { if (e.button === 0) { const id = Number(e.currentTarget.getAttribute('data-id')); e.preventDefault(); - this.context.router.push(`/accounts/${id}`); + this.context.router.history.push(`/accounts/${id}`); } } diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 99e01d972..800f0d0fb 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -53,7 +53,7 @@ class StatusActionBar extends ImmutablePureComponent { ] handleReplyClick = () => { - this.props.onReply(this.props.status, this.context.router); + this.props.onReply(this.props.status, this.context.router.history); } handleFavouriteClick = () => { @@ -69,7 +69,7 @@ class StatusActionBar extends ImmutablePureComponent { } handleMentionClick = () => { - this.props.onMention(this.props.status.get('account'), this.context.router); + this.props.onMention(this.props.status.get('account'), this.context.router.history); } handleMuteClick = () => { @@ -81,12 +81,12 @@ class StatusActionBar extends ImmutablePureComponent { } handleOpen = () => { - this.context.router.push(`/statuses/${this.props.status.get('id')}`); + this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); } handleReport = () => { this.props.onReport(this.props.status); - this.context.router.push('/report'); + this.context.router.history.push('/report'); } handleConversationMuteClick = () => { diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 0a71cbba7..d02083d3e 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -56,7 +56,7 @@ class StatusContent extends React.PureComponent { onMentionClick = (mention, e) => { if (e.button === 0) { e.preventDefault(); - this.context.router.push(`/accounts/${mention.get('id')}`); + this.context.router.history.push(`/accounts/${mention.get('id')}`); } } @@ -65,7 +65,7 @@ class StatusContent extends React.PureComponent { if (e.button === 0) { e.preventDefault(); - this.context.router.push(`/timelines/tag/${hashtag}`); + this.context.router.history.push(`/timelines/tag/${hashtag}`); } } diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 5e009cfa3..d44cb1be4 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -12,35 +12,10 @@ import { } from '../actions/timelines'; import { showOnboardingOnce } from '../actions/onboarding'; import { updateNotifications, refreshNotifications } from '../actions/notifications'; -import createBrowserHistory from 'history/lib/createBrowserHistory'; -import applyRouterMiddleware from 'react-router/lib/applyRouterMiddleware'; -import useRouterHistory from 'react-router/lib/useRouterHistory'; -import Router from 'react-router/lib/Router'; -import Route from 'react-router/lib/Route'; -import IndexRedirect from 'react-router/lib/IndexRedirect'; -import IndexRoute from 'react-router/lib/IndexRoute'; -import { useScroll } from 'react-router-scroll'; +import BrowserRouter from 'react-router-dom/BrowserRouter'; +import Route from 'react-router-dom/Route'; +import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext'; import UI from '../features/ui'; -import Status from '../features/status'; -import GettingStarted from '../features/getting_started'; -import PublicTimeline from '../features/public_timeline'; -import CommunityTimeline from '../features/community_timeline'; -import AccountTimeline from '../features/account_timeline'; -import AccountGallery from '../features/account_gallery'; -import HomeTimeline from '../features/home_timeline'; -import Compose from '../features/compose'; -import Followers from '../features/followers'; -import Following from '../features/following'; -import Reblogs from '../features/reblogs'; -import Favourites from '../features/favourites'; -import HashtagTimeline from '../features/hashtag_timeline'; -import Notifications from '../features/notifications'; -import FollowRequests from '../features/follow_requests'; -import GenericNotFound from '../features/generic_not_found'; -import FavouritedStatuses from '../features/favourited_statuses'; -import Blocks from '../features/blocks'; -import Mutes from '../features/mutes'; -import Report from '../features/report'; import { hydrateStore } from '../actions/store'; import createStream from '../stream'; import { IntlProvider, addLocaleData } from 'react-intl'; @@ -52,10 +27,6 @@ const store = configureStore(); const initialState = JSON.parse(document.getElementById('initial-state').textContent); store.dispatch(hydrateStore(initialState)); -const browserHistory = useRouterHistory(createBrowserHistory)({ - basename: '/web', -}); - class Mastodon extends React.PureComponent { componentDidMount() { @@ -136,36 +107,11 @@ class Mastodon extends React.PureComponent { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + ); diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 15fdd1a50..0ea8ad347 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import DropdownMenu from '../../../components/dropdown_menu'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index d2fe86476..55fdc4167 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -33,12 +33,12 @@ class Header extends ImmutablePureComponent { } handleMention = () => { - this.props.onMention(this.props.account, this.context.router); + this.props.onMention(this.props.account, this.context.router.history); } handleReport = () => { this.props.onReport(this.props.account); - this.context.router.push('/report'); + this.context.router.history.push('/report'); } handleMute = () => { diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 6f3dbc5af..1c135a733 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -5,7 +5,7 @@ import IconButton from '../../../components/icon_button'; import DisplayName from '../../../components/display_name'; import Permalink from '../../../components/permalink'; import { FormattedMessage } from 'react-intl'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import ImmutablePureComponent from 'react-immutable-pure-component'; class NavigationBar extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index 8ad401121..474549a5f 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -31,7 +31,7 @@ class ReplyIndicator extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0) { e.preventDefault(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } } diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index a553a8280..26d766a1c 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import ImmutablePureComponent from 'react-immutable-pure-component'; class SearchResults extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index e7d933e86..a87e48a23 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -5,7 +5,7 @@ import NavigationContainer from './containers/navigation_container'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { mountCompose, unmountCompose } from '../../actions/compose'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import { injectIntl, defineMessages } from 'react-intl'; import SearchContainer from './containers/search_container'; import Motion from 'react-motion/lib/Motion'; diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 446fdbc6e..286f128f4 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -54,6 +54,7 @@ class FollowRequests extends ImmutablePureComponent { return ( +
{accountIds.map(id => diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index f30dea446..a4549e609 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -2,7 +2,7 @@ import React from 'react'; import Column from '../ui/components/column'; import ColumnLink from '../ui/components/column_link'; import ColumnSubheading from '../ui/components/column_subheading'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 6d3968751..ddebf2379 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -8,7 +8,7 @@ import ColumnHeader from '../../components/column_header'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; const messages = defineMessages({ title: { id: 'column.home', defaultMessage: 'Home' }, diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js index 217802b5c..23aba39de 100644 --- a/app/javascript/mastodon/features/report/index.js +++ b/app/javascript/mastodon/features/report/index.js @@ -52,7 +52,7 @@ class Report extends React.PureComponent { componentWillMount () { if (!this.props.account) { - this.context.router.replace('/'); + this.context.router.history.replace('/'); } } @@ -76,7 +76,7 @@ class Report extends React.PureComponent { handleSubmit = () => { this.props.dispatch(submitReport()); - this.context.router.replace('/'); + this.context.router.history.replace('/'); } render () { diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 16ea83e40..21c2fd682 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -50,12 +50,12 @@ class ActionBar extends React.PureComponent { } handleMentionClick = () => { - this.props.onMention(this.props.status.get('account'), this.context.router); + this.props.onMention(this.props.status.get('account'), this.context.router.history); } handleReport = () => { this.props.onReport(this.props.status); - this.context.router.push('/report'); + this.context.router.history.push('/report'); } render () { diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 6bbb8ca33..a77c4f0bb 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -7,7 +7,7 @@ import StatusContent from '../../../components/status_content'; import MediaGallery from '../../../components/media_gallery'; import VideoPlayer from '../../../components/video_player'; import AttachmentList from '../../../components/attachment_list'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; import { FormattedDate, FormattedNumber } from 'react-intl'; import CardContainer from '../containers/card_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -28,7 +28,7 @@ class DetailedStatus extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0) { e.preventDefault(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } e.stopPropagation(); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index f8a87ccb1..19cee2435 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -93,7 +93,7 @@ class Status extends ImmutablePureComponent { } handleReplyClick = (status) => { - this.props.dispatch(replyCompose(status, this.context.router)); + this.props.dispatch(replyCompose(status, this.context.router.history)); } handleModalReblog = (status) => { diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index 9d99b5336..da2be5264 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -40,7 +40,7 @@ class BoostModal extends ImmutablePureComponent { if (e.button === 0) { e.preventDefault(); this.props.onClose(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } } diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js index 24387af57..cbdb6534f 100644 --- a/app/javascript/mastodon/features/ui/components/column_link.js +++ b/app/javascript/mastodon/features/ui/components/column_link.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Link from 'react-router/lib/Link'; +import Link from 'react-router-dom/Link'; const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => { if (href) { diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index b108e8244..265cb818b 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -1,5 +1,5 @@ import React from 'react'; -import Link from 'react-router/lib/Link'; +import NavLink from 'react-router-dom/NavLink'; import { FormattedMessage } from 'react-intl'; class TabsBar extends React.Component { @@ -7,14 +7,14 @@ class TabsBar extends React.Component { render () { return (
- - - + + + - - + + - +
); } diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 9f31b5443..39600607f 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -1,4 +1,7 @@ import React from 'react'; +import Switch from 'react-router-dom/Switch'; +import Route from 'react-router-dom/Route'; +import Redirect from 'react-router-dom/Redirect'; import NotificationsContainer from './containers/notifications_container'; import PropTypes from 'prop-types'; import LoadingBarContainer from './containers/loading_bar_container'; @@ -13,6 +16,67 @@ import { refreshNotifications } from '../../actions/notifications'; import UploadArea from './components/upload_area'; import ColumnsAreaContainer from './containers/columns_area_container'; +import Status from '../../features/status'; +import GettingStarted from '../../features/getting_started'; +import PublicTimeline from '../../features/public_timeline'; +import CommunityTimeline from '../../features/community_timeline'; +import AccountTimeline from '../../features/account_timeline'; +import AccountGallery from '../../features/account_gallery'; +import HomeTimeline from '../../features/home_timeline'; +import Compose from '../../features/compose'; +import Followers from '../../features/followers'; +import Following from '../../features/following'; +import Reblogs from '../../features/reblogs'; +import Favourites from '../../features/favourites'; +import HashtagTimeline from '../../features/hashtag_timeline'; +import Notifications from '../../features/notifications'; +import FollowRequests from '../../features/follow_requests'; +import GenericNotFound from '../../features/generic_not_found'; +import FavouritedStatuses from '../../features/favourited_statuses'; +import Blocks from '../../features/blocks'; +import Mutes from '../../features/mutes'; +import Report from '../../features/report'; + +// Small wrapper to pass multiColumn to the route components +const WrappedSwitch = ({ multiColumn, children }) => ( + + {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))} + +); + +WrappedSwitch.propTypes = { + multiColumn: PropTypes.bool, + children: PropTypes.node, +}; + +// Small Wraper to extract the params from the route and pass +// them to the rendered component, together with the content to +// be rendered inside (the children) +class WrappedRoute extends React.Component { + + static propTypes = { + component: PropTypes.func.isRequired, + content: PropTypes.node, + multiColumn: PropTypes.bool, + } + + renderComponent = ({ match: { params } }) => { + const { component: Component, content, multiColumn } = this.props; + + return {content}; + } + + render () { + const { component: Component, content, ...rest } = this.props; + + return ; + } + +} + +const noOp = () => false; + + class UI extends React.PureComponent { static propTypes = { @@ -119,7 +183,36 @@ class UI extends React.PureComponent { return (
- {children} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json index 0abea3235..e6fc02bbb 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "glob": "^7.1.1", - "history": "^3.3.0", "http-link-header": "^0.8.0", "immutable": "^3.8.1", "intersection-observer": "^0.3.0", @@ -86,8 +85,8 @@ "react-notification": "^6.7.0", "react-redux": "^5.0.4", "react-redux-loading-bar": "^2.9.2", - "react-router": "^3.0.5", - "react-router-scroll": "^0.4.2", + "react-router-dom": "^4.1.1", + "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", "react-textarea-autosize": "^5.0.6", "react-toggle": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 5532efa6b..26c6f7c7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1906,7 +1906,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.5.3: +create-react-class@^15.5.2, create-react-class@^15.5.3: version "15.5.3" resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.5.3.tgz#fb0f7cae79339e9a179e194ef466efa3923820fe" dependencies: @@ -3244,13 +3244,14 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" -history@^3.0.0, history@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c" +history@^4.5.1, history@^4.6.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.6.1.tgz#911cf8eb65728555a94f2b12780a0c531a14d2fd" dependencies: invariant "^2.2.1" loose-envify "^1.2.0" - query-string "^4.2.2" + resolve-pathname "^2.0.0" + value-equal "^0.2.0" warning "^3.0.0" hmac-drbg@^1.0.0: @@ -4806,7 +4807,7 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" -path-to-regexp@^1.7.0: +path-to-regexp@^1.5.3, path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -5511,7 +5512,7 @@ qs@6.4.0, qs@^6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -query-string@^4.1.0, query-string@^4.2.2: +query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" dependencies: @@ -5711,24 +5712,32 @@ react-redux@^5.0.4: loose-envify "^1.1.0" prop-types "^15.5.10" -react-router-scroll@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/react-router-scroll/-/react-router-scroll-0.4.2.tgz#4b90e8707edf96eba7f066d94c5b4338bd6848b7" +react-router-dom@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.1.1.tgz#3021ade1f2c160af97cf94e25594c5f294583025" dependencies: - prop-types "^15.5.6" - scroll-behavior "^0.9.3" + history "^4.5.1" + loose-envify "^1.3.1" + prop-types "^15.5.4" + react-router "^4.1.1" + +react-router-scroll@ytase/react-router-scroll#build: + version "0.4.1" + resolved "https://codeload.github.com/ytase/react-router-scroll/tar.gz/991ecddb08885e1fb80ec1e9dbf3a35844b7d4cd" + dependencies: + scroll-behavior "^0.9.1" warning "^3.0.0" -react-router@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.0.5.tgz#c3b7873758045a8bbc9562aef4ff4bc8cce7c136" +react-router@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.1.1.tgz#d448f3b7c1b429a6fbb03395099949c606b1fe95" dependencies: - create-react-class "^15.5.1" - history "^3.0.0" + history "^4.6.0" hoist-non-react-statics "^1.2.0" - invariant "^2.2.1" - loose-envify "^1.2.0" - prop-types "^15.5.6" + invariant "^2.2.2" + loose-envify "^1.3.1" + path-to-regexp "^1.5.3" + prop-types "^15.5.4" warning "^3.0.0" react-simple-di@^1.2.0: @@ -6085,9 +6094,13 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve-pathname@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.1.0.tgz#e8358801b86b83b17560d4e3c382d7aef2100944" + resolve-url-loader@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.2.tgz#c465e97ea0a4791f3961f766cea775ff2e3ceb8c" + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.3.tgz#f54cd1b040e8f0ab72b2cb32c9fbb8544152d9e9" dependencies: adjust-sourcemap-loader "^1.1.0" camelcase "^4.0.0" @@ -6201,7 +6214,7 @@ schema-utils@^0.3.0, schema-utils@^0.x: dependencies: ajv "^5.0.0" -scroll-behavior@^0.9.3: +scroll-behavior@^0.9.1: version "0.9.3" resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.3.tgz#e48bcc8af364f3f07176e8dbca3968bd5e71557b" dependencies: @@ -6977,6 +6990,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +value-equal@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.2.1.tgz#c220a304361fce6994dbbedaa3c7e1a1b895871d" + vary@~1.1.0, vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" From 356df7ae6b6edfc600fbc532f32fbc46314e00a0 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Wed, 21 Jun 2017 03:40:45 +0900 Subject: [PATCH 032/382] Update fabricator for MediaAttachment to attach a file according to type (#3862) This fixes a random spec failures since commit d55f207274648369cba30ff001aa3e354fa30dca. --- spec/fabricators/media_attachment_fabricator.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb index c5dfe12e5..bb938e36d 100644 --- a/spec/fabricators/media_attachment_fabricator.rb +++ b/spec/fabricators/media_attachment_fabricator.rb @@ -1,4 +1,16 @@ Fabricator(:media_attachment) do account - file { [attachment_fixture(['attachment.gif', 'attachment.jpg', 'attachment.webm'].sample), nil].sample } + file do |attrs| + [ + case attrs[:type] + when :gifv + attachment_fixture ['attachment.gif', 'attachment.webm'].sample + when :image + attachment_fixture 'attachment.jpg' + when nil + attachment_fixture ['attachment.gif', 'attachment.jpg', 'attachment.webm'].sample + end, + nil + ].sample + end end From a20cf3b64e93d764f1dfe88ecdb39f3fd4eefe03 Mon Sep 17 00:00:00 2001 From: unarist Date: Wed, 21 Jun 2017 03:40:56 +0900 Subject: [PATCH 033/382] Fix RemoteFollow behavior (#3868) * Invalid acct is an error. not "2 errors". * Empty input should be different error from invalid acct --- app/models/remote_follow.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb index 3da3ba0ae..8366d43c5 100644 --- a/app/models/remote_follow.rb +++ b/app/models/remote_follow.rb @@ -5,11 +5,15 @@ class RemoteFollow attr_accessor :acct, :addressable_template + validates :acct, presence: true + def initialize(attrs = {}) @acct = attrs[:acct].gsub(/\A@/, '').strip unless attrs[:acct].nil? end def valid? + return false unless super + populate_template errors.empty? end @@ -39,7 +43,6 @@ class RemoteFollow def acct_resource @_acct_resource ||= Goldfinger.finger("acct:#{acct}") rescue Goldfinger::Error - missing_resource_error nil end From bab5a18232a163b0c3c6a245f7f95d50d7022b36 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Wed, 21 Jun 2017 03:41:23 +0900 Subject: [PATCH 034/382] Filter direct statuses in Status.as_home_timeline (#3842) The classes using Status.as_home_timeline, namely Feed and PrecomputeFeedService are expected to filter direct statuses as FanOutWriteService does, but their filtering were incomplete or missing. This commit solves the problem by filtering direct statuses in as_home_timeline as the other similar methods such as as_public_timeline does. --- app/models/status.rb | 10 ++++++++- app/services/precompute_feed_service.rb | 6 +----- spec/models/status_spec.rb | 28 +++++++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/models/status.rb b/app/models/status.rb index 24d3db2bf..544d2e005 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -131,7 +131,15 @@ class Status < ApplicationRecord end def as_home_timeline(account) - where(account: [account] + account.following) + # 'references' is a workaround for the following issue: + # Inconsistent results with #or in ActiveRecord::Relation with respect to documentation Issue #24055 rails/rails + # https://github.com/rails/rails/issues/24055 + references(:mentions) + .where.not(visibility: :direct) + .or(where(mentions: { account: account })) + .where(follows: { account_id: account }) + .or(references(:mentions, :follows).where(account: account)) + .left_outer_joins(account: :followers).left_outer_joins(:mentions).group(:id) end def as_public_timeline(account = nil, local_only = false) diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index 626ec2f6c..83765bb05 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -23,11 +23,7 @@ class PrecomputeFeedService < BaseService end def process_status(status) - add_status_to_feed(status) unless skip_status?(status) - end - - def skip_status?(status) - status.direct_visibility? || status_filtered?(status) + add_status_to_feed(status) unless status_filtered?(status) end def add_status_to_feed(status) diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index dd52a5d43..a3ced1abc 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -181,15 +181,18 @@ RSpec.describe Status, type: :model do end describe '.as_home_timeline' do + let(:account) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:not_followed) { Fabricate(:account) } + before do - account = Fabricate(:account) - followed = Fabricate(:account) - not_followed = Fabricate(:account) Fabricate(:follow, account: account, target_account: followed) - @self_status = Fabricate(:status, account: account) - @followed_status = Fabricate(:status, account: followed) - @not_followed_status = Fabricate(:status, account: not_followed) + @self_status = Fabricate(:status, account: account, visibility: :public) + @self_direct_status = Fabricate(:status, account: account, visibility: :direct) + @followed_status = Fabricate(:status, account: followed, visibility: :public) + @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct) + @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public) @results = Status.as_home_timeline(account) end @@ -198,10 +201,23 @@ RSpec.describe Status, type: :model do expect(@results).to include(@self_status) end + it 'includes direct statuses from self' do + expect(@results).to include(@self_direct_status) + end + it 'includes statuses from followed' do expect(@results).to include(@followed_status) end + it 'includes direct statuses mentioning recipient from followed' do + Fabricate(:mention, account: account, status: @followed_direct_status) + expect(@results).to include(@followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from followed' do + expect(@results).not_to include(@followed_direct_status) + end + it 'does not include statuses from non-followed' do expect(@results).not_to include(@not_followed_status) end From d8ec83280637e53ded67d4938a198cbeb9e8db05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=AB=E5=90=B8=E8=A1=80=E9=AC=BC=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=95=E3=83=AA=E3=82=B9=20/=20=E7=8C=AB=E3=83=AD=E3=82=ADP?= Date: Wed, 21 Jun 2017 03:41:41 +0900 Subject: [PATCH 035/382] Fix streaming server. Redis connection subscribe for each channel. (#3828) --- streaming/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/streaming/index.js b/streaming/index.js index 270ed6f70..5afdd5961 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -115,7 +115,7 @@ const startWorker = (workerId) => { const subs = {}; - redisSubscribeClient.on('pmessage', (_, channel, message) => { + redisSubscribeClient.on('message', (channel, message) => { const callbacks = subs[channel]; log.silly(`New message on channel ${channel}`); @@ -127,8 +127,6 @@ const startWorker = (workerId) => { callbacks.forEach(callback => callback(message)); }); - redisSubscribeClient.psubscribe(`${redisPrefix}timeline:*`); - const subscriptionHeartbeat = (channel) => { const interval = 6*60; const tellSubscribed = () => { @@ -144,12 +142,20 @@ const startWorker = (workerId) => { const subscribe = (channel, callback) => { log.silly(`Adding listener for ${channel}`); subs[channel] = subs[channel] || []; + if (subs[channel].length === 0) { + log.verbose(`Subscribe ${channel}`); + redisSubscribeClient.subscribe(channel); + } subs[channel].push(callback); }; const unsubscribe = (channel, callback) => { log.silly(`Removing listener for ${channel}`); subs[channel] = subs[channel].filter(item => item !== callback); + if (subs[channel].length === 0) { + log.verbose(`Unsubscribe ${channel}`); + redisSubscribeClient.unsubscribe(channel); + } }; const allowCrossDomain = (req, res, next) => { From 15b43f555dcb06742c677d84c25d05774ef1bde2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 20 Jun 2017 20:44:32 +0200 Subject: [PATCH 036/382] Fix conversations (fixes #3869) (#3870) * Actually create conversations given explicit URIs * Try to get the parent toot in before validation, to avoid creating a new conversation --- app/services/process_feed_service.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index eabeb1799..fbdf92caa 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -66,8 +66,6 @@ class ProcessFeedService < BaseService status.reblog = original_status.reblog? ? original_status.reblog : original_status end - status.thread = find_status(thread(@xml).first) if thread?(@xml) - status.save! end @@ -155,7 +153,8 @@ class ProcessFeedService < BaseService reply: thread?(entry), language: content_language(entry), visibility: visibility_scope(entry), - conversation: find_or_create_conversation(entry) + conversation: find_or_create_conversation(entry), + thread: thread?(entry) ? find_status(thread(entry).first) : nil ) mentions_from_xml(status, entry) @@ -174,7 +173,7 @@ class ProcessFeedService < BaseService return Conversation.find_by(id: local_id) end - Conversation.find_by(uri: uri) + Conversation.find_by(uri: uri) || Conversation.create!(uri: uri) end def find_status(uri) From 1585b0c6cce6cebda3fdc8ba944aa2247e76d1be Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 20 Jun 2017 21:32:37 +0200 Subject: [PATCH 037/382] Bump version to 1.4.4 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 234073f3a..5f90e83bb 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 3 + 4 end def pre From 31cd64904198551e222a47aa7b2ce4cd8d4370d2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 21 Jun 2017 01:33:14 +0200 Subject: [PATCH 038/382] Revert "Don't attach IntersectionObserver for wrapped statuses" (#3877) * Revert "Bump version to 1.4.4" This reverts commit 1585b0c6cce6cebda3fdc8ba944aa2247e76d1be. * Revert "Fix conversations (fixes #3869) (#3870)" This reverts commit 15b43f555dcb06742c677d84c25d05774ef1bde2. * Revert "Fix streaming server. Redis connection subscribe for each channel. (#3828)" This reverts commit d8ec83280637e53ded67d4938a198cbeb9e8db05. * Revert "Filter direct statuses in Status.as_home_timeline (#3842)" This reverts commit bab5a18232a163b0c3c6a245f7f95d50d7022b36. * Revert "Fix RemoteFollow behavior (#3868)" This reverts commit a20cf3b64e93d764f1dfe88ecdb39f3fd4eefe03. * Revert "Update fabricator for MediaAttachment to attach a file according to type (#3862)" This reverts commit 356df7ae6b6edfc600fbc532f32fbc46314e00a0. * Revert "Upgrade React Router (#3677)" This reverts commit 8f03fdce7fa16fcaa829a40f395cefc85eb957d5. * Revert "Do not call setState from unmounted component (#3853)" This reverts commit 1fc6cb499742c1a872ad717a689c58a80aeb714d. * Revert "Replace TextIconButton for SensitiveButton to IconButton (#3759)" This reverts commit eb832e88f44d661a504a091defc051e052eb1252. * Revert "Fix RTL detection on Ruby side (#3867)" This reverts commit b16b69350eb4ded2e1011931433b51dac5e34b53. * Revert "i18n: Fixed typo in Polish translation (#3864)" This reverts commit da6fa029f66d50f38f2b6c85687994793f7766aa. * Revert "Don't attach IntersectionObserver for wrapped statuses (#3863)" This reverts commit 94ad0706f514e25c58c0a8f3201f96c1d1ccbbd8. --- app/javascript/mastodon/components/status.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index b2214f72a..e77d1eb91 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -162,10 +162,7 @@ class Status extends ImmutablePureComponent { render () { let media = null; let statusAvatar; - - // Exclude intersectionObserverWrapper from `other` variable - // because intersection is managed in here. - const { status, account, intersectionObserverWrapper, ...other } = this.props; + const { status, account, ...other } = this.props; const { isExpanded, isIntersecting, isHidden } = this.state; if (status === null) { From 946a166791e5a1a10778e15ca4a9f5f10c961134 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 21 Jun 2017 01:37:15 +0200 Subject: [PATCH 039/382] Revert #3851 (#3878) --- app/javascript/mastodon/components/status.js | 8 -------- .../features/ui/util/intersection_observer_wrapper.js | 7 ------- 2 files changed, 15 deletions(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index e77d1eb91..325aceb5b 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -91,14 +91,6 @@ class Status extends ImmutablePureComponent { } componentWillUnmount () { - if (!this.props.intersectionObserverWrapper) { - // TODO: enable IntersectionObserver optimization for notification statuses. - // These are managed in notifications/index.js rather than status_list.js - return; - } - - this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node); - this.componentMounted = false; } diff --git a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js index b15f8b9ef..0e959f9ae 100644 --- a/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js +++ b/app/javascript/mastodon/features/ui/util/intersection_observer_wrapper.js @@ -37,13 +37,6 @@ class IntersectionObserverWrapper { } } - unobserve (id, node) { - if (this.observer) { - delete this.callbacks[id]; - this.observer.unobserve(node); - } - } - disconnect () { if (this.observer) { this.observer.disconnect(); From cc382c5006ed5c4c18c3547150b214807b217c7b Mon Sep 17 00:00:00 2001 From: unarist Date: Wed, 21 Jun 2017 13:47:36 +0900 Subject: [PATCH 040/382] Don't attach IntersectionObserver for wrapped statuses (#3883) (This patch has been merged as bugfix and reverted, but still valuable as improvement) Previously, we've attached IntersectionObserver twice for boosted statuses: wrapper Status and wrapped Status. but wrapped Status don't need to manage intersection and visibility by itself, because it's a part of wrapper Status. --- app/javascript/mastodon/components/status.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 325aceb5b..1363956cf 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -154,7 +154,10 @@ class Status extends ImmutablePureComponent { render () { let media = null; let statusAvatar; - const { status, account, ...other } = this.props; + + // Exclude intersectionObserverWrapper from `other` variable + // because intersection is managed in here. + const { status, account, intersectionObserverWrapper, ...other } = this.props; const { isExpanded, isIntersecting, isHidden } = this.state; if (status === null) { From 0190aac240fd804180a56b5fe174526a9e4c3f6d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Jun 2017 02:38:50 +0200 Subject: [PATCH 041/382] Fix regression from #3842 (#3892) * Fix regression from #3842 Simplify the query by omitting all direct statuses. Private statuses are allowed because they are from accounts we are following (so by definition) Resolves #3887 (alternative) * Adjust test --- app/models/status.rb | 10 +--------- spec/models/status_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/app/models/status.rb b/app/models/status.rb index 544d2e005..791d96df1 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -131,15 +131,7 @@ class Status < ApplicationRecord end def as_home_timeline(account) - # 'references' is a workaround for the following issue: - # Inconsistent results with #or in ActiveRecord::Relation with respect to documentation Issue #24055 rails/rails - # https://github.com/rails/rails/issues/24055 - references(:mentions) - .where.not(visibility: :direct) - .or(where(mentions: { account: account })) - .where(follows: { account_id: account }) - .or(references(:mentions, :follows).where(account: account)) - .left_outer_joins(account: :followers).left_outer_joins(:mentions).group(:id) + where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private]) end def as_public_timeline(account = nil, local_only = false) diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index a3ced1abc..0b90205ee 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -201,17 +201,17 @@ RSpec.describe Status, type: :model do expect(@results).to include(@self_status) end - it 'includes direct statuses from self' do - expect(@results).to include(@self_direct_status) + it 'does not include direct statuses from self' do + expect(@results).to_not include(@self_direct_status) end it 'includes statuses from followed' do expect(@results).to include(@followed_status) end - it 'includes direct statuses mentioning recipient from followed' do + it 'does not include direct statuses mentioning recipient from followed' do Fabricate(:mention, account: account, status: @followed_direct_status) - expect(@results).to include(@followed_direct_status) + expect(@results).to_not include(@followed_direct_status) end it 'does not include direct statuses not mentioning recipient from followed' do From f566c47ddafd1e119887133c35581a5aef1c07fc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 22 Jun 2017 13:24:00 +0200 Subject: [PATCH 042/382] Fix travis builds https://github.com/travis-ci/travis-ci/issues/7941#issuecomment-310320597 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8bef9c4ae..4bb332666 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ cache: - public/assets - public/packs-test dist: trusty -sudo: false +sudo: required notifications: email: false From 98fab24beada0bb681a25d9a48918f3ea903a90a Mon Sep 17 00:00:00 2001 From: Quent-in Date: Thu, 22 Jun 2017 13:39:13 +0200 Subject: [PATCH 043/382] Update of doorkeeper.oc.yml (#3896) Just some adjustements --- config/locales/doorkeeper.oc.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/locales/doorkeeper.oc.yml b/config/locales/doorkeeper.oc.yml index b60c28404..9f5d3fe55 100644 --- a/config/locales/doorkeeper.oc.yml +++ b/config/locales/doorkeeper.oc.yml @@ -25,18 +25,18 @@ oc: confirmations: destroy: Sètz segur ? edit: - title: Modificar l'aplicacion + title: Modificar l’aplicacion form: error: Ops ! Verificatz vòstre formulari help: - native_redirect_uri: Emplegatz %{native_redirect_uri} per d'ensages locales + native_redirect_uri: Emplegatz %{native_redirect_uri} per d’ensages locales redirect_uri: Utilizatz una linha per URI scopes: Separatz los encastres amb d’espacis. Daissatz void per utilizar l’encastre per defaut. index: callback_url: URL de rapèl name: Nom new: Nòva aplicacion - title: Vòstra aplicacions + title: Vòstras aplicacions new: title: Nòva aplicacion show: @@ -45,7 +45,7 @@ oc: callback_urls: urls de rapèls scopes: Encastres secret: Secret - title: 'Aplicacion: %{name}' + title: 'Aplicacion : %{name}' authorizations: buttons: authorize: Autorizar @@ -62,7 +62,7 @@ oc: buttons: revoke: Revocar confirmations: - revoke: Ne sètz segur? + revoke: Ne sètz segur ? index: application: Aplicacion created_at: Creada lo @@ -75,7 +75,7 @@ oc: credential_flow_not_configured: Lo flux de qualificacion del senhal del proprietari de la ressorça capitèt pas pr’amor que Doorkeeper.configure.resource_owner_from_credentials es pas configurat. invalid_client: L’autorizacion del client capitèt pas pr’amor que lo client es desconegut, l’autorizacion del client es pas enclús, o lo metòde d’autorizacion es pas suportat. invalid_grant: L’acòrdi d’autorizacion donadat es pas valid, expirat, revocat, una redireccion URI utilizat en la demanda d’autorizacion no correspond, o a estat desliurat a un altre client. - invalid_redirect_uri: L'URL de redireccion es pas valida. + invalid_redirect_uri: L’URL de redireccion es pas valida. invalid_request: La demanda a un paramètre que li manca, a una valor qu’es pas suportada, o quicòm mal format. invalid_resource_owner: La qualificacion del proprietari de la ressorça donada es pas valid, o lo proprietari de la ressorça se pòt pas trobar. invalid_scope: L’encastre demandat es pas valid, o mal format. @@ -109,5 +109,5 @@ oc: title: Cal una autorizacion OAuth scopes: follow: sègre, blocar, quitar de blocar e quitar de sègre de comptes - read: legissètz las donadas de vòstre compte - write: publicatz per vos + read: legir las donadas de vòstre compte + write: publicar per vos From e27f792c24d1040246bc6ae890e6052f763cdb84 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Fri, 23 Jun 2017 01:34:27 +0900 Subject: [PATCH 044/382] Some minor change and spec for Account (#3813) * Introduce domains method to Account relation Account had followers_domains method, which was excessively specific. Let relation of Account have domains method instead. * Move follow_mapping in Account to AccountInteractions * Introduce shared examples for AccountAvatar inclusion * Cover Account more --- app/models/account.rb | 12 +- app/models/concerns/account_interactions.rb | 6 + .../pubsubhubbub/distribution_worker.rb | 2 +- spec/models/account_spec.rb | 417 +++++++++++++++--- .../models/concerns/account_avatar.rb | 19 + 5 files changed, 380 insertions(+), 76 deletions(-) create mode 100644 spec/support/examples/models/concerns/account_avatar.rb diff --git a/app/models/account.rb b/app/models/account.rb index 72bad51a2..2b54cee5f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -124,10 +124,6 @@ class Account < ApplicationRecord subscription_expires_at.present? end - def followers_domains - followers.reorder(nil).pluck('distinct accounts.domain') - end - def keypair OpenSSL::PKey::RSA.new(private_key || public_key) end @@ -163,6 +159,10 @@ class Account < ApplicationRecord end class << self + def domains + reorder(nil).pluck('distinct accounts.domain') + end + def triadic_closures(account, limit: 5, offset: 0) sql = <<-SQL.squish WITH first_degree AS ( @@ -236,10 +236,6 @@ class Account < ApplicationRecord [textsearch, query] end - - def follow_mapping(query, field) - query.pluck(field).each_with_object({}) { |id, mapping| mapping[id] = true } - end end before_create :generate_keys diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index de5979958..9ffed2910 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -29,6 +29,12 @@ module AccountInteractions blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain) accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h end + + private + + def follow_mapping(query, field) + query.pluck(field).each_with_object({}) { |id, mapping| mapping[id] = true } + end end included do diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb index da01dcd91..b41cec90d 100644 --- a/app/workers/pubsubhubbub/distribution_worker.rb +++ b/app/workers/pubsubhubbub/distribution_worker.rb @@ -33,7 +33,7 @@ class Pubsubhubbub::DistributionWorker return if stream_entries.empty? @payload = AtomSerializer.render(AtomSerializer.new.feed(@account, stream_entries)) - @domains = @account.followers_domains + @domains = @account.followers.domains Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions.reject { |s| !allowed_to_receive?(s.callback_url) }) do |subscription| [subscription.id, @payload] diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 73ae34f8e..eeaebb779 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -1,10 +1,9 @@ require 'rails_helper' RSpec.describe Account, type: :model do - subject { Fabricate(:account, username: 'alice') } - context do let(:bob) { Fabricate(:account, username: 'bob') } + subject { Fabricate(:account) } describe '#follow!' do it 'creates a follow' do @@ -45,12 +44,13 @@ RSpec.describe Account, type: :model do describe '#local?' do it 'returns true when the account is local' do - expect(subject.local?).to be true + account = Fabricate(:account, domain: nil) + expect(account.local?).to be true end it 'returns false when the account is on a different domain' do - subject.domain = 'foreign.tld' - expect(subject.local?).to be false + account = Fabricate(:account, domain: 'foreign.tld') + expect(account.local?).to be false end end @@ -61,6 +61,8 @@ RSpec.describe Account, type: :model do Rails.configuration.x.local_domain = before end + subject { Fabricate(:account, domain: nil, username: 'alice') } + describe '#to_webfinger_s' do it 'returns a webfinger string for the account' do Rails.configuration.x.local_domain = 'example.com' @@ -80,41 +82,72 @@ RSpec.describe Account, type: :model do describe '#acct' do it 'returns username for local users' do - expect(subject.acct).to eql 'alice' + account = Fabricate(:account, domain: nil, username: 'alice') + expect(account.acct).to eql 'alice' end it 'returns username@domain for foreign users' do - subject.domain = 'foreign.tld' - expect(subject.acct).to eql 'alice@foreign.tld' + account = Fabricate(:account, domain: 'foreign.tld', username: 'alice') + expect(account.acct).to eql 'alice@foreign.tld' + end + end + + describe '#save_with_optional_media!' do + it 'sets default avatar, header, avatar_remote_url, and header_remote_url if some of them are invalid' do + stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt')) + stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt')) + account = Fabricate(:account, + avatar_remote_url: 'https://remote/valid_avatar', + header_remote_url: 'https://remote/valid_avatar') + + account.avatar_remote_url = 'https://remote/invalid_avatar' + account.save_with_optional_media! + + account.reload + expect(account.avatar_remote_url).to eq '' + expect(account.header_remote_url).to eq '' + expect(account.avatar_file_name).to eq nil + expect(account.header_file_name).to eq nil end end describe '#subscribed?' do it 'returns false when no subscription expiration information is present' do - expect(subject.subscribed?).to be false + account = Fabricate(:account, subscription_expires_at: nil) + expect(account.subscribed?).to be false end it 'returns true when subscription expiration has been set' do - subject.subscription_expires_at = 30.days.from_now - expect(subject.subscribed?).to be true + account = Fabricate(:account, subscription_expires_at: 30.days.from_now) + expect(account.subscribed?).to be true + end + end + + describe '#to_param' do + it 'returns username' do + account = Fabricate(:account, username: 'alice') + expect(account.to_param).to eq 'alice' end end describe '#keypair' do it 'returns an RSA key pair' do - expect(subject.keypair).to be_instance_of OpenSSL::PKey::RSA + account = Fabricate(:account) + expect(account.keypair).to be_instance_of OpenSSL::PKey::RSA end end describe '#subscription' do it 'returns an OStatus subscription' do - expect(subject.subscription('')).to be_instance_of OStatus2::Subscription + account = Fabricate(:account) + expect(account.subscription('')).to be_instance_of OStatus2::Subscription end end describe '#object_type' do it 'is always a person' do - expect(subject.object_type).to be :person + account = Fabricate(:account) + expect(account.object_type).to be :person end end @@ -124,6 +157,8 @@ RSpec.describe Account, type: :model do Fabricate(:status, account: author) end + subject { Fabricate(:account) } + context 'when the status is a reblog of another status' do let(:original_reblog) do author = Fabricate(:account, username: 'original_reblogger') @@ -160,6 +195,8 @@ RSpec.describe Account, type: :model do Fabricate(:status, account: author) end + subject { Fabricate(:account) } + context 'when the status is a reblog of another status'do let(:original_reblog) do author = Fabricate(:account, username: 'original_reblogger') @@ -205,14 +242,16 @@ RSpec.describe Account, type: :model do end end + describe '#excluded_from_timeline_domains' do + it 'returns the domains blocked by the account' do + account = Fabricate(:account) + account.block_domain!('domain') + expect(account.excluded_from_timeline_domains).to match_array ['domain'] + end + end + describe '.search_for' do before do - @match = Fabricate( - :account, - display_name: "Display Name", - username: "username", - domain: "example.com" - ) _missing = Fabricate( :account, display_name: "Missing", @@ -221,33 +260,103 @@ RSpec.describe Account, type: :model do ) end + it 'accepts ?, \, : and space as delimiter' do + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = Account.search_for('A?l\i:c e') + expect(results).to eq [match] + end + it 'finds accounts with matching display_name' do + match = Fabricate( + :account, + display_name: "Display Name", + username: "username", + domain: "example.com" + ) + results = Account.search_for("display") - expect(results).to eq [@match] + expect(results).to eq [match] end it 'finds accounts with matching username' do + match = Fabricate( + :account, + display_name: "Display Name", + username: "username", + domain: "example.com" + ) + results = Account.search_for("username") - expect(results).to eq [@match] + expect(results).to eq [match] end it 'finds accounts with matching domain' do + match = Fabricate( + :account, + display_name: "Display Name", + username: "username", + domain: "example.com" + ) + results = Account.search_for("example") - expect(results).to eq [@match] + expect(results).to eq [match] + end + + it 'limits by 10 by default' do + 11.times.each { Fabricate(:account, display_name: "Display Name") } + results = Account.search_for("display") + expect(results.size).to eq 10 + end + + it 'accepts arbitrary limits' do + 2.times.each { Fabricate(:account, display_name: "Display Name") } + results = Account.search_for("display", 1) + expect(results.size).to eq 1 end it 'ranks multiple matches higher' do - account = Fabricate( - :account, - username: "username", - display_name: "username" - ) + matches = [ + { username: "username", display_name: "username" }, + { display_name: "Display Name", username: "username", domain: "example.com" }, + ].map(&method(:Fabricate).curry(2).call(:account)) + results = Account.search_for("username") - expect(results).to eq [account, @match] + expect(results).to eq matches end end describe '.advanced_search_for' do + it 'accepts ?, \, : and space as delimiter' do + account = Fabricate(:account) + match = Fabricate( + :account, + display_name: 'A & l & i & c & e', + username: 'username', + domain: 'example.com' + ) + + results = Account.advanced_search_for('A?l\i:c e', account) + expect(results).to eq [match] + end + + it 'limits by 10 by default' do + 11.times { Fabricate(:account, display_name: "Display Name") } + results = Account.search_for("display") + expect(results.size).to eq 10 + end + + it 'accepts arbitrary limits' do + 2.times { Fabricate(:account, display_name: "Display Name") } + results = Account.search_for("display", 1) + expect(results.size).to eq 1 + end + it 'ranks followed accounts higher' do account = Fabricate(:account) match = Fabricate(:account, username: "Matching") @@ -260,9 +369,14 @@ RSpec.describe Account, type: :model do end end - describe '.triadic_closures' do - subject { described_class.triadic_closures(me) } + describe '.domains' do + it 'returns domains' do + Fabricate(:account, domain: 'domain') + expect(Account.domains).to match_array(['domain']) + end + end + describe '.triadic_closures' do let!(:me) { Fabricate(:account) } let!(:friend) { Fabricate(:account) } let!(:friends_friend) { Fabricate(:account) } @@ -277,7 +391,39 @@ RSpec.describe Account, type: :model do end it 'finds accounts you dont follow which are followed by accounts you do follow' do - is_expected.to eq [friends_friend] + expect(described_class.triadic_closures(me)).to eq [friends_friend] + end + + it 'limits by 5 with offset 0 by defualt' do + first_degree = 6.times.map { Fabricate(:account) } + matches = 5.times.map { Fabricate(:account) } + first_degree.each { |account| me.follow!(account) } + matches.each do |match| + first_degree.each { |account| account.follow!(match) } + first_degree.shift + end + + expect(described_class.triadic_closures(me)).to eq matches + end + + it 'accepts arbitrary limits' do + another_friend = Fabricate(:account) + higher_friends_friend = Fabricate(:account) + me.follow!(another_friend) + friend.follow!(higher_friends_friend) + another_friend.follow!(higher_friends_friend) + + expect(described_class.triadic_closures(me, limit: 1)).to eq [higher_friends_friend] + end + + it 'acceps arbitrary offset' do + another_friend = Fabricate(:account) + higher_friends_friend = Fabricate(:account) + me.follow!(another_friend) + friend.follow!(higher_friends_friend) + another_friend.follow!(higher_friends_friend) + + expect(described_class.triadic_closures(me, offset: 1)).to eq [friends_friend] end context 'when you block account' do @@ -286,7 +432,7 @@ RSpec.describe Account, type: :model do end it 'rejects blocked accounts' do - is_expected.to be_empty + expect(described_class.triadic_closures(me)).to be_empty end end @@ -296,7 +442,7 @@ RSpec.describe Account, type: :model do end it 'rejects muted accounts' do - is_expected.to be_empty + expect(described_class.triadic_closures(me)).to be_empty end end end @@ -374,26 +520,26 @@ RSpec.describe Account, type: :model do expect(account).to model_have_error_on_field(:username) end - it 'is invalid if the username already exists' do - account_1 = Fabricate(:account, username: 'the_doctor') - account_2 = Fabricate.build(:account, username: 'the_doctor') - account_2.valid? - expect(account_2).to model_have_error_on_field(:username) - end - - it 'is invalid if the username is reserved' do - account = Fabricate.build(:account, username: 'support') - account.valid? - expect(account).to model_have_error_on_field(:username) - end - - it 'is valid when username is reserved but record has already been created' do - account = Fabricate.build(:account, username: 'support') - account.save(validate: false) - expect(account.valid?).to be true - end - context 'when is local' do + it 'is invalid if the username is not unique in case-insensitive comparsion among local accounts' do + account_1 = Fabricate(:account, username: 'the_doctor') + account_2 = Fabricate.build(:account, username: 'the_Doctor') + account_2.valid? + expect(account_2).to model_have_error_on_field(:username) + end + + it 'is invalid if the username is reserved' do + account = Fabricate.build(:account, username: 'support') + account.valid? + expect(account).to model_have_error_on_field(:username) + end + + it 'is valid when username is reserved but record has already been created' do + account = Fabricate.build(:account, username: 'support') + account.save(validate: false) + expect(account.valid?).to be true + end + it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do account = Fabricate.build(:account, username: 'the-doctor') account.valid? @@ -405,10 +551,109 @@ RSpec.describe Account, type: :model do account.valid? expect(account).to model_have_error_on_field(:username) end + + it 'is invalid if the display name is longer than 30 characters' do + account = Fabricate.build(:account, display_name: Faker::Lorem.characters(31)) + account.valid? + expect(account).to model_have_error_on_field(:display_name) + end + + it 'is invalid if the note is longer than 160 characters' do + account = Fabricate.build(:account, note: Faker::Lorem.characters(161)) + account.valid? + expect(account).to model_have_error_on_field(:note) + end + end + + context 'when is remote' do + it 'is invalid if the username is not unique in case-sensitive comparison among accounts in the same normalized domain' do + Fabricate(:account, domain: 'にゃん', username: 'username') + account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'username') + account.valid? + expect(account).to model_have_error_on_field(:username) + end + + it 'is valid even if the username is unique only in case-sensitive comparison among accounts in the same normalized domain' do + Fabricate(:account, domain: 'にゃん', username: 'username') + account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'Username') + account.valid? + expect(account).not_to model_have_error_on_field(:username) + end + + it 'is valid even if the username doesn\'t only contains letters, numbers and underscores' do + account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor') + account.valid? + expect(account).not_to model_have_error_on_field(:username) + end + + it 'is valid even if the username is longer then 30 characters' do + account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(31)) + account.valid? + expect(account).not_to model_have_error_on_field(:username) + end + + it 'is valid even if the display name is longer than 30 characters' do + account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(31)) + account.valid? + expect(account).not_to model_have_error_on_field(:display_name) + end + + it 'is valid even if the note is longer than 160 characters' do + account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(161)) + account.valid? + expect(account).not_to model_have_error_on_field(:note) + end end end describe 'scopes' do + describe 'alphabetic' do + it 'sorts by alphabetic order of domain and username' do + matches = [ + { username: 'a', domain: 'a' }, + { username: 'b', domain: 'a' }, + { username: 'a', domain: 'b' }, + { username: 'b', domain: 'b' }, + ].map(&method(:Fabricate).curry(2).call(:account)) + + expect(Account.alphabetic).to eq matches + end + end + + describe 'matches_display_name' do + it 'matches display name which starts with the given string' do + match = Fabricate(:account, display_name: 'pattern and suffix') + Fabricate(:account, display_name: 'prefix and pattern') + + expect(Account.matches_display_name('pattern')).to eq [match] + end + end + + describe 'matches_username' do + it 'matches display name which starts with the given string' do + match = Fabricate(:account, username: 'pattern_and_suffix') + Fabricate(:account, username: 'prefix_and_pattern') + + expect(Account.matches_username('pattern')).to eq [match] + end + end + + describe 'expiring' do + it 'returns remote accounts with followers whose subscription expiration date is past or not given' do + local = Fabricate(:account, domain: nil) + matches = [ + { domain: 'remote', subscription_expires_at: nil }, + { domain: 'remote', subscription_expires_at: '2000-01-01T00:00:00Z' }, + ].map(&method(:Fabricate).curry(2).call(:account)) + matches.each(&local.method(:follow!)) + Fabricate(:account, domain: 'remote', subscription_expires_at: nil) + local.follow!(Fabricate(:account, domain: 'remote', subscription_expires_at: '2000-01-03T00:00:00Z')) + local.follow!(Fabricate(:account, domain: nil, subscription_expires_at: nil)) + + expect(Account.expiring('2000-01-02T00:00:00Z').recent).to eq matches.reverse + end + end + describe 'remote' do it 'returns an array of accounts who have a domain' do account_1 = Fabricate(:account, domain: nil) @@ -439,6 +684,24 @@ RSpec.describe Account, type: :model do end end + describe 'partitioned' do + it 'returns a relation of accounts partitioned by domain' do + matches = ['a', 'b', 'a', 'b'] + matches.size.times.to_a.shuffle.each do |index| + matches[index] = Fabricate(:account, domain: matches[index]) + end + + expect(Account.partitioned).to match_array(matches) + end + end + + describe 'recent' do + it 'returns a relation of accounts sorted by recent creation' do + matches = 2.times.map { Fabricate(:account) } + expect(Account.recent).to match_array(matches) + end + end + describe 'silenced' do it 'returns an array of accounts who are silenced' do account_1 = Fabricate(:account, silenced: true) @@ -454,25 +717,45 @@ RSpec.describe Account, type: :model do expect(Account.suspended).to match_array([account_1]) end end - end - describe 'static avatars' do - describe 'when GIF' do - it 'creates a png static style' do - subject.avatar = attachment_fixture('avatar.gif') - subject.save - - expect(subject.avatar_static_url).to_not eq subject.avatar_original_url + describe 'without_followers' do + it 'returns a relation of accounts without followers' do + account_1 = Fabricate(:account) + account_2 = Fabricate(:account) + Fabricate(:follow, account: account_1, target_account: account_2) + expect(Account.without_followers).to match_array([account_1]) end end - describe 'when non-GIF' do - it 'does not create extra static style' do - subject.avatar = attachment_fixture('attachment.jpg') - subject.save - - expect(subject.avatar_static_url).to eq subject.avatar_original_url + describe 'with_followers' do + it 'returns a relation of accounts with followers' do + account_1 = Fabricate(:account) + account_2 = Fabricate(:account) + Fabricate(:follow, account: account_1, target_account: account_2) + expect(Account.with_followers).to match_array([account_2]) end end end + + context 'when is local' do + it 'generates keys' do + account = Account.create!(domain: nil, username: Faker::Internet.user_name(nil, ['_'])) + expect(account.keypair.private?).to eq true + end + end + + context 'when is remote' do + it 'does not generate keys' do + key = OpenSSL::PKey::RSA.new(1024).public_key + account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(nil, ['_']), public_key: key.to_pem) + expect(account.keypair.params).to eq key.params + end + + it 'normalizes domain' do + account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(nil, ['_'])) + expect(account.domain).to eq 'xn--r9j5b5b' + end + end + + include_examples 'AccountAvatar', :account end diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb new file mode 100644 index 000000000..f2a8a2459 --- /dev/null +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +shared_examples 'AccountAvatar' do |fabricator| + describe 'static avatars' do + describe 'when GIF' do + it 'creates a png static style' do + account = Fabricate(fabricator, avatar: attachment_fixture('avatar.gif')) + expect(account.avatar_static_url).to_not eq account.avatar_original_url + end + end + + describe 'when non-GIF' do + it 'does not create extra static style' do + account = Fabricate(fabricator, avatar: attachment_fixture('attachment.jpg')) + expect(account.avatar_static_url).to eq account.avatar_original_url + end + end + end +end From 7bc18058270bd4d3b376bf0bf8f104ebdd178230 Mon Sep 17 00:00:00 2001 From: Charlotte Fields Date: Fri, 23 Jun 2017 02:35:27 +1000 Subject: [PATCH 045/382] fixed vagrantfile (#3897) --- Vagrantfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index f2302b24f..77b5e8350 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -45,6 +45,9 @@ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB8 curl -sSL https://get.rvm.io | bash -s stable --ruby=$RUBY_VERSION source /home/vagrant/.rvm/scripts/rvm +# Install Ruby +rvm install ruby-$RUBY_VERSION + # Configure database sudo -u postgres createuser -U postgres vagrant -s sudo -u postgres createdb -U postgres mastodon_development From 3e8e9c8ae40991ed17fecd504018c2b2fedb0c21 Mon Sep 17 00:00:00 2001 From: nightpool Date: Thu, 22 Jun 2017 17:28:52 -0400 Subject: [PATCH 046/382] Use the stable RVM installer (#3901) as mentioned by ElvenSpellmaker here: https://github.com/rvm/rvm/issues/4068 Adds a workaround for the issue mentioned by @abcang here: https://github.com/tootsuite/mastodon/pull/3897#issuecomment-310436668 and makes sure that we're using the stable installer to install the stable version. --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 77b5e8350..1f56fcfb3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -42,7 +42,7 @@ sudo apt-get install \ # Install rvm read RUBY_VERSION < .ruby-version gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 -curl -sSL https://get.rvm.io | 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 # Install Ruby From 9ca02a00a62855fa0daa39870e03bc88f695bbc0 Mon Sep 17 00:00:00 2001 From: Ratmir Karabut Date: Fri, 23 Jun 2017 00:28:58 +0300 Subject: [PATCH 047/382] Update Russian translation (#3902) * Add Russian translation (ru) * Fix a missing comma * Fix the wording for better consistency * Update Russian translation * Arrange Russian setting alphabetically * Fix syntax error * Update Russian translation * Fix formatting error * Update Russian translation * Update Russian translation * Update ru.jsx * Fix syntax error * Remove two_factor_auth.warning (appears obsolete) * Add missing strings in ru.yml A lot of new strings translated, especially for the newly added admin section * Fix translation consistency * Update Russian translation * Update Russian translation (pluralizations) * Update Russian translation * Update Russian translation * Update Russian translation (pin) * Update Russian translation (account deletion) * Fix extra line --- config/locales/ru.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b8c8b98b3..c16ab6869 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -169,6 +169,8 @@ ru: invalid_url: Введенный URL неверен auth: change_password: Изменить пароль + delete_account: Удалить аккаунт + delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете перейти сюда. У Вас будет запрошено подтверждение. didnt_get_confirmation: Не получили инструкцию для подтверждения? forgot_password: Забыли пароль? login: Войти @@ -196,6 +198,14 @@ ru: x_minutes: "%{count}мин" x_months: "%{count}мес" x_seconds: "%{count}сек" + deletes: + bad_password_msg: Не вышло, хакеры! Неверный пароль + confirm_password: Введите текущий пароль для подтверждения Вашей личности + description_html: Это действие перманентно и необратимо удалит контент Вашего аккаунта и деактивирует его. Ваше имя пользователя будет зарезервировано для предотвращения имперсонации в будущем. + proceed: Удалить аккаунт + success_msg: Ваш аккаунт был успешно удален + warning_html: Гарантируется удаление контента только на этом узле. Широко распространившийся контент, скорее всего, оставит следы. Сервера, отключенные от сети или отписавшиеся от Ваших обновлений, не обновят свои базы данных. + warning_title: О доступности распространившегося контента errors: '403': У Вас нет доступа к просмотру этой страницы. '404': Страница, которую Вы искали, не существует. @@ -203,6 +213,8 @@ ru: '422': content: Проверка безопасности не удалась. Возможно, Вы блокируете cookies? title: Проверка безопасности не удалась. + '429': Слишком много запросов + noscript: Для работы с Mastodon, пожалуйста, включите JavaScript. exports: blocks: Список блокировки csv: CSV @@ -283,6 +295,7 @@ ru: settings: authorized_apps: Авторизованные приложения back: Назад в Mastodon + delete: Удаление аккаунта edit_profile: Изменить профиль export: Экспорт данных followers: Авторизованные подписчики From 3d403a013d3ad4878d47b8dbd24ce371efecdfbf Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Thu, 8 Jun 2017 17:36:01 +0200 Subject: [PATCH 048/382] chore(yarn): Install react-swipeable --- package.json | 1 + yarn.lock | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/package.json b/package.json index e6fc02bbb..4a611e685 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "react-router-dom": "^4.1.1", "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", + "react-swipeable": "^4.0.1", "react-textarea-autosize": "^5.0.6", "react-toggle": "^4.0.1", "redis": "^2.7.1", diff --git a/yarn.lock b/yarn.lock index 26c6f7c7a..2eb1a5c75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5774,6 +5774,12 @@ react-style-proptype@^3.0.0: dependencies: prop-types "^15.5.4" +react-swipeable@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-4.0.1.tgz#2cb3a04a52ccebf5361881b30e233dc13ee4b115" + dependencies: + prop-types "^15.5.8" + react-test-renderer@^15.5.4: version "15.5.4" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc" From a6d8d1036a1a4336cb8fde6e3c318685cf6a7226 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Thu, 8 Jun 2017 17:41:32 +0200 Subject: [PATCH 049/382] feat: Swipeable columns --- .../features/ui/components/columns_area.js | 26 +++++++++++- .../features/ui/components/tabs_bar.js | 40 +++++++++++++++---- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 6ed8bc20d..43be34840 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -2,12 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ReactSwipeable from 'react-swipeable'; import HomeTimeline from '../../home_timeline'; import Notifications from '../../notifications'; import PublicTimeline from '../../public_timeline'; import CommunityTimeline from '../../community_timeline'; import HashtagTimeline from '../../hashtag_timeline'; import Compose from '../../compose'; +import { getPreviousLink, getNextLink } from './tabs_bar'; const componentMap = { 'COMPOSE': Compose, @@ -20,20 +22,40 @@ const componentMap = { class ColumnsArea extends ImmutablePureComponent { + static contextTypes = { + router: PropTypes.object.isRequired, + }; + static propTypes = { columns: ImmutablePropTypes.list.isRequired, singleColumn: PropTypes.bool, children: PropTypes.node, }; + handleRightSwipe = () => { + const previousLink = getPreviousLink(this.context.router.history.location.pathname); + + if (previousLink) { + this.context.router.history.push(previousLink); + } + } + + handleLeftSwipe = () => { + const previousLink = getNextLink(this.context.router.history.location.pathname); + + if (previousLink) { + this.context.router.history.push(previousLink); + } + }; + render () { const { columns, children, singleColumn } = this.props; if (singleColumn) { return ( -
+ {children} -
+ ); } diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 265cb818b..09acee067 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -2,19 +2,43 @@ import React from 'react'; import NavLink from 'react-router-dom/NavLink'; import { FormattedMessage } from 'react-intl'; +const links = [ + , + , + , + + , + , + + , +]; + +export function getPreviousLink (path) { + const index = links.findIndex(link => link.props.to === path); + + if (index > 0) { + return links[index - 1].props.to; + } + + return null; +}; + +export function getNextLink (path) { + const index = links.findIndex(link => link.props.to === path); + + if (index !== -1 && index < links.length - 1) { + return links[index + 1].props.to; + } + + return null; +}; + class TabsBar extends React.Component { render () { return (
- - - - - - - - + {React.Children.toArray(links)}
); } From bc6e95822930dc21f4f1a2c264ed9b976ef162f4 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Thu, 8 Jun 2017 17:41:43 +0200 Subject: [PATCH 050/382] feat: Swipeable media --- .../mastodon/features/ui/components/media_modal.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index cff1a0cf5..c6b293aeb 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -1,4 +1,5 @@ import React from 'react'; +import ReactSwipeable from 'react-swipeable'; import LoadingIndicator from '../../../components/loading_indicator'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; @@ -84,7 +85,9 @@ class MediaModal extends ImmutablePureComponent {
- {content} + + {content} +
{rightNav} From d8c47813771795893fe739e066708b84974130b8 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Sat, 10 Jun 2017 20:47:07 +0200 Subject: [PATCH 051/382] fix: Apply :hover, :focus and :active only when multiple columns --- app/javascript/styles/components.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index ecfc186eb..c2062c398 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1473,8 +1473,10 @@ &:hover, &:focus, &:active { - background: lighten($ui-base-color, 14%); - transition: all 100ms linear; + @media screen and (min-width: 1025px) { + background: lighten($ui-base-color, 14%); + transition: all 100ms linear; + } } span { From 6fbb3841a6dd4ec3d84b098437e35f41d41e8407 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 23 Jun 2017 20:55:05 +0900 Subject: [PATCH 052/382] Add prefix to setting toggle ID (#3907) --- .../components/column_settings.js | 6 ++--- .../components/column_settings.js | 24 +++++++++---------- .../components/setting_toggle.js | 9 +++---- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.js b/app/javascript/mastodon/features/home_timeline/components/column_settings.js index 104d8ff37..ba870a360 100644 --- a/app/javascript/mastodon/features/home_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.js @@ -28,17 +28,17 @@ class ColumnSettings extends React.PureComponent {
- } /> + } />
- } /> + } />
- +
); diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index 7bfd02f11..13ac78826 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -31,33 +31,33 @@ class ColumnSettings extends React.PureComponent {
- - - + + +
- - - + + +
- - - + + +
- - - + + +
); diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js index a37abbd9c..aefc14ee3 100644 --- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js +++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js @@ -6,19 +6,20 @@ import Toggle from 'react-toggle'; class SettingToggle extends React.PureComponent { static propTypes = { + prefix: PropTypes.string, settings: ImmutablePropTypes.map.isRequired, settingKey: PropTypes.array.isRequired, label: PropTypes.node.isRequired, onChange: PropTypes.func.isRequired, } - onChange = (e) => { - this.props.onChange(this.props.settingKey, e.target.checked); + onChange = ({ target }) => { + this.props.onChange(this.props.settingKey, target.checked); } render () { - const { settings, settingKey, label, onChange } = this.props; - const id = `setting-toggle-${settingKey.join('-')}`; + const { prefix, settings, settingKey, label, onChange } = this.props; + const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-'); return (
From eff94164696ecb126ab47b85ee842e65692b649a Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 23 Jun 2017 23:05:04 +0900 Subject: [PATCH 053/382] Remove unused variables (#3906) --- .eslintrc.yml | 13 ++++- app/javascript/mastodon/actions/accounts.js | 5 +- .../mastodon/actions/domain_blocks.js | 4 +- .../mastodon/actions/notifications.js | 2 +- app/javascript/mastodon/actions/statuses.js | 6 +-- .../mastodon/components/dropdown_menu.js | 2 +- .../mastodon/components/media_gallery.js | 2 +- app/javascript/mastodon/components/status.js | 1 - .../mastodon/components/status_content.js | 1 - .../mastodon/components/status_list.js | 2 +- .../mastodon/containers/mastodon.js | 1 - .../mastodon/containers/status_container.js | 2 - .../features/account/components/header.js | 2 +- .../features/account_gallery/index.js | 1 - .../components/column_settings.js | 5 +- .../containers/column_settings_container.js | 6 +-- .../features/community_timeline/index.js | 1 - .../compose/components/compose_form.js | 5 +- .../components/emoji_picker_dropdown.js | 2 +- .../compose/components/navigation_bar.js | 3 -- .../compose/components/privacy_dropdown.js | 2 +- .../features/compose/components/search.js | 2 +- .../compose/components/search_results.js | 2 +- .../compose/components/upload_button.js | 2 +- .../containers/navigation_container.js | 2 +- .../containers/reply_indicator_container.js | 2 +- .../containers/upload_form_container.js | 2 +- .../containers/upload_progress_container.js | 2 +- .../mastodon/features/compose/index.js | 1 - .../features/favourited_statuses/index.js | 7 +-- .../containers/account_authorize_container.js | 4 +- .../features/getting_started/index.js | 1 - .../features/hashtag_timeline/index.js | 1 - .../components/column_settings.js | 4 +- .../components/column_settings.js | 3 +- .../notifications/components/notification.js | 1 - .../components/setting_toggle.js | 2 +- .../containers/column_settings_container.js | 6 +-- .../features/public_timeline/index.js | 1 - .../mastodon/features/report/index.js | 2 +- .../mastodon/features/status/index.js | 13 +---- .../features/ui/components/boost_modal.js | 3 +- .../ui/components/confirmation_modal.js | 2 +- .../features/ui/components/image_loader.js | 2 +- .../features/ui/components/media_modal.js | 1 - .../ui/components/onboarding_modal.js | 5 +- .../features/ui/components/video_modal.js | 1 - .../ui/containers/notifications_container.js | 7 +-- app/javascript/mastodon/features/ui/index.js | 3 -- app/javascript/mastodon/middleware/errors.js | 2 - app/javascript/mastodon/middleware/sounds.js | 2 +- app/javascript/mastodon/reducers/compose.js | 1 - app/javascript/mastodon/reducers/modal.js | 1 - app/javascript/mastodon/reducers/search.js | 54 ------------------- app/javascript/mastodon/reducers/timelines.js | 6 --- app/javascript/mastodon/selectors/index.js | 3 -- .../mastodon/store/configureStore.js | 1 - spec/javascript/.eslintrc.yml | 3 ++ .../components/loading_indicator.test.js | 8 --- storybook/config.js | 1 - storybook/stories/character_counter.story.js | 1 - storybook/stories/loading_indicator.story.js | 1 - streaming/index.js | 6 +-- 63 files changed, 60 insertions(+), 182 deletions(-) create mode 100644 spec/javascript/.eslintrc.yml delete mode 100644 spec/javascript/components/loading_indicator.test.js diff --git a/.eslintrc.yml b/.eslintrc.yml index 2fb54ae66..a816bffef 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,7 +1,9 @@ --- +root: true + env: browser: true - node: false + node: true es6: true parser: babel-eslint @@ -52,8 +54,14 @@ rules: 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 @@ -81,7 +89,10 @@ rules: - 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 diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index a862798f9..03e3d3d9f 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -1,5 +1,4 @@ import api, { getLinks } from '../api'; -import Immutable from 'immutable'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; @@ -597,7 +596,7 @@ export function authorizeFollowRequest(id) { api(getState) .post(`/api/v1/follow_requests/${id}/authorize`) - .then(response => dispatch(authorizeFollowRequestSuccess(id))) + .then(() => dispatch(authorizeFollowRequestSuccess(id))) .catch(error => dispatch(authorizeFollowRequestFail(id, error))); }; }; @@ -631,7 +630,7 @@ export function rejectFollowRequest(id) { api(getState) .post(`/api/v1/follow_requests/${id}/reject`) - .then(response => dispatch(rejectFollowRequestSuccess(id))) + .then(() => dispatch(rejectFollowRequestSuccess(id))) .catch(error => dispatch(rejectFollowRequestFail(id, error))); }; }; diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index 530ba9cf1..44363697a 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -16,7 +16,7 @@ export function blockDomain(domain, accountId) { return (dispatch, getState) => { dispatch(blockDomainRequest(domain)); - api(getState).post('/api/v1/domain_blocks', { domain }).then(response => { + api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { dispatch(blockDomainSuccess(domain, accountId)); }).catch(err => { dispatch(blockDomainFail(domain, err)); @@ -51,7 +51,7 @@ export function unblockDomain(domain, accountId) { return (dispatch, getState) => { dispatch(unblockDomainRequest(domain)); - api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => { + api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { dispatch(unblockDomainSuccess(domain, accountId)); }).catch(err => { dispatch(unblockDomainFail(domain, err)); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index d3de2d871..cda636139 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -17,7 +17,7 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; -const messages = defineMessages({ +defineMessages({ mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, }); diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 6956447ba..8d385715c 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -74,7 +74,7 @@ export function deleteStatus(id) { return (dispatch, getState) => { dispatch(deleteStatusRequest(id)); - api(getState).delete(`/api/v1/statuses/${id}`).then(response => { + api(getState).delete(`/api/v1/statuses/${id}`).then(() => { dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); }).catch(error => { @@ -152,7 +152,7 @@ export function muteStatus(id) { return (dispatch, getState) => { dispatch(muteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/mute`).then(response => { + api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => { dispatch(muteStatusSuccess(id)); }).catch(error => { dispatch(muteStatusFail(id, error)); @@ -186,7 +186,7 @@ export function unmuteStatus(id) { return (dispatch, getState) => { dispatch(unmuteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/unmute`).then(response => { + api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => { dispatch(unmuteStatusSuccess(id)); }).catch(error => { dispatch(unmuteStatusFail(id, error)); diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 6e394a95d..deaab938e 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -56,7 +56,7 @@ class DropdownMenu extends React.PureComponent { return
  • ; } - const { text, action, href = '#' } = item; + const { text, href = '#' } = item; return (
  • diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 465130cec..cbed90f82 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -138,7 +138,7 @@ class MediaGallery extends React.PureComponent { visible: !this.props.sensitive, }; - handleOpen = (e) => { + handleOpen = () => { this.setState({ visible: !this.state.visible }); } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 1363956cf..3be3685ec 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -7,7 +7,6 @@ import RelativeTimestamp from './relative_timestamp'; import DisplayName from './display_name'; import MediaGallery from './media_gallery'; import VideoPlayer from './video_player'; -import AttachmentList from './attachment_list'; import StatusContent from './status_content'; import StatusActionBar from './status_action_bar'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index d02083d3e..605d42138 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -32,7 +32,6 @@ class StatusContent extends React.PureComponent { for (var i = 0; i < links.length; ++i) { let link = links[i]; let mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); - let media = this.props.status.get('media_attachments').find(item => link.href === item.get('text_url') || (item.get('remote_url').length > 0 && link.href === item.get('remote_url'))); if (mention) { link.addEventListener('click', this.onMentionClick.bind(this, mention), false); diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index b73a73d1b..40e9d38c1 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -99,7 +99,7 @@ class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; + const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props; let loadMore = null; let scrollableArea = null; diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index d44cb1be4..e3cb815c9 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -3,7 +3,6 @@ import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; import { - refreshTimelineSuccess, updateTimeline, deleteFromTimelines, refreshHomeTimeline, diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 2592e9a69..438ecfe43 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -19,8 +19,6 @@ import { import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses'; import { initReport } from '../actions/reports'; import { openModal } from '../actions/modal'; -import { createSelector } from 'reselect'; -import { isMobile } from '../is_mobile'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 653023cba..80a671a01 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -17,7 +17,7 @@ const messages = defineMessages({ }); const makeMapStateToProps = () => { - const mapStateToProps = (state, props) => ({ + const mapStateToProps = state => ({ autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index fcbee3c89..1e4af30a4 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -7,7 +7,6 @@ import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../a import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; -import Immutable from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { getAccountGallery } from '../../selectors'; import MediaItem from './components/media_item'; diff --git a/app/javascript/mastodon/features/community_timeline/components/column_settings.js b/app/javascript/mastodon/features/community_timeline/components/column_settings.js index dbbe8ceaa..aa487e34e 100644 --- a/app/javascript/mastodon/features/community_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/community_timeline/components/column_settings.js @@ -2,8 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnCollapsable from '../../../components/column_collapsable'; -import SettingToggle from '../../notifications/components/setting_toggle'; import SettingText from '../../../components/setting_text'; const messages = defineMessages({ @@ -16,12 +14,11 @@ class ColumnSettings extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; render () { - const { settings, onChange, onSave, intl } = this.props; + const { settings, onChange, intl } = this.props; return (
    diff --git a/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js index 1efc2ef33..f3489b409 100644 --- a/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import ColumnSettings from '../components/column_settings'; -import { changeSetting, saveSettings } from '../../../actions/settings'; +import { changeSetting } from '../../../actions/settings'; const mapStateToProps = state => ({ settings: state.getIn(['settings', 'community']), @@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({ dispatch(changeSetting(['community', ...key], checked)); }, - onSave () { - dispatch(saveSettings()); - }, - }); export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 4fbe67038..6c4b5dacf 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -14,7 +14,6 @@ import { } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import ColumnSettingsContainer from './containers/column_settings_container'; import createStream from '../../stream'; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 0ee45c978..d75bbdf9c 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -7,15 +7,13 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import { debounce } from 'lodash'; import UploadButtonContainer from '../containers/upload_button_container'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Toggle from 'react-toggle'; +import { defineMessages, injectIntl } from 'react-intl'; import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; import EmojiPickerDropdown from './emoji_picker_dropdown'; import UploadFormContainer from '../containers/upload_form_container'; -import TextIconButton from './text_icon_button'; import WarningContainer from '../containers/warning_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; @@ -141,7 +139,6 @@ class ComposeForm extends ImmutablePureComponent { const text = [this.props.spoiler_text, this.props.text].join(''); let publishText = ''; - let reply_to_other = false; if (this.props.privacy === 'private' || this.props.privacy === 'direct') { publishText = {intl.formatMessage(messages.publish)}; diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index 9ac674bb3..afaff1be1 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -52,7 +52,7 @@ class EmojiPickerDropdown extends React.PureComponent { import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => { EmojiPicker = TheEmojiPicker.default; this.setState({ loading: false }); - }).catch(err => { + }).catch(() => { // TODO: show the user an error? this.setState({ loading: false }); }); diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 1c135a733..00f27dea1 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -1,11 +1,8 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from '../../../components/avatar'; -import IconButton from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; import Permalink from '../../../components/permalink'; import { FormattedMessage } from 'react-intl'; -import Link from 'react-router-dom/Link'; import ImmutablePureComponent from 'react-immutable-pure-component'; class NavigationBar extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index 49f1179a0..f368186a5 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -64,7 +64,7 @@ class PrivacyDropdown extends React.PureComponent { } render () { - const { value, onChange, intl } = this.props; + const { value, intl } = this.props; const { open } = this.state; const options = [ diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index 800080a7d..21b3cf34b 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 26d766a1c..1a2605c15 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -1,6 +1,6 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; import Link from 'react-router-dom/Link'; diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js index 326b9851a..0f11b9e8b 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.js +++ b/app/javascript/mastodon/features/compose/components/upload_button.js @@ -11,7 +11,7 @@ const messages = defineMessages({ }); const makeMapStateToProps = () => { - const mapStateToProps = (state, props) => ({ + const mapStateToProps = state => ({ acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), }); diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js index 75f288f18..8cc53c087 100644 --- a/app/javascript/mastodon/features/compose/containers/navigation_container.js +++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import NavigationBar from '../components/navigation_bar'; -const mapStateToProps = (state, props) => { +const mapStateToProps = state => { return { account: state.getIn(['accounts', state.getIn(['meta', 'me'])]), }; diff --git a/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js b/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js index 7f3eeb89c..73f394c1a 100644 --- a/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js +++ b/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js @@ -6,7 +6,7 @@ import ReplyIndicator from '../components/reply_indicator'; const makeMapStateToProps = () => { const getStatus = makeGetStatus(); - const mapStateToProps = (state, props) => ({ + const mapStateToProps = state => ({ status: getStatus(state, state.getIn(['compose', 'in_reply_to'])), }); diff --git a/app/javascript/mastodon/features/compose/containers/upload_form_container.js b/app/javascript/mastodon/features/compose/containers/upload_form_container.js index 3125564c2..4612599f1 100644 --- a/app/javascript/mastodon/features/compose/containers/upload_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/upload_form_container.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import UploadForm from '../components/upload_form'; import { undoUploadCompose } from '../../../actions/compose'; -const mapStateToProps = (state, props) => ({ +const mapStateToProps = state => ({ media: state.getIn(['compose', 'media_attachments']), }); diff --git a/app/javascript/mastodon/features/compose/containers/upload_progress_container.js b/app/javascript/mastodon/features/compose/containers/upload_progress_container.js index 51af4440c..0cfee96da 100644 --- a/app/javascript/mastodon/features/compose/containers/upload_progress_container.js +++ b/app/javascript/mastodon/features/compose/containers/upload_progress_container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import UploadProgress from '../components/upload_progress'; -const mapStateToProps = (state, props) => ({ +const mapStateToProps = state => ({ active: state.getIn(['compose', 'is_uploading']), progress: state.getIn(['compose', 'progress']), }); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index a87e48a23..0452de856 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -1,6 +1,5 @@ import React from 'react'; import ComposeFormContainer from './containers/compose_form_container'; -import UploadFormContainer from './containers/upload_form_container'; import NavigationContainer from './containers/navigation_container'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index 2b343ba5a..caf0d2ca2 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites'; import Column from '../ui/components/column'; @@ -15,19 +14,15 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ - statusIds: state.getIn(['status_lists', 'favourites', 'items']), loaded: state.getIn(['status_lists', 'favourites', 'loaded']), - me: state.getIn(['meta', 'me']), }); class Favourites extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, loaded: PropTypes.bool, intl: PropTypes.object.isRequired, - me: PropTypes.number.isRequired, }; componentWillMount () { @@ -39,7 +34,7 @@ class Favourites extends ImmutablePureComponent { } render () { - const { statusIds, loaded, intl, me } = this.props; + const { loaded, intl } = this.props; if (!loaded) { return ( diff --git a/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js b/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js index a423bc79b..8db471f73 100644 --- a/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js +++ b/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js @@ -14,11 +14,11 @@ const makeMapStateToProps = () => { }; const mapDispatchToProps = (dispatch, { id }) => ({ - onAuthorize (account) { + onAuthorize () { dispatch(authorizeFollowRequest(id)); }, - onReject (account) { + onReject () { dispatch(rejectFollowRequest(id)); }, }); diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index a4549e609..c1eb06fcb 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -2,7 +2,6 @@ import React from 'react'; import Column from '../ui/components/column'; import ColumnLink from '../ui/components/column_link'; import ColumnSubheading from '../ui/components/column_subheading'; -import Link from 'react-router-dom/Link'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 3b2f1ba93..853434d4b 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -11,7 +11,6 @@ import { deleteFromTimelines, } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import { FormattedMessage } from 'react-intl'; import createStream from '../../stream'; diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.js b/app/javascript/mastodon/features/home_timeline/components/column_settings.js index ba870a360..47cd340af 100644 --- a/app/javascript/mastodon/features/home_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnCollapsable from '../../../components/column_collapsable'; import SettingToggle from '../../notifications/components/setting_toggle'; import SettingText from '../../../components/setting_text'; @@ -16,12 +15,11 @@ class ColumnSettings extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, - onSave: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; render () { - const { settings, onChange, onSave, intl } = this.props; + const { settings, onChange, intl } = this.props; return (
    diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index 13ac78826..2051e0c86 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; -import ColumnCollapsable from '../../../components/column_collapsable'; import ClearColumnButton from './clear_column_button'; import SettingToggle from './setting_toggle'; @@ -16,7 +15,7 @@ class ColumnSettings extends React.PureComponent { }; render () { - const { settings, onChange, onSave, onClear } = this.props; + const { settings, onChange, onClear } = this.props; const alertStr = ; const showStr = ; diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 6ec4d5dc6..ede37f66a 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -2,7 +2,6 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContainer from '../../../containers/status_container'; import AccountContainer from '../../../containers/account_container'; -import Avatar from '../../../components/avatar'; import { FormattedMessage } from 'react-intl'; import Permalink from '../../../components/permalink'; import emojify from '../../../emoji'; diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js index aefc14ee3..8707a993e 100644 --- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js +++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js @@ -18,7 +18,7 @@ class SettingToggle extends React.PureComponent { } render () { - const { prefix, settings, settingKey, label, onChange } = this.props; + const { prefix, settings, settingKey, label } = this.props; const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-'); return ( diff --git a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js index 62d4e7e5a..203e1da92 100644 --- a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import ColumnSettings from '../../community_timeline/components/column_settings'; -import { changeSetting, saveSettings } from '../../../actions/settings'; +import { changeSetting } from '../../../actions/settings'; const mapStateToProps = state => ({ settings: state.getIn(['settings', 'public']), @@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({ dispatch(changeSetting(['public', ...key], checked)); }, - onSave () { - dispatch(saveSettings()); - }, - }); export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 02ddb418f..3de54ef8b 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -14,7 +14,6 @@ import { } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import ColumnSettingsContainer from './containers/column_settings_container'; import createStream from '../../stream'; diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js index 23aba39de..0a5268430 100644 --- a/app/javascript/mastodon/features/report/index.js +++ b/app/javascript/mastodon/features/report/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { cancelReport, changeReportComment, submitReport } from '../../actions/reports'; +import { changeReportComment, submitReport } from '../../actions/reports'; import { refreshAccountTimeline } from '../../actions/timelines'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 19cee2435..afd8a7811 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -3,8 +3,6 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { fetchStatus } from '../../actions/statuses'; -import Immutable from 'immutable'; -import EmbeddedStatus from '../../components/status'; import MissingIndicator from '../../components/missing_indicator'; import DetailedStatus from './components/detailed_status'; import ActionBar from './components/action_bar'; @@ -21,17 +19,12 @@ import { } from '../../actions/compose'; import { deleteStatus } from '../../actions/statuses'; import { initReport } from '../../actions/reports'; -import { - makeGetStatus, - getStatusAncestors, - getStatusDescendants, -} from '../../selectors'; +import { makeGetStatus } from '../../selectors'; import { ScrollContainer } from 'react-router-scroll'; import ColumnBackButton from '../../components/column_back_button'; import StatusContainer from '../../containers/status_container'; import { openModal } from '../../actions/modal'; -import { isMobile } from '../../is_mobile'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ @@ -159,8 +152,6 @@ class Status extends ImmutablePureComponent { ); } - const account = status.get('account'); - if (ancestorsIds && ancestorsIds.size > 0) { ancestors =
    {this.renderChildren(ancestorsIds)}
    ; } diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index da2be5264..9a8b96333 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -2,7 +2,6 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import IconButton from '../../../components/icon_button'; import Button from '../../../components/button'; import StatusContent from '../../../components/status_content'; import Avatar from '../../../components/avatar'; @@ -49,7 +48,7 @@ class BoostModal extends ImmutablePureComponent { } render () { - const { status, intl, onClose } = this.props; + const { status, intl } = this.props; return (
    diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modal.js b/app/javascript/mastodon/features/ui/components/confirmation_modal.js index f33bfd445..a45c220fa 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modal.js +++ b/app/javascript/mastodon/features/ui/components/confirmation_modal.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { injectIntl, FormattedMessage } from 'react-intl'; import Button from '../../../components/button'; class ConfirmationModal extends React.PureComponent { diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index a2514d6be..94bf55bad 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -41,7 +41,7 @@ class ImageLoader extends React.PureComponent { render() { const { alt, src, previewSrc, width, height } = this.props; - const { loading, error } = this.state; + const { loading } = this.state; return (
    diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index cff1a0cf5..0f4105ee1 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -1,5 +1,4 @@ import React from 'react'; -import LoadingIndicator from '../../../components/loading_indicator'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js index c8985dc83..279599169 100644 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js @@ -72,7 +72,7 @@ PageTwo.propTypes = { me: ImmutablePropTypes.map.isRequired, }; -const PageThree = ({ me, domain }) => ( +const PageThree = ({ me }) => (
    ( PageThree.propTypes = { me: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired, }; const PageFour = ({ domain, intl }) => ( @@ -187,7 +186,7 @@ class OnboardingModal extends React.PureComponent { this.pages = [ , , - , + , , , ]; diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js index c622085f9..3599ab775 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.js @@ -1,5 +1,4 @@ import React from 'react'; -import LoadingIndicator from '../../../components/loading_indicator'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js index 8bc30df35..5924197f1 100644 --- a/app/javascript/mastodon/features/ui/containers/notifications_container.js +++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js @@ -1,12 +1,9 @@ import { connect } from 'react-redux'; import { NotificationStack } from 'react-notification'; -import { - dismissAlert, - clearAlerts, -} from '../../../actions/alerts'; +import { dismissAlert } from '../../../actions/alerts'; import { getAlerts } from '../../../selectors'; -const mapStateToProps = (state, props) => ({ +const mapStateToProps = state => ({ notifications: getAlerts(state), }); diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 39600607f..e48e9dbe9 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -74,9 +74,6 @@ class WrappedRoute extends React.Component { } -const noOp = () => false; - - class UI extends React.PureComponent { static propTypes = { diff --git a/app/javascript/mastodon/middleware/errors.js b/app/javascript/mastodon/middleware/errors.js index 4aca75f1e..b2c5f0898 100644 --- a/app/javascript/mastodon/middleware/errors.js +++ b/app/javascript/mastodon/middleware/errors.js @@ -1,13 +1,11 @@ import { showAlert } from '../actions/alerts'; -const defaultSuccessSuffix = 'SUCCESS'; const defaultFailSuffix = 'FAIL'; export default function errorsMiddleware() { return ({ dispatch }) => next => action => { if (action.type && !action.skipAlert) { const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); - const isSuccess = new RegExp(`${defaultSuccessSuffix}$`, 'g'); if (action.type.match(isFail)) { if (action.error.response) { diff --git a/app/javascript/mastodon/middleware/sounds.js b/app/javascript/mastodon/middleware/sounds.js index fd5a2b960..372e7c835 100644 --- a/app/javascript/mastodon/middleware/sounds.js +++ b/app/javascript/mastodon/middleware/sounds.js @@ -32,7 +32,7 @@ export default function soundsMiddleware() { ]), }; - return ({ dispatch }) => next => (action) => { + return () => next => action => { if (action.meta && action.meta.sound && soundCache[action.meta.sound]) { play(soundCache[action.meta.sound]); } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 2413df9e2..d0b47a85c 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -20,7 +20,6 @@ import { COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_VISIBILITY_CHANGE, - COMPOSE_LISTABILITY_CHANGE, COMPOSE_EMOJI_INSERT, } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js index 8fd9a69cd..599a2443e 100644 --- a/app/javascript/mastodon/reducers/modal.js +++ b/app/javascript/mastodon/reducers/modal.js @@ -1,5 +1,4 @@ import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; -import Immutable from 'immutable'; const initialState = { modalType: null, diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index ed395427e..0a3adac05 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -14,60 +14,6 @@ const initialState = Immutable.Map({ results: Immutable.Map(), }); -const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => { - let newSuggestions = []; - - if (accounts.length > 0) { - newSuggestions.push({ - title: 'account', - items: accounts.map(item => ({ - type: 'account', - id: item.id, - value: item.acct, - })), - }); - } - - if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 || hashtags.length > 0) { - let hashtagItems = hashtags.map(item => ({ - type: 'hashtag', - id: item, - value: `#${item}`, - })); - - if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) { - hashtagItems.unshift({ - type: 'hashtag', - id: value, - value: `#${value}`, - }); - } - - if (hashtagItems.length > 0) { - newSuggestions.push({ - title: 'hashtag', - items: hashtagItems, - }); - } - } - - if (statuses.length > 0) { - newSuggestions.push({ - title: 'status', - items: statuses.map(item => ({ - type: 'status', - id: item.id, - value: item.id, - })), - }); - } - - return state.withMutations(map => { - map.set('suggestions', newSuggestions); - map.set('loaded_value', value); - }); -}; - export default function search(state = initialState, action) { switch(action.type) { case SEARCH_CHANGE: diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 2bc1c8050..1b738a16a 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -11,12 +11,6 @@ import { TIMELINE_CONNECT, TIMELINE_DISCONNECT, } from '../actions/timelines'; -import { - REBLOG_SUCCESS, - UNREBLOG_SUCCESS, - FAVOURITE_SUCCESS, - UNFAVOURITE_SUCCESS, -} from '../actions/interactions'; import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index d5d736e2f..07d9a2629 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -1,9 +1,6 @@ import { createSelector } from 'reselect'; import Immutable from 'immutable'; -const getStatuses = state => state.get('statuses'); -const getAccounts = state => state.get('accounts'); - const getAccountBase = (state, id) => state.getIn(['accounts', id], null); const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); diff --git a/app/javascript/mastodon/store/configureStore.js b/app/javascript/mastodon/store/configureStore.js index a92d756f5..1376d4cba 100644 --- a/app/javascript/mastodon/store/configureStore.js +++ b/app/javascript/mastodon/store/configureStore.js @@ -4,7 +4,6 @@ import appReducer from '../reducers'; import loadingBarMiddleware from '../middleware/loading_bar'; import errorsMiddleware from '../middleware/errors'; import soundsMiddleware from '../middleware/sounds'; -import Immutable from 'immutable'; export default function configureStore() { return createStore(appReducer, compose(applyMiddleware( diff --git a/spec/javascript/.eslintrc.yml b/spec/javascript/.eslintrc.yml new file mode 100644 index 000000000..6db2a46c5 --- /dev/null +++ b/spec/javascript/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +env: + mocha: true diff --git a/spec/javascript/components/loading_indicator.test.js b/spec/javascript/components/loading_indicator.test.js deleted file mode 100644 index 0859dd192..000000000 --- a/spec/javascript/components/loading_indicator.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { expect } from 'chai'; -import { shallow } from 'enzyme'; -import React from 'react'; -import LoadingIndicator from '../../../app/javascript/mastodon/components/loading_indicator'; - -describe('', () => { - -}); diff --git a/storybook/config.js b/storybook/config.js index 1078059a7..87479560f 100644 --- a/storybook/config.js +++ b/storybook/config.js @@ -1,5 +1,4 @@ import { configure } from '@storybook/react'; -import React from 'react'; import { addLocaleData } from 'react-intl'; import en from 'react-intl/locale-data/en'; import '../app/javascript/styles/application.scss'; diff --git a/storybook/stories/character_counter.story.js b/storybook/stories/character_counter.story.js index 15a401a25..39d9afb56 100644 --- a/storybook/stories/character_counter.story.js +++ b/storybook/stories/character_counter.story.js @@ -1,6 +1,5 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import CharacterCounter from 'mastodon/features/compose/components/character_counter'; storiesOf('CharacterCounter', module) diff --git a/storybook/stories/loading_indicator.story.js b/storybook/stories/loading_indicator.story.js index 3e12f61ca..6ee822758 100644 --- a/storybook/stories/loading_indicator.story.js +++ b/storybook/stories/loading_indicator.story.js @@ -1,7 +1,6 @@ import React from 'react'; import { IntlProvider } from 'react-intl'; import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import en from 'mastodon/locales/en.json'; import LoadingIndicator from 'mastodon/components/loading_indicator'; diff --git a/streaming/index.js b/streaming/index.js index 5afdd5961..fb23be34d 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -242,7 +242,7 @@ const startWorker = (workerId) => { accountFromRequest(req, next); }; - const errorMiddleware = (err, req, res, next) => { + const errorMiddleware = (err, req, res) => { log.error(req.requestId, err.toString()); res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.statusCode ? err.toString() : 'An unexpected error occurred' })); @@ -366,7 +366,7 @@ const startWorker = (workerId) => { } }); - ws.on('error', e => { + ws.on('error', () => { log.verbose(req.requestId, `Ending stream for ${req.accountId}`); unsubscribe(id, listener); if (closeHandler) { @@ -443,7 +443,7 @@ const startWorker = (workerId) => { } }); - const wsInterval = setInterval(() => { + setInterval(() => { wss.clients.forEach(ws => { if (ws.isAlive === false) { ws.terminate(); From 3f59238207e7cd9b2b1b73e3d4259bd6e82ebc64 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Jun 2017 17:01:53 +0200 Subject: [PATCH 054/382] Add important test for full-width hashtags (#3911) --- spec/models/tag_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 2496946cb..7c574eabe 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -11,6 +11,10 @@ RSpec.describe Tag, type: :model do it 'does not match URLs with hashtag-like anchors' do expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil end + + it 'matches #aesthetic' do + expect(subject.match('this is #aesthetic')).to_not be_nil + end end describe '.search_for' do From 7b13e6efc2402bde71d3bcf69c859ce8974ac9cb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Jun 2017 17:02:14 +0200 Subject: [PATCH 055/382] Bump version to 1.4.6 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 5f90e83bb..44f3e4390 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 4 + 6 end def pre From e078919f07c3542f8cc6aedc3c554404f328e907 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 23 Jun 2017 08:44:55 -0700 Subject: [PATCH 056/382] Upgrade to Webpack 3 with module concatenation (#3912) --- config/webpack/production.js | 1 + package.json | 2 +- yarn.lock | 56 +++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/config/webpack/production.js b/config/webpack/production.js index 303fca81b..0d2c9acfb 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -12,6 +12,7 @@ module.exports = merge(sharedConfig, { stats: 'normal', plugins: [ + new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, mangle: true, diff --git a/package.json b/package.json index e6fc02bbb..79e8467cb 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "tiny-queue": "^0.2.1", "uuid": "^3.0.1", "uws": "^0.14.5", - "webpack": "^2.5.1", + "webpack": "^3.0.0", "webpack-bundle-analyzer": "^2.8.2", "webpack-manifest-plugin": "^1.1.0", "webpack-merge": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 26c6f7c7a..a03f0cdc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,6 +192,10 @@ ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" @@ -199,7 +203,7 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0: +ajv@^5.0.0, ajv@^5.1.5: version "5.1.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.5.tgz#8734931b601f00d4feef7c65738d77d1b65d1f68" dependencies: @@ -3944,7 +3948,7 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.x: +loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.x: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -6675,7 +6679,7 @@ sugarss@^1.0.0: dependencies: postcss "^6.0.0" -supports-color@3.1.2, supports-color@^3.1.0, supports-color@^3.1.1: +supports-color@3.1.2, supports-color@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" dependencies: @@ -6685,7 +6689,7 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.2.3: +supports-color@^3.1.0, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: @@ -6873,10 +6877,27 @@ uglify-js@^2.8.27: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^2.8.29: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" +uglifyjs-webpack-plugin@^0.4.4: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -7158,6 +7179,33 @@ webpack@^2.5.1: webpack-sources "^0.2.3" yargs "^6.0.0" +webpack@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.0.0.tgz#ee9bcebf21247f7153cb410168cab45e3a59d4d7" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^5.1.5" + ajv-keywords "^2.0.0" + async "^2.1.2" + enhanced-resolve "^3.0.0" + escope "^3.6.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^3.1.0" + tapable "~0.2.5" + uglifyjs-webpack-plugin "^0.4.4" + watchpack "^1.3.1" + webpack-sources "^1.0.1" + yargs "^6.0.0" + websocket-driver@>=0.5.1: version "0.6.5" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" From 6f34a6a77f5df6ddd6980c85c9edcc6aa400df73 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Sat, 24 Jun 2017 00:46:00 +0900 Subject: [PATCH 057/382] Add index statuses on account_id and id (#3895) --- ...610000000_add_statuses_index_on_account_id_id.rb | 13 +++++++++++++ db/schema.rb | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb diff --git a/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb b/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb new file mode 100644 index 000000000..3e74346a8 --- /dev/null +++ b/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb @@ -0,0 +1,13 @@ +class AddStatusesIndexOnAccountIdId < ActiveRecord::Migration[5.1] + disable_ddl_transaction! + + def change + # Statuses queried by account_id are often sorted by id. Querying statuses + # of an account to show them in his status page is one of the most + # significant examples. + # Add this index to improve the performance in such cases. + add_index 'statuses', ['account_id', 'id'], algorithm: :concurrently, name: 'index_statuses_on_account_id_id' + + remove_index 'statuses', algorithm: :concurrently, column: 'account_id', name: 'index_statuses_on_account_id' + end +end diff --git a/db/schema.rb b/db/schema.rb index ba6c0e876..2f12c7308 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170609145826) do +ActiveRecord::Schema.define(version: 20170610000000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -279,7 +279,7 @@ ActiveRecord::Schema.define(version: 20170609145826) do t.integer "reblogs_count", default: 0, null: false t.string "language" t.bigint "conversation_id" - t.index ["account_id"], name: "index_statuses_on_account_id" + t.index ["account_id", "id"], name: "index_statuses_on_account_id_id" t.index ["conversation_id"], name: "index_statuses_on_conversation_id" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id" t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id" From 3783cadf2d7a2b7ace078d1d337645f53c190c69 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 23 Jun 2017 09:21:33 -0700 Subject: [PATCH 058/382] Apply babel to react-intl to remove prop-types (#3914) --- config/webpack/loaders/babel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/webpack/loaders/babel.js b/config/webpack/loaders/babel.js index 1acf5e0e6..c23aa9375 100644 --- a/config/webpack/loaders/babel.js +++ b/config/webpack/loaders/babel.js @@ -1,6 +1,7 @@ module.exports = { test: /\.js$/, - exclude: /node_modules/, + // include react-intl because transform-react-remove-prop-types needs to apply to it + exclude: /node_modules\/(?!react-intl)/, loader: 'babel-loader', options: { forceEnv: process.env.NODE_ENV || 'development', From 2211e8d1cd6eb97a8a04e24c1fea7031a201edb5 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Fri, 23 Jun 2017 18:50:53 +0200 Subject: [PATCH 059/382] Revocable sessions (#3616) * feat: Revocable sessions * fix: Tests using sign_in * feat: Configuration entry for the maximum number of session activations --- app/models/session_activation.rb | 38 +++++++++++++++++++ app/models/user.rb | 14 +++++++ config/initializers/devise.rb | 16 ++++++++ config/initializers/session_activations.rb | 5 +++ ...170623152212_create_session_activations.rb | 13 +++++++ db/schema.rb | 11 +++++- .../session_activation_fabricator.rb | 4 ++ spec/models/session_activation_spec.rb | 5 +++ spec/rails_helper.rb | 11 ++++++ 9 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 app/models/session_activation.rb create mode 100644 config/initializers/session_activations.rb create mode 100644 db/migrate/20170623152212_create_session_activations.rb create mode 100644 spec/fabricators/session_activation_fabricator.rb create mode 100644 spec/models/session_activation_spec.rb diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb new file mode 100644 index 000000000..71e9f023c --- /dev/null +++ b/app/models/session_activation.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: session_activations +# +# id :integer not null, primary key +# user_id :integer not null +# session_id :string not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class SessionActivation < ApplicationRecord + LIMIT = Rails.configuration.x.max_session_activations + + def self.active?(id) + id && where(session_id: id).exists? + end + + def self.activate(id) + activation = create!(session_id: id) + purge_old + activation + end + + def self.deactivate(id) + return unless id + where(session_id: id).destroy_all + end + + def self.purge_old + order('created_at desc').offset(LIMIT).destroy_all + end + + def self.exclusive(id) + where('session_id != ?', id).destroy_all + end +end diff --git a/app/models/user.rb b/app/models/user.rb index ca11f2f5d..fccf1089b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,6 +63,8 @@ class User < ApplicationRecord # handle this itself, and this can be removed from our User class. attribute :otp_secret + has_many :session_activations, dependent: :destroy + def confirmed? confirmed_at.present? end @@ -89,6 +91,18 @@ class User < ApplicationRecord settings.auto_play_gif end + def activate_session + session_activations.activate(SecureRandom.hex).session_id + end + + def exclusive_session(id) + session_activations.exclusive(id) + end + + def session_active?(id) + session_activations.active? id + end + protected def send_devise_notification(notification, *args) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 4754c2c8c..6d3a73ef6 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,3 +1,19 @@ +Warden::Manager.after_set_user except: :fetch do |user, warden| + SessionActivation.deactivate warden.raw_session['auth_id'] + warden.raw_session['auth_id'] = user.activate_session +end + +Warden::Manager.after_fetch do |user, warden| + unless user.session_active?(warden.raw_session['auth_id']) + warden.logout + throw :warden, message: :unauthenticated + end +end + +Warden::Manager.before_logout do |_, warden| + SessionActivation.deactivate warden.raw_session['auth_id'] +end + Devise.setup do |config| config.warden do |manager| manager.default_strategies(scope: :user).unshift :two_factor_authenticatable diff --git a/config/initializers/session_activations.rb b/config/initializers/session_activations.rb new file mode 100644 index 000000000..ff3efc852 --- /dev/null +++ b/config/initializers/session_activations.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Rails.application.configure do + config.x.max_session_activations = ENV['MAX_SESSION_ACTIVATIONS'] || 10 +end diff --git a/db/migrate/20170623152212_create_session_activations.rb b/db/migrate/20170623152212_create_session_activations.rb new file mode 100644 index 000000000..81c776135 --- /dev/null +++ b/db/migrate/20170623152212_create_session_activations.rb @@ -0,0 +1,13 @@ +class CreateSessionActivations < ActiveRecord::Migration[5.1] + def change + create_table :session_activations do |t| + t.integer :user_id, null: false + t.string :session_id, null: false + + t.timestamps + end + + add_index :session_activations, :user_id + add_index :session_activations, :session_id, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 2f12c7308..b6aceb930 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170610000000) do +ActiveRecord::Schema.define(version: 20170623152212) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -250,6 +250,15 @@ ActiveRecord::Schema.define(version: 20170610000000) do t.index ["target_account_id"], name: "index_reports_on_target_account_id" end + create_table "session_activations", force: :cascade do |t| + t.integer "user_id", null: false + t.string "session_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true + t.index ["user_id"], name: "index_session_activations_on_user_id" + end + create_table "settings", id: :serial, force: :cascade do |t| t.string "var", null: false t.text "value" diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb new file mode 100644 index 000000000..46050bdab --- /dev/null +++ b/spec/fabricators/session_activation_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:session_activation) do + user_id 1 + session_id "MyString" +end diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb new file mode 100644 index 000000000..49c72fbd4 --- /dev/null +++ b/spec/models/session_activation_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe SessionActivation, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c9bdc8add..31c94b1e4 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -16,6 +16,17 @@ WebMock.disable_net_connect! Sidekiq::Testing.inline! Sidekiq::Logging.logger = nil +Devise::Test::ControllerHelpers.module_eval do + alias_method :original_sign_in, :sign_in + + def sign_in(resource, deprecated = nil, scope: nil) + original_sign_in(resource, scope: scope) + + SessionActivation.deactivate warden.raw_session["auth_id"] + warden.raw_session["auth_id"] = resource.activate_session + end +end + RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/spec/fixtures" config.use_transactional_fixtures = true From 0c44316b22c3711ad5369dae2f66f0fd5c79799e Mon Sep 17 00:00:00 2001 From: Takuya Yoshida Date: Sat, 24 Jun 2017 02:22:02 +0900 Subject: [PATCH 060/382] Fix errorMiddleware to prevent "TypeError: res.writeHead is not a function" (#3913) * Fix errorMiddleware * Add "eslint-disable-line no-unused-vars" --- streaming/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/index.js b/streaming/index.js index fb23be34d..156e1d4bc 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -242,7 +242,7 @@ const startWorker = (workerId) => { accountFromRequest(req, next); }; - const errorMiddleware = (err, req, res) => { + const errorMiddleware = (err, req, res, next) => { // eslint-disable-line no-unused-vars log.error(req.requestId, err.toString()); res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.statusCode ? err.toString() : 'An unexpected error occurred' })); From c1a8e3d1ebaa6d69e1ae23369a5dedd93ba7226f Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 24 Jun 2017 02:36:54 +0900 Subject: [PATCH 061/382] Use Class and Property Decorators (#3730) ref https://tc39.github.io/proposal-decorators/ --- .babelrc | 1 + app/javascript/mastodon/components/account.js | 5 ++--- .../mastodon/components/attachment_list.js | 4 +--- .../components/autosuggest_textarea.js | 4 +--- app/javascript/mastodon/components/avatar.js | 4 +--- .../mastodon/components/avatar_overlay.js | 4 +--- app/javascript/mastodon/components/button.js | 4 +--- app/javascript/mastodon/components/column.js | 4 +--- .../mastodon/components/column_back_button.js | 4 +--- .../components/column_back_button_slim.js | 4 +--- .../mastodon/components/column_collapsable.js | 4 +--- .../mastodon/components/column_header.js | 4 +--- .../mastodon/components/display_name.js | 4 +--- .../mastodon/components/dropdown_menu.js | 4 +--- .../components/extended_video_player.js | 4 +--- .../mastodon/components/icon_button.js | 4 +--- .../mastodon/components/load_more.js | 4 +--- .../mastodon/components/media_gallery.js | 5 ++--- .../mastodon/components/permalink.js | 4 +--- .../mastodon/components/relative_timestamp.js | 5 ++--- .../mastodon/components/setting_text.js | 4 +--- app/javascript/mastodon/components/status.js | 4 +--- .../mastodon/components/status_action_bar.js | 5 ++--- .../mastodon/components/status_content.js | 4 +--- .../mastodon/components/status_list.js | 4 +--- .../mastodon/components/video_player.js | 5 ++--- .../mastodon/containers/mastodon.js | 12 +++++------ .../features/account/components/action_bar.js | 5 ++--- .../features/account/components/header.js | 6 +++--- .../account_gallery/components/media_item.js | 4 +--- .../features/account_gallery/index.js | 5 ++--- .../account_timeline/components/header.js | 4 +--- .../features/account_timeline/index.js | 5 ++--- .../mastodon/features/blocks/index.js | 6 +++--- .../components/column_settings.js | 5 ++--- .../features/community_timeline/index.js | 6 +++--- .../compose/components/autosuggest_account.js | 4 +--- .../compose/components/character_counter.js | 4 +--- .../compose/components/compose_form.js | 5 ++--- .../components/emoji_picker_dropdown.js | 5 ++--- .../compose/components/navigation_bar.js | 4 +--- .../compose/components/privacy_dropdown.js | 5 ++--- .../compose/components/reply_indicator.js | 5 ++--- .../features/compose/components/search.js | 5 ++--- .../compose/components/search_results.js | 4 +--- .../compose/components/text_icon_button.js | 4 +--- .../compose/components/upload_button.js | 6 +++--- .../compose/components/upload_form.js | 5 ++--- .../compose/components/upload_progress.js | 4 +--- .../features/compose/components/warning.js | 4 +--- .../mastodon/features/compose/index.js | 6 +++--- .../features/favourited_statuses/index.js | 6 +++--- .../mastodon/features/favourites/index.js | 5 ++--- .../components/account_authorize.js | 5 ++--- .../features/follow_requests/index.js | 6 +++--- .../mastodon/features/followers/index.js | 5 ++--- .../mastodon/features/following/index.js | 5 ++--- .../features/getting_started/index.js | 6 +++--- .../features/hashtag_timeline/index.js | 5 ++--- .../components/column_settings.js | 5 ++--- .../mastodon/features/home_timeline/index.js | 6 +++--- .../mastodon/features/mutes/index.js | 20 +++++++++---------- .../components/clear_column_button.js | 4 +--- .../components/column_settings.js | 4 +--- .../notifications/components/notification.js | 4 +--- .../components/setting_toggle.js | 4 +--- .../mastodon/features/notifications/index.js | 6 +++--- .../features/public_timeline/index.js | 6 +++--- .../mastodon/features/reblogs/index.js | 5 ++--- .../report/components/status_check_box.js | 4 +--- .../mastodon/features/report/index.js | 6 +++--- .../features/status/components/action_bar.js | 5 ++--- .../features/status/components/card.js | 4 +--- .../status/components/detailed_status.js | 4 +--- .../mastodon/features/status/index.js | 6 +++--- .../features/ui/components/boost_modal.js | 5 ++--- .../mastodon/features/ui/components/column.js | 4 +--- .../features/ui/components/column_header.js | 4 +--- .../features/ui/components/columns_area.js | 4 +--- .../ui/components/confirmation_modal.js | 5 ++--- .../features/ui/components/image_loader.js | 4 +--- .../features/ui/components/media_modal.js | 5 ++--- .../features/ui/components/modal_root.js | 4 +--- .../ui/components/onboarding_modal.js | 6 +++--- .../features/ui/components/tabs_bar.js | 4 +--- .../features/ui/components/upload_area.js | 4 +--- .../features/ui/components/video_modal.js | 5 ++--- app/javascript/mastodon/features/ui/index.js | 5 ++--- package.json | 1 + yarn.lock | 12 +++++++++-- 90 files changed, 168 insertions(+), 274 deletions(-) diff --git a/.babelrc b/.babelrc index 081c4f963..292d52e27 100644 --- a/.babelrc +++ b/.babelrc @@ -15,6 +15,7 @@ "plugins": [ "syntax-dynamic-import", ["transform-object-rest-spread", { "useBuiltIns": true }], + "transform-decorators-legacy", "transform-class-properties", [ "react-intl", diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js index 960d136d3..b6ca0661f 100644 --- a/app/javascript/mastodon/components/account.js +++ b/app/javascript/mastodon/components/account.js @@ -16,7 +16,8 @@ const messages = defineMessages({ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, }); -class Account extends ImmutablePureComponent { +@injectIntl +export default class Account extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -82,5 +83,3 @@ class Account extends ImmutablePureComponent { } } - -export default injectIntl(Account); diff --git a/app/javascript/mastodon/components/attachment_list.js b/app/javascript/mastodon/components/attachment_list.js index a57c25ad0..b3d00b335 100644 --- a/app/javascript/mastodon/components/attachment_list.js +++ b/app/javascript/mastodon/components/attachment_list.js @@ -4,7 +4,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; -class AttachmentList extends ImmutablePureComponent { +export default class AttachmentList extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, @@ -31,5 +31,3 @@ class AttachmentList extends ImmutablePureComponent { } } - -export default AttachmentList; diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 9a5760a2c..fa41e59e1 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -31,7 +31,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => { } }; -class AutosuggestTextarea extends ImmutablePureComponent { +export default class AutosuggestTextarea extends ImmutablePureComponent { static propTypes = { value: PropTypes.string, @@ -196,5 +196,3 @@ class AutosuggestTextarea extends ImmutablePureComponent { } } - -export default AutosuggestTextarea; diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js index 3531a42b5..4f8170657 100644 --- a/app/javascript/mastodon/components/avatar.js +++ b/app/javascript/mastodon/components/avatar.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class Avatar extends React.PureComponent { +export default class Avatar extends React.PureComponent { static propTypes = { src: PropTypes.string.isRequired, @@ -66,5 +66,3 @@ class Avatar extends React.PureComponent { } } - -export default Avatar; diff --git a/app/javascript/mastodon/components/avatar_overlay.js b/app/javascript/mastodon/components/avatar_overlay.js index c82c89637..de43e0ef5 100644 --- a/app/javascript/mastodon/components/avatar_overlay.js +++ b/app/javascript/mastodon/components/avatar_overlay.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class AvatarOverlay extends React.PureComponent { +export default class AvatarOverlay extends React.PureComponent { static propTypes = { staticSrc: PropTypes.string.isRequired, @@ -28,5 +28,3 @@ class AvatarOverlay extends React.PureComponent { } } - -export default AvatarOverlay; diff --git a/app/javascript/mastodon/components/button.js b/app/javascript/mastodon/components/button.js index 52af193e7..51e2e6a7a 100644 --- a/app/javascript/mastodon/components/button.js +++ b/app/javascript/mastodon/components/button.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -class Button extends React.PureComponent { +export default class Button extends React.PureComponent { static propTypes = { text: PropTypes.node, @@ -61,5 +61,3 @@ class Button extends React.PureComponent { } } - -export default Button; diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js index 157a89c0e..3cbb745c5 100644 --- a/app/javascript/mastodon/components/column.js +++ b/app/javascript/mastodon/components/column.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import scrollTop from '../scroll'; -class Column extends React.PureComponent { +export default class Column extends React.PureComponent { static propTypes = { children: PropTypes.node, @@ -41,5 +41,3 @@ class Column extends React.PureComponent { } } - -export default Column; diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js index 191c0f597..ba2736d7a 100644 --- a/app/javascript/mastodon/components/column_back_button.js +++ b/app/javascript/mastodon/components/column_back_button.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -class ColumnBackButton extends React.PureComponent { +export default class ColumnBackButton extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -23,5 +23,3 @@ class ColumnBackButton extends React.PureComponent { } } - -export default ColumnBackButton; diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js index ffb05fa02..3b4f46d99 100644 --- a/app/javascript/mastodon/components/column_back_button_slim.js +++ b/app/javascript/mastodon/components/column_back_button_slim.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -class ColumnBackButtonSlim extends React.PureComponent { +export default class ColumnBackButtonSlim extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -25,5 +25,3 @@ class ColumnBackButtonSlim extends React.PureComponent { } } - -export default ColumnBackButtonSlim; diff --git a/app/javascript/mastodon/components/column_collapsable.js b/app/javascript/mastodon/components/column_collapsable.js index c7c953acd..d6b4edb9f 100644 --- a/app/javascript/mastodon/components/column_collapsable.js +++ b/app/javascript/mastodon/components/column_collapsable.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class ColumnCollapsable extends React.PureComponent { +export default class ColumnCollapsable extends React.PureComponent { static propTypes = { icon: PropTypes.string.isRequired, @@ -48,5 +48,3 @@ class ColumnCollapsable extends React.PureComponent { } } - -export default ColumnCollapsable; diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index 076f1cdad..a309f74e8 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; -class ColumnHeader extends React.PureComponent { +export default class ColumnHeader extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -141,5 +141,3 @@ class ColumnHeader extends React.PureComponent { } } - -export default ColumnHeader; diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js index 6fbc1dfc0..dc3665a2b 100644 --- a/app/javascript/mastodon/components/display_name.js +++ b/app/javascript/mastodon/components/display_name.js @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import escapeTextContentForBrowser from 'escape-html'; import emojify from '../emoji'; -class DisplayName extends React.PureComponent { +export default class DisplayName extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -21,5 +21,3 @@ class DisplayName extends React.PureComponent { } } - -export default DisplayName; diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index deaab938e..12e1b44fa 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -2,7 +2,7 @@ import React from 'react'; import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; import PropTypes from 'prop-types'; -class DropdownMenu extends React.PureComponent { +export default class DropdownMenu extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -92,5 +92,3 @@ class DropdownMenu extends React.PureComponent { } } - -export default DropdownMenu; diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js index 4d92bd779..4c62fa7b3 100644 --- a/app/javascript/mastodon/components/extended_video_player.js +++ b/app/javascript/mastodon/components/extended_video_player.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class ExtendedVideoPlayer extends React.PureComponent { +export default class ExtendedVideoPlayer extends React.PureComponent { static propTypes = { src: PropTypes.string.isRequired, @@ -44,5 +44,3 @@ class ExtendedVideoPlayer extends React.PureComponent { } } - -export default ExtendedVideoPlayer; diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index 302e63df5..ac734f5ad 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -3,7 +3,7 @@ import Motion from 'react-motion/lib/Motion'; import spring from 'react-motion/lib/spring'; import PropTypes from 'prop-types'; -class IconButton extends React.PureComponent { +export default class IconButton extends React.PureComponent { static propTypes = { className: PropTypes.string, @@ -86,5 +86,3 @@ class IconButton extends React.PureComponent { } } - -export default IconButton; diff --git a/app/javascript/mastodon/components/load_more.js b/app/javascript/mastodon/components/load_more.js index fa0caaae9..2996d4dc8 100644 --- a/app/javascript/mastodon/components/load_more.js +++ b/app/javascript/mastodon/components/load_more.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -class LoadMore extends React.PureComponent { +export default class LoadMore extends React.PureComponent { static propTypes = { onClick: PropTypes.func, @@ -17,5 +17,3 @@ class LoadMore extends React.PureComponent { } } - -export default LoadMore; diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index cbed90f82..78ff35130 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -123,7 +123,8 @@ class Item extends React.PureComponent { } -class MediaGallery extends React.PureComponent { +@injectIntl +export default class MediaGallery extends React.PureComponent { static propTypes = { sensitive: PropTypes.bool, @@ -183,5 +184,3 @@ class MediaGallery extends React.PureComponent { } } - -export default injectIntl(MediaGallery); diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js index 7149e8380..5d3e4738d 100644 --- a/app/javascript/mastodon/components/permalink.js +++ b/app/javascript/mastodon/components/permalink.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class Permalink extends React.PureComponent { +export default class Permalink extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -32,5 +32,3 @@ class Permalink extends React.PureComponent { } } - -export default Permalink; diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js index 3eed88df8..2717d2326 100644 --- a/app/javascript/mastodon/components/relative_timestamp.js +++ b/app/javascript/mastodon/components/relative_timestamp.js @@ -11,7 +11,8 @@ const dateFormatOptions = { minute: '2-digit', }; -class RelativeTimestamp extends React.Component { +@injectIntl +export default class RelativeTimestamp extends React.Component { static propTypes = { intl: PropTypes.object.isRequired, @@ -37,5 +38,3 @@ class RelativeTimestamp extends React.Component { } } - -export default injectIntl(RelativeTimestamp); diff --git a/app/javascript/mastodon/components/setting_text.js b/app/javascript/mastodon/components/setting_text.js index d4f177f8a..dd975bc99 100644 --- a/app/javascript/mastodon/components/setting_text.js +++ b/app/javascript/mastodon/components/setting_text.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -class SettingText extends React.PureComponent { +export default class SettingText extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, @@ -29,5 +29,3 @@ class SettingText extends React.PureComponent { } } - -export default SettingText; diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 3be3685ec..81196c82a 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -15,7 +15,7 @@ import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; -class Status extends ImmutablePureComponent { +export default class Status extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -233,5 +233,3 @@ class Status extends ImmutablePureComponent { } } - -export default Status; diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 800f0d0fb..edb2d6eb0 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -22,7 +22,8 @@ const messages = defineMessages({ unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, }); -class StatusActionBar extends ImmutablePureComponent { +@injectIntl +export default class StatusActionBar extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -149,5 +150,3 @@ class StatusActionBar extends ImmutablePureComponent { } } - -export default injectIntl(StatusActionBar); diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 605d42138..0acea033c 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -7,7 +7,7 @@ import { isRtl } from '../rtl'; import { FormattedMessage } from 'react-intl'; import Permalink from './permalink'; -class StatusContent extends React.PureComponent { +export default class StatusContent extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -171,5 +171,3 @@ class StatusContent extends React.PureComponent { } } - -export default StatusContent; diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 40e9d38c1..5bc46e8ee 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; import { debounce } from 'lodash'; -class StatusList extends ImmutablePureComponent { +export default class StatusList extends ImmutablePureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, @@ -142,5 +142,3 @@ class StatusList extends ImmutablePureComponent { } } - -export default StatusList; diff --git a/app/javascript/mastodon/components/video_player.js b/app/javascript/mastodon/components/video_player.js index 66c3a64bc..452a84319 100644 --- a/app/javascript/mastodon/components/video_player.js +++ b/app/javascript/mastodon/components/video_player.js @@ -11,7 +11,8 @@ const messages = defineMessages({ expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }, }); -class VideoPlayer extends React.PureComponent { +@injectIntl +export default class VideoPlayer extends React.PureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, @@ -193,5 +194,3 @@ class VideoPlayer extends React.PureComponent { } } - -export default injectIntl(VideoPlayer); diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index e3cb815c9..3bd89902f 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -26,7 +26,11 @@ const store = configureStore(); const initialState = JSON.parse(document.getElementById('initial-state').textContent); store.dispatch(hydrateStore(initialState)); -class Mastodon extends React.PureComponent { +export default class Mastodon extends React.PureComponent { + + static propTypes = { + locale: PropTypes.string.isRequired, + }; componentDidMount() { const { locale } = this.props; @@ -117,9 +121,3 @@ class Mastodon extends React.PureComponent { } } - -Mastodon.propTypes = { - locale: PropTypes.string.isRequired, -}; - -export default Mastodon; diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index 0ea8ad347..374da103e 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -21,7 +21,8 @@ const messages = defineMessages({ unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, }); -class ActionBar extends React.PureComponent { +@injectIntl +export default class ActionBar extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -105,5 +106,3 @@ class ActionBar extends React.PureComponent { } } - -export default injectIntl(ActionBar); diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 80a671a01..3239b1085 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -70,7 +70,9 @@ class Avatar extends ImmutablePureComponent { } -class Header extends ImmutablePureComponent { +@connect(makeMapStateToProps) +@injectIntl +export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, @@ -140,5 +142,3 @@ class Header extends ImmutablePureComponent { } } - -export default connect(makeMapStateToProps)(injectIntl(Header)); diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js index 31c05c866..dda3d4e37 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Permalink from '../../../components/permalink'; -class MediaItem extends ImmutablePureComponent { +export default class MediaItem extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, @@ -37,5 +37,3 @@ class MediaItem extends ImmutablePureComponent { } } - -export default MediaItem; diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 1e4af30a4..0cfd98f23 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -22,7 +22,8 @@ const mapStateToProps = (state, props) => ({ autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); -class AccountGallery extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class AccountGallery extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -111,5 +112,3 @@ class AccountGallery extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(AccountGallery); diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 55fdc4167..7f80e39e8 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -6,7 +6,7 @@ import ActionBar from '../../account/components/action_bar'; import MissingIndicator from '../../../components/missing_indicator'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class Header extends ImmutablePureComponent { +export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, @@ -91,5 +91,3 @@ class Header extends ImmutablePureComponent { } } - -export default Header; diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 1aab8f130..955d0000e 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -19,7 +19,8 @@ const mapStateToProps = (state, props) => ({ me: state.getIn(['meta', 'me']), }); -class AccountTimeline extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class AccountTimeline extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -77,5 +78,3 @@ class AccountTimeline extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(AccountTimeline); diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js index de02e53cc..b16af4b28 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.js @@ -19,7 +19,9 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'blocks', 'items']), }); -class Blocks extends ImmutablePureComponent { +@connect(mapStateToProps) +@injectIntl +export default class Blocks extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -66,5 +68,3 @@ class Blocks extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(injectIntl(Blocks)); diff --git a/app/javascript/mastodon/features/community_timeline/components/column_settings.js b/app/javascript/mastodon/features/community_timeline/components/column_settings.js index aa487e34e..a992b27bb 100644 --- a/app/javascript/mastodon/features/community_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/community_timeline/components/column_settings.js @@ -9,7 +9,8 @@ const messages = defineMessages({ settings: { id: 'home.settings', defaultMessage: 'Column settings' }, }); -class ColumnSettings extends React.PureComponent { +@injectIntl +export default class ColumnSettings extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, @@ -32,5 +33,3 @@ class ColumnSettings extends React.PureComponent { } } - -export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index 6c4b5dacf..0e2300f8c 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -27,7 +27,9 @@ const mapStateToProps = state => ({ accessToken: state.getIn(['meta', 'access_token']), }); -class CommunityTimeline extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class CommunityTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -141,5 +143,3 @@ class CommunityTimeline extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); diff --git a/app/javascript/mastodon/features/compose/components/autosuggest_account.js b/app/javascript/mastodon/features/compose/components/autosuggest_account.js index 23665811e..ebfa3c247 100644 --- a/app/javascript/mastodon/features/compose/components/autosuggest_account.js +++ b/app/javascript/mastodon/features/compose/components/autosuggest_account.js @@ -4,7 +4,7 @@ import DisplayName from '../../../components/display_name'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class AutosuggestAccount extends ImmutablePureComponent { +export default class AutosuggestAccount extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -22,5 +22,3 @@ class AutosuggestAccount extends ImmutablePureComponent { } } - -export default AutosuggestAccount; diff --git a/app/javascript/mastodon/features/compose/components/character_counter.js b/app/javascript/mastodon/features/compose/components/character_counter.js index e35f2b879..6c488b661 100644 --- a/app/javascript/mastodon/features/compose/components/character_counter.js +++ b/app/javascript/mastodon/features/compose/components/character_counter.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { length } from 'stringz'; -class CharacterCounter extends React.PureComponent { +export default class CharacterCounter extends React.PureComponent { static propTypes = { text: PropTypes.string.isRequired, @@ -23,5 +23,3 @@ class CharacterCounter extends React.PureComponent { } } - -export default CharacterCounter; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index d75bbdf9c..c379c1855 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -25,7 +25,8 @@ const messages = defineMessages({ publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' }, }); -class ComposeForm extends ImmutablePureComponent { +@injectIntl +export default class ComposeForm extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, @@ -199,5 +200,3 @@ class ComposeForm extends ImmutablePureComponent { } } - -export default injectIntl(ComposeForm); diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index afaff1be1..c83dbb63e 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -24,7 +24,8 @@ const settings = { let EmojiPicker; // load asynchronously -class EmojiPickerDropdown extends React.PureComponent { +@injectIntl +export default class EmojiPickerDropdown extends React.PureComponent { static propTypes = { intl: PropTypes.object.isRequired, @@ -123,5 +124,3 @@ class EmojiPickerDropdown extends React.PureComponent { } } - -export default injectIntl(EmojiPickerDropdown); diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index 00f27dea1..fa4f560f3 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -5,7 +5,7 @@ import Permalink from '../../../components/permalink'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class NavigationBar extends ImmutablePureComponent { +export default class NavigationBar extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -30,5 +30,3 @@ class NavigationBar extends ImmutablePureComponent { } } - -export default NavigationBar; diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index f368186a5..9524f7501 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -20,7 +20,8 @@ const iconStyle = { lineHeight: '27px', }; -class PrivacyDropdown extends React.PureComponent { +@injectIntl +export default class PrivacyDropdown extends React.PureComponent { static propTypes = { value: PropTypes.string.isRequired, @@ -95,5 +96,3 @@ class PrivacyDropdown extends React.PureComponent { } } - -export default injectIntl(PrivacyDropdown); diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index 474549a5f..da00e46c5 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -12,7 +12,8 @@ const messages = defineMessages({ cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, }); -class ReplyIndicator extends ImmutablePureComponent { +@injectIntl +export default class ReplyIndicator extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -61,5 +62,3 @@ class ReplyIndicator extends ImmutablePureComponent { } } - -export default injectIntl(ReplyIndicator); diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js index 21b3cf34b..cdc7952c0 100644 --- a/app/javascript/mastodon/features/compose/components/search.js +++ b/app/javascript/mastodon/features/compose/components/search.js @@ -6,7 +6,8 @@ const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, }); -class Search extends React.PureComponent { +@injectIntl +export default class Search extends React.PureComponent { static propTypes = { value: PropTypes.string.isRequired, @@ -70,5 +71,3 @@ class Search extends React.PureComponent { } } - -export default injectIntl(Search); diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 1a2605c15..ae4d1e86a 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -6,7 +6,7 @@ import StatusContainer from '../../../containers/status_container'; import Link from 'react-router-dom/Link'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class SearchResults extends ImmutablePureComponent { +export default class SearchResults extends ImmutablePureComponent { static propTypes = { results: ImmutablePropTypes.map.isRequired, @@ -63,5 +63,3 @@ class SearchResults extends ImmutablePureComponent { } } - -export default SearchResults; diff --git a/app/javascript/mastodon/features/compose/components/text_icon_button.js b/app/javascript/mastodon/features/compose/components/text_icon_button.js index cc0fbd11a..9c8ffab1f 100644 --- a/app/javascript/mastodon/features/compose/components/text_icon_button.js +++ b/app/javascript/mastodon/features/compose/components/text_icon_button.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class TextIconButton extends React.PureComponent { +export default class TextIconButton extends React.PureComponent { static propTypes = { label: PropTypes.string.isRequired, @@ -27,5 +27,3 @@ class TextIconButton extends React.PureComponent { } } - -export default TextIconButton; diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js index 0f11b9e8b..badd6cfc5 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.js +++ b/app/javascript/mastodon/features/compose/components/upload_button.js @@ -23,7 +23,9 @@ const iconStyle = { lineHeight: '27px', }; -class UploadButton extends ImmutablePureComponent { +@connect(makeMapStateToProps) +@injectIntl +export default class UploadButton extends ImmutablePureComponent { static propTypes = { disabled: PropTypes.bool, @@ -70,5 +72,3 @@ class UploadButton extends ImmutablePureComponent { } } - -export default connect(makeMapStateToProps)(injectIntl(UploadButton)); diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js index 7e665683a..78473dab4 100644 --- a/app/javascript/mastodon/features/compose/components/upload_form.js +++ b/app/javascript/mastodon/features/compose/components/upload_form.js @@ -11,7 +11,8 @@ const messages = defineMessages({ undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }, }); -class UploadForm extends React.PureComponent { +@injectIntl +export default class UploadForm extends React.PureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, @@ -48,5 +49,3 @@ class UploadForm extends React.PureComponent { } } - -export default injectIntl(UploadForm); diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.js index 8c8ce3835..3e49098c7 100644 --- a/app/javascript/mastodon/features/compose/components/upload_progress.js +++ b/app/javascript/mastodon/features/compose/components/upload_progress.js @@ -4,7 +4,7 @@ import Motion from 'react-motion/lib/Motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; -class UploadProgress extends React.PureComponent { +export default class UploadProgress extends React.PureComponent { static propTypes = { active: PropTypes.bool, @@ -40,5 +40,3 @@ class UploadProgress extends React.PureComponent { } } - -export default UploadProgress; diff --git a/app/javascript/mastodon/features/compose/components/warning.js b/app/javascript/mastodon/features/compose/components/warning.js index d0e75a5c3..75f36b840 100644 --- a/app/javascript/mastodon/features/compose/components/warning.js +++ b/app/javascript/mastodon/features/compose/components/warning.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class Warning extends React.PureComponent { +export default class Warning extends React.PureComponent { static propTypes = { message: PropTypes.node.isRequired, @@ -18,5 +18,3 @@ class Warning extends React.PureComponent { } } - -export default Warning; diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 0452de856..747fe4216 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -23,7 +23,9 @@ const mapStateToProps = state => ({ showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), }); -class Compose extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class Compose extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -82,5 +84,3 @@ class Compose extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(Compose)); diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js index caf0d2ca2..137e55089 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.js @@ -17,7 +17,9 @@ const mapStateToProps = state => ({ loaded: state.getIn(['status_lists', 'favourites', 'loaded']), }); -class Favourites extends ImmutablePureComponent { +@connect(mapStateToProps) +@injectIntl +export default class Favourites extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -53,5 +55,3 @@ class Favourites extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(injectIntl(Favourites)); diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js index 94f9f268b..dc8109d16 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.js @@ -14,7 +14,8 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)]), }); -class Favourites extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class Favourites extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -57,5 +58,3 @@ class Favourites extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(Favourites); diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js b/app/javascript/mastodon/features/follow_requests/components/account_authorize.js index e41597c17..566953ddd 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.js @@ -14,7 +14,8 @@ const messages = defineMessages({ reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }, }); -class AccountAuthorize extends ImmutablePureComponent { +@injectIntl +export default class AccountAuthorize extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, @@ -47,5 +48,3 @@ class AccountAuthorize extends ImmutablePureComponent { } } - -export default injectIntl(AccountAuthorize); diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js index 286f128f4..4c9e514cb 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.js @@ -19,7 +19,9 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'follow_requests', 'items']), }); -class FollowRequests extends ImmutablePureComponent { +@connect(mapStateToProps) +@injectIntl +export default class FollowRequests extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -67,5 +69,3 @@ class FollowRequests extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(injectIntl(FollowRequests)); diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js index e9910dce4..2d85b9cc0 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.js @@ -21,7 +21,8 @@ const mapStateToProps = (state, props) => ({ hasMore: !!state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'next']), }); -class Followers extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class Followers extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -90,5 +91,3 @@ class Followers extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(Followers); diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js index 764f702ff..e4e2a4811 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.js @@ -21,7 +21,8 @@ const mapStateToProps = (state, props) => ({ hasMore: !!state.getIn(['user_lists', 'following', Number(props.params.accountId), 'next']), }); -class Following extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class Following extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -90,5 +91,3 @@ class Following extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(Following); diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index c1eb06fcb..f8ea01024 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -30,7 +30,9 @@ const mapStateToProps = state => ({ columns: state.getIn(['settings', 'columns']), }); -class GettingStarted extends ImmutablePureComponent { +@connect(mapStateToProps) +@injectIntl +export default class GettingStarted extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, @@ -105,5 +107,3 @@ class GettingStarted extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(injectIntl(GettingStarted)); diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index 853434d4b..b17e8e1a5 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -20,7 +20,8 @@ const mapStateToProps = state => ({ accessToken: state.getIn(['meta', 'access_token']), }); -class HashtagTimeline extends React.PureComponent { +@connect(mapStateToProps) +export default class HashtagTimeline extends React.PureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -136,5 +137,3 @@ class HashtagTimeline extends React.PureComponent { } } - -export default connect(mapStateToProps)(HashtagTimeline); diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.js b/app/javascript/mastodon/features/home_timeline/components/column_settings.js index 47cd340af..43172bd25 100644 --- a/app/javascript/mastodon/features/home_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.js @@ -10,7 +10,8 @@ const messages = defineMessages({ settings: { id: 'home.settings', defaultMessage: 'Column settings' }, }); -class ColumnSettings extends React.PureComponent { +@injectIntl +export default class ColumnSettings extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, @@ -43,5 +44,3 @@ class ColumnSettings extends React.PureComponent { } } - -export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index ddebf2379..6021299d6 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -19,7 +19,9 @@ const mapStateToProps = state => ({ hasFollows: state.getIn(['accounts_counters', state.getIn(['meta', 'me']), 'following_count']) > 0, }); -class HomeTimeline extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class HomeTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -96,5 +98,3 @@ class HomeTimeline extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(HomeTimeline)); diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js index f0d8856be..25ca921ae 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.js @@ -19,7 +19,16 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'mutes', 'items']), }); -class Mutes extends ImmutablePureComponent { +@connect(mapStateToProps) +@injectIntl +export default class Mutes extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired, + }; componentWillMount () { this.props.dispatch(fetchMutes()); @@ -59,12 +68,3 @@ class Mutes extends ImmutablePureComponent { } } - -Mutes.propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: PropTypes.object.isRequired, -}; - -export default connect(mapStateToProps)(injectIntl(Mutes)); diff --git a/app/javascript/mastodon/features/notifications/components/clear_column_button.js b/app/javascript/mastodon/features/notifications/components/clear_column_button.js index 54beb1c4d..22a10753f 100644 --- a/app/javascript/mastodon/features/notifications/components/clear_column_button.js +++ b/app/javascript/mastodon/features/notifications/components/clear_column_button.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -class ClearColumnButton extends React.Component { +export default class ClearColumnButton extends React.Component { static propTypes = { onClick: PropTypes.func.isRequired, @@ -15,5 +15,3 @@ class ClearColumnButton extends React.Component { } } - -export default ClearColumnButton; diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index 2051e0c86..260594894 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl'; import ClearColumnButton from './clear_column_button'; import SettingToggle from './setting_toggle'; -class ColumnSettings extends React.PureComponent { +export default class ColumnSettings extends React.PureComponent { static propTypes = { settings: ImmutablePropTypes.map.isRequired, @@ -63,5 +63,3 @@ class ColumnSettings extends React.PureComponent { } } - -export default ColumnSettings; diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index ede37f66a..9d631644a 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -8,7 +8,7 @@ import emojify from '../../../emoji'; import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class Notification extends ImmutablePureComponent { +export default class Notification extends ImmutablePureComponent { static propTypes = { notification: ImmutablePropTypes.map.isRequired, @@ -86,5 +86,3 @@ class Notification extends ImmutablePureComponent { } } - -export default Notification; diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js index 8707a993e..510820358 100644 --- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js +++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Toggle from 'react-toggle'; -class SettingToggle extends React.PureComponent { +export default class SettingToggle extends React.PureComponent { static propTypes = { prefix: PropTypes.string, @@ -30,5 +30,3 @@ class SettingToggle extends React.PureComponent { } } - -export default SettingToggle; diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index b85d6d692..1dd1b9a71 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -30,7 +30,9 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['notifications', 'next']), }); -class Notifications extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class Notifications extends React.PureComponent { static propTypes = { columnId: PropTypes.string, @@ -173,5 +175,3 @@ class Notifications extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(Notifications)); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index 3de54ef8b..c6cad02d6 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -27,7 +27,9 @@ const mapStateToProps = state => ({ accessToken: state.getIn(['meta', 'access_token']), }); -class PublicTimeline extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class PublicTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -141,5 +143,3 @@ class PublicTimeline extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(PublicTimeline)); diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js index 13fd1b20e..dc940ae01 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.js @@ -14,7 +14,8 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)]), }); -class Reblogs extends ImmutablePureComponent { +@connect(mapStateToProps) +export default class Reblogs extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, @@ -57,5 +58,3 @@ class Reblogs extends ImmutablePureComponent { } } - -export default connect(mapStateToProps)(Reblogs); diff --git a/app/javascript/mastodon/features/report/components/status_check_box.js b/app/javascript/mastodon/features/report/components/status_check_box.js index a31eabc21..6a1a84c28 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.js +++ b/app/javascript/mastodon/features/report/components/status_check_box.js @@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import emojify from '../../../emoji'; import Toggle from 'react-toggle'; -class StatusCheckBox extends React.PureComponent { +export default class StatusCheckBox extends React.PureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, @@ -36,5 +36,3 @@ class StatusCheckBox extends React.PureComponent { } } - -export default StatusCheckBox; diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js index 0a5268430..bfb09e193 100644 --- a/app/javascript/mastodon/features/report/index.js +++ b/app/javascript/mastodon/features/report/index.js @@ -35,7 +35,9 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -class Report extends React.PureComponent { +@connect(makeMapStateToProps) +@injectIntl +export default class Report extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -121,5 +123,3 @@ class Report extends React.PureComponent { } } - -export default connect(makeMapStateToProps)(injectIntl(Report)); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 21c2fd682..29080529d 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -15,7 +15,8 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, }); -class ActionBar extends React.PureComponent { +@injectIntl +export default class ActionBar extends React.PureComponent { static contextTypes = { router: PropTypes.object, @@ -91,5 +92,3 @@ class ActionBar extends React.PureComponent { } } - -export default injectIntl(ActionBar); diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 415587d6e..bfb40468b 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -17,7 +17,7 @@ const getHostname = url => { return parser.hostname; }; -class Card extends React.PureComponent { +export default class Card extends React.PureComponent { static propTypes = { card: ImmutablePropTypes.map, @@ -97,5 +97,3 @@ class Card extends React.PureComponent { } } - -export default Card; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index a77c4f0bb..619957dbe 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -12,7 +12,7 @@ import { FormattedDate, FormattedNumber } from 'react-intl'; import CardContainer from '../containers/card_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; -class DetailedStatus extends ImmutablePureComponent { +export default class DetailedStatus extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -87,5 +87,3 @@ class DetailedStatus extends ImmutablePureComponent { } } - -export default DetailedStatus; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index afd8a7811..cbabdd5bc 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -48,7 +48,9 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -class Status extends ImmutablePureComponent { +@injectIntl +@connect(makeMapStateToProps) +export default class Status extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -195,5 +197,3 @@ class Status extends ImmutablePureComponent { } } - -export default injectIntl(connect(makeMapStateToProps)(Status)); diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js index 9a8b96333..6c80a1084 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.js @@ -13,7 +13,8 @@ const messages = defineMessages({ reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, }); -class BoostModal extends ImmutablePureComponent { +@injectIntl +export default class BoostModal extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -81,5 +82,3 @@ class BoostModal extends ImmutablePureComponent { } } - -export default injectIntl(BoostModal); diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.js index 970d625b0..ce1dca171 100644 --- a/app/javascript/mastodon/features/ui/components/column.js +++ b/app/javascript/mastodon/features/ui/components/column.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { debounce } from 'lodash'; import scrollTop from '../../../scroll'; -class Column extends React.PureComponent { +export default class Column extends React.PureComponent { static propTypes = { heading: PropTypes.string, @@ -59,5 +59,3 @@ class Column extends React.PureComponent { } } - -export default Column; diff --git a/app/javascript/mastodon/features/ui/components/column_header.js b/app/javascript/mastodon/features/ui/components/column_header.js index 578432fec..dc601d6e1 100644 --- a/app/javascript/mastodon/features/ui/components/column_header.js +++ b/app/javascript/mastodon/features/ui/components/column_header.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class ColumnHeader extends React.PureComponent { +export default class ColumnHeader extends React.PureComponent { static propTypes = { icon: PropTypes.string, @@ -34,5 +34,3 @@ class ColumnHeader extends React.PureComponent { } } - -export default ColumnHeader; diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 43be34840..3c3e9425d 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -20,7 +20,7 @@ const componentMap = { 'HASHTAG': HashtagTimeline, }; -class ColumnsArea extends ImmutablePureComponent { +export default class ColumnsArea extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object.isRequired, @@ -73,5 +73,3 @@ class ColumnsArea extends ImmutablePureComponent { } } - -export default ColumnsArea; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modal.js b/app/javascript/mastodon/features/ui/components/confirmation_modal.js index a45c220fa..86588c46a 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modal.js +++ b/app/javascript/mastodon/features/ui/components/confirmation_modal.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { injectIntl, FormattedMessage } from 'react-intl'; import Button from '../../../components/button'; -class ConfirmationModal extends React.PureComponent { +@injectIntl +export default class ConfirmationModal extends React.PureComponent { static propTypes = { message: PropTypes.node.isRequired, @@ -50,5 +51,3 @@ class ConfirmationModal extends React.PureComponent { } } - -export default injectIntl(ConfirmationModal); diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index 94bf55bad..5c3879970 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -class ImageLoader extends React.PureComponent { +export default class ImageLoader extends React.PureComponent { static propTypes = { alt: PropTypes.string, @@ -65,5 +65,3 @@ class ImageLoader extends React.PureComponent { } } - -export default ImageLoader; diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index 0209bc99b..8bb81ca01 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -12,7 +12,8 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -class MediaModal extends ImmutablePureComponent { +@injectIntl +export default class MediaModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, @@ -95,5 +96,3 @@ class MediaModal extends ImmutablePureComponent { } } - -export default injectIntl(MediaModal); diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 0f68cfbdf..2e4f9876d 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -16,7 +16,7 @@ const MODAL_COMPONENTS = { 'CONFIRM': ConfirmationModal, }; -class ModalRoot extends React.PureComponent { +export default class ModalRoot extends React.PureComponent { static propTypes = { type: PropTypes.string, @@ -87,5 +87,3 @@ class ModalRoot extends React.PureComponent { } } - -export default ModalRoot; diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js index 279599169..d2e02d63b 100644 --- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js +++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js @@ -167,7 +167,9 @@ const mapStateToProps = state => ({ domain: state.getIn(['meta', 'domain']), }); -class OnboardingModal extends React.PureComponent { +@connect(mapStateToProps) +@injectIntl +export default class OnboardingModal extends React.PureComponent { static propTypes = { onClose: PropTypes.func.isRequired, @@ -322,5 +324,3 @@ class OnboardingModal extends React.PureComponent { } } - -export default connect(mapStateToProps)(injectIntl(OnboardingModal)); diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js index 09acee067..c2e6c88b5 100644 --- a/app/javascript/mastodon/features/ui/components/tabs_bar.js +++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js @@ -33,7 +33,7 @@ export function getNextLink (path) { return null; }; -class TabsBar extends React.Component { +export default class TabsBar extends React.Component { render () { return ( @@ -44,5 +44,3 @@ class TabsBar extends React.Component { } } - -export default TabsBar; diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.js index c40fe1fb9..030c3db2e 100644 --- a/app/javascript/mastodon/features/ui/components/upload_area.js +++ b/app/javascript/mastodon/features/ui/components/upload_area.js @@ -4,7 +4,7 @@ import Motion from 'react-motion/lib/Motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; -class UploadArea extends React.PureComponent { +export default class UploadArea extends React.PureComponent { static propTypes = { active: PropTypes.bool, @@ -51,5 +51,3 @@ class UploadArea extends React.PureComponent { } } - -export default UploadArea; diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js index 3599ab775..9a9a49dfb 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.js @@ -10,7 +10,8 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -class VideoModal extends ImmutablePureComponent { +@injectIntl +export default class VideoModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, @@ -35,5 +36,3 @@ class VideoModal extends ImmutablePureComponent { } } - -export default injectIntl(VideoModal); diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index e48e9dbe9..8453679b0 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -74,7 +74,8 @@ class WrappedRoute extends React.Component { } -class UI extends React.PureComponent { +@connect() +export default class UI extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, @@ -219,5 +220,3 @@ class UI extends React.PureComponent { } } - -export default connect()(UI); diff --git a/package.json b/package.json index dc08fc106..2e89b5c0a 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "babel-plugin-react-transform": "^2.0.2", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-react-jsx-self": "^6.22.0", diff --git a/yarn.lock b/yarn.lock index 85de4b546..00dff65c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -717,7 +717,7 @@ babel-plugin-syntax-class-properties@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" -babel-plugin-syntax-decorators@^6.13.0: +babel-plugin-syntax-decorators@^6.1.18, babel-plugin-syntax-decorators@^6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" @@ -790,6 +790,14 @@ babel-plugin-transform-class-properties@6.24.1, babel-plugin-transform-class-pro babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-plugin-transform-decorators-legacy@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz#741b58f6c5bce9e6027e0882d9c994f04f366925" + dependencies: + babel-plugin-syntax-decorators "^6.1.18" + babel-runtime "^6.2.0" + babel-template "^6.3.0" + babel-plugin-transform-decorators@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" @@ -1270,7 +1278,7 @@ babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-template@^6.24.1, babel-template@^6.25.0: +babel-template@^6.24.1, babel-template@^6.25.0, babel-template@^6.3.0: version "6.25.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" dependencies: From 676f577e7e18ea6a2980205e5502291506fef981 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 23 Jun 2017 19:40:51 +0200 Subject: [PATCH 062/382] Fix webpack-dev-server until it's fixed upstream (#3916) --- package.json | 2 +- yarn.lock | 138 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 122 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 2e89b5c0a..8cbcea92b 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "react-intl-translations-manager": "^5.0.0", "react-test-renderer": "^15.5.4", "sinon": "^2.3.4", - "webpack-dev-server": "^2.4.5", + "webpack-dev-server": "lencioni/webpack-dev-server#patch-1", "yargs": "^8.0.1" }, "optionalDependencies": { diff --git a/yarn.lock b/yarn.lock index 00dff65c8..ef870d7e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -302,6 +302,10 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-includes@^3.0.2, array-includes@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" @@ -1369,6 +1373,17 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -1471,6 +1486,10 @@ browserslist@^2.1.2, browserslist@^2.1.3: caniuse-lite "^1.0.30000670" electron-to-chromium "^1.3.11" +buffer-indexof@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" + buffer-writer@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" @@ -2215,6 +2234,17 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2260,6 +2290,23 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -3168,6 +3215,16 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globule@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" @@ -3474,6 +3531,12 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +internal-ip@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" @@ -3520,6 +3583,10 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" @@ -4227,7 +4294,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" dependencies: @@ -4370,6 +4437,17 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" @@ -4403,6 +4481,10 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" + node-gyp@^3.3.1: version "3.6.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" @@ -4740,6 +4822,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + packet-reader@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" @@ -4904,6 +4990,10 @@ pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pinkie-promise@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-1.0.0.tgz#d1da67f5482563bb7cf57f286ae2822ecfbf3670" @@ -6254,6 +6344,12 @@ select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" +selfsigned@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a" + dependencies: + node-forge "0.6.33" + "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -6693,7 +6789,7 @@ sugarss@^1.0.0: dependencies: postcss "^6.0.0" -supports-color@3.1.2, supports-color@^3.1.1: +supports-color@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" dependencies: @@ -6703,7 +6799,7 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.2.3: +supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: @@ -6787,6 +6883,10 @@ through@2, through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + timers-browserify@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" @@ -6882,16 +6982,7 @@ ua-parser-js@^0.7.9: version "0.7.12" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" -uglify-js@^2.8.27: - version "2.8.28" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.28.tgz#e335032df9bb20dcb918f164589d5af47f38834a" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-js@^2.8.29: +uglify-js@^2.8.27, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: @@ -7102,26 +7193,39 @@ webpack-dev-middleware@^1.10.2: path-is-absolute "^1.0.0" range-parser "^1.0.3" -webpack-dev-server@^2.4.5: - version "2.4.5" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.5.tgz#31384ce81136be1080b4b4cde0eb9b90e54ee6cf" +webpack-dev-middleware@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" + dependencies: + memory-fs "~0.4.1" + mime "^1.3.4" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + +webpack-dev-server@lencioni/webpack-dev-server#patch-1: + version "2.5.0" + resolved "https://codeload.github.com/lencioni/webpack-dev-server/tar.gz/8978059d9b880c6c28908a6ed4608e27d40f2f69" dependencies: ansi-html "0.0.7" + bonjour "^3.5.0" chokidar "^1.6.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" + del "^3.0.0" express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" + internal-ip "^1.2.0" opn "4.0.2" portfinder "^1.0.9" + selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" sockjs-client "1.1.2" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" - webpack-dev-middleware "^1.10.2" + webpack-dev-middleware "^1.11.0" yargs "^6.0.0" webpack-hot-middleware@^2.18.0: From c0979381a4dd7541ab1d70647dbb2838e7496100 Mon Sep 17 00:00:00 2001 From: Daniel Hunsaker Date: Fri, 23 Jun 2017 15:13:27 -0600 Subject: [PATCH 063/382] Fix a typo and give CW'd statuses the right cursor (#3918) --- app/javascript/mastodon/components/status_content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 0acea033c..19bde01bd 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -135,7 +135,7 @@ export default class StatusContent extends React.PureComponent { } return ( -
    +
    {interpolatedStyles => ( -
    + {interpolatedStyles.map(({ key, data, style }, i) => { const className = classNames('onboarding-modal__page__wrapper', { 'onboarding-modal__page__wrapper--active': i === currentIndex, @@ -283,7 +284,7 @@ export default class OnboardingModal extends React.PureComponent {
    {data}
    ); })} -
    + )}
    From 21c2bc119ca2bd278339799960914a91fdfa30c1 Mon Sep 17 00:00:00 2001 From: unarist Date: Sun, 25 Jun 2017 06:18:11 +0900 Subject: [PATCH 068/382] Clean column collapsible (#3931) * Remove unused column_collapsable.js * Remove old styles * Extract `> div` style to independent class --- .../mastodon/components/column_collapsable.js | 50 ------------------- .../mastodon/components/column_header.js | 2 +- app/javascript/styles/components.scss | 41 ++------------- 3 files changed, 6 insertions(+), 87 deletions(-) delete mode 100644 app/javascript/mastodon/components/column_collapsable.js diff --git a/app/javascript/mastodon/components/column_collapsable.js b/app/javascript/mastodon/components/column_collapsable.js deleted file mode 100644 index d6b4edb9f..000000000 --- a/app/javascript/mastodon/components/column_collapsable.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class ColumnCollapsable extends React.PureComponent { - - static propTypes = { - icon: PropTypes.string.isRequired, - title: PropTypes.string, - fullHeight: PropTypes.number.isRequired, - children: PropTypes.node, - onCollapse: PropTypes.func, - }; - - state = { - collapsed: true, - animating: false, - }; - - handleToggleCollapsed = () => { - const currentState = this.state.collapsed; - - this.setState({ collapsed: !currentState, animating: true }); - - if (!currentState && this.props.onCollapse) { - this.props.onCollapse(); - } - } - - handleTransitionEnd = () => { - this.setState({ animating: false }); - } - - render () { - const { icon, title, fullHeight, children } = this.props; - const { collapsed, animating } = this.state; - - return ( -
    -
    - -
    - -
    - {(!collapsed || animating) && children} -
    -
    - ); - } - -} diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js index a309f74e8..ec9379320 100644 --- a/app/javascript/mastodon/components/column_header.js +++ b/app/javascript/mastodon/components/column_header.js @@ -132,7 +132,7 @@ export default class ColumnHeader extends React.PureComponent {
    -
    +
    {(!collapsed || animating) && collapsedContent}
    diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index c2062c398..4b4f72cf7 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -58,37 +58,6 @@ position: relative; } -.column-collapsable { - position: relative; - - .column-collapsable__content { - overflow: auto; - transition: 300ms ease; - opacity: 1; - max-height: 70vh; - } - - &.collapsed .column-collapsable__content { - height: 0 !important; - opacity: 0; - } - - .column-collapsable__button { - color: $primary-text-color; - background: lighten($ui-base-color, 8%); - - &:hover { - color: $primary-text-color; - background: lighten($ui-base-color, 8%); - } - } - - &.collapsed .column-collapsable__button { - color: $ui-primary-color; - background: lighten($ui-base-color, 4%); - } -} - .column-icon { background: lighten($ui-base-color, 4%); color: $ui-primary-color; @@ -2110,11 +2079,6 @@ button.icon-button.active i.fa-retweet { transition: max-height 150ms ease-in-out, opacity 300ms linear; opacity: 1; - & > div { - background: lighten($ui-base-color, 8%); - padding: 15px; - } - &.collapsed { max-height: 0; opacity: 0.5; @@ -2125,6 +2089,11 @@ button.icon-button.active i.fa-retweet { } } +.column-header__collapsible-inner { + background: lighten($ui-base-color, 8%); + padding: 15px; +} + .column-header__setting-btn { &:hover { color: lighten($ui-primary-color, 4%); From 1fc096ec7598e688b1cd804188575a853ac84541 Mon Sep 17 00:00:00 2001 From: unarist Date: Sun, 25 Jun 2017 06:18:32 +0900 Subject: [PATCH 069/382] Fix elephant in onboarding modal being very small sized on small devices (#3932) --- app/javascript/styles/components.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 4b4f72cf7..bb9723f5a 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3126,6 +3126,7 @@ button.icon-button.active i.fa-retweet { @media screen and (max-width: 400px) { .onboarding-modal__page-one { flex-direction: column; + align-items: normal; } .onboarding-modal__page-one__elephant-friend { From 68dca26a5d36eacb7d7e691635a14b6562ba7cf1 Mon Sep 17 00:00:00 2001 From: unarist Date: Sun, 25 Jun 2017 19:49:53 +0900 Subject: [PATCH 070/382] Fix react-intl/locale-data import issue on production build (#3937) Webpack seems to fail to import `react-intl/locale-data/*.js` if those files has been proceed by babel, and this also breaks applying our translation. Note that this won't be a problem on English locale, because react-intl includes it as default and works fine without manually added locale-data. Also this issue seems to only occurs on production build, but I'm not sure about reason. --- config/webpack/loaders/babel.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/webpack/loaders/babel.js b/config/webpack/loaders/babel.js index ae65db9eb..a1992a450 100644 --- a/config/webpack/loaders/babel.js +++ b/config/webpack/loaders/babel.js @@ -1,7 +1,10 @@ module.exports = { test: /\.js$/, // include react-intl because transform-react-remove-prop-types needs to apply to it - exclude: /node_modules[\/\\](?!react-intl)/, + exclude: { + test: /node_modules/, + exclude: /react-intl[\/\\](?!locale-data)/, + }, loader: 'babel-loader', options: { forceEnv: process.env.NODE_ENV || 'development', From 3d4e21f1ecd001d82e8363eb7d4086c7fc6064ba Mon Sep 17 00:00:00 2001 From: unarist Date: Sun, 25 Jun 2017 19:52:42 +0900 Subject: [PATCH 071/382] Don't set ASSET_HOST on build:development (#3936) Setting ASSET_HOST to `http://0.0.0.0:8080` makes urls in manifest.json to be invalid, e.g. `http://0.0.0.0:8080/packs/application.js`. Anyway, we don't need set this on build:development because assets would be delivered from same origin in development (and w/o dev-server). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8cbcea92b..d1fd22fb5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0", "scripts": { "postversion": "git push --tags", - "build:development": "cross-env RAILS_ENV=development ASSET_HOST=http://0.0.0.0:8080 ./bin/webpack", + "build:development": "cross-env RAILS_ENV=development ./bin/webpack", "build:production": "cross-env RAILS_ENV=production ./bin/webpack", "manage:translations": "node ./config/webpack/translationRunner.js", "start": "rimraf ./tmp/streaming && babel ./streaming/index.js --out-dir ./tmp && node ./tmp/streaming/index.js", From 099a3b4eaccc37338eda9f45fc26991ea7115200 Mon Sep 17 00:00:00 2001 From: PFM Date: Sun, 25 Jun 2017 23:02:56 +0900 Subject: [PATCH 072/382] Fix "undefined" in className (#3939) --- app/javascript/mastodon/components/permalink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js index 5d3e4738d..0b7d0a65a 100644 --- a/app/javascript/mastodon/components/permalink.js +++ b/app/javascript/mastodon/components/permalink.js @@ -25,7 +25,7 @@ export default class Permalink extends React.PureComponent { const { href, children, className, ...other } = this.props; return ( - + {children} ); From f7301bd5b94d3033b5dbb9ff65dd1ed8ac825ce5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Jun 2017 16:54:30 +0200 Subject: [PATCH 073/382] Add overview of active sessions (#3929) * Add overview of active sessions * Better display of browser/platform name * Improve how browser information is stored and displayed for sessions overview * Fix test --- Gemfile | 1 + Gemfile.lock | 2 + .../auth/registrations_controller.rb | 5 ++ app/helpers/settings_helper.rb | 12 +++++ app/javascript/styles/tables.scss | 12 +++++ app/models/session_activation.rb | 48 +++++++++++++------ app/models/user.rb | 6 ++- .../auth/registrations/_sessions.html.haml | 23 +++++++++ app/views/auth/registrations/edit.html.haml | 4 ++ config/initializers/devise.rb | 2 +- config/locales/en.yml | 37 ++++++++++++++ ..._add_description_to_session_activations.rb | 7 +++ db/schema.rb | 5 +- spec/rails_helper.rb | 2 +- yarn.lock | 11 +---- 15 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 app/views/auth/registrations/_sessions.html.haml create mode 100644 db/migrate/20170624134742_add_description_to_session_activations.rb diff --git a/Gemfile b/Gemfile index 77fffe7a6..aecd82702 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'paperclip-av-transcoder', '~> 0.6' gem 'addressable', '~> 2.5' gem 'bootsnap' +gem 'browser' gem 'cld3', '~> 3.1' gem 'devise', '~> 4.2' gem 'devise-two-factor', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 00ce84556..627a01787 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -70,6 +70,7 @@ GEM bootsnap (1.0.0) msgpack (~> 1.0) brakeman (3.6.2) + browser (2.4.0) builder (3.2.3) bullet (5.5.1) activesupport (>= 3.0.0) @@ -483,6 +484,7 @@ DEPENDENCIES binding_of_caller (~> 0.7) bootsnap brakeman (~> 3.6) + browser bullet (~> 5.5) bundler-audit (~> 0.5) capistrano (~> 3.8) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index d385c08e1..60ace04d7 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -5,6 +5,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :check_enabled_registrations, only: [:new, :create] before_action :configure_sign_up_params, only: [:create] + before_action :set_sessions, only: [:edit, :update] def destroy not_found @@ -41,4 +42,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController def determine_layout %w(edit update).include?(action_name) ? 'admin' : 'auth' end + + def set_sessions + @sessions = current_user.session_activations + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 172ef33ca..847eff2e7 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -41,4 +41,16 @@ module SettingsHelper def hash_to_object(hash) HashObject.new(hash) end + + def session_device_icon(session) + device = session.detection.device + + if device.mobile? + 'mobile' + elsif device.tablet? + 'tablet' + else + 'desktop' + end + end end diff --git a/app/javascript/styles/tables.scss b/app/javascript/styles/tables.scss index f7def8cf3..6e54c59c0 100644 --- a/app/javascript/styles/tables.scss +++ b/app/javascript/styles/tables.scss @@ -42,6 +42,18 @@ strong { font-weight: 500; } + + &.inline-table { + td, + th { + padding: 8px 0; + } + + & > tbody > tr:nth-child(odd) > td, + & > tbody > tr:nth-child(odd) > th { + background: transparent; + } + } } samp { diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index 71e9f023c..75339b5f7 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -8,31 +8,49 @@ # session_id :string not null # created_at :datetime not null # updated_at :datetime not null +# user_agent :string default(""), not null +# ip :inet # class SessionActivation < ApplicationRecord - LIMIT = Rails.configuration.x.max_session_activations - - def self.active?(id) - id && where(session_id: id).exists? + def detection + @detection ||= Browser.new(user_agent) end - def self.activate(id) - activation = create!(session_id: id) - purge_old - activation + def browser + detection.id end - def self.deactivate(id) - return unless id - where(session_id: id).destroy_all + def platform + detection.platform.id end - def self.purge_old - order('created_at desc').offset(LIMIT).destroy_all + before_save do + self.user_agent = '' if user_agent.nil? end - def self.exclusive(id) - where('session_id != ?', id).destroy_all + class << self + def active?(id) + id && where(session_id: id).exists? + end + + def activate(options = {}) + activation = create!(options) + purge_old + activation + end + + def deactivate(id) + return unless id + where(session_id: id).destroy_all + end + + def purge_old + order('created_at desc').offset(Rails.configuration.x.max_session_activations).destroy_all + end + + def exclusive(id) + where('session_id != ?', id).destroy_all + end end end diff --git a/app/models/user.rb b/app/models/user.rb index fccf1089b..c31a0c644 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,8 +91,10 @@ class User < ApplicationRecord settings.auto_play_gif end - def activate_session - session_activations.activate(SecureRandom.hex).session_id + def activate_session(request) + session_activations.activate(session_id: SecureRandom.hex, + user_agent: request.user_agent, + ip: request.ip).session_id end def exclusive_session(id) diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml new file mode 100644 index 000000000..11c0d4e31 --- /dev/null +++ b/app/views/auth/registrations/_sessions.html.haml @@ -0,0 +1,23 @@ +%h6= t 'sessions.title' +%p.muted-hint= t 'sessions.explanation' + +%table.table.inline-table + %thead + %tr + %th= t 'sessions.browser' + %th= t 'sessions.ip' + %th= t 'sessions.activity' + %tbody + - @sessions.each do |session| + %tr + %td + %span{ title: session.user_agent }= fa_icon session_device_icon(session) + = ' ' + = t 'sessions.description', browser: t("sessions.browsers.#{session.browser}"), platform: t("sessions.platforms.#{session.platform}") + %td + %samp= session.ip + %td + - if request.session['auth_id'] == session.session_id + = t 'sessions.current_session' + - else + %time.time-ago{ datetime: session.updated_at.iso8601, title: l(session.updated_at) }= l(session.updated_at) diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 38d4349cb..fbc8d017b 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -12,6 +12,10 @@ .actions = f.button :button, t('generic.save_changes'), type: :submit +%hr/ + += render 'sessions' + - if open_deletion? %hr/ diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 6d3a73ef6..d51471d30 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,6 +1,6 @@ Warden::Manager.after_set_user except: :fetch do |user, warden| SessionActivation.deactivate warden.raw_session['auth_id'] - warden.raw_session['auth_id'] = user.activate_session + warden.raw_session['auth_id'] = user.activate_session(warden.request) end Warden::Manager.after_fetch do |user, warden| diff --git a/config/locales/en.yml b/config/locales/en.yml index 0d33aae3f..1d8e3f6b0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -320,6 +320,43 @@ en: missing_resource: Could not find the required redirect URL for your account proceed: Proceed to follow prompt: 'You are going to follow:' + sessions: + activity: Last activity + browser: Browser + browsers: + alipay: Alipay + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: Unknown browser + ie: Internet Explorer + micro_messenger: MicroMessenger + nokia: Nokia S40 Ovi Browser + opera: Opera + phantom_js: PhantomJS + qq: QQ Browser + safari: Safari + uc_browser: UCBrowser + weibo: Weibo + current_session: Current session + description: "%{browser} on %{platform}" + explanation: These are the web browsers currently logged in to your Mastodon account. + ip: IP + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: unknown platform + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + title: Sessions settings: authorized_apps: Authorized apps back: Back to Mastodon diff --git a/db/migrate/20170624134742_add_description_to_session_activations.rb b/db/migrate/20170624134742_add_description_to_session_activations.rb new file mode 100644 index 000000000..9dbb15564 --- /dev/null +++ b/db/migrate/20170624134742_add_description_to_session_activations.rb @@ -0,0 +1,7 @@ +class AddDescriptionToSessionActivations < ActiveRecord::Migration[5.1] + def change + add_column :session_activations, :user_agent, :string, null: false, default: '' + add_column :session_activations, :ip, :inet + add_foreign_key :session_activations, :users, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index b6aceb930..1e7d6c0b3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170623152212) do +ActiveRecord::Schema.define(version: 20170624134742) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -255,6 +255,8 @@ ActiveRecord::Schema.define(version: 20170623152212) do t.string "session_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "user_agent", default: "", null: false + t.inet "ip" t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true t.index ["user_id"], name: "index_session_activations_on_user_id" end @@ -404,6 +406,7 @@ ActiveRecord::Schema.define(version: 20170623152212) do add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", on_delete: :nullify add_foreign_key "reports", "accounts", column: "target_account_id", on_delete: :cascade add_foreign_key "reports", "accounts", on_delete: :cascade + add_foreign_key "session_activations", "users", on_delete: :cascade add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", on_delete: :nullify add_foreign_key "statuses", "accounts", on_delete: :cascade add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 31c94b1e4..cfc9eec9e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -23,7 +23,7 @@ Devise::Test::ControllerHelpers.module_eval do original_sign_in(resource, scope: scope) SessionActivation.deactivate warden.raw_session["auth_id"] - warden.raw_session["auth_id"] = resource.activate_session + warden.raw_session["auth_id"] = resource.activate_session(warden.request) end end diff --git a/yarn.lock b/yarn.lock index ef870d7e2..d1a1687a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7184,16 +7184,7 @@ webpack-bundle-analyzer@^2.8.2: opener "^1.4.3" ws "^2.3.1" -webpack-dev-middleware@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1" - dependencies: - memory-fs "~0.4.1" - mime "^1.3.4" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - -webpack-dev-middleware@^1.11.0: +webpack-dev-middleware@^1.10.2, webpack-dev-middleware@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" dependencies: From 87efa3872193084468c25f410f5cf9189c0b976e Mon Sep 17 00:00:00 2001 From: amazedkoumei Date: Mon, 26 Jun 2017 01:13:31 +0900 Subject: [PATCH 074/382] more free pgconfig by .env (#3909) * more free pgconfig for streaming by .env * fix wrong default values * database.yml read ENV as same as streaming server --- config/database.yml | 8 ++++++++ streaming/index.js | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/config/database.yml b/config/database.yml index 39393e93a..079ea7b4a 100644 --- a/config/database.yml +++ b/config/database.yml @@ -7,6 +7,10 @@ default: &default development: <<: *default database: mastodon_development + username: <%= ENV['DB_USER'] %> + password: <%= ENV['DB_PASS'] %> + host: <%= ENV['DB_HOST'] %> + port: <%= ENV['DB_PORT'] %> # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". @@ -14,6 +18,10 @@ development: test: <<: *default database: mastodon_test<%= ENV['TEST_ENV_NUMBER'] %> + username: <%= ENV['TEST_DB_USER'] %> + password: <%= ENV['TEST_DB_PASS'] %> + host: <%= ENV['TEST_DB_HOST'] %> + port: <%= ENV['TEST_DB_PORT'] %> production: <<: *default diff --git a/streaming/index.js b/streaming/index.js index 156e1d4bc..701cb2f55 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -78,7 +78,11 @@ const startWorker = (workerId) => { const pgConfigs = { development: { - database: 'mastodon_development', + user: process.env.DB_USER || pg.defaults.user, + password: process.env.DB_PASS || pg.defaults.password, + database: process.env.DB_NAME || 'mastodon_development', + host: process.env.DB_HOST || pg.defaults.host, + port: process.env.DB_PORT || pg.defaults.port, max: 10, }, From 8f991831b8de01b69d6b0f069aa43d9326c93a1d Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 26 Jun 2017 04:42:36 +0900 Subject: [PATCH 075/382] Cover Admin::DomainBlocksController more (#3329) Also domain_block fabricator now sets unique domains --- .../admin/domain_blocks_controller_spec.rb | 34 ++++++++++++++++--- spec/fabricators/domain_block_fabricator.rb | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index 0ca41d7d4..b9e73c04b 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -8,17 +8,30 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do end describe 'GET #index' do - it 'returns http success' do - get :index + around do |example| + default_per_page = DomainBlock.default_per_page + DomainBlock.paginates_per 1 + example.run + DomainBlock.paginates_per default_per_page + end + it 'renders domain blocks' do + 2.times { Fabricate(:domain_block) } + + get :index, params: { page: 2 } + + assigned = assigns(:domain_blocks) + expect(assigned.count).to eq 1 + expect(assigned.klass).to be DomainBlock expect(response).to have_http_status(:success) end end describe 'GET #new' do - it 'returns http success' do + it 'assigns a new domain block' do get :new + expect(assigns(:domain_block)).to be_instance_of(DomainBlock) expect(response).to have_http_status(:success) end end @@ -33,13 +46,25 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do end describe 'POST #create' do - it 'blocks the domain' do + it 'blocks the domain when succeeded to save' do allow(DomainBlockWorker).to receive(:perform_async).and_return(true) + post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } } expect(DomainBlockWorker).to have_received(:perform_async) + expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg') expect(response).to redirect_to(admin_domain_blocks_path) end + + it 'renders new when failed to save' do + Fabricate(:domain_block, domain: 'example.com') + allow(DomainBlockWorker).to receive(:perform_async).and_return(true) + + post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } } + + expect(DomainBlockWorker).not_to have_received(:perform_async) + expect(response).to render_template :new + end end describe 'DELETE #destroy' do @@ -50,6 +75,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do delete :destroy, params: { id: domain_block.id, domain_block: { retroactive: '1' } } expect(service).to have_received(:call).with(domain_block, true) + expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.destroyed_msg') expect(response).to redirect_to(admin_domain_blocks_path) end end diff --git a/spec/fabricators/domain_block_fabricator.rb b/spec/fabricators/domain_block_fabricator.rb index 563a0f65b..cc1f928e5 100644 --- a/spec/fabricators/domain_block_fabricator.rb +++ b/spec/fabricators/domain_block_fabricator.rb @@ -1,3 +1,3 @@ Fabricator(:domain_block) do - domain "example.com" + domain { sequence(:domain) { |i| "#{i}#{Faker::Internet.domain_name}" } } end From 67243bda3157a9046148b41579f0b1e8cf4a7116 Mon Sep 17 00:00:00 2001 From: "Akihiko Odaki (@fn_aki@pawoo.net)" Date: Mon, 26 Jun 2017 04:42:55 +0900 Subject: [PATCH 076/382] Cover Auth::RegistrationsController more (#3353) --- .../auth/registrations_controller_spec.rb | 105 +++++++++++++++--- 1 file changed, 89 insertions(+), 16 deletions(-) diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index df0a3bfa6..97d2c53df 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -3,37 +3,110 @@ require 'rails_helper' RSpec.describe Auth::RegistrationsController, type: :controller do render_views + shared_examples 'checks for enabled registrations' do |path| + around do |example| + open_registrations = Setting.open_registrations + example.run + Setting.open_registrations = open_registrations + end + + it 'redirects if it is in single user mode while it is open for registration' do + Fabricate(:account) + Setting.open_registrations = true + expect(Rails.configuration.x).to receive(:single_user_mode).and_return(true) + + get path + + expect(response).to redirect_to '/' + end + + it 'redirects if it is not open for registration while it is not in single user mode' do + Setting.open_registrations = false + expect(Rails.configuration.x).to receive(:single_user_mode).and_return(false) + + get path + + expect(response).to redirect_to '/' + end + end + + describe 'GET #edit' do + it 'returns http success' do + request.env["devise.mapping"] = Devise.mappings[:user] + sign_in(Fabricate(:user)) + get :edit + expect(response).to have_http_status(:success) + end + end + + describe 'GET #update' do + it 'returns http success' do + request.env["devise.mapping"] = Devise.mappings[:user] + sign_in(Fabricate(:user), scope: :user) + post :update + expect(response).to have_http_status(:success) + end + end + describe 'GET #new' do before do - Setting.open_registrations = true request.env["devise.mapping"] = Devise.mappings[:user] end - it 'returns http success' do - get :new - expect(response).to have_http_status(:success) + context do + around do |example| + open_registrations = Setting.open_registrations + example.run + Setting.open_registrations = open_registrations + end + + it 'returns http success' do + Setting.open_registrations = true + get :new + expect(response).to have_http_status(:success) + end end + + include_examples 'checks for enabled registrations', :new end describe 'POST #create' do let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s } - before do - Setting.open_registrations = true - request.env["devise.mapping"] = Devise.mappings[:user] - request.headers["Accept-Language"] = accept_language - post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } } + before { request.env["devise.mapping"] = Devise.mappings[:user] } + + context do + around do |example| + open_registrations = Setting.open_registrations + example.run + Setting.open_registrations = open_registrations + end + + subject do + Setting.open_registrations = true + request.headers["Accept-Language"] = accept_language + post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } } + end + + it 'redirects to login page' do + subject + expect(response).to redirect_to new_user_session_path + end + + it 'creates user' do + subject + user = User.find_by(email: 'test@example.com') + expect(user).to_not be_nil + expect(user.locale).to eq(accept_language) + end end - it 'redirects to login page' do - expect(response).to redirect_to new_user_session_path + it 'does nothing if user already exists' do + Fabricate(:user, account: Fabricate(:account, username: 'test')) + subject end - it 'creates user' do - user = User.find_by(email: 'test@example.com') - expect(user).to_not be_nil - expect(user.locale).to eq(accept_language) - end + include_examples 'checks for enabled registrations', :create end describe 'DELETE #destroy' do From 4ce154009426038f0bb88d2e89b408ade928b1a2 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Sun, 25 Jun 2017 21:43:27 +0200 Subject: [PATCH 077/382] fix(features/compose): Handle external changes to the textarea (#3632) --- .../mastodon/features/compose/components/compose_form.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index c379c1855..f7eeedc69 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -67,6 +67,12 @@ export default class ComposeForm extends ImmutablePureComponent { } handleSubmit = () => { + if (this.props.text !== this.autosuggestTextarea.textarea.value) { + // Something changed the text inside the textarea (e.g. browser extensions like Grammarly) + // Update the state to match the current text + this.props.onChange(this.autosuggestTextarea.textarea.value); + } + this.props.onSubmit(); } From d821aba002002f79385abd7f052f68c383d5cd0c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Jun 2017 22:13:02 +0200 Subject: [PATCH 078/382] Rename "Credentials" page to "Security" for clarity (#3941) * Rename "Credentials" page to "Security" for clarity * Change "security" icon from cog to lock --- config/locales/en.yml | 2 +- config/navigation.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 1d8e3f6b0..7238949dc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -200,7 +200,7 @@ en: applications: invalid_url: The provided URL is invalid auth: - change_password: Credentials + change_password: Security delete_account: Delete account delete_account_html: If you wish to delete your account, you can proceed here. You will be asked for confirmation. didnt_get_confirmation: Didn't receive confirmation instructions? diff --git a/config/navigation.rb b/config/navigation.rb index d7508f019..535d033f5 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -7,7 +7,7 @@ SimpleNavigation::Configuration.run do |navigation| primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings| settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url - settings.item :password, safe_join([fa_icon('cog fw'), t('auth.change_password')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete} + settings.item :password, safe_join([fa_icon('lock fw'), t('auth.change_password')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete} settings.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_url, highlights_on: %r{/settings/two_factor_authentication} settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url From 436ce03772c8c87a215cdcd88020edfb8c241d38 Mon Sep 17 00:00:00 2001 From: amazedkoumei Date: Mon, 26 Jun 2017 06:29:22 +0900 Subject: [PATCH 079/382] fix unnecessary variable (#3947) --- config/database.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/database.yml b/config/database.yml index 079ea7b4a..f74635a36 100644 --- a/config/database.yml +++ b/config/database.yml @@ -18,10 +18,10 @@ development: test: <<: *default database: mastodon_test<%= ENV['TEST_ENV_NUMBER'] %> - username: <%= ENV['TEST_DB_USER'] %> - password: <%= ENV['TEST_DB_PASS'] %> - host: <%= ENV['TEST_DB_HOST'] %> - port: <%= ENV['TEST_DB_PORT'] %> + username: <%= ENV['DB_USER'] %> + password: <%= ENV['DB_PASS'] %> + host: <%= ENV['DB_HOST'] %> + port: <%= ENV['DB_PORT'] %> production: <<: *default From ed7dc1704dc3ce82567d9aac366b095f02ce181f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Jun 2017 23:51:32 +0200 Subject: [PATCH 080/382] Bind web UI access tokens to sessions (#3940) * Add overview of active sessions * Better display of browser/platform name * Improve how browser information is stored and displayed for sessions overview * Fix test * Fix #2347 - Bind web UI access token to session When you logout, session also destroys the access token, so it's no longer valid. If access token is destroyed some other way, the session is also destroyed, requiring a re-login. Fix #1681 - Add scheduler to remove revoked access tokens and grants * Fix test --- app/controllers/application_controller.rb | 5 +++ app/controllers/home_controller.rb | 12 +---- app/models/session_activation.rb | 44 ++++++++++++++----- .../scheduler/doorkeeper_cleanup_scheduler.rb | 11 +++++ config/sidekiq.yml | 3 ++ ..._access_token_id_to_session_activations.rb | 6 +++ db/schema.rb | 4 +- 7 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 app/workers/scheduler/doorkeeper_cleanup_scheduler.rb create mode 100644 db/migrate/20170625140443_add_access_token_id_to_session_activations.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9cb397aa8..865fcd125 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base include UserTrackingConcern helper_method :current_account + helper_method :current_session helper_method :single_user_mode? rescue_from ActionController::RoutingError, with: :not_found @@ -68,6 +69,10 @@ class ApplicationController < ActionController::Base @current_account ||= current_user.try(:account) end + def current_session + @current_session ||= SessionActivation.find_by(session_id: session['auth_id']) + end + def cache_collection(raw, klass) return raw unless klass.respond_to?(:with_includes) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 1d41892cd..6209a3ae9 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -5,7 +5,7 @@ class HomeController < ApplicationController def index @body_classes = 'app-body' - @token = find_or_create_access_token.token + @token = current_session.token @web_settings = Web::Setting.find_by(user: current_user)&.data || {} @admin = Account.find_local(Setting.site_contact_username) @streaming_api_base_url = Rails.configuration.x.streaming_api_base_url @@ -16,14 +16,4 @@ class HomeController < ApplicationController def authenticate_user! redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in? end - - def find_or_create_access_token - Doorkeeper::AccessToken.find_or_create_for( - Doorkeeper::Application.where(superapp: true).first, - current_user.id, - Doorkeeper::OAuth::Scopes.from_string('read write follow'), - Doorkeeper.configuration.access_token_expires_in, - Doorkeeper.configuration.refresh_token_enabled? - ) - end end diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index 75339b5f7..02a918e8a 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -3,16 +3,23 @@ # # Table name: session_activations # -# id :integer not null, primary key -# user_id :integer not null -# session_id :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_agent :string default(""), not null -# ip :inet +# id :integer not null, primary key +# user_id :integer not null +# session_id :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_agent :string default(""), not null +# ip :inet +# access_token_id :integer # class SessionActivation < ApplicationRecord + belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy + + delegate :token, + to: :access_token, + allow_nil: true + def detection @detection ||= Browser.new(user_agent) end @@ -25,9 +32,8 @@ class SessionActivation < ApplicationRecord detection.platform.id end - before_save do - self.user_agent = '' if user_agent.nil? - end + before_create :assign_access_token + before_save :assign_user_agent class << self def active?(id) @@ -53,4 +59,22 @@ class SessionActivation < ApplicationRecord where('session_id != ?', id).destroy_all end end + + private + + def assign_user_agent + self.user_agent = '' if user_agent.nil? + end + + def assign_access_token + superapp = Doorkeeper::Application.find_by(superapp: true) + + return if superapp.nil? + + self.access_token = Doorkeeper::AccessToken.create!(application_id: superapp.id, + resource_owner_id: user_id, + scopes: 'read write follow', + expires_in: Doorkeeper.configuration.access_token_expires_in, + use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?) + end end diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb new file mode 100644 index 000000000..6488798cd --- /dev/null +++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +require 'sidekiq-scheduler' + +class Scheduler::DoorkeeperCleanupScheduler + include Sidekiq::Worker + + def perform + Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all + Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all + end +end diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 6ed0aa4b5..78aaa311c 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -15,3 +15,6 @@ feed_cleanup_scheduler: cron: '0 0 * * *' class: Scheduler::FeedCleanupScheduler + doorkeeper_cleanup_scheduler: + cron: '1 1 * * 0' + class: Scheduler::DoorkeeperCleanupScheduler diff --git a/db/migrate/20170625140443_add_access_token_id_to_session_activations.rb b/db/migrate/20170625140443_add_access_token_id_to_session_activations.rb new file mode 100644 index 000000000..213a77a83 --- /dev/null +++ b/db/migrate/20170625140443_add_access_token_id_to_session_activations.rb @@ -0,0 +1,6 @@ +class AddAccessTokenIdToSessionActivations < ActiveRecord::Migration[5.1] + def change + add_column :session_activations, :access_token_id, :integer + add_foreign_key :session_activations, :oauth_access_tokens, column: :access_token_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 1e7d6c0b3..159704c6a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170624134742) do +ActiveRecord::Schema.define(version: 20170625140443) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -257,6 +257,7 @@ ActiveRecord::Schema.define(version: 20170624134742) do t.datetime "updated_at", null: false t.string "user_agent", default: "", null: false t.inet "ip" + t.integer "access_token_id" t.index ["session_id"], name: "index_session_activations_on_session_id", unique: true t.index ["user_id"], name: "index_session_activations_on_user_id" end @@ -406,6 +407,7 @@ ActiveRecord::Schema.define(version: 20170624134742) do add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", on_delete: :nullify add_foreign_key "reports", "accounts", column: "target_account_id", on_delete: :cascade add_foreign_key "reports", "accounts", on_delete: :cascade + add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade add_foreign_key "session_activations", "users", on_delete: :cascade add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", on_delete: :nullify add_foreign_key "statuses", "accounts", on_delete: :cascade From 5e8d037e271bdd230fc7ab1e91bcee16ac87e0e1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 25 Jun 2017 23:51:46 +0200 Subject: [PATCH 081/382] Fix #3910 - Require OTP authentication to disable 2FA (#3935) * Fix #3910 - Require OTP authentication to disable 2FA. Also, remove ability to generate new OTP backup codes *after* initial backup codes were handed out during activation * Restore recovery code re-generation * Improve display of some 2FA elements --- .../two_factor_authentications_controller.rb | 20 +++++++-- app/javascript/styles/admin.scss | 5 +++ app/javascript/styles/forms.scss | 1 - app/javascript/styles/lists.scss | 1 - .../recovery_codes/index.html.haml | 2 +- .../two_factor_authentications/show.html.haml | 44 +++++++++++-------- config/locales/ca.yml | 2 +- config/locales/de.yml | 2 +- config/locales/en.yml | 8 +++- config/locales/fa.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/he.yml | 2 +- config/locales/id.yml | 2 +- config/locales/io.yml | 2 +- config/locales/ja.yml | 2 +- config/locales/nl.yml | 2 +- config/locales/no.yml | 2 +- config/locales/oc.yml | 6 +-- config/locales/pl.yml | 2 +- config/locales/pt-BR.yml | 2 +- config/locales/ru.yml | 2 +- config/locales/th.yml | 2 +- config/locales/tr.yml | 2 +- config/locales/uk.yml | 2 +- config/locales/zh-CN.yml | 2 +- config/locales/zh-HK.yml | 2 +- ..._factor_authentications_controller_spec.rb | 40 ++++++++++++++--- 27 files changed, 109 insertions(+), 54 deletions(-) diff --git a/app/controllers/settings/two_factor_authentications_controller.rb b/app/controllers/settings/two_factor_authentications_controller.rb index f66c3a908..983483881 100644 --- a/app/controllers/settings/two_factor_authentications_controller.rb +++ b/app/controllers/settings/two_factor_authentications_controller.rb @@ -7,7 +7,9 @@ module Settings before_action :authenticate_user! before_action :verify_otp_required, only: [:create] - def show; end + def show + @confirmation = Form::TwoFactorConfirmation.new + end def create current_user.otp_secret = User.generate_otp_secret(32) @@ -16,13 +18,23 @@ module Settings end def destroy - current_user.otp_required_for_login = false - current_user.save! - redirect_to settings_two_factor_authentication_path + if current_user.validate_and_consume_otp!(confirmation_params[:code]) + current_user.otp_required_for_login = false + current_user.save! + redirect_to settings_two_factor_authentication_path + else + flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code') + @confirmation = Form::TwoFactorConfirmation.new + render :show + end end private + def confirmation_params + params.require(:form_two_factor_confirmation).permit(:code) + end + def verify_otp_required redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login? end diff --git a/app/javascript/styles/admin.scss b/app/javascript/styles/admin.scss index c2bfc10a0..3bc713566 100644 --- a/app/javascript/styles/admin.scss +++ b/app/javascript/styles/admin.scss @@ -129,6 +129,11 @@ color: $ui-primary-color; } } + + .positive-hint { + color: $valid-value-color; + font-weight: 500; + } } .simple_form { diff --git a/app/javascript/styles/forms.scss b/app/javascript/styles/forms.scss index 059c4a7d8..7a181f36b 100644 --- a/app/javascript/styles/forms.scss +++ b/app/javascript/styles/forms.scss @@ -358,7 +358,6 @@ code { } .user_filtered_languages { - & > label { font-family: inherit; font-size: 16px; diff --git a/app/javascript/styles/lists.scss b/app/javascript/styles/lists.scss index 47805663f..6019cd800 100644 --- a/app/javascript/styles/lists.scss +++ b/app/javascript/styles/lists.scss @@ -10,7 +10,6 @@ .recovery-codes { list-style: none; margin: 0 auto; - text-align: center; li { font-size: 125%; diff --git a/app/views/settings/two_factor_authentication/recovery_codes/index.html.haml b/app/views/settings/two_factor_authentication/recovery_codes/index.html.haml index 7d409826e..d47ee840e 100644 --- a/app/views/settings/two_factor_authentication/recovery_codes/index.html.haml +++ b/app/views/settings/two_factor_authentication/recovery_codes/index.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('settings.two_factor_authentication') -%p.hint= t('two_factor_authentication.recovery_instructions') +%p.hint= t('two_factor_authentication.recovery_instructions_html') %ol.recovery-codes - @recovery_codes.each do |code| diff --git a/app/views/settings/two_factor_authentications/show.html.haml b/app/views/settings/two_factor_authentications/show.html.haml index 88b5bd20e..8ba42a101 100644 --- a/app/views/settings/two_factor_authentications/show.html.haml +++ b/app/views/settings/two_factor_authentications/show.html.haml @@ -1,26 +1,34 @@ - content_for :page_title do = t('settings.two_factor_authentication') -.simple_form - %p.hint - = t('two_factor_authentication.description_html') +- if current_user.otp_required_for_login + %p.positive-hint + = fa_icon 'check' + = ' ' + = t 'two_factor_authentication.enabled' + + %hr/ + + = simple_form_for @confirmation, url: settings_two_factor_authentication_path, method: :delete do |f| + = f.input :code, hint: t('two_factor_authentication.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt') + + .actions + = f.button :button, t('two_factor_authentication.disable'), type: :submit + + %hr/ + + %h6= t('two_factor_authentication.recovery_codes') + %p.muted-hint + = t('two_factor_authentication.lost_recovery_codes') + = link_to t('two_factor_authentication.generate_recovery_codes'), + settings_two_factor_authentication_recovery_codes_path, + data: { method: :post } + +- else + .simple_form + %p.hint= t('two_factor_authentication.description_html') - - if current_user.otp_required_for_login - = link_to t('two_factor_authentication.disable'), - settings_two_factor_authentication_path, - data: { method: :delete }, - class: 'block-button' - - else = link_to t('two_factor_authentication.setup'), settings_two_factor_authentication_path, data: { method: :post }, class: 'block-button' - -- if current_user.otp_required_for_login - .simple_form - %p.hint - = t('two_factor_authentication.lost_recovery_codes') - = link_to t('two_factor_authentication.generate_recovery_codes'), - settings_two_factor_authentication_recovery_codes_path, - data: { method: :post }, - class: 'block-button' diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 24fc5690d..2fbc63ef9 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -360,7 +360,7 @@ ca: lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els teus codis de recuperació els pots regenerar aquí. Els codis de recuperació anteriors seran anul·lats. manual_instructions: 'Si no pots escanejar el codi QR code i necessites introduir-lo manualment, aquí tens el secret en text plà:' recovery_codes_regenerated: Codis de recuperació regenerats amb èxit - recovery_instructions: Si alguna vegada perds l'accéss al telèfon pots utilitzar un dels codis de recuperació a continuació per recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur, per exemple imprimint-los i guardar-los amb altres documents importants. + recovery_instructions_html: Si alguna vegada perds l'accéss al telèfon pots utilitzar un dels codis de recuperació a continuació per recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur, per exemple imprimint-los i guardar-los amb altres documents importants. setup: Establir wrong_code: El codi introduït es invalid! Es correcta la hora del servidor i del dispositiu? users: diff --git a/config/locales/de.yml b/config/locales/de.yml index 72d60d2a0..f2841d0b7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -304,7 +304,7 @@ de: lost_recovery_codes: Wiederherstellungscodes erlauben dir, wieder den Zugang zu deinem Konto zu erlangen, falls du dein Telefon verlierst. Wenn du deine Wiederherstellungscodes verloren hast, kannst du sie hier regenerieren. Deine alten Wiederherstellungscodes werden damit ungültig gemacht. manual_instructions: 'Wenn du den QR-Code nicht einlesen kannst und ihn manuell eingeben musst, ist hier das Klartext-Geheimnis:' recovery_codes_regenerated: Wiederherstellungscodes erfolgreich regeneriert - recovery_instructions: Wenn du jemals den Zugang zu deinem Telefon verlierst, kannst du einen der Wiederherstellungscodes unten benutzen, um wieder auf dein Konto zugreifen zu können. Bewahre die Wiederherstellungscodes sicher auf, indem du sie beispielsweise ausdruckst und sie zusammen mit anderen wichtigen Dokumenten lagerst. + recovery_instructions_html: Wenn du jemals den Zugang zu deinem Telefon verlierst, kannst du einen der Wiederherstellungscodes unten benutzen, um wieder auf dein Konto zugreifen zu können. Bewahre die Wiederherstellungscodes sicher auf, indem du sie beispielsweise ausdruckst und sie zusammen mit anderen wichtigen Dokumenten lagerst. setup: Einrichten wrong_code: Der eingegebene Code war ungültig! Sind die Server- und die Gerätezeit korrekt? users: diff --git a/config/locales/en.yml b/config/locales/en.yml index 7238949dc..9daaf53ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -391,13 +391,17 @@ en: description_html: If you enable two-factor authentication, logging in will require you to be in possession of your phone, which will generate tokens for you to enter. disable: Disable enable: Enable + enabled: Two-factor authentication is enabled enabled_success: Two-factor authentication successfully enabled - generate_recovery_codes: Generate Recovery Codes + generate_recovery_codes: Generate recovery codes instructions_html: "Scan this QR code into Google Authenticator or a similiar TOTP app on your phone. From now on, that app will generate tokens that you will have to enter when logging in." lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated. manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:' + recovery_codes: Backup recovery codes recovery_codes_regenerated: Recovery codes successfully regenerated - recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. (For example, you may print them and store them with other important documents.) + recovery_instructions_html: + If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. + For example, you may print them and store them with other important documents. setup: Set up wrong_code: The entered code was invalid! Are server time and device time correct? users: diff --git a/config/locales/fa.yml b/config/locales/fa.yml index a65de2365..515443608 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -334,7 +334,7 @@ fa: lost_recovery_codes: با کدهای بازیابی می‌توانید اگر تلفن خود را گم کردید به حساب خود دسترسی داشته باشید. اگر کدهای بازیابی خود را گم کردید، آن‌ها را این‌جا دوباره بسازید. کدهای بازیابی قبلی شما نامعتبر خواهند شد. manual_instructions: 'اگر نمی‌توانید کدها را اسکن کنید و باید آن‌ها را دستی وارد کنید، متن کد امنیتی این‌جاست:' recovery_codes_regenerated: کدهای بازیابی با موفقیت ساخته شدند - recovery_instructions: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. این کدها را در جای امنی نگه دارید، مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید + recovery_instructions_html: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. این کدها را در جای امنی نگه دارید، مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید setup: راه اندازی wrong_code: کدی که وارد کردید نامعتبر بود! آیا ساعت سرور و ساعت دستگاه شما درست تنظیم شده‌اند؟ users: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7f348986e..0c3f3b1d5 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -300,7 +300,7 @@ fr: lost_recovery_codes: Les codes de récupération vous permettent de retrouver les accès à votre comptre si vous perdez votre téléphone. Si vous perdez vos codes de récupération, vous pouvez les générer à nouveau ici. Vos anciens codes de récupération seront invalidés. manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez l''entrer manuellement, voici le secret en clair :' recovery_codes_regenerated: Codes de récupération régénérés avec succès - recovery_instructions: Si vous perdez l'accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour récupérer l'accès à votre compte. Conservez les codes de récupération en toute sécurité, par exemple, en les imprimant et en les stockant avec vos autres documents importants. + recovery_instructions_html: Si vous perdez l'accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour récupérer l'accès à votre compte. Conservez les codes de récupération en toute sécurité, par exemple, en les imprimant et en les stockant avec vos autres documents importants. setup: Installer wrong_code: Les codes entrés sont incorrects ! L'heure du serveur et celle de votre appareil sont-elles correctes ? users: diff --git a/config/locales/he.yml b/config/locales/he.yml index 7e3b40b1c..ec7d972ec 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -342,7 +342,7 @@ he: lost_recovery_codes: קודי האחזור מאפשרים אחזור גישה לחשבון במידה ומכשירך אבד. במידה וקודי האחזור אבדו, ניתן לייצרם מחדש כאן. תוקף קודי האחזור הישנים יפוג. manual_instructions: 'במידה ולא ניתן לסרוק את קוד ה-QR אלא יש צורך להקליד אותו ידנית, להלן סוד כמוס בלתי מוצפן:' recovery_codes_regenerated: קודי האחזור יוצרו בהצלחה - recovery_instructions: במידה והגישה למכשירך תאבד, ניתן לייצר קודי אחזור למטה על מנת לאחזר גישה לחשבונך בכל עת. נא לשמור על קודי הגישה במקום בטוח )לדוגמא על ידי הדפסתם ושמירתם עם מסמכים חשובים אחרים, או שימוש בתוכנה ייעודית לניהול סיסמאות וסודות( + recovery_instructions_html: במידה והגישה למכשירך תאבד, ניתן לייצר קודי אחזור למטה על מנת לאחזר גישה לחשבונך בכל עת. נא לשמור על קודי הגישה במקום בטוח )לדוגמא על ידי הדפסתם ושמירתם עם מסמכים חשובים אחרים, או שימוש בתוכנה ייעודית לניהול סיסמאות וסודות( setup: הכנה wrong_code: הקוד שהוזן שגוי! האם הזמן בשרת והזמן במכשירך נכונים? users: diff --git a/config/locales/id.yml b/config/locales/id.yml index 300612b31..fc4ffd046 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -331,7 +331,7 @@ id: lost_recovery_codes: Kode pemulihan bisa anda gunakan untuk mendapatkan kembali akses pada akun anda jika anda kehilangan handphone anda. Jika anda kehilangan kode pemulihan, anda bisa membuatnya ulang disini. Kode pemulihan anda yang lama tidak akan bisa digunakan lagi. manual_instructions: 'Jika anda tidak bisa memindai kode QR dan harus memasukkannya secara manual, ini dia kode yang harus dimasukkan:' recovery_codes_regenerated: Kode Pemulihan berhasil dibuat ulang - recovery_instructions: Jika anda kehilangan akses pada handphone anda, anda bisa menggunakan kode pemulihan dibawah ini untuk mendapatkan kembali akses pada akun anda. Simpan kode pemulihan anda baik-baik, misalnya dengan mencetaknya atau menyimpannya bersama dokumen penting lainnya. + recovery_instructions_html: Jika anda kehilangan akses pada handphone anda, anda bisa menggunakan kode pemulihan dibawah ini untuk mendapatkan kembali akses pada akun anda. Simpan kode pemulihan anda baik-baik, misalnya dengan mencetaknya atau menyimpannya bersama dokumen penting lainnya. setup: Persiapan wrong_code: Kode yang dimasukkan tidak cocok! Apa waktu server dan waktu di handphone sudah cocok? users: diff --git a/config/locales/io.yml b/config/locales/io.yml index def5b9524..db430b0fe 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -303,7 +303,7 @@ io: lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated. manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:' recovery_codes_regenerated: Recovery codes successfully regenerated - recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents. + recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents. setup: Set up wrong_code: The entered code was invalid! Are server time and device time correct? users: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 94f02e940..80169339d 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -360,7 +360,7 @@ ja: lost_recovery_codes: リカバリーコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリーコードは無効になります。 manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:' recovery_codes_regenerated: リカバリーコードが再生成されました。 - recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。 リカバリーコードは印刷して安全に保管してください。 + recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。 リカバリーコードは印刷して安全に保管してください。 setup: 初期設定 wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。 users: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 15d963808..d9b02e09c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -228,7 +228,7 @@ nl: lost_recovery_codes: Met herstelcodes kun je toegang tot jouw account krijgen wanneer je jouw telefoon bent kwijtgeraakt. Wanneer je jouw herstelcodes bent kwijtgeraakt, kan je ze hier opnieuw genereren. Jouw oude herstelcodes zijn daarna ongeldig. manual_instructions: 'Hieronder vind je de geheime code in platte tekst. Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren.' recovery_codes_regenerated: Opnieuw genereren herstelcodes geslaagd - recovery_instructions: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. Zorg ervoor dat je de herstelcodes op een veilige plek bewaard. (Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.) + recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. Zorg ervoor dat je de herstelcodes op een veilige plek bewaard. (Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.) setup: Instellen wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat? users: diff --git a/config/locales/no.yml b/config/locales/no.yml index 1cd6620b6..f71c08c6a 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -335,7 +335,7 @@ lost_recovery_codes: Gjenopprettingskoder lar deg gjenoppnå tilgang til din konto hvis du mister din telefon. Hvis du har mistet gjenopprettingskodene, kan du regenerere dem her. Dine gamle gjenopprettingskoder vil bli ugyldige. manual_instructions: 'Hvis du ikke får scannet QR-koden må du skrive inn følgende kode manuelt:' recovery_codes_regenerated: Generering av gjenopprettingskoder vellykket - recovery_instructions: Hvis du skulle miste tilgang til telefonen din, kan du bruke en av gjenopprettingskodene nedenfor til å gjenopprette tilgang til din konto. Oppbevar gjenopprettingskodene sikkert, for eksempel ved å skrive dem ut og lagre dem sammen med andre viktige dokumenter. + recovery_instructions_html: Hvis du skulle miste tilgang til telefonen din, kan du bruke en av gjenopprettingskodene nedenfor til å gjenopprette tilgang til din konto. Oppbevar gjenopprettingskodene sikkert, for eksempel ved å skrive dem ut og lagre dem sammen med andre viktige dokumenter. setup: Sett opp wrong_code: Den angitte koden var ugyldig! Stemmer instansens tid overalt med enhetens tid? users: diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 3770c0671..c882b43a1 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -220,7 +220,7 @@ oc: - dv - ds abbr_month_names: - - + - - gen - feb - mar @@ -246,7 +246,7 @@ oc: long: Lo %B %d de %Y short: "%b %d" month_names: - - + - - de genièr - de febrièr - de març @@ -411,7 +411,7 @@ oc: lost_recovery_codes: Los còdi de recuperacion vos permeton d’accedir a vòstre compte se perdètz vòstre mobil. S’avètz perdut vòstres còdis de recuperacion los podètz tornar generar aquí. Los ancians còdis seràn pas mai valides. manual_instructions: 'Se podètz pas numerizar lo còdi QR e que vos cal picar lo còdi a la man, vaquí lo còdi en clar :' recovery_codes_regenerated: Los còdis de recuperacion son ben estats tornats generar - recovery_instructions: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants. + recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants. setup: Paramètres wrong_code: Lo còdi picat es invalid ! L’ora es la bona sul servidor e lo mobil ? users: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 7376c3e2b..97d20aa41 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -364,7 +364,7 @@ pl: lost_recovery_codes: Kody zapasowe pozwolą uzyskać dostęp do portalu, jeżeli utracisz dostęp do telefonu. Jeżeli utracisz dostęp do nich, możesz wygenerować je ponownie tutaj. Poprzednie zostaną unieważnione. manual_instructions: 'Jeżeli nie możesz zeskanować kodu QR, musisz wprowadzić ten kod ręcznie:' recovery_codes_regenerated: Pomyślnie wygenerowano ponownie kody zapasowe - recovery_instructions: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. Trzymaj je w bezpiecznym miejscu. (Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami.) + recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. Trzymaj je w bezpiecznym miejscu. (Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami.) setup: Skonfiguruj wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny? users: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index b6f5497bd..973a8d401 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -333,7 +333,7 @@ pt-BR: lost_recovery_codes: Códigos de recuperação permite que você recupere o acesso a sua conta se você perder seu telefone. Se você perder os códigos de recuperação, você pode regera-los aqui. Seus códigos antigos serão invalidados. manual_instructions: 'Se você não puder scanear o código QR e precisa digita-los manualmente, aqui está o segredo em texto.:' recovery_codes_regenerated: Códigos de recuperação foram gerados com sucesso - recovery_instructions: Se algum dia você perder o acesso ao seu telefone, você pode usar um dos códigos de abaixo para recupera o acesso a sua conta. Guarde os códigos de acesso em local seguro, por exemplo imprimindo ou guardados com documentos importantes. + recovery_instructions_html: Se algum dia você perder o acesso ao seu telefone, você pode usar um dos códigos de abaixo para recupera o acesso a sua conta. Guarde os códigos de acesso em local seguro, por exemplo imprimindo ou guardados com documentos importantes. setup: Configurar wrong_code: O código digitado é inválido! Os relógios do servidor e do dispositivo estão corretos? users: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index c16ab6869..9cf067d88 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -332,7 +332,7 @@ ru: lost_recovery_codes: Коды восстановления позволяют вернуть доступ к аккаунту в случае утери телефона. Если Вы потеряли Ваши коды восстановления, вы можете заново сгенерировать их здесь. Ваши старые коды восстановления будут аннулированы. manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:' recovery_codes_regenerated: Коды восстановления успешно сгенерированы - recovery_instructions: В случае утери доступа к Вашему телефону Вы можете использовать один из кодов восстановления, указанных ниже, чтобы вернуть доступ к аккаунту. Держите коды восстановления в безопасности, например, распечатав их и храня с другими важными документами. + recovery_instructions_html: В случае утери доступа к Вашему телефону Вы можете использовать один из кодов восстановления, указанных ниже, чтобы вернуть доступ к аккаунту. Держите коды восстановления в безопасности, например, распечатав их и храня с другими важными документами. setup: Настроить wrong_code: Введенный код неверен! Правильно ли установлены серверное время и время устройства? users: diff --git a/config/locales/th.yml b/config/locales/th.yml index 6ef4b6789..322e5e74b 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -335,7 +335,7 @@ th: lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated. manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:' recovery_codes_regenerated: Recovery codes successfully regenerated - recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents. + recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents. setup: ตั้งค่า wrong_code: รหัสที่กรอกไม่ถูกต้อง! Are server time and device time correct? users: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index a4c870b64..0e33e2efe 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -333,7 +333,7 @@ tr: lost_recovery_codes: Kurtarma kodları telefonunuzu kaybettiğiniz durumlarda hesabınıza erişim yapabilmenize olanak tanır. Eğer kurtarma kodlarınızı kaybettiyseniz burada tekrar oluşturabilirsiniz. Eski kurtarma kodlarınız geçersiz hale gelecektir. manual_instructions: 'Eğer QR kodunu taratamıyorsanız ve elle giriş yapmanız gerekiyorsa buradaki gizli düz metni girebilirsiniz:' recovery_codes_regenerated: Kurtarma kodları başarıyla oluşturuldu - recovery_instructions: 'Eğer telefonunuza erişiminizi kaybederseniz, aşağıdaki kurtarma kodlarından birini kullanarak hesabınıza giriş yapabilirsiniz. Kurtarma kodlarınızı güvenli halde tutunuz. Örneğin: kodların çıktısını alıp diğer önemli belgeleriniz ile birlikte saklayabilirsiniz.' + recovery_instructions_html: 'Eğer telefonunuza erişiminizi kaybederseniz, aşağıdaki kurtarma kodlarından birini kullanarak hesabınıza giriş yapabilirsiniz. Kurtarma kodlarınızı güvenli halde tutunuz. Örneğin: kodların çıktısını alıp diğer önemli belgeleriniz ile birlikte saklayabilirsiniz.' setup: Kuruluma başla wrong_code: Girdiğiniz kod geçersiz! Telefonunuzun saati geri/ileri kalmış olabilir. users: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index c1ec61cda..1327c1a7b 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -319,7 +319,7 @@ uk: lost_recovery_codes: Коди відновлення дозволяють повернути доступ до акаунту у випадку втрати телефону. Якщо Ви втратили Ваші коди відновлення, Ви можете знову згенерувати їх тут. Ваші старі коди відновлення будуть анульовані. manual_instructions: 'Якщо Ви не можете відсканувати QR-код та хочете ввести його вручну, секрет представлений тут відкритим текстом:' recovery_codes_regenerated: Коди відновлення успішно згенеровані - recovery_instructions: У випадку втрати доступу до Вашого телефона Ви можете використати один з кодів відновлення, вказаних нижче, щоб повернути доступ до акаунту. Тримайте коди відновлення у безпеці, наприклад, роздрукувавши їх та тримаючи їх з іншими важливими документами. + recovery_instructions_html: У випадку втрати доступу до Вашого телефона Ви можете використати один з кодів відновлення, вказаних нижче, щоб повернути доступ до акаунту. Тримайте коди відновлення у безпеці, наприклад, роздрукувавши їх та тримаючи їх з іншими важливими документами. setup: Налаштувати wrong_code: Введений код неправильний! Чи правильно встановлені серверний час та час пристрою? users: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 9bf338ea4..6c8e9fc6d 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -339,7 +339,7 @@ zh-CN: lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的账户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。(废话) manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰ recovery_codes_regenerated: 已成功重新生成恢复代码 - recovery_instructions: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的账号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。 + recovery_instructions_html: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的账号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。 setup: 设置 wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。 users: diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 9a110f7da..4d8262c5b 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -334,7 +334,7 @@ zh-HK: lost_recovery_codes: 讓你可以在遺失電話時,使用備用驗證碼登入。如果你遺失了備用驗證碼,可以在這裏產生一批新的,舊有的備用驗證碼將會失效。 manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰ recovery_codes_regenerated: 成功產生新的備用驗證碼 - recovery_instructions: 如果你遺失了安裝認證器的裝置(如︰你的電話),你可以使用備用驗證碼進行登入。請確保將備用驗證碼收藏穩當,(如列印出來,和你其他重要文件一起存放) + recovery_instructions_html: 如果你遺失了安裝認證器的裝置(如︰你的電話),你可以使用備用驗證碼進行登入。請確保將備用驗證碼收藏穩當,(如列印出來,和你其他重要文件一起存放) setup: 設定 wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。 users: diff --git a/spec/controllers/settings/two_factor_authentications_controller_spec.rb b/spec/controllers/settings/two_factor_authentications_controller_spec.rb index 4d1a01fcf..6c49f6f0d 100644 --- a/spec/controllers/settings/two_factor_authentications_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentications_controller_spec.rb @@ -79,13 +79,41 @@ describe Settings::TwoFactorAuthenticationsController do user.update(otp_required_for_login: true) end - it 'turns off otp requirement if signed in' do - sign_in user, scope: :user - post :destroy + context 'when signed in' do + before do + sign_in user, scope: :user + end - expect(response).to redirect_to(settings_two_factor_authentication_path) - user.reload - expect(user.otp_required_for_login).to eq(false) + it 'turns off otp requirement with correct code' do + expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, arg| + expect(value).to eq user + expect(arg).to eq '123456' + true + end + + post :destroy, params: { form_two_factor_confirmation: { code: '123456' } } + + expect(response).to redirect_to(settings_two_factor_authentication_path) + user.reload + expect(user.otp_required_for_login).to eq(false) + end + + it 'does not turn off otp if code is incorrect' do + expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, arg| + expect(value).to eq user + expect(arg).to eq '057772' + false + end + + post :destroy, params: { form_two_factor_confirmation: { code: '057772' } } + + user.reload + expect(user.otp_required_for_login).to eq(true) + end + + it 'raises ActionController::ParameterMissing if code is missing' do + expect { post :destroy }.to raise_error(ActionController::ParameterMissing) + end end it 'redirects if not signed in' do From c972e1ee1fdc7ebc8bd4ad764cdf189ca4874764 Mon Sep 17 00:00:00 2001 From: unarist Date: Mon, 26 Jun 2017 08:45:50 +0900 Subject: [PATCH 082/382] Ignore DB_NAME for development env on streaming as well as rails side (#3948) --- streaming/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/index.js b/streaming/index.js index 701cb2f55..d69f8fa19 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -80,7 +80,7 @@ const startWorker = (workerId) => { development: { user: process.env.DB_USER || pg.defaults.user, password: process.env.DB_PASS || pg.defaults.password, - database: process.env.DB_NAME || 'mastodon_development', + database: 'mastodon_development', host: process.env.DB_HOST || pg.defaults.host, port: process.env.DB_PORT || pg.defaults.port, max: 10, From e5563843a2c063ab0295f778428183361e0aa326 Mon Sep 17 00:00:00 2001 From: Takuya Yoshida Date: Mon, 26 Jun 2017 08:46:15 +0900 Subject: [PATCH 083/382] Re-fix errorMiddleware (#3922) --- streaming/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/index.js b/streaming/index.js index d69f8fa19..e641c6548 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -246,7 +246,7 @@ const startWorker = (workerId) => { accountFromRequest(req, next); }; - const errorMiddleware = (err, req, res, next) => { // eslint-disable-line no-unused-vars + const errorMiddleware = (err, req, res, {}) => { log.error(req.requestId, err.toString()); res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.statusCode ? err.toString() : 'An unexpected error occurred' })); From 285038972b724e646aa1a3aa2096b161524bce09 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Mon, 26 Jun 2017 11:49:39 +0900 Subject: [PATCH 084/382] Stop using Babel with streaming server (#3950) --- package.json | 2 +- streaming/.babelrc | 15 --------------- streaming/index.js | 22 +++++++++++----------- 3 files changed, 12 insertions(+), 27 deletions(-) delete mode 100644 streaming/.babelrc diff --git a/package.json b/package.json index d1fd22fb5..4b967bad7 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build:development": "cross-env RAILS_ENV=development ./bin/webpack", "build:production": "cross-env RAILS_ENV=production ./bin/webpack", "manage:translations": "node ./config/webpack/translationRunner.js", - "start": "rimraf ./tmp/streaming && babel ./streaming/index.js --out-dir ./tmp && node ./tmp/streaming/index.js", + "start": "node ./streaming/index.js", "storybook": "cross-env NODE_ENV=test start-storybook -s ./public -p 9001 -c storybook", "test": "npm run test:lint && npm run test:mocha", "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ spec/javascript/ storybook/ streaming/", diff --git a/streaming/.babelrc b/streaming/.babelrc deleted file mode 100644 index dc1ec4303..000000000 --- a/streaming/.babelrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "targets": { - "node": "current" - } - } - ] - ], - "plugins": [ - "transform-object-rest-spread" - ] -} diff --git a/streaming/index.js b/streaming/index.js index e641c6548..400456d24 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -1,14 +1,14 @@ -import os from 'os'; -import throng from 'throng'; -import dotenv from 'dotenv'; -import express from 'express'; -import http from 'http'; -import redis from 'redis'; -import pg from 'pg'; -import log from 'npmlog'; -import url from 'url'; -import WebSocket from 'uws'; -import uuid from 'uuid'; +const os = require('os'); +const throng = require('throng'); +const dotenv = require('dotenv'); +const express = require('express'); +const http = require('http'); +const redis = require('redis'); +const pg = require('pg'); +const log = require('npmlog'); +const url = require('url'); +const WebSocket = require('uws'); +const uuid = require('uuid'); const env = process.env.NODE_ENV || 'development'; From f53ed108b0c6218eef118bbd0b77078d36c46a96 Mon Sep 17 00:00:00 2001 From: Alda Marteau-Hardi Date: Mon, 26 Jun 2017 13:04:36 +0200 Subject: [PATCH 085/382] Translate pin/unpin and fix some inconsistencies in gender neutral strings (#3952) --- app/javascript/mastodon/locales/fr.json | 6 +++--- config/locales/fr.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 52d6b0f87..1a69235c8 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -27,8 +27,8 @@ "column.notifications": "Notifications", "column.public": "Fil public global", "column_back_button.label": "Retour", - "column_header.pin": "Pin", - "column_header.unpin": "Unpin", + "column_header.pin": "Épingler", + "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", "compose_form.lock_disclaimer": "Votre compte n'est pas {locked}. Tout le monde peut vous suivre et voir vos pouets restreints.", @@ -101,7 +101,7 @@ "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?", "notifications.column_settings.alert": "Notifications locales", "notifications.column_settings.favourite": "Favoris :", - "notifications.column_settings.follow": "Nouveaux abonné⋅e⋅s :", + "notifications.column_settings.follow": "Nouveaux⋅elles abonn⋅é⋅s :", "notifications.column_settings.mention": "Mentions :", "notifications.column_settings.reblog": "Partages :", "notifications.column_settings.show": "Afficher dans la colonne", diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0c3f3b1d5..dfe5ff990 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -238,7 +238,7 @@ fr: mention: "%{name} vous a mentionné⋅e" new_followers_summary: one: Vous avez un⋅e nouvel⋅le abonné⋅e ! Youpi ! - other: Vous avez %{count} nouveaux abonné⋅es ! Incroyable ! + other: Vous avez %{count} nouveaux⋅elles abonné⋅e⋅s ! Incroyable ! subject: one: "Une nouvelle notification depuis votre dernière visite \U0001F418" other: "%{count} nouvelles notifications depuis votre dernière visite \U0001F418" From 7aeb9168b01971e4e2c4c9b757dff09638b171ac Mon Sep 17 00:00:00 2001 From: Daniel Hunsaker Date: Mon, 26 Jun 2017 05:15:24 -0600 Subject: [PATCH 086/382] Add .gitattributes file to avoid unwanted CRLF (#3954) When Windows checks out files, it defaults to changing line endings to CRLF. If these files are then copied to a Linux system to be run, and the endings aren't changed at some point in that process, things break. This file forces git to use LF for all text files on all systems (except the request testing specfiles) to prevent issues everywhere. --- .gitattributes | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..e69f2a0ad --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +* text=auto eol=lf +*.eot -text +*.gif -text +*.gz -text +*.ico -text +*.jpg -text +*.mp3 -text +*.ogg -text +*.png -text +*.ttf -text +*.webm -text +*.woff -text +*.woff2 -text +spec/fixtures/requests/** -text !eol From ae2b722f5510cc5ab54cd5e6419136f1a63892df Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Mon, 26 Jun 2017 17:10:54 +0200 Subject: [PATCH 087/382] i18n: Warning to look into the spam folder (pl) (#3955) --- config/locales/devise.pl.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml index d4ee1b6ae..792e0d81e 100644 --- a/config/locales/devise.pl.yml +++ b/config/locales/devise.pl.yml @@ -3,8 +3,8 @@ pl: devise: confirmations: confirmed: Twój adres e-mail został poprawnie zweryfikowany. - send_instructions: W ciągu kilku minut otrzymasz wiadomosć e-mail z instrukcją jak potwierdzić Twój adres e-mail. - send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją jak potwierdzić Twój adres e-mail. + send_instructions: W ciągu kilku minut otrzymasz wiadomosć e-mail z instrukcją jak potwierdzić Twój adres e-mail. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. + send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją jak potwierdzić Twój adres e-mail. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. failure: already_authenticated: Jesteś już zalogowany/zalogowana. inactive: Twoje konto nie zostało jeszcze aktywowane. @@ -29,8 +29,8 @@ pl: success: Uwierzytelnienie przez %{kind} powiodło się. passwords: no_token: Dostęp do tej strony możliwy jest wyłącznie za pomocą odnośnika z e-maila z instrukcjami ustawienia nowego hasła. Jeśli skorzystałeś/aś z takiego odnośnika, upewnij się, że został wykorzystany/skopiowany cały odnośnik. - send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją ustawienia nowego hasła. - send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail zawierającą odnośnik pozwalający na ustawienie nowego hasła. + send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją ustawienia nowego hasła. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. + send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail zawierającą odnośnik pozwalający na ustawienie nowego hasła. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. updated: Twoje hasło zostało zmienione. Jesteś zalogowany/a. updated_not_active: Twoje hasło zostało zmienione. registrations: @@ -38,16 +38,16 @@ pl: signed_up: Twoje konto zostało utworzone. Witamy! signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane. signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane. - signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik aby aktywować konto. - update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. + signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik aby aktywować konto. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. + update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. updated: Konto zostało zaktualizowane. sessions: already_signed_out: Zostałeś/aś wylogowany/a. signed_in: Zostałeś/aś zalogowany/a. signed_out: Zostałeś/aś wylogowany/a. unlocks: - send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcjami odblokowania konta. - send_paranoid_instructions: Jeśli Twoje konto istnieje, instrukcje odblokowania go otrzymasz w wiadomości e-mail w ciągu kilku minut. + send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcjami odblokowania konta. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. + send_paranoid_instructions: Jeśli Twoje konto istnieje, instrukcje odblokowania go otrzymasz w wiadomości e-mail w ciągu kilku minut. Jeżeli nie otrzymano wiadomości, sprawdź folder ze spamem. unlocked: Twoje konto zostało odblokowane. Zaloguj się aby kontynuować. errors: messages: From 646de92781ba9d211255a7eaf7ebca7b365612f6 Mon Sep 17 00:00:00 2001 From: m4sk1n Date: Mon, 26 Jun 2017 17:18:45 +0200 Subject: [PATCH 088/382] i18n: Updated Polish translation (#3956) * i18n: Updated Polish translation * Update pl.yml --- config/locales/pl.yml | 47 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 97d20aa41..81eee8dc3 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -200,7 +200,7 @@ pl: applications: invalid_url: Ten URL jest nieprawidłowy auth: - change_password: Uwierzytelnienie + change_password: Bezpieczeństwo delete_account: Usunięcie konta delete_account_html: Jeżeli próbowałeś usunąć konto, przejdź tutaj. Otrzymasz prośbę o potwierdzenie. didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji? @@ -323,7 +323,44 @@ pl: acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny proceed: Śledź - prompt: 'Śledzony będzie:' + prompt: 'Zamierzasz śledzić:' + sessions: + activity: Ostatnia aktywność + browser: Przeglądarka + browsers: + alipay: Alipay + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: nieznana przeglądarka + ie: Internet Explorer + micro_messenger: MicroMessenger + nokia: Nokia S40 Ovi Browser + opera: Opera + phantom_js: PhantomJS + qq: QQ Browser + safari: Safari + uc_browser: UCBrowser + weibo: Weibo + current_session: Obecna sesja + description: "%{browser} na %{platform}" + explanation: Przeglądarki z aktywną sesją Twojego konta. + ip: Adres IP + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: macOS + other: nieznana platforma + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + title: Sesje settings: authorized_apps: Uwierzytelnione aplikacje back: Powrót do Mastodona @@ -358,13 +395,17 @@ pl: description_html: Jeśli włączysz uwierzytelnianie dwuetapowe, logowanie się będzie wymagało podania tokenu wyświetlonego na Twoim telefonie. disable: Wyłącz enable: Włącz + enabled: Uwierzytelnianie dwuetapowe jest włączone enabled_success: Pomyślnie aktywowano uwierzytelnianie dwuetapowe generate_recovery_codes: Generuj kody zapasowe instructions_html: "Zeskanuj ten kod QR na swoim urządzeniu za pomocą Google Authenticator, FreeOTP lub podobnej aplikacji. Od teraz będzie ona generowała kody wymagane przy logowaniu." lost_recovery_codes: Kody zapasowe pozwolą uzyskać dostęp do portalu, jeżeli utracisz dostęp do telefonu. Jeżeli utracisz dostęp do nich, możesz wygenerować je ponownie tutaj. Poprzednie zostaną unieważnione. manual_instructions: 'Jeżeli nie możesz zeskanować kodu QR, musisz wprowadzić ten kod ręcznie:' + recovery_codes: Przywróć kody zapasowe recovery_codes_regenerated: Pomyślnie wygenerowano ponownie kody zapasowe - recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. Trzymaj je w bezpiecznym miejscu. (Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami.) + recovery_instructions_html: + Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. Trzymaj je w bezpiecznym miejscu. + Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami. setup: Skonfiguruj wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny? users: From a91d968cab5120ca389fcc7d6788cafca85e69e7 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 26 Jun 2017 19:39:58 +0200 Subject: [PATCH 089/382] Raise an error if salmon request response is unsatisfactory (#3960) --- app/services/send_interaction_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb index 504f41c72..34c8f9e34 100644 --- a/app/services/send_interaction_service.rb +++ b/app/services/send_interaction_service.rb @@ -13,7 +13,8 @@ class SendInteractionService < BaseService return if block_notification? envelope = salmon.pack(@xml, @source_account.keypair) - salmon.post(@target_account.salmon_url, envelope) + delivery = salmon.post(@target_account.salmon_url, envelope) + raise "Delivery failed for #{target_account.salmon_url}: HTTP #{delivery.code}" unless delivery.code > 199 && delivery.code < 300 end private From 42b82206322c73c4a4d7ac29ca9a781ab11e7b1a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Jun 2017 00:04:00 +0200 Subject: [PATCH 090/382] Fix #1624 - Send e-mail notifications to admins about new reports (#3949) --- app/controllers/api/v1/reports_controller.rb | 3 +++ app/mailers/admin_mailer.rb | 13 +++++++++++++ app/mailers/application_mailer.rb | 8 ++++++++ app/mailers/notification_mailer.rb | 8 -------- app/views/admin_mailer/new_report.text.erb | 5 +++++ config/locales/en.yml | 8 +++++--- .../controllers/api/v1/reports_controller_spec.rb | 15 ++++++++++++--- 7 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 app/mailers/admin_mailer.rb create mode 100644 app/views/admin_mailer/new_report.text.erb diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 71df76e92..8e7070d07 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -17,6 +17,9 @@ class Api::V1::ReportsController < Api::BaseController status_ids: reported_status_ids, comment: report_params[:comment] ) + + User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } + render :show end diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb new file mode 100644 index 000000000..fc19a6d40 --- /dev/null +++ b/app/mailers/admin_mailer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AdminMailer < ApplicationMailer + def new_report(recipient, report) + @report = report + @me = recipient + @instance = Rails.configuration.x.local_domain + + locale_for_account(@me) do + mail to: @me.user_email, subject: I18n.t('admin_mailer.new_report.subject', instance: @instance, id: @report.id) + end + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index e5dbfeeda..2e730c19b 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -4,4 +4,12 @@ class ApplicationMailer < ActionMailer::Base default from: ENV.fetch('SMTP_FROM_ADDRESS') { 'notifications@localhost' } layout 'mailer' helper :instance + + protected + + def locale_for_account(account) + I18n.with_locale(account.user_locale || I18n.default_locale) do + yield + end + end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index a944db137..12b92bf45 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -67,12 +67,4 @@ class NotificationMailer < ApplicationMailer ) end end - - private - - def locale_for_account(account) - I18n.with_locale(account.user_locale || I18n.default_locale) do - yield - end - end end diff --git a/app/views/admin_mailer/new_report.text.erb b/app/views/admin_mailer/new_report.text.erb new file mode 100644 index 000000000..6fa744bc3 --- /dev/null +++ b/app/views/admin_mailer/new_report.text.erb @@ -0,0 +1,5 @@ +<%= display_name(@me) %>, + +<%= raw t('admin_mailer.new_report.body', target: @report.target_account.acct, reporter: @report.account.acct) %> + +<%= raw t('application_mailer.view')%> <%= admin_report_url(@report) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9daaf53ec..944c24c60 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -193,6 +193,10 @@ en: title: PubSubHubbub topic: Topic title: Administration + admin_mailer: + new_report: + body: "%{reporter} has reported %{target}" + subject: New report for %{instance} (#%{id}) application_mailer: settings: 'Change e-mail preferences: %{link}' signature: Mastodon notifications from %{instance} @@ -399,9 +403,7 @@ en: manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:' recovery_codes: Backup recovery codes recovery_codes_regenerated: Recovery codes successfully regenerated - recovery_instructions_html: - If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. - For example, you may print them and store them with other important documents. + recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. For example, you may print them and store them with other important documents. setup: Set up wrong_code: The entered code was invalid! Are server time and device time correct? users: diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb index 3df6cdfe7..471ea4e0b 100644 --- a/spec/controllers/api/v1/reports_controller_spec.rb +++ b/spec/controllers/api/v1/reports_controller_spec.rb @@ -21,12 +21,21 @@ RSpec.describe Api::V1::ReportsController, type: :controller do end describe 'POST #create' do - it 'creates a report' do - status = Fabricate(:status) - post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } + let!(:status) { Fabricate(:status) } + let!(:admin) { Fabricate(:user, admin: true) } + before do + allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil)) + post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } + end + + it 'creates a report' do expect(status.reload.account.targeted_reports).not_to be_empty expect(response).to have_http_status(:success) end + + it 'sends e-mails to admins' do + expect(AdminMailer).to have_received(:new_report).with(admin.account, Report) + end end end From 98eaa2aa27100ac0037cced9d4d4c86bcb9396e7 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 27 Jun 2017 20:41:03 +0900 Subject: [PATCH 091/382] Update Rails to v5.1.2 (#3968) --- Gemfile.lock | 64 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 627a01787..c19f31e01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,40 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.1.1) - actionpack (= 5.1.1) + actioncable (5.1.2) + actionpack (= 5.1.2) nio4r (~> 2.0) websocket-driver (~> 0.6.1) - actionmailer (5.1.1) - actionpack (= 5.1.1) - actionview (= 5.1.1) - activejob (= 5.1.1) + actionmailer (5.1.2) + actionpack (= 5.1.2) + actionview (= 5.1.2) + activejob (= 5.1.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.1) - actionview (= 5.1.1) - activesupport (= 5.1.1) + actionpack (5.1.2) + actionview (= 5.1.2) + activesupport (= 5.1.2) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.1) - activesupport (= 5.1.1) + actionview (5.1.2) + activesupport (= 5.1.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) active_record_query_trace (1.5.4) - activejob (5.1.1) - activesupport (= 5.1.1) + activejob (5.1.2) + activesupport (= 5.1.2) globalid (>= 0.3.6) - activemodel (5.1.1) - activesupport (= 5.1.1) - activerecord (5.1.1) - activemodel (= 5.1.1) - activesupport (= 5.1.1) + activemodel (5.1.2) + activesupport (= 5.1.2) + activerecord (5.1.2) + activemodel (= 5.1.2) + activesupport (= 5.1.2) arel (~> 8.0) - activesupport (5.1.1) + activesupport (5.1.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) @@ -298,17 +298,17 @@ GEM rack-test (0.6.3) rack (>= 1.0) rack-timeout (0.4.2) - rails (5.1.1) - actioncable (= 5.1.1) - actionmailer (= 5.1.1) - actionpack (= 5.1.1) - actionview (= 5.1.1) - activejob (= 5.1.1) - activemodel (= 5.1.1) - activerecord (= 5.1.1) - activesupport (= 5.1.1) + rails (5.1.2) + actioncable (= 5.1.2) + actionmailer (= 5.1.2) + actionpack (= 5.1.2) + actionview (= 5.1.2) + activejob (= 5.1.2) + activemodel (= 5.1.2) + activerecord (= 5.1.2) + activesupport (= 5.1.2) bundler (>= 1.3.0, < 2.0) - railties (= 5.1.1) + railties (= 5.1.2) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.2) actionpack (~> 5.x, >= 5.0.1) @@ -324,9 +324,9 @@ GEM railties (~> 5.0) rails-settings-cached (0.6.5) rails (>= 4.2.0) - railties (5.1.1) - actionpack (= 5.1.1) - activesupport (= 5.1.1) + railties (5.1.2) + actionpack (= 5.1.2) + activesupport (= 5.1.2) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) From 8f2c91568c7ab552a87d02813e6b02be65f8707f Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 27 Jun 2017 20:43:53 +0900 Subject: [PATCH 092/382] Maintain aspect ratio for preview image (#3966) --- .../features/ui/components/image_loader.js | 122 ++++++++++++++---- app/javascript/styles/components.scss | 30 +++-- 2 files changed, 116 insertions(+), 36 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js index 5c3879970..52c3a898b 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export default class ImageLoader extends React.PureComponent { @@ -20,46 +21,121 @@ export default class ImageLoader extends React.PureComponent { error: false, } - componentWillMount() { - this._loadImage(this.props.src); + removers = []; + + get canvasContext() { + if (!this.canvas) { + return null; + } + this._canvasContext = this._canvasContext || this.canvas.getContext('2d'); + return this._canvasContext; } - componentWillReceiveProps(props) { - this._loadImage(props.src); + componentDidMount () { + this.loadImage(this.props); } - _loadImage(src) { + componentWillReceiveProps (nextProps) { + if (this.props.src !== nextProps.src) { + this.loadImage(nextProps); + } + } + + loadImage (props) { + this.removeEventListeners(); + this.setState({ loading: true, error: false }); + Promise.all([ + this.loadPreviewCanvas(props), + this.loadOriginalImage(props), + ]) + .then(() => { + this.setState({ loading: false, error: false }); + this.clearPreviewCanvas(); + }) + .catch(() => this.setState({ loading: false, error: true })); + } + + loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => { const image = new Image(); + const removeEventListeners = () => { + image.removeEventListener('error', handleError); + image.removeEventListener('load', handleLoad); + }; + const handleError = () => { + removeEventListeners(); + reject(); + }; + const handleLoad = () => { + removeEventListeners(); + this.canvasContext.drawImage(image, 0, 0, width, height); + resolve(); + }; + image.addEventListener('error', handleError); + image.addEventListener('load', handleLoad); + image.src = previewSrc; + this.removers.push(removeEventListeners); + }) - image.onerror = () => this.setState({ loading: false, error: true }); - image.onload = () => this.setState({ loading: false, error: false }); - - image.src = src; - - this.setState({ loading: true }); + clearPreviewCanvas () { + const { width, height } = this.canvas; + this.canvasContext.clearRect(0, 0, width, height); } - render() { - const { alt, src, previewSrc, width, height } = this.props; + loadOriginalImage = ({ src }) => new Promise((resolve, reject) => { + const image = new Image(); + const removeEventListeners = () => { + image.removeEventListener('error', handleError); + image.removeEventListener('load', handleLoad); + }; + const handleError = () => { + removeEventListeners(); + reject(); + }; + const handleLoad = () => { + removeEventListeners(); + resolve(); + }; + image.addEventListener('error', handleError); + image.addEventListener('load', handleLoad); + image.src = src; + this.removers.push(removeEventListeners); + }); + + removeEventListeners () { + this.removers.forEach(listeners => listeners()); + this.removers = []; + } + + setCanvasRef = c => { + this.canvas = c; + } + + render () { + const { alt, src, width, height } = this.props; const { loading } = this.state; + const className = classNames('image-loader', { + 'image-loader--loading': loading, + }); + return ( -
    - {alt} + - {loading && + {!loading && ( - } + )}
    ); } diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index bb9723f5a..91ebd91fd 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1099,20 +1099,22 @@ .image-loader { position: relative; -} -.image-loader__preview-img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - filter: blur(2px); -} + &.image-loader--loading { + .image-loader__preview-canvas { + filter: blur(2px); + } + } -.media-modal img.image-loader__preview-img { - width: 100%; - height: 100%; + .image-loader__img { + position: absolute; + top: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + background-image: none; + } } .navigation-bar { @@ -2933,6 +2935,7 @@ button.icon-button.active i.fa-retweet { position: relative; img, + canvas, video { max-width: 80vw; max-height: 80vh; @@ -2940,7 +2943,8 @@ button.icon-button.active i.fa-retweet { height: auto; } - img { + img, + canvas { display: block; background: url('../images/void.png') repeat; } From e2dd576a1b20c324c88afadaaa4f23e554b73ec7 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 27 Jun 2017 20:46:11 +0900 Subject: [PATCH 093/382] Update dependencies for Node.js (#3967) * Update @storybook/addon-actions to v3.1.6 * Update @storybook/react to v3.1.6 * Update babel-loader to v7.1.0 * Update babel-plugin-transform-react-remove-prop-types to v0.4.6 * Update enzyme to v2.9.1 * Update fsevents to v1.1.2 * Update intersection-observer to v0.3.2 * Update npmlog to v4.1.2 * Update pg to v6.4.0 * Update postcss-loader to v2.0.6 * Update rails-ujs to v5.1.2 * Update react to v15.6.1 * Update react-addons-shallow-compare to v15.6.0 * Update react-dom to v15.6.0 * Update react-notification to v6.7.1 * Update react-test-renderer to v15.6.1 * Update react-textarea-autosize to v5.0.7 * Update redux to v3.7.1 * Update resolve-url-loader to v2.1.0 * Update sass-loader to v6.0.6 * Update sinon to v2.3.5 * Update stringz to v0.2.2 * Update uuid to v3.1.0 * Update websocket.js to v0.1.12 * Update yargs to v8.0.2 * yarn upgrade --- package.json | 48 ++--- yarn.lock | 567 +++++++++++++++++++++++++++++---------------------- 2 files changed, 348 insertions(+), 267 deletions(-) diff --git a/package.json b/package.json index 4b967bad7..60f9af1e9 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "axios": "^0.16.2", "babel-cli": "^6.24.1", "babel-core": "^6.25.0", - "babel-loader": "^7.0.0", + "babel-loader": "^7.1.0", "babel-plugin-lodash": "^3.2.11", "babel-plugin-react-intl": "^2.3.1", "babel-plugin-react-transform": "^2.0.2", @@ -35,7 +35,7 @@ "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-react-jsx-self": "^6.22.0", "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.5", + "babel-plugin-transform-react-remove-prop-types": "^0.4.6", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.5.2", "babel-preset-react": "^6.24.1", @@ -55,7 +55,7 @@ "glob": "^7.1.1", "http-link-header": "^0.8.0", "immutable": "^3.8.1", - "intersection-observer": "^0.3.0", + "intersection-observer": "^0.3.2", "intl": "^1.2.5", "intl-relativeformat": "^1.3.0", "is-nan": "^1.2.1", @@ -65,71 +65,71 @@ "marky": "^1.2.0", "mkdirp": "^0.5.1", "node-sass": "^4.5.2", - "npmlog": "^4.1.0", + "npmlog": "^4.1.2", "object-assign": "^4.1.1", "path-complete-extname": "^0.1.0", - "pg": "^6.2.4", - "postcss-loader": "^2.0.5", + "pg": "^6.4.0", + "postcss-loader": "^2.0.6", "postcss-smart-import": "^0.7.4", "precss": "^1.4.0", "prop-types": "^15.5.10", "punycode": "^2.1.0", - "rails-ujs": "^5.1.1", - "react": "^15.6.0", + "rails-ujs": "^5.1.2", + "react": "^15.6.1", "react-addons-perf": "^15.4.2", - "react-addons-shallow-compare": "^15.5.2", - "react-dom": "^15.6.0", + "react-addons-shallow-compare": "^15.6.0", + "react-dom": "^15.6.1", "react-immutable-proptypes": "^2.1.0", "react-immutable-pure-component": "^1.0.0", "react-intl": "^2.3.0", "react-motion": "^0.5.0", - "react-notification": "^6.7.0", + "react-notification": "^6.7.1", "react-redux": "^5.0.4", "react-redux-loading-bar": "^2.9.2", "react-router-dom": "^4.1.1", "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", "react-swipeable": "^4.0.1", - "react-textarea-autosize": "^5.0.6", + "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", "redis": "^2.7.1", - "redux": "^3.6.0", + "redux": "^3.7.1", "redux-immutable": "^4.0.0", "redux-thunk": "^2.2.0", "requestidlecallback": "^0.3.0", "reselect": "^3.0.1", - "resolve-url-loader": "^2.0.2", + "resolve-url-loader": "^2.1.0", "rimraf": "^2.6.1", - "sass-loader": "^6.0.5", - "stringz": "^0.2.1", + "sass-loader": "^6.0.6", + "stringz": "^0.2.2", "style-loader": "^0.18.2", "throng": "^4.0.0", "tiny-queue": "^0.2.1", - "uuid": "^3.0.1", + "uuid": "^3.1.0", "uws": "^0.14.5", "webpack": "^3.0.0", "webpack-bundle-analyzer": "^2.8.2", "webpack-manifest-plugin": "^1.1.0", "webpack-merge": "^4.1.0", - "websocket.js": "^0.1.10" + "websocket.js": "^0.1.12" }, "devDependencies": { - "@storybook/addon-actions": "^3.1.2", - "@storybook/react": "^3.1.3", + "@storybook/addon-actions": "^3.1.6", + "@storybook/react": "^3.1.6", "babel-eslint": "^7.2.3", "chai": "^4.0.1", "chai-enzyme": "^0.7.1", - "enzyme": "^2.8.2", + "enzyme": "^2.9.1", "eslint": "^3.19.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", "jsdom": "^10.1.0", "mocha": "^3.4.1", "react-intl-translations-manager": "^5.0.0", - "react-test-renderer": "^15.5.4", - "sinon": "^2.3.4", + "react-test-renderer": "^15.6.1", + "sinon": "^2.3.5", "webpack-dev-server": "lencioni/webpack-dev-server#patch-1", - "yargs": "^8.0.1" + "yargs": "^8.0.2" }, "optionalDependencies": { "fsevents": "*" diff --git a/yarn.lock b/yarn.lock index d1a1687a0..b8af49d62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,37 +2,38 @@ # yarn lockfile v1 -"@storybook/addon-actions@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.1.2.tgz#0f28ae69277964d098fcd5e3f4a74b09a233a48c" +"@storybook/addon-actions@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.1.6.tgz#0cbf00ede57ff00d1dfe02e554043d6963940064" dependencies: - "@storybook/addons" "^3.1.2" + "@storybook/addons" "^3.1.6" deep-equal "^1.0.1" json-stringify-safe "^5.0.1" prop-types "^15.5.8" react-inspector "^2.0.0" + uuid "^3.1.0" -"@storybook/addon-links@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.1.2.tgz#43d34eb7ee8c056d13f854ec1fe9d1cd90889706" +"@storybook/addon-links@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.1.6.tgz#62c8a839e54ff0adb04c6023dae467b336ced5d9" dependencies: - "@storybook/addons" "^3.1.2" + "@storybook/addons" "^3.1.6" -"@storybook/addons@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.1.2.tgz#6ea3f1e14e303ff5ad481dd424211f8230df878b" +"@storybook/addons@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.1.6.tgz#29ef2348550f5a74d5e83dd75d04714cac751c39" -"@storybook/channel-postmessage@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.1.2.tgz#a8a6eec904c6d37b938db9f8251ee760b1c3556c" +"@storybook/channel-postmessage@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.1.6.tgz#867768a2ca2efbd796432300fe5e9b834d9c2ca5" dependencies: - "@storybook/channels" "^3.1.2" + "@storybook/channels" "^3.1.6" global "^4.3.2" json-stringify-safe "^5.0.1" -"@storybook/channels@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.1.2.tgz#56f9bb6acf0c2ff58123f1fccadc8498d5372b70" +"@storybook/channels@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.1.6.tgz#81d61591bf7613dd2bcd81d26da40aeaa2899034" "@storybook/react-fuzzy@^0.4.0": version "0.4.0" @@ -43,15 +44,15 @@ fuse.js "^3.0.1" prop-types "^15.5.9" -"@storybook/react@^3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.1.3.tgz#e2d7d2ecf4d7ff3dfab1d7b324e2c19e04be5934" +"@storybook/react@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.1.6.tgz#9393bb987ff08ee5f49c4557d12eb84377dee5d2" dependencies: - "@storybook/addon-actions" "^3.1.2" - "@storybook/addon-links" "^3.1.2" - "@storybook/addons" "^3.1.2" - "@storybook/channel-postmessage" "^3.1.2" - "@storybook/ui" "^3.1.3" + "@storybook/addon-actions" "^3.1.6" + "@storybook/addon-links" "^3.1.6" + "@storybook/addons" "^3.1.6" + "@storybook/channel-postmessage" "^3.1.6" + "@storybook/ui" "^3.1.6" airbnb-js-shims "^1.1.1" autoprefixer "^7.1.1" babel-core "^6.24.1" @@ -72,6 +73,8 @@ express "^4.15.3" file-loader "^0.11.1" find-cache-dir "^1.0.0" + glamor "^2.20.25" + glamorous "^3.22.1" global "^4.3.2" json-loader "^0.5.4" json-stringify-safe "^5.0.1" @@ -94,9 +97,9 @@ webpack-dev-middleware "^1.10.2" webpack-hot-middleware "^2.18.0" -"@storybook/ui@^3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.1.3.tgz#806c6bc4474b1437edf305c50b88662869ad5c31" +"@storybook/ui@^3.1.6": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.1.6.tgz#5d47c6003a2d78c06ede43861089747d986d918e" dependencies: "@storybook/react-fuzzy" "^0.4.0" babel-runtime "^6.23.0" @@ -204,10 +207,12 @@ ajv@^4.7.0, ajv@^4.9.1: json-stable-stringify "^1.0.1" ajv@^5.0.0, ajv@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.5.tgz#8734931b601f00d4feef7c65738d77d1b65d1f68" + version "5.2.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486" dependencies: co "^4.6.0" + fast-deep-equal "^0.1.0" + json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" align-text@^0.1.1, align-text@^0.1.3: @@ -238,6 +243,10 @@ ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -397,8 +406,8 @@ async@^1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.1.2, async@^2.1.4, async@^2.1.5: - version "2.4.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" dependencies: lodash "^4.14.0" @@ -648,11 +657,11 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-loader@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.0.0.tgz#2e43a66bee1fff4470533d0402c8a4532fafbaf7" +babel-loader@^7.0.0, babel-loader@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.0.tgz#3fbf2581f085774bd9642dca9990e6d6c1491144" dependencies: - find-cache-dir "^0.1.1" + find-cache-dir "^1.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" @@ -1057,9 +1066,9 @@ babel-plugin-transform-react-jsx@6.24.1, babel-plugin-transform-react-jsx@^6.24. babel-plugin-syntax-jsx "^6.8.0" babel-runtime "^6.22.0" -babel-plugin-transform-react-remove-prop-types@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.5.tgz#79d1958437ae23d4fbc0b11d1a041498ddb23877" +babel-plugin-transform-react-remove-prop-types@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.6.tgz#c3d20ff4e97fb08fa63e86a97b2daab6ad365a19" dependencies: babel-traverse "^6.24.1" @@ -1316,8 +1325,8 @@ babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25 to-fast-properties "^1.0.1" babylon@^6.17.0, babylon@^6.17.2: - version "6.17.3" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.3.tgz#1327d709950b558f204e5352587fd0290f8d8e48" + version "6.17.4" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" babylon@~5.8.3: version "5.8.38" @@ -1337,13 +1346,17 @@ balanced-match@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" -balanced-match@^0.4.1, balanced-match@^0.4.2: +balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + base64-js@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" batch@0.6.1: version "0.6.1" @@ -1370,8 +1383,8 @@ block-stream@*: inherits "~2.0.0" bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + version "4.11.7" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" bonjour@^3.5.0: version "3.5.0" @@ -1399,10 +1412,10 @@ bowser@^1.6.0: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003" brace-expansion@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" dependencies: - balanced-match "^0.4.1" + balanced-match "^1.0.0" concat-map "0.0.1" braces@^1.8.2: @@ -1413,6 +1426,10 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +brcast@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/brcast/-/brcast-2.0.0.tgz#9e627ab82209895664c1d6c1f45cd8c43422e3f6" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -1480,11 +1497,11 @@ browserslist@^1.3.6, browserslist@^1.4.0, browserslist@^1.5.2, browserslist@^1.7 electron-to-chromium "^1.2.7" browserslist@^2.1.2, browserslist@^2.1.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.1.4.tgz#cc526af4a1312b7d2e05653e56d0c8ab70c0e053" + version "2.1.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.1.5.tgz#e882550df3d1cd6d481c1a3e0038f2baf13a4711" dependencies: - caniuse-lite "^1.0.30000670" - electron-to-chromium "^1.3.11" + caniuse-lite "^1.0.30000684" + electron-to-chromium "^1.3.14" buffer-indexof@^1.0.0: version "1.1.0" @@ -1561,12 +1578,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000683" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000683.tgz#58b57ed1e0bb9da54eaf1462985147bbe16679fa" + version "1.0.30000696" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000696.tgz#e71f5c61e1f96c7a3af4e791ac5db55e11737604" -caniuse-lite@^1.0.30000670: - version "1.0.30000683" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000683.tgz#a7573707cf2acc9217ca6484d1dfbc9f13898364" +caniuse-lite@^1.0.30000670, caniuse-lite@^1.0.30000684: + version "1.0.30000696" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000696.tgz#30f2695d2a01a0dfd779a26ab83f4d134b3da5cc" case-sensitive-paths-webpack-plugin@^2.0.0: version "2.1.1" @@ -1662,8 +1679,8 @@ circular-json@^0.3.1: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" clap@^1.0.9: - version "1.1.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b" + version "1.2.0" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857" dependencies: chalk "^1.1.3" @@ -1697,14 +1714,13 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" +clone-deep@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8" dependencies: - for-own "^0.1.3" + for-own "^1.0.0" is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" + kind-of "^3.2.2" shallow-clone "^0.1.2" clone@^1.0.2: @@ -1726,8 +1742,8 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" collapse-white-space@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d" + version "1.0.3" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" color-convert@^1.3.0: version "1.9.0" @@ -1771,12 +1787,18 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0, commander@^2.8.1, commander@^2.9.0: +commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" +commander@^2.8.1, commander@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe" + dependencies: + graceful-readlink ">= 1.0.0" + common-tags@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0" @@ -1937,9 +1959,9 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.2, create-react-class@^15.5.3: - version "15.5.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.5.3.tgz#fb0f7cae79339e9a179e194ef466efa3923820fe" +create-react-class@^15.5.2, create-react-class@^15.5.3, create-react-class@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4" dependencies: fbjs "^0.8.9" loose-envify "^1.3.1" @@ -2397,7 +2419,7 @@ ejs@^2.5.6: version "2.5.6" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.11: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14: version "1.3.14" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.14.tgz#64af0f9efd3c3c6acd57d71f83b49ca7ee9c4b43" @@ -2464,20 +2486,20 @@ entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -enzyme@^2.8.2: - version "2.8.2" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.8.2.tgz#6c8bcb05012abc4aa4bc3213fb23780b9b5b1714" +enzyme@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.9.1.tgz#07d5ce691241240fb817bf2c4b18d6e530240df6" dependencies: cheerio "^0.22.0" function.prototype.name "^1.0.0" is-subset "^0.1.1" - lodash "^4.17.2" + lodash "^4.17.4" object-is "^1.0.1" object.assign "^4.0.4" - object.entries "^1.0.3" - object.values "^1.0.3" - prop-types "^15.5.4" - uuid "^2.0.3" + object.entries "^1.0.4" + object.values "^1.0.4" + prop-types "^15.5.10" + uuid "^3.0.1" errno@^0.1.3: version "0.1.4" @@ -2679,24 +2701,20 @@ esquery@^1.0.0: estraverse "^4.0.0" esrecurse@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" dependencies: - estraverse "~4.1.0" + estraverse "^4.1.0" object-assign "^4.0.1" estraverse@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" -estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" -estraverse@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" - esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -2820,10 +2838,18 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +fast-deep-equal@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fast-memoize@^2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.2.7.tgz#f145c5c22039cedf0a1d4ff6ca592ad0268470ca" + fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" @@ -2840,7 +2866,7 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" -fbjs@^0.8.4, fbjs@^0.8.9: +fbjs@^0.8.4, fbjs@^0.8.8, fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" dependencies: @@ -2902,14 +2928,6 @@ finalhandler@~1.0.3: statuses "~1.3.1" unpipe "~1.0.0" -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" - dependencies: - commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" - find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -2945,8 +2963,8 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" follow-redirects@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.3.tgz#01abaeca85e3609837d9fcda3167a7e42fdaca21" + version "1.2.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" dependencies: debug "^2.4.5" @@ -2962,12 +2980,18 @@ for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" -for-own@^0.1.3, for-own@^0.1.4: +for-own@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" dependencies: for-in "^1.0.1" +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -3036,11 +3060,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" fsevents@*, fsevents@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.1.tgz#f19fd28f43eeaf761680e519a203c4d0b3d31aff" + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" dependencies: nan "^2.3.0" - node-pre-gyp "^0.6.29" + node-pre-gyp "^0.6.36" fstream-ignore@^1.0.5: version "1.0.5" @@ -3072,8 +3096,8 @@ function.prototype.name@^1.0.0: is-callable "^1.1.2" fuse.js@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.0.4.tgz#cd4e2ec63ac324e28f5d17fcc3d01584379d3717" + version "3.0.5" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.0.5.tgz#b58d85878802321de94461654947b93af1086727" fuzzysearch@^1.0.3: version "1.0.3" @@ -3137,6 +3161,25 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glamor@^2.20.25: + version "2.20.25" + resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.25.tgz#71b84b82b67a9327771ac59de53ee915d148a4a3" + dependencies: + babel-runtime "^6.18.0" + fbjs "^0.8.8" + object-assign "^4.1.0" + prop-types "^15.5.8" + +glamorous@^3.22.1: + version "3.23.4" + resolved "https://registry.yarnpkg.com/glamorous/-/glamorous-3.23.4.tgz#a1e5f8045c332850105777dea4d3b21c5bdc4796" + dependencies: + brcast "^2.0.0" + fast-memoize "^2.2.7" + html-tag-names "^1.1.1" + react-html-attributes "^1.3.0" + svg-tag-names "^1.1.0" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -3282,6 +3325,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -3299,10 +3346,11 @@ hash-base@^2.0.0: inherits "^2.0.1" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573" + version "1.1.2" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.2.tgz#bf5c887825cfe40b9efde7bf11bd2db26e6bf01b" dependencies: - inherits "^2.0.1" + inherits "^2.0.3" + minimalistic-assert "^1.0.0" hawk@~3.1.3: version "3.1.3" @@ -3314,8 +3362,8 @@ hawk@~3.1.3: sntp "1.x.x" history@^4.5.1, history@^4.6.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.6.1.tgz#911cf8eb65728555a94f2b12780a0c531a14d2fd" + version "4.6.3" + resolved "https://registry.yarnpkg.com/history/-/history-4.6.3.tgz#6d723a8712c581d6bef37e8c26f4aedc6eb86967" dependencies: invariant "^2.2.1" loose-envify "^1.2.0" @@ -3347,8 +3395,8 @@ home-or-tmp@^2.0.0: os-tmpdir "^1.0.1" hosted-git-info@^2.1.4: - version "2.4.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67" + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" hpack.js@^2.1.6: version "2.1.6" @@ -3363,6 +3411,10 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" +html-element-attributes@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/html-element-attributes/-/html-element-attributes-1.3.0.tgz#f06ebdfce22de979db82020265cac541fb17d4fc" + html-encoding-sniffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" @@ -3373,6 +3425,10 @@ html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" +html-tag-names@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.2.tgz#f65168964c5a9c82675efda882875dcb2a875c22" + html@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/html/-/html-1.0.0.tgz#a544fa9ea5492bfb3a2cca8210a10be7b5af1f61" @@ -3494,7 +3550,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3507,8 +3563,8 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" inline-style-prefixer@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.5.tgz#0092881b3a2eadf1bd619dc43726557316f76042" + version "3.0.6" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.6.tgz#b27fe309b4168a31eaf38c8e8c60ab9e7c11731f" dependencies: bowser "^1.6.0" css-in-js-utils "^1.0.3" @@ -3541,9 +3597,9 @@ interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" -intersection-observer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.3.0.tgz#80d581c5507de1114d43a8591952927bb23480a7" +intersection-observer@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.3.2.tgz#9ed30021c08b29e9e8565c8d512ed84515727433" intl-format-cache@^2.0.5: version "2.0.5" @@ -3893,6 +3949,10 @@ json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -3952,7 +4012,7 @@ kind-of@^2.0.1: dependencies: is-buffer "^1.0.2" -kind-of@^3.0.2: +kind-of@^3.0.2, kind-of@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" dependencies: @@ -4023,7 +4083,7 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.x: +loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -4531,7 +4591,7 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-pre-gyp@^0.6.29, node-pre-gyp@^0.6.4: +node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.4: version "0.6.36" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" dependencies: @@ -4591,8 +4651,8 @@ nopt@^4.0.1: osenv "^0.1.4" normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: hosted-git-info "^2.1.4" is-builtin-module "^1.0.0" @@ -4624,9 +4684,9 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5" +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" @@ -4648,8 +4708,8 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" "nwmatcher@>= 1.3.9 < 2.0.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.0.tgz#b4389362170e7ef9798c3c7716d80ebc0106fccf" + version "1.4.1" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.1.tgz#7ae9b07b0ea804db7e25f05cb5fe4097d4e4949f" oauth-sign@~0.8.1: version "0.8.2" @@ -4683,7 +4743,7 @@ object.assign@^4.0.4: function-bind "^1.1.0" object-keys "^1.0.10" -object.entries@^1.0.3: +object.entries@^1.0.3, object.entries@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" dependencies: @@ -4706,7 +4766,7 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -object.values@^1.0.3: +object.values@^1.0.3, object.values@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" dependencies: @@ -4952,8 +5012,8 @@ pg-connection-string@0.1.3: resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" pg-pool@1.*: - version "1.7.1" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.7.1.tgz#421105cb7469979dcc48d6fc4fe3fe4659437437" + version "1.8.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37" dependencies: generic-pool "2.4.3" object-assign "4.1.0" @@ -4968,9 +5028,9 @@ pg-types@1.*: postgres-date "~1.0.0" postgres-interval "^1.1.0" -pg@^6.2.4: - version "6.2.4" - resolved "https://registry.yarnpkg.com/pg/-/pg-6.2.4.tgz#4f7ede70241e97506627d5d6078360701a647c45" +pg@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-6.4.0.tgz#cb76ba2e7c2eab89fc64bf7a9fe648ced72436dc" dependencies: buffer-writer "1.0.1" packet-reader "0.3.1" @@ -5014,12 +5074,6 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" - dependencies: - find-up "^1.0.0" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -5164,7 +5218,7 @@ postcss-flexbugs-fixes@^3.0.0: dependencies: postcss "^6.0.1" -postcss-load-config@^1.x: +postcss-load-config@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a" dependencies: @@ -5187,14 +5241,14 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" -postcss-loader@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.5.tgz#c19d3e8b83eb1ac316f5621ef4c0ef5b3d1b8b3a" +postcss-loader@^2.0.5, postcss-loader@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc" dependencies: - loader-utils "^1.x" - postcss "^6.x" - postcss-load-config "^1.x" - schema-utils "^0.x" + loader-utils "^1.1.0" + postcss "^6.0.2" + postcss-load-config "^1.2.0" + schema-utils "^0.3.0" postcss-media-minmax@^2.1.0: version "2.1.2" @@ -5380,10 +5434,10 @@ postcss-sass@^0.1.0: postcss "^5.2.6" postcss-scss@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-1.0.1.tgz#71e6baa2bc5688ffc5bca6abc4a8199badea8fb6" + version "1.0.2" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-1.0.2.tgz#ff45cf3354b879ee89a4eb68680f46ac9bb14f94" dependencies: - postcss "^6.0.1" + postcss "^6.0.3" postcss-selector-matches@^2.0.0: version "2.0.5" @@ -5467,13 +5521,13 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0. source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.x: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" +postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.2, postcss@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.3.tgz#b7f565b3d956fbb8565ca7c1e239d0506e427d8b" dependencies: chalk "^1.1.3" source-map "^0.5.6" - supports-color "^3.2.3" + supports-color "^4.0.0" postgres-array@~1.0.0: version "1.0.2" @@ -5557,12 +5611,12 @@ promise-each@^2.2.0: any-promise "^0.1.0" promise@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@~15.5.7: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -5643,9 +5697,9 @@ raf@^3.1.0: dependencies: performance-now "^2.1.0" -rails-ujs@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/rails-ujs/-/rails-ujs-5.1.1.tgz#0d6e660533141f1ac352830b31c5bfe1b58f79aa" +rails-ujs@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/rails-ujs/-/rails-ujs-5.1.2.tgz#94919e35e7fa07467223e9c81444704593559ef5" randomatic@^1.1.3: version "1.1.7" @@ -5680,16 +5734,16 @@ react-addons-perf@^15.4.2: fbjs "^0.8.4" object-assign "^4.1.0" -react-addons-shallow-compare@>=0.14.0, react-addons-shallow-compare@^15.5.2: - version "15.5.2" - resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.5.2.tgz#7cb0ee7acc8d7c93fcc202df0bf47ba916a7bdad" +react-addons-shallow-compare@>=0.14.0, react-addons-shallow-compare@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.0.tgz#b7a4e5ff9f2704c20cf686dd8a05dd08b26de252" dependencies: fbjs "^0.8.4" object-assign "^4.1.0" react-docgen@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-2.15.0.tgz#11aced462256e4862b14e6af6e46bdcefacb28bb" + version "2.16.0" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-2.16.0.tgz#03c9eba935de8031d791ab62657b7b6606ec5da6" dependencies: async "^2.1.4" babel-runtime "^6.9.2" @@ -5699,14 +5753,18 @@ react-docgen@^2.15.0: node-dir "^0.1.10" recast "^0.11.5" -react-dom@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.0.tgz#8bc23cb0c80e706355b76ca9f8ce47cf7bdfb6d1" +react-dom-factories@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.0.tgz#f43c05e5051b304f33251618d5bc859b29e46b6d" + +react-dom@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470" dependencies: fbjs "^0.8.9" loose-envify "^1.1.0" object-assign "^4.1.0" - prop-types "~15.5.7" + prop-types "^15.5.10" react-element-to-jsx-string@^5.0.0: version "5.0.7" @@ -5719,6 +5777,12 @@ react-element-to-jsx-string@^5.0.0: stringify-object "2.4.0" traverse "^0.6.6" +react-html-attributes@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-html-attributes/-/react-html-attributes-1.3.0.tgz#c97896e9cac47ad9c4e6618b835029a826f5d28c" + dependencies: + html-element-attributes "^1.0.0" + react-immutable-proptypes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4" @@ -5773,14 +5837,15 @@ react-komposer@^2.0.0: shallowequal "^0.2.2" react-modal@^1.7.6, react-modal@^1.7.7: - version "1.7.12" - resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.7.12.tgz#d3e29c07ddff8cd44c723a92dcd15e66a51a95bc" + version "1.9.7" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" dependencies: create-react-class "^15.5.2" element-class "^0.2.0" exenv "1.2.0" lodash.assign "^4.2.0" prop-types "^15.5.7" + react-dom-factories "^1.0.0" react-motion@^0.5.0: version "0.5.0" @@ -5790,11 +5855,11 @@ react-motion@^0.5.0: prop-types "^15.5.8" raf "^3.1.0" -react-notification@^6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.7.0.tgz#58f16976b0850d6a6c70e078796b82032d5301e2" +react-notification@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.7.1.tgz#fec45cc6d369f4bbf7b4072fe58ddb3bc262c898" dependencies: - prop-types "^15.5.8" + prop-types "^15.5.10" react-redux-loading-bar@^2.9.2: version "2.9.2" @@ -5882,16 +5947,16 @@ react-swipeable@^4.0.1: dependencies: prop-types "^15.5.8" -react-test-renderer@^15.5.4: - version "15.5.4" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc" +react-test-renderer@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.1.tgz#026f4a5bb5552661fd2cc4bbcd0d4bc8a35ebf7e" dependencies: fbjs "^0.8.9" object-assign "^4.1.0" -react-textarea-autosize@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-5.0.6.tgz#a3742e0a319484021b4dbfa1519df287768f2133" +react-textarea-autosize@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-5.0.7.tgz#cad511cf1111ab1482fbc8bd679d5d41e8e52b1f" dependencies: prop-types "^15.5.8" @@ -5902,8 +5967,8 @@ react-toggle@^4.0.1: classnames "^2.2.5" react-virtualized@^9.7.4: - version "9.7.6" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.7.6.tgz#6be9e7ae8b214a848116f1ebd48ff1aac26fcc2f" + version "9.8.0" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.8.0.tgz#7c1fe9b723ce39a1c4916cabe1c4f1bda5dbc04b" dependencies: babel-runtime "^6.11.6" classnames "^2.2.3" @@ -5911,15 +5976,15 @@ react-virtualized@^9.7.4: loose-envify "^1.3.0" prop-types "^15.5.4" -react@>=0.14.0, react@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/react/-/react-15.6.0.tgz#c23299b48e30ed302508ce89e1a02c919f826bce" +react@>=0.14.0, react@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" dependencies: - create-react-class "^15.5.2" + create-react-class "^15.6.0" fbjs "^0.8.9" loose-envify "^1.1.0" object-assign "^4.1.0" - prop-types "^15.5.7" + prop-types "^15.5.10" read-cache@^1.0.0: version "1.0.0" @@ -5958,14 +6023,14 @@ read-pkg@^2.0.0: path-type "^2.0.0" readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9: - version "2.2.11" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.11.tgz#0796b31f8d7688007ff0b93a8088d34aa17c0f72" + version "2.3.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.2.tgz#5a04df05e4f57fe3f0dc68fdd11dc5c97c7e6f4d" dependencies: core-util-is "~1.0.0" - inherits "~2.0.1" + inherits "~2.0.3" isarray "~1.0.0" process-nextick-args "~1.0.6" - safe-buffer "~5.0.1" + safe-buffer "~5.1.0" string_decoder "~1.0.0" util-deprecate "~1.0.1" @@ -6046,14 +6111,14 @@ redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" -redux@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d" +redux@^3.6.0, redux@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.1.tgz#bfc535c757d3849562ead0af18ac52122cd7268e" dependencies: lodash "^4.2.1" lodash-es "^4.2.1" loose-envify "^1.1.0" - symbol-observable "^1.0.2" + symbol-observable "^1.0.3" regenerate@^1.2.1: version "1.3.2" @@ -6206,9 +6271,9 @@ resolve-pathname@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.1.0.tgz#e8358801b86b83b17560d4e3c382d7aef2100944" -resolve-url-loader@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.3.tgz#f54cd1b040e8f0ab72b2cb32c9fbb8544152d9e9" +resolve-url-loader@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.0.tgz#27c95cc16a4353923fdbdc2dbaf5eef22232c477" dependencies: adjust-sourcemap-loader "^1.1.0" camelcase "^4.0.0" @@ -6285,9 +6350,9 @@ safe-buffer@5.0.1, safe-buffer@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" -safe-buffer@^5.0.1, safe-buffer@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223" +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" samsam@1.x, samsam@^1.1.3: version "1.2.1" @@ -6302,21 +6367,21 @@ sass-graph@^2.1.1: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-loader@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.5.tgz#a847910f36442aa56c5985879d54eb519e24a328" +sass-loader@^6.0.6: + version "6.0.6" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9" dependencies: async "^2.1.5" - clone-deep "^0.2.4" + clone-deep "^0.3.0" loader-utils "^1.0.1" lodash.tail "^4.1.1" - pify "^2.3.0" + pify "^3.0.0" sax@^1.2.1, sax@~1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -schema-utils@^0.3.0, schema-utils@^0.x: +schema-utils@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" dependencies: @@ -6466,9 +6531,9 @@ signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sinon@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.4.tgz#466ad8d1bae86d6db51aa218b92e997bc3e5db88" +sinon@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.3.5.tgz#9a2fc0ff8d526da716f30953aa2c65d518917f6c" dependencies: diff "^3.1.0" formatio "1.2.0" @@ -6667,8 +6732,8 @@ stream-browserify@^2.0.1: readable-stream "^2.0.2" stream-http@^2.3.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.1.tgz#546a51741ad5a6b07e9e31b0b10441a917df528a" + version "2.7.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" @@ -6693,11 +6758,11 @@ string-width@^1.0.1, string-width@^1.0.2: strip-ansi "^3.0.0" string-width@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" + version "2.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" dependencies: is-fullwidth-code-point "^2.0.0" - strip-ansi "^3.0.0" + strip-ansi "^4.0.0" string.prototype.padend@^3.0.0: version "3.0.0" @@ -6720,10 +6785,10 @@ string_decoder@^0.10.25: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" string_decoder@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.2.tgz#b29e1f4e1125fa97a10382b8a533737b7491e179" + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: - safe-buffer "~5.0.1" + safe-buffer "~5.1.0" stringify-object@2.4.0: version "2.4.0" @@ -6736,9 +6801,9 @@ stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" -stringz@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/stringz/-/stringz-0.2.1.tgz#9f134564e086b4a35f99b6efc1dd88a8d9cb7e4e" +stringz@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stringz/-/stringz-0.2.2.tgz#0c23c48c4933928be4fee8e2c83f71c3b1e077ba" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" @@ -6746,6 +6811,12 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -6805,6 +6876,16 @@ supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" +supports-color@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.0.0.tgz#33a7c680aa512c9d03ef929cacbb974d203d2790" + dependencies: + has-flag "^2.0.0" + +svg-tag-names@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/svg-tag-names/-/svg-tag-names-1.1.1.tgz#9641b29ef71025ee094c7043f7cdde7d99fbd50a" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -6817,7 +6898,7 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -symbol-observable@^1.0.2: +symbol-observable@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -6979,8 +7060,8 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" ua-parser-js@^0.7.9: - version "0.7.12" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" + version "0.7.13" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" uglify-js@^2.8.27, uglify-js@^2.8.29: version "2.8.29" @@ -7040,8 +7121,8 @@ urix@^0.1.0, urix@~0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" url-loader@^0.5.8: - version "0.5.8" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.8.tgz#b9183b1801e0f847718673673040bc9dc1c715c5" + version "0.5.9" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295" dependencies: loader-utils "^1.0.2" mime "1.3.x" @@ -7091,13 +7172,13 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" -uuid@^2.0.2, uuid@^2.0.3: +uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" uws@^0.14.5: version "0.14.5" @@ -7325,9 +7406,9 @@ websocket-extensions@>=0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" -websocket.js@^0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/websocket.js/-/websocket.js-0.1.10.tgz#6bad57a24d0210561018294b49234d21fa9fd7ee" +websocket.js@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/websocket.js/-/websocket.js-0.1.12.tgz#46c980787c57ebc8edcf44a0263e5d639367b85b" dependencies: backoff "^2.4.1" @@ -7490,9 +7571,9 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" -yargs@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.1.tgz#420ef75e840c1457a80adcca9bc6fa3849de51aa" +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" dependencies: camelcase "^4.1.0" cliui "^3.2.0" From be92babd008a7356738dec1aa7f36500638d00e5 Mon Sep 17 00:00:00 2001 From: Sorin Davidoi Date: Tue, 27 Jun 2017 13:46:37 +0200 Subject: [PATCH 094/382] Responsive images in media gallery (#3963) * feat(components/media_gallery): Responsive images * fix(components/media_gallery): Link to image URL --- .../mastodon/components/media_gallery.js | 18 ++++++++++++++---- app/javascript/styles/components.scss | 11 ++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 78ff35130..2cb1ce51c 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -85,14 +85,24 @@ class Item extends React.PureComponent { let thumbnail = ''; if (attachment.get('type') === 'image') { + const previewUrl = attachment.get('preview_url'); + const previewWidth = attachment.getIn(['meta', 'small', 'width']); + + const originalUrl = attachment.get('url'); + const originalWidth = attachment.getIn(['meta', 'original', 'width']); + + const srcSet = `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w`; + const sizes = `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw`; + thumbnail = ( - + > + + ); } else if (attachment.get('type') === 'gifv') { const autoPlay = !isIOS() && this.props.autoPlayGif; diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 91ebd91fd..84732ed4a 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3453,10 +3453,15 @@ button.icon-button.active i.fa-retweet { background-repeat: no-repeat; background-size: cover; cursor: zoom-in; - display: block; - height: 100%; + display: flex; + align-items: center; text-decoration: none; - width: 100%; + height: 100%; + + &, + img { + width: 100%; + } } .media-gallery__gifv { From da9317fa56d2b38e74781aa3a8dc6920298f824d Mon Sep 17 00:00:00 2001 From: Debanshu Kundu Date: Tue, 27 Jun 2017 17:48:53 +0530 Subject: [PATCH 095/382] #1456 Added rake task to add a user. (#1482) --- lib/tasks/mastodon.rake | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 6c7326b74..0e182c755 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -42,6 +42,37 @@ namespace :mastodon do end end + desc 'Add a user by providing their email, username and initial password.' \ + 'The user will receive a confirmation email, then they must reset their password before logging in.' + task add_user: :environment do + print 'Enter email: ' + email = STDIN.gets.chomp + + print 'Enter username: ' + username = STDIN.gets.chomp + + print 'Create user and send them confirmation mail [y/N]: ' + confirm = STDIN.gets.chomp + puts + + if confirm.casecmp?('y') + password = SecureRandom.hex + user = User.new(email: email, password: password, account_attributes: { username: username }) + if user.save + puts 'User added and confirmation mail sent to user\'s email address.' + puts "Here is the random password generated for the user: #{password}" + else + puts 'Following errors occured while creating new user:' + user.errors.each do |key, val| + puts "#{key}: #{val}" + end + end + else + puts 'Aborted by user.' + end + puts + end + namespace :media do desc 'Removes media attachments that have not been assigned to any status for longer than a day' task clear: :environment do From 16d0aed403485e5a98afd9cbf7e35bb929443731 Mon Sep 17 00:00:00 2001 From: Midgard Date: Tue, 27 Jun 2017 14:22:36 +0200 Subject: [PATCH 096/382] Use instance name in email notifications instead of "Mastodon" (#3763) * Use instance name in "password changed" mail instead of "Mastodon". Fixes tootsuite#2620. * Use instance name in password reset mail instead of "Mastodon". --- app/views/user_mailer/password_change.en.html.erb | 2 +- app/views/user_mailer/password_change.en.text.erb | 2 +- app/views/user_mailer/reset_password_instructions.en.html.erb | 2 +- app/views/user_mailer/reset_password_instructions.en.text.erb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/user_mailer/password_change.en.html.erb b/app/views/user_mailer/password_change.en.html.erb index a1bc77463..414e05a29 100644 --- a/app/views/user_mailer/password_change.en.html.erb +++ b/app/views/user_mailer/password_change.en.html.erb @@ -1,3 +1,3 @@

    Hello <%= @resource.email %>!

    -

    We're contacting you to notify you that your password on Mastodon has been changed.

    +

    We're contacting you to notify you that your password on <%= @instance %> has been changed.

    diff --git a/app/views/user_mailer/password_change.en.text.erb b/app/views/user_mailer/password_change.en.text.erb index 27581e604..3ae461c97 100644 --- a/app/views/user_mailer/password_change.en.text.erb +++ b/app/views/user_mailer/password_change.en.text.erb @@ -1,3 +1,3 @@ Hello <%= @resource.email %>! -We're contacting you to notify you that your password on Mastodon has been changed. +We're contacting you to notify you that your password on <%= @instance %> has been changed. diff --git a/app/views/user_mailer/reset_password_instructions.en.html.erb b/app/views/user_mailer/reset_password_instructions.en.html.erb index 643b43319..cfb129e22 100644 --- a/app/views/user_mailer/reset_password_instructions.en.html.erb +++ b/app/views/user_mailer/reset_password_instructions.en.html.erb @@ -1,6 +1,6 @@

    Hello <%= @resource.email %>!

    -

    Someone has requested a link to change your password on Mastodon. You can do this through the link below.

    +

    Someone has requested a link to change your password on <%= @instance %>. You can do this through the link below.

    <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

    diff --git a/app/views/user_mailer/reset_password_instructions.en.text.erb b/app/views/user_mailer/reset_password_instructions.en.text.erb index fe73b0165..7ed22dc2c 100644 --- a/app/views/user_mailer/reset_password_instructions.en.text.erb +++ b/app/views/user_mailer/reset_password_instructions.en.text.erb @@ -1,6 +1,6 @@ Hello <%= @resource.email %>! -Someone has requested a link to change your password on Mastodon. You can do this through the link below. +Someone has requested a link to change your password on <%= @instance %>. You can do this through the link below. <%= edit_password_url(@resource, reset_password_token: @token) %> From 12e7c81dd8739a0f83513054c0fda22e098e2458 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 27 Jun 2017 18:07:21 +0200 Subject: [PATCH 097/382] Turn report screen into a modal (#3965) --- app/javascript/mastodon/actions/reports.js | 18 ++- .../mastodon/components/status_action_bar.js | 1 - .../account_timeline/components/header.js | 1 - .../features/status/components/action_bar.js | 1 - .../features/ui/components/modal_root.js | 2 + .../components/report_modal.js} | 72 +++++------- app/javascript/mastodon/features/ui/index.js | 3 - .../mastodon/locales/defaultMessages.json | 17 +++ app/javascript/mastodon/locales/en.json | 4 +- app/javascript/styles/components.scss | 104 +++++++----------- 10 files changed, 98 insertions(+), 125 deletions(-) rename app/javascript/mastodon/features/{report/index.js => ui/components/report_modal.js} (60%) diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js index 9b632be74..b19a07285 100644 --- a/app/javascript/mastodon/actions/reports.js +++ b/app/javascript/mastodon/actions/reports.js @@ -1,4 +1,5 @@ import api from '../api'; +import { openModal, closeModal } from './modal'; export const REPORT_INIT = 'REPORT_INIT'; export const REPORT_CANCEL = 'REPORT_CANCEL'; @@ -11,10 +12,14 @@ export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; export function initReport(account, status) { - return { - type: REPORT_INIT, - account, - status, + return dispatch => { + dispatch({ + type: REPORT_INIT, + account, + status, + }); + + dispatch(openModal('REPORT')); }; }; @@ -40,7 +45,10 @@ export function submitReport() { account_id: getState().getIn(['reports', 'new', 'account_id']), status_ids: getState().getIn(['reports', 'new', 'status_ids']), comment: getState().getIn(['reports', 'new', 'comment']), - }).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error))); + }).then(response => { + dispatch(closeModal()); + dispatch(submitReportSuccess(response.data)); + }).catch(error => dispatch(submitReportFail(error))); }; }; diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index edb2d6eb0..fd7c99054 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -87,7 +87,6 @@ export default class StatusActionBar extends ImmutablePureComponent { handleReport = () => { this.props.onReport(this.props.status); - this.context.router.history.push('/report'); } handleConversationMuteClick = () => { diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 7f80e39e8..167a2097e 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -38,7 +38,6 @@ export default class Header extends ImmutablePureComponent { handleReport = () => { this.props.onReport(this.props.account); - this.context.router.history.push('/report'); } handleMute = () => { diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index 29080529d..5e150842e 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -56,7 +56,6 @@ export default class ActionBar extends React.PureComponent { handleReport = () => { this.props.onReport(this.props.status); - this.context.router.history.push('/report'); } render () { diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js index 2e4f9876d..48b048eb7 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.js @@ -5,6 +5,7 @@ import OnboardingModal from './onboarding_modal'; import VideoModal from './video_modal'; import BoostModal from './boost_modal'; import ConfirmationModal from './confirmation_modal'; +import ReportModal from './report_modal'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; @@ -14,6 +15,7 @@ const MODAL_COMPONENTS = { 'VIDEO': VideoModal, 'BOOST': BoostModal, 'CONFIRM': ConfirmationModal, + 'REPORT': ReportModal, }; export default class ModalRoot extends React.PureComponent { diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/ui/components/report_modal.js similarity index 60% rename from app/javascript/mastodon/features/report/index.js rename to app/javascript/mastodon/features/ui/components/report_modal.js index bfb09e193..c989d2c9b 100644 --- a/app/javascript/mastodon/features/report/index.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -1,19 +1,17 @@ import React from 'react'; import { connect } from 'react-redux'; -import { changeReportComment, submitReport } from '../../actions/reports'; -import { refreshAccountTimeline } from '../../actions/timelines'; +import { changeReportComment, submitReport } from '../../../actions/reports'; +import { refreshAccountTimeline } from '../../../actions/timelines'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Column from '../ui/components/column'; -import Button from '../../components/button'; -import { makeGetAccount } from '../../selectors'; +import { makeGetAccount } from '../../../selectors'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import StatusCheckBox from './containers/status_check_box_container'; +import StatusCheckBox from '../../report/containers/status_check_box_container'; import Immutable from 'immutable'; -import ColumnBackButtonSlim from '../../components/column_back_button_slim'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Button from '../../../components/button'; const messages = defineMessages({ - heading: { id: 'report.heading', defaultMessage: 'New report' }, placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, submit: { id: 'report.submit', defaultMessage: 'Submit' }, }); @@ -37,11 +35,7 @@ const makeMapStateToProps = () => { @connect(makeMapStateToProps) @injectIntl -export default class Report extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; +export default class ReportModal extends ImmutablePureComponent { static propTypes = { isSubmitting: PropTypes.bool, @@ -52,17 +46,15 @@ export default class Report extends React.PureComponent { intl: PropTypes.object.isRequired, }; - componentWillMount () { - if (!this.props.account) { - this.context.router.history.replace('/'); - } + handleCommentChange = (e) => { + this.props.dispatch(changeReportComment(e.target.value)); + } + + handleSubmit = () => { + this.props.dispatch(submitReport()); } componentDidMount () { - if (!this.props.account) { - return; - } - this.props.dispatch(refreshAccountTimeline(this.props.account.get('id'))); } @@ -72,15 +64,6 @@ export default class Report extends React.PureComponent { } } - handleCommentChange = (e) => { - this.props.dispatch(changeReportComment(e.target.value)); - } - - handleSubmit = () => { - this.props.dispatch(submitReport()); - this.context.router.history.replace('/'); - } - render () { const { account, comment, intl, statusIds, isSubmitting } = this.props; @@ -89,36 +72,33 @@ export default class Report extends React.PureComponent { } return ( - - +
    +
    + {account.get('acct')} }} /> +
    -
    -
    - - {account.get('acct')} -
    - -
    +
    +
    {statusIds.map(statusId => )}
    -
    +