diff --git a/.buildpacks b/.buildpacks new file mode 100644 index 00000000..d295b0f5 --- /dev/null +++ b/.buildpacks @@ -0,0 +1,2 @@ +https://github.com/Scalingo/nodejs-buildpack +https://github.com/Scalingo/ruby-buildpack diff --git a/.codeclimate.yml b/.codeclimate.yml index f0c238b1..8558e313 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,6 +1,8 @@ engines: duplication: enabled: true + exclude_paths: + - app/assets/javascripts/components/locales/ config: languages: - ruby diff --git a/.dockerignore b/.dockerignore index 7892e503..21d1f59a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ public/assets node_modules storybook neo4j +vendor/bundle diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5f8702cf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.env.production.sample b/.env.production.sample index bd81b8fc..7c82c014 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -22,13 +22,24 @@ OTP_SECRET= # SINGLE_USER_MODE=true # Prevent registrations with following e-mail domains # EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc +# Only allow registrations with the following e-mail domains +# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc + +# Optionally change default language +# DEFAULT_LOCALE=de # E-mail configuration +# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers SMTP_SERVER=smtp.mailgun.org SMTP_PORT=587 SMTP_LOGIN= SMTP_PASSWORD= SMTP_FROM_ADDRESS=notifications@example.com +#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail +#SMTP_AUTH_METHOD=plain +#SMTP_OPENSSL_VERIFY_MODE=peer +#SMTP_ENABLE_STARTTLS_AUTO=true + # Optional asset host for multi-server setups # CDN_HOST=assets.example.com @@ -42,8 +53,22 @@ SMTP_FROM_ADDRESS=notifications@example.com # S3_PROTOCOL=http # S3_HOSTNAME=192.168.1.123:9000 +# S3 (Minio Config (optional) Please check Minio instance for details) +# S3_ENABLED=true +# S3_BUCKET= +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# S3_REGION= +# S3_PROTOCOL=https +# S3_HOSTNAME= +# S3_ENDPOINT= + # Optional alias for S3 if you want to use Cloudfront or Cloudflare in front # S3_CLOUDFRONT_HOST= # Streaming API integration # STREAMING_API_BASE_URL= + +# Advanced settings +# If you need to use pgBouncer, you need to disable prepared statements: +# PREPARED_STATEMENTS=false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..6d540c41 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,30 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +!/log/.keep +/tmp +coverage +public/system +public/assets +.env +.env.production +node_modules/ +neo4j/ + +# Ignore Vagrant files +.vagrant/ + +# Ignore Capistrano customizations +config/deploy/* diff --git a/.gitignore b/.gitignore index 6d540c41..5c95f780 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,11 @@ neo4j/ # Ignore Capistrano customizations config/deploy/* + + +# Ignore IDE files +.vscode/ + +# Ignore postgres + redis volume optionally created by docker-compose +postgres +redis diff --git a/.ruby-version b/.ruby-version index 2bf1c1cc..005119ba 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.1 +2.4.1 diff --git a/.slugignore b/.slugignore new file mode 100644 index 00000000..b0141b0e --- /dev/null +++ b/.slugignore @@ -0,0 +1,5 @@ +node_modules/ +.cache/ +docs/ +spec/ +storybook/ diff --git a/.travis.yml b/.travis.yml index b1b0c2bc..a9824ccf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ addons: postgresql: 9.4 rvm: - - 2.3.1 + - 2.4.1 services: - redis-server diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfc771ab..9ca01a56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository: 2. By working on the back-end application 3. By working on the front-end application -Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. +Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation). Below are the guidelines for working on pull requests: @@ -41,3 +41,4 @@ It is expected that you have a working development environment set up (see back- * If you are introducing new strings, they must be using localization methods If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet. + diff --git a/Dockerfile b/Dockerfile index bcc91134..a05525b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,16 @@ -FROM ruby:2.3.1-alpine +FROM ruby:2.4.1-alpine + +LABEL maintainer="https://github.com/tootsuite/mastodon" \ + description="A GNU Social-compatible microblogging server" ENV RAILS_ENV=production \ NODE_ENV=production +EXPOSE 3000 4000 + WORKDIR /mastodon -COPY . /mastodon +COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/ RUN BUILD_DEPS=" \ postgresql-dev \ @@ -24,8 +29,11 @@ RUN BUILD_DEPS=" \ && npm install -g npm@3 && npm install -g yarn \ && bundle install --deployment --without test development \ && yarn \ - && npm cache clean \ + && yarn cache clean \ + && npm -g cache clean \ && apk del $BUILD_DEPS \ && rm -rf /tmp/* /var/cache/apk/* +COPY . /mastodon + VOLUME /mastodon/public/system /mastodon/public/assets diff --git a/Gemfile b/Gemfile index 46baed30..9a179262 100644 --- a/Gemfile +++ b/Gemfile @@ -1,15 +1,13 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '2.3.1' +ruby '2.4.1' gem 'rails', '~> 5.0.2' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.1.0' gem 'jquery-rails' -gem 'jbuilder', '~> 2.0' -gem 'sdoc', '~> 0.4.0', group: :doc gem 'puma' gem 'hamlit-rails' @@ -23,34 +21,37 @@ gem 'paperclip', '~> 5.1' gem 'paperclip-av-transcoder' gem 'aws-sdk', '>= 2.0' -gem 'http' -gem 'httplog' gem 'addressable' -gem 'nokogiri' -gem 'link_header' -gem 'ostatus2' -gem 'goldfinger' gem 'devise' gem 'devise-two-factor' gem 'doorkeeper' -gem 'rabl' -gem 'rqrcode' -gem 'twitter-text' -gem 'oj' -gem 'hiredis' -gem 'redis', '~>3.2' gem 'fast_blank' +gem 'goldfinger' +gem 'hiredis' gem 'htmlentities' -gem 'simple_form' -gem 'will_paginate' +gem 'http' +gem 'http_accept_language' +gem 'httplog' +gem 'kaminari' +gem 'link_header' +gem 'nokogiri' +gem 'oj' +gem 'ostatus2' +gem 'ox' +gem 'rabl' gem 'rack-attack' gem 'rack-cors', require: 'rack/cors' -gem 'sidekiq' -gem 'rails-settings-cached' -gem 'simple-navigation' -gem 'statsd-instrument' -gem 'ruby-oembed', require: 'oembed' gem 'rack-timeout' +gem 'rails-settings-cached' +gem 'redis', '~>3.2', require: ['redis', 'redis/connection/hiredis'] +gem 'rqrcode' +gem 'ruby-oembed', require: 'oembed' +gem 'sidekiq' +gem 'sidekiq-unique-jobs' +gem 'simple-navigation' +gem 'simple_form' +gem 'statsd-instrument' +gem 'twitter-text' gem 'tzinfo-data' gem 'react-rails' @@ -66,9 +67,11 @@ group :development, :test do end group :test do + gem 'faker' + gem 'rails-controller-testing' + gem 'rspec-sidekiq' gem 'simplecov', require: false gem 'webmock' - gem 'rspec-sidekiq' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 6e311524..f1bc9880 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,7 +24,7 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_record_query_trace (1.5.3) + active_record_query_trace (1.5.4) activejob (5.0.2) activesupport (= 5.0.2) globalid (>= 0.3.6) @@ -39,7 +39,7 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.5.0) + addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) airbrussh (1.1.2) sshkit (>= 1.6.1, != 1.7.0) @@ -47,17 +47,17 @@ GEM ast (2.3.0) attr_encrypted (3.0.3) encryptor (~> 3.0.0) - autoprefixer-rails (6.5.0.2) + autoprefixer-rails (6.7.7.1) execjs av (0.9.0) cocaine (~> 0.5.3) - aws-sdk (2.6.28) - aws-sdk-resources (= 2.6.28) - aws-sdk-core (2.6.28) + aws-sdk (2.9.6) + aws-sdk-resources (= 2.9.6) + aws-sdk-core (2.9.6) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.6.28) - aws-sdk-core (= 2.6.28) + aws-sdk-resources (2.9.6) + aws-sdk-core (= 2.9.6) aws-sigv4 (1.0.0) babel-source (5.8.35) babel-transpiler (0.7.0) @@ -78,12 +78,11 @@ GEM railties (>= 4.0.0, < 5.1) sprockets (>= 3.6.0) builder (3.2.3) - bullet (5.3.0) + bullet (5.5.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) - capistrano (3.7.2) + capistrano (3.8.0) airbrussh (>= 1.0.0) - capistrano-harrow i18n rake (>= 10.0.0) sshkit (>= 1.9.0) @@ -92,8 +91,7 @@ GEM sshkit (~> 1.2) capistrano-faster-assets (1.0.2) capistrano (>= 3.1) - capistrano-harrow (0.5.3) - capistrano-rails (1.2.2) + capistrano-rails (1.2.3) capistrano (~> 3.1) capistrano-bundler (~> 1.1) capistrano-rbenv (2.1.0) @@ -119,7 +117,7 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) debug_inspector (0.0.2) - devise (4.2.0) + devise (4.2.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0, < 5.1) @@ -131,16 +129,16 @@ GEM devise (~> 4.0) railties rotp (~> 2.0) - diff-lcs (1.2.5) + diff-lcs (1.3) docile (1.1.5) - domain_name (0.5.20161129) + domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.2.0) + doorkeeper (4.2.5) railties (>= 4.2) - dotenv (2.1.1) - dotenv-rails (2.1.1) - dotenv (= 2.1.1) - railties (>= 4.0, < 5.1) + dotenv (2.2.0) + dotenv-rails (2.2.0) + dotenv (= 2.2.0) + railties (>= 3.2, < 5.1) easy_translate (0.5.0) json thread @@ -148,12 +146,14 @@ GEM encryptor (3.0.0) erubis (2.7.0) execjs (2.7.0) - fabrication (2.15.2) + fabrication (2.16.1) + faker (1.7.3) + i18n (~> 0.5) fast_blank (1.0.0) - font-awesome-rails (4.6.3.1) + font-awesome-rails (4.7.0.1) railties (>= 3.2, < 5.1) - fuubar (2.1.1) - rspec (~> 3.0) + fuubar (2.2.0) + rspec-core (~> 3.0) ruby-progressbar (~> 1.4) globalid (0.3.7) activesupport (>= 4.1.0) @@ -161,20 +161,20 @@ GEM addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) - hamlit (2.7.2) - temple (~> 0.7.6) + hamlit (2.8.1) + temple (>= 0.8.0) thor tilt - hamlit-rails (0.1.0) + hamlit-rails (0.2.0) actionpack (>= 4.0.1) activesupport (>= 4.0.1) hamlit (>= 1.2.0) railties (>= 4.0.1) - hashdiff (0.3.0) + hashdiff (0.3.2) highline (1.7.8) hiredis (0.6.1) htmlentities (4.3.4) - http (2.1.0) + http (2.2.1) addressable (~> 2.3) http-cookie (~> 1.0) http-form_data (~> 1.0.1) @@ -182,11 +182,12 @@ GEM http-cookie (1.0.3) domain_name (~> 0.5) http-form_data (1.0.1) + http_accept_language (2.1.0) http_parser.rb (0.6.0) - httplog (0.3.2) + httplog (0.99.2) colorize i18n (0.8.1) - i18n-tasks (0.9.6) + i18n-tasks (0.9.13) activesupport (>= 4.0.2) ast (>= 2.1.0) easy_translate (>= 0.5.0) @@ -194,22 +195,31 @@ GEM highline (>= 1.7.3) i18n parser (>= 2.2.3.0) - term-ansicolor (>= 1.3.2) + rainbow (~> 2.2) terminal-table (>= 1.5.1) - jbuilder (2.6.0) - activesupport (>= 3.0.0, < 5.1) - multi_json (~> 1.2) jmespath (1.3.1) - jquery-rails (4.1.1) + jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (1.8.3) + json (2.0.3) + kaminari (1.0.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.0.1) + kaminari-activerecord (= 1.0.1) + kaminari-core (= 1.0.1) + kaminari-actionview (1.0.1) + actionview + kaminari-core (= 1.0.1) + kaminari-activerecord (1.0.1) + activerecord + kaminari-core (= 1.0.1) + kaminari-core (1.0.1) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) launchy (~> 2.2) - letter_opener_web (1.3.0) + letter_opener_web (1.3.1) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) @@ -229,19 +239,19 @@ GEM mimemagic (0.3.2) mini_portile2 (2.1.0) minitest (5.10.1) - multi_json (1.12.1) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (4.0.1) + net-ssh (4.1.0) nio4r (2.0.0) nokogiri (1.7.1) mini_portile2 (~> 2.1.0) - oj (2.17.3) + oj (2.18.5) orm_adapter (0.5.0) ostatus2 (1.0.2) addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) + ox (2.4.11) paperclip (5.1.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -251,26 +261,26 @@ GEM paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) - parser (2.3.1.2) + parser (2.4.0.0) ast (~> 2.2) - pg (0.18.4) - pghero (1.6.2) + pg (0.20.0) + pghero (1.6.4) activerecord powerpack (0.1.1) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - pry-rails (0.3.4) - pry (>= 0.9.10) - public_suffix (2.0.4) - puma (3.6.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (2.0.5) + puma (3.8.2) rabl (0.13.1) activesupport (>= 2.3.14) rack (2.0.1) rack-attack (5.0.1) rack - rack-cors (0.4.0) + rack-cors (0.4.1) rack-protection (1.5.3) rack rack-test (0.6.3) @@ -288,6 +298,10 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 5.0.2) sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.1) + actionpack (~> 5.x) + actionview (~> 5.x) + activesupport (~> 5.x) rails-dom-testing (2.0.2) activesupport (>= 4.2.0, < 6.0) nokogiri (~> 1.6) @@ -306,44 +320,37 @@ GEM method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.1.0) + rainbow (2.2.1) rake (12.0.0) - rdoc (4.2.2) - json (~> 1.4) - react-rails (1.10.0) + react-rails (1.11.0) babel-transpiler (>= 0.7.0) - coffee-script-source (~> 1.8) connection_pool execjs railties (>= 3.2) tilt - redis (3.3.2) - redis-actionpack (5.0.0) - actionpack (>= 4.0.0, < 6) - redis-rack (~> 2.0.0.pre) - redis-store (~> 1.2.0.pre) - redis-activesupport (5.0.1) + redis (3.3.3) + redis-actionpack (5.0.1) + actionpack (>= 4.0, < 6) + redis-rack (>= 1, < 3) + redis-store (>= 1.1.0, < 1.4.0) + redis-activesupport (5.0.2) activesupport (>= 3, < 6) - redis-store (~> 1.2.0) - redis-rack (2.0.0) - rack (~> 2.0) - redis-store (~> 1.2.0) - redis-rails (5.0.1) - redis-actionpack (~> 5.0.0) - redis-activesupport (~> 5.0.0) - redis-store (~> 1.2.0) - redis-store (1.2.0) + redis-store (~> 1.3.0) + redis-rack (2.0.1) + rack (>= 2.0, < 3) + redis-store (>= 1.2, < 1.4) + redis-rails (5.0.2) + redis-actionpack (>= 5.0, < 6) + redis-activesupport (>= 5.0, < 6) + redis-store (>= 1.2, < 2) + redis-store (1.3.0) redis (>= 2.2) responders (2.3.0) railties (>= 4.2.0, < 5.1) rotp (2.1.2) rqrcode (0.10.1) chunky_png (~> 1.0) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.2) + rspec-core (3.5.4) rspec-support (~> 3.5.0) rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) @@ -351,7 +358,7 @@ GEM rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) - rspec-rails (3.5.1) + rspec-rails (3.5.2) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -359,40 +366,40 @@ GEM rspec-expectations (~> 3.5.0) rspec-mocks (~> 3.5.0) rspec-support (~> 3.5.0) - rspec-sidekiq (2.2.0) - rspec (~> 3.0, >= 3.0.0) + rspec-sidekiq (3.0.0) + rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.5.0) - rubocop (0.42.0) - parser (>= 2.3.1.1, < 3.0) + rubocop (0.48.1) + parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-oembed (0.10.1) + ruby-oembed (0.12.0) ruby-progressbar (1.8.1) safe_yaml (1.0.4) - sass (3.4.22) + sass (3.4.23) sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - sdoc (0.4.1) - json (~> 1.7, >= 1.7.7) - rdoc (~> 4.0) - sidekiq (4.2.7) + sidekiq (4.2.10) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) redis (~> 3.2, >= 3.2.1) - simple-navigation (4.0.3) + sidekiq-unique-jobs (5.0.0) + sidekiq (>= 4.0) + thor + simple-navigation (4.0.5) activesupport (>= 2.3.2) - simple_form (3.2.1) + simple_form (3.4.0) actionpack (> 4, < 5.1) activemodel (> 4, < 5.1) - simplecov (0.12.0) + simplecov (0.14.1) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) @@ -405,43 +412,39 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.11.5) + sshkit (1.13.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) statsd-instrument (2.1.2) - temple (0.7.7) - term-ansicolor (1.4.0) - tins (~> 1.0) - terminal-table (1.7.0) - unicode-display_width (~> 1.1) + temple (0.8.0) + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) thor (0.19.4) thread (0.2.2) thread_safe (0.3.6) - tilt (2.0.6) - tins (1.12.0) + tilt (2.0.7) twitter-text (1.14.5) unf (~> 0.1.0) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) tzinfo-data (1.2017.2) tzinfo (>= 1.0.0) - uglifier (3.0.1) + uglifier (3.2.0) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.1.0) + unicode-display_width (1.1.3) uniform_notifier (1.10.0) - warden (1.2.6) + warden (1.2.7) rack (>= 1.0) - webmock (2.1.0) + webmock (2.3.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) - will_paginate (3.1.0) PLATFORMS ruby @@ -467,6 +470,7 @@ DEPENDENCIES doorkeeper dotenv-rails fabrication + faker fast_blank font-awesome-rails fuubar @@ -475,10 +479,11 @@ DEPENDENCIES hiredis htmlentities http + http_accept_language httplog i18n-tasks (~> 0.9.6) - jbuilder (~> 2.0) jquery-rails + kaminari letter_opener letter_opener_web link_header @@ -486,6 +491,7 @@ DEPENDENCIES nokogiri oj ostatus2 + ox paperclip (~> 5.1) paperclip-av-transcoder pg @@ -497,6 +503,7 @@ DEPENDENCIES rack-cors rack-timeout rails (~> 5.0.2) + rails-controller-testing rails-settings-cached rails_12factor react-rails @@ -508,8 +515,8 @@ DEPENDENCIES rubocop ruby-oembed sass-rails (~> 5.0) - sdoc (~> 0.4.0) sidekiq + sidekiq-unique-jobs simple-navigation simple_form simplecov @@ -518,10 +525,9 @@ DEPENDENCIES tzinfo-data uglifier (>= 1.3.0) webmock - will_paginate RUBY VERSION - ruby 2.3.1p112 + ruby 2.4.1p111 BUNDLED WITH - 1.14.3 + 1.14.6 diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..8394b242 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,5 @@ +[Issue text goes here]. + +* * * * + +- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate. diff --git a/Procfile b/Procfile index 6cdd8951..646e2605 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: bundle exec puma -C config/puma.rb -worker: bundle exec sidekiq -q default -q mailers -q push +worker: bundle exec sidekiq -q default -q push -q pull -q mailers diff --git a/README.md b/README.md index 20499e6e..6a253b28 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Click on the screenshot to watch a demo of the UI: [youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU -Focus of the project on a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided. +The project focus is a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided. If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd` @@ -25,11 +25,11 @@ If you would like, you can [support the development of this project on Patreon][ ## Resources -- [List of Mastodon instances](docs/Using-Mastodon/List-of-Mastodon-instances.md) +- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md) - [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com) -- [API overview](docs/Using-the-API/API.md) -- [Frequently Asked Questions](docs/Using-Mastodon/FAQ.md) -- [List of apps](docs/Using-Mastodon/Apps.md) +- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md) +- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md) +- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md) ## Features @@ -65,23 +65,54 @@ Consult the example configuration file, `.env.production.sample` for the full li ## Running with Docker and Docker-Compose -The project now includes a `Dockerfile` and a `docker-compose.yml`. You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can: +[![](https://images.microbadger.com/badges/version/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com") + +The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`). + +Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location, +so you may need or want to adjust the settings there. + +Before running the first time, you need to build the images: docker-compose build -And finally +Then, you need to fill in the `.env.production` file: - docker-compose up -d + cp .env.production.sample .env.production + nano .env.production -As usual, the first thing you would need to do would be to run migrations: +Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations. + +You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use: + + docker-compose run --rm web rake secret + +Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field. + +Then you should run the `db:migrate` command to create the database, or migrate it from an older release: docker-compose run --rm web rails db:migrate -And since the instance running in the container will be running in production mode, you need to pre-compile assets: +Then, you will also need to precompile the assets: docker-compose run --rm web rails assets:precompile -The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up. +before you can launch the docker image with: + + docker-compose up + +If you wish to run this as a daemon process instead of monitoring it on console, use instead: + + docker-compose up -d + +Then you may login to your new Mastodon instance by browsing to http://localhost:3000/ + +Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how +to configure Nginx to make your Mastodon instance available to the rest of the world. + +The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases. + +The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up. **Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up. @@ -101,33 +132,33 @@ Running any of these tasks via docker-compose would look like this: This approach makes updating to the latest version a real breeze. - git pull - -To pull down the updates, re-run - - docker-compose build - -And finally, - - docker-compose up -d - -Which will re-create the updated containers, leaving databases and data as is. Depending on what files have been updated, you might need to re-run migrations and asset compilation. +1. `git pull` to download updates from the repository +2. `docker-compose build` to compile the Docker image out of the changed source files +3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date +4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets +5. `docker-compose up -d` to re-create (restart) containers and pick up the changes ## Deployment without Docker -Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions. +Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions. + +## Deployment on Scalingo + +[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master) + +[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md) ## Deployment on Heroku (experimental) [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) -Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. [You can view a guide for deployment on Heroku here.](docs/Running-Mastodon/Heroku-guide.md) +Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned. ## Development with Vagrant A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed. -[You can find the guide for setting up a Vagrant development environment here.](docs/Running-Mastodon/Vagrant-guide.md) +[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md) ## Contributing diff --git a/Vagrantfile b/Vagrantfile index 154d0e89..90f60464 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -46,12 +46,12 @@ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build export PATH="$HOME/.rbenv/bin::$PATH" eval "$(rbenv init -)" -echo "Compiling Ruby 2.3.1: warning, this takes a while!!!" -rbenv install 2.3.1 -rbenv global 2.3.1 - cd /vagrant +echo "Compiling Ruby $(cat .ruby-version): warning, this takes a while!!!" +rbenv install $(cat .ruby-version) +rbenv global $(cat .ruby-version) + # Configure database sudo -u postgres createuser -U postgres vagrant -s sudo -u postgres createdb -U postgres mastodon_development @@ -84,6 +84,16 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.provider :virtualbox do |vb| vb.name = "mastodon" vb.customize ["modifyvm", :id, "--memory", "1024"] + + # Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions. + # https://github.com/mitchellh/vagrant/issues/1172 + vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"] + vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"] + + # Use "virtio" network interfaces for better performance. + vb.customize ["modifyvm", :id, "--nictype1", "virtio"] + vb.customize ["modifyvm", :id, "--nictype2", "virtio"] + end config.vm.hostname = "mastodon.dev" @@ -91,12 +101,14 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # This uses the vagrant-hostsupdater plugin, and lets you # access the development site at http://mastodon.dev. # To install: - # $ vagrant plugin install hostsupdater + # $ vagrant plugin install vagrant-hostsupdater if defined?(VagrantPlugins::HostsUpdater) - config.vm.network :private_network, ip: "192.168.42.42" + config.vm.network :private_network, ip: "192.168.42.42", nictype: "virtio" config.hostsupdater.remove_on_suspend = false end + config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp'] + # Otherwise, you can access the site at http://localhost:3000 config.vm.network :forwarded_port, guest: 80, host: 3000 diff --git a/app.json b/app.json index c5016f69..6c4294c7 100644 --- a/app.json +++ b/app.json @@ -26,6 +26,10 @@ "description": "The secret key base", "generator": "secret" }, + "OTP_SECRET": { + "description": "One-time password secret", + "generator": "secret" + }, "SINGLE_USER_MODE": { "description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)", "value": "false", @@ -75,6 +79,18 @@ "SMTP_FROM_ADDRESS": { "description": "Address to send emails from", "required": false + }, + "SMTP_AUTH_METHOD": { + "description": "Authentication method to use with SMTP server. Default is 'plain'.", + "required": false + }, + "SMTP_OPENSSL_VERIFY_MODE": { + "description": "SMTP server certificate verification mode. Defaults is 'peer'.", + "required": false + }, + "SMTP_ENABLE_STARTTLS_AUTO": { + "description": "Enable STARTTLS if SMTP server supports it? Default is true.", + "required": false } }, "buildpacks": [ diff --git a/app/assets/images/background-photo.jpeg b/app/assets/images/background-photo.jpeg index b0a88ff3..03341b8e 100644 Binary files a/app/assets/images/background-photo.jpeg and b/app/assets/images/background-photo.jpeg differ diff --git a/app/assets/images/elephant-friend.png b/app/assets/images/elephant-friend.png new file mode 100644 index 00000000..3c5145ba Binary files /dev/null and b/app/assets/images/elephant-friend.png differ diff --git a/app/assets/images/fluffy-elephant-friend.png b/app/assets/images/fluffy-elephant-friend.png index 11787e93..f0df2992 100644 Binary files a/app/assets/images/fluffy-elephant-friend.png and b/app/assets/images/fluffy-elephant-friend.png differ diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg new file mode 100644 index 00000000..52bf86b0 --- /dev/null +++ b/app/assets/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx index 1b3cc60d..88e91c35 100644 --- a/app/assets/javascripts/components/actions/compose.jsx +++ b/app/assets/javascripts/components/actions/compose.jsx @@ -2,6 +2,8 @@ import api from '../api'; import { updateTimeline } from './timelines'; +import * as emojione from 'emojione'; + export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; @@ -72,9 +74,8 @@ export function mentionCompose(account, router) { export function submitCompose() { return function (dispatch, getState) { dispatch(submitComposeRequest()); - api(getState).post('/api/v1/statuses', { - status: getState().getIn(['compose', 'text'], ''), + status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), sensitive: getState().getIn(['compose', 'sensitive']), diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx index 980b7d63..b09ca085 100644 --- a/app/assets/javascripts/components/actions/notifications.jsx +++ b/app/assets/javascripts/components/actions/notifications.jsx @@ -50,6 +50,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) { }; }; +const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); + export function refreshNotifications() { return (dispatch, getState) => { dispatch(refreshNotificationsRequest()); @@ -61,6 +63,8 @@ export function refreshNotifications() { params.since_id = ids.first().get('id'); } + params.exclude_types = excludeTypesFromSettings(getState()); + api(getState).get('/api/v1/notifications', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); @@ -105,11 +109,11 @@ export function expandNotifications() { dispatch(expandNotificationsRequest()); - api(getState).get(url, { - params: { - limit: 5 - } - }).then(response => { + const params = {}; + + params.exclude_types = excludeTypesFromSettings(getState()); + + api(getState).get(url, params).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); diff --git a/app/assets/javascripts/components/actions/onboarding.jsx b/app/assets/javascripts/components/actions/onboarding.jsx index a161c50e..804cdb55 100644 --- a/app/assets/javascripts/components/actions/onboarding.jsx +++ b/app/assets/javascripts/components/actions/onboarding.jsx @@ -5,10 +5,10 @@ export function showOnboardingOnce() { return (dispatch, getState) => { const alreadySeen = getState().getIn(['settings', 'onboarded']); - if (!alreadySeen) { + //if (!alreadySeen) { dispatch(openModal('ONBOARDING')); dispatch(changeSetting(['onboarded'], true)); dispatch(saveSettings()); - } + //} }; }; diff --git a/app/assets/javascripts/components/actions/reports.jsx b/app/assets/javascripts/components/actions/reports.jsx index 2c1245dc..094670d6 100644 --- a/app/assets/javascripts/components/actions/reports.jsx +++ b/app/assets/javascripts/components/actions/reports.jsx @@ -7,7 +7,8 @@ export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; -export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; +export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; +export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; export function initReport(account, status) { return { @@ -62,3 +63,10 @@ export function submitReportFail(error) { error }; }; + +export function changeReportComment(comment) { + return { + type: REPORT_COMMENT_CHANGE, + comment + }; +}; diff --git a/app/assets/javascripts/components/api.jsx b/app/assets/javascripts/components/api.jsx index 93cfc804..185729ce 100644 --- a/app/assets/javascripts/components/api.jsx +++ b/app/assets/javascripts/components/api.jsx @@ -1,5 +1,5 @@ import axios from 'axios'; -import LinkHeader from 'http-link-header'; +import LinkHeader from './link_header'; export const getLinks = response => { const value = response.headers.link; diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx index 7a1c9f5c..8ce9b192 100644 --- a/app/assets/javascripts/components/components/account.jsx +++ b/app/assets/javascripts/components/components/account.jsx @@ -65,7 +65,7 @@ const Account = React.createClass({
-
+
diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx index 0237a190..673b1a24 100644 --- a/app/assets/javascripts/components/components/avatar.jsx +++ b/app/assets/javascripts/components/components/avatar.jsx @@ -1,103 +1,18 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; -// From: http://stackoverflow.com/a/18320662 -const resample = (canvas, width, height, resize_canvas) => { - let width_source = canvas.width; - let height_source = canvas.height; - width = Math.round(width); - height = Math.round(height); - - let ratio_w = width_source / width; - let ratio_h = height_source / height; - let ratio_w_half = Math.ceil(ratio_w / 2); - let ratio_h_half = Math.ceil(ratio_h / 2); - - let ctx = canvas.getContext("2d"); - let img = ctx.getImageData(0, 0, width_source, height_source); - let img2 = ctx.createImageData(width, height); - let data = img.data; - let data2 = img2.data; - - for (let j = 0; j < height; j++) { - for (let i = 0; i < width; i++) { - let x2 = (i + j * width) * 4; - let weight = 0; - let weights = 0; - let weights_alpha = 0; - let gx_r = 0; - let gx_g = 0; - let gx_b = 0; - let gx_a = 0; - let center_y = (j + 0.5) * ratio_h; - let yy_start = Math.floor(j * ratio_h); - let yy_stop = Math.ceil((j + 1) * ratio_h); - - for (let yy = yy_start; yy < yy_stop; yy++) { - let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half; - let center_x = (i + 0.5) * ratio_w; - let w0 = dy * dy; //pre-calc part of w - let xx_start = Math.floor(i * ratio_w); - let xx_stop = Math.ceil((i + 1) * ratio_w); - - for (let xx = xx_start; xx < xx_stop; xx++) { - let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half; - let w = Math.sqrt(w0 + dx * dx); - - if (w >= 1) { - // pixel too far - continue; - } - - // hermite filter - weight = 2 * w * w * w - 3 * w * w + 1; - let pos_x = 4 * (xx + yy * width_source); - - // alpha - gx_a += weight * data[pos_x + 3]; - weights_alpha += weight; - - // colors - if (data[pos_x + 3] < 255) - weight = weight * data[pos_x + 3] / 250; - - gx_r += weight * data[pos_x]; - gx_g += weight * data[pos_x + 1]; - gx_b += weight * data[pos_x + 2]; - weights += weight; - } - } - - data2[x2] = gx_r / weights; - data2[x2 + 1] = gx_g / weights; - data2[x2 + 2] = gx_b / weights; - data2[x2 + 3] = gx_a / weights_alpha; - } - } - - // clear and resize canvas - if (resize_canvas === true) { - canvas.width = width; - canvas.height = height; - } else { - ctx.clearRect(0, 0, width_source, height_source); - } - - // draw - ctx.putImageData(img2, 0, 0); -}; - const Avatar = React.createClass({ propTypes: { src: React.PropTypes.string.isRequired, + staticSrc: React.PropTypes.string, size: React.PropTypes.number.isRequired, style: React.PropTypes.object, - animated: React.PropTypes.bool + animate: React.PropTypes.bool }, getDefaultProps () { return { - animated: true + animate: false }; }, @@ -117,38 +32,30 @@ const Avatar = React.createClass({ this.setState({ hovering: false }); }, - handleLoad () { - this.canvas.width = this.image.naturalWidth; - this.canvas.height = this.image.naturalHeight; - this.canvas.getContext('2d').drawImage(this.image, 0, 0); - - resample(this.canvas, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio, true); - }, - - setImageRef (c) { - this.image = c; - }, - - setCanvasRef (c) { - this.canvas = c; - }, - render () { + const { src, size, staticSrc, animate } = this.props; const { hovering } = this.state; - if (this.props.animated) { - return ( -
- -
- ); + const style = { + ...this.props.style, + width: `${size}px`, + height: `${size}px`, + backgroundSize: `${size}px ${size}px` + }; + + if (hovering || animate) { + style.backgroundImage = `url(${src})`; + } else { + style.backgroundImage = `url(${staticSrc})`; } return ( -
- - -
+
); } diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx index 44added8..85ce6383 100644 --- a/app/assets/javascripts/components/components/column_collapsable.jsx +++ b/app/assets/javascripts/components/components/column_collapsable.jsx @@ -15,6 +15,7 @@ const ColumnCollapsable = React.createClass({ propTypes: { icon: React.PropTypes.string.isRequired, + title: React.PropTypes.string, fullHeight: React.PropTypes.number.isRequired, children: React.PropTypes.node, onCollapse: React.PropTypes.func @@ -39,13 +40,13 @@ const ColumnCollapsable = React.createClass({ }, render () { - const { icon, fullHeight, children } = this.props; + const { icon, title, fullHeight, children } = this.props; const { collapsed } = this.state; const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable'; return (
-
+
{({ opacity, height }) => diff --git a/app/assets/javascripts/components/components/extended_video_player.jsx b/app/assets/javascripts/components/components/extended_video_player.jsx index 66e5dee1..a6451558 100644 --- a/app/assets/javascripts/components/components/extended_video_player.jsx +++ b/app/assets/javascripts/components/components/extended_video_player.jsx @@ -3,15 +3,43 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; const ExtendedVideoPlayer = React.createClass({ propTypes: { - src: React.PropTypes.string.isRequired + src: React.PropTypes.string.isRequired, + time: React.PropTypes.number, + controls: React.PropTypes.bool.isRequired, + muted: React.PropTypes.bool.isRequired }, mixins: [PureRenderMixin], + handleLoadedData () { + if (this.props.time) { + this.video.currentTime = this.props.time; + } + }, + + componentDidMount () { + this.video.addEventListener('loadeddata', this.handleLoadedData); + }, + + componentWillUnmount () { + this.video.removeEventListener('loadeddata', this.handleLoadedData); + }, + + setRef (c) { + this.video = c; + }, + render () { return ( -
-